Skip to main content
This guide takes you from an empty machine to a working Ooneex application with its first domain. You install the CLI, scaffold the project, then build a Movie resource — a module, an entity, a repository, and a controller. You can build that resource in two ways: by running the CLI commands yourself, or by handing a single detailed prompt to your AI agent. Both produce the same result.

Prerequisites

The CLI runs on Bun. Install it first:
curl -fsSL https://bun.sh/install | bash
Verify the installation:
bun --version

Step 1: Install the CLI

1

Install globally

Install @ooneex/cli globally with Bun. It exposes two interchangeable binaries, ooneex and its short alias oo.
bun add -g @ooneex/cli
2

Verify

Confirm the CLI is available:
ooneex help
Prefer not to install globally? Run any command through bunx instead, for example bunx @ooneex/cli@latest app:create. See Installation for Zsh completions and more.

Step 2: Create the application

Scaffold a complete application with app:create. Run it with no flags to be prompted for the name and destination:
ooneex app:create
Or pass them directly:
ooneex app:create --name=MovieApp --destination=movie-app
This creates the app and shared modules, the entrypoint, the shared database, roles, Docker files, and config, then installs all dependencies. You are also offered optional CI/CD files for GitHub, GitLab, or Bitbucket. Move into the project and start it:
cd movie-app
ooneex app:start
app:start brings up the Docker services defined by the app module, then runs every api, microservice, and spa module concurrently — api and microservice modules serve their entrypoint with hot reload, while spa modules run their dev server.
The environment file is generated at modules/shared/.env.yml. Edit it to point at your database, Redis, and other services before starting.

Start only part of the app

Pass a type flag to narrow what runs. A bare flag keeps every module of that type; --<type>=a,b keeps only the named ones. Flags combine, so you can start several types at once.
ooneex app:start --api                            # only the api modules
ooneex app:start --microservice                   # only the microservice modules
ooneex app:start --spa                            # only the spa modules
ooneex app:start --microservice=billing,payments  # only the named microservices
ooneex app:start --api --spa                       # the api and spa modules together
The shared Docker stack still comes up first regardless of the flags — they only narrow which modules are run. When you’re done, stop the app:
ooneex app:stop
app:stop brings down the app module’s shared Docker stack with docker compose down. The running module processes — spa dev servers and hot-reloaded entrypoints — stop when you interrupt the app:start process with Ctrl+C. A type flag narrows the stop to the named modules that ship their own docker-compose.yml:
ooneex app:stop --microservice=billing   # bring down a microservice's own Docker stack
Build the app for production:
ooneex app:build

Project structure

app:create generates a Bun workspace organized around modules. Every module under modules/<name>/ owns its own controllers, services, repositories, entities, and config — a self-contained vertical slice of your domain.
movie-app/
├── modules/
│   ├── app/                    # API module — the application entrypoint
│   │   ├── src/
│   │   │   ├── AppModule.ts     # Registers controllers, middlewares, cron jobs, events
│   │   │   ├── index.ts         # Boots the App (routing, cache, rate limit, loggers)
│   │   │   └── OnAppStart.ts    # Startup hook
│   │   ├── tests/
│   │   ├── var/
│   │   ├── app.yml              # Module config (type: api)
│   │   ├── docker-compose.yml
│   │   ├── Dockerfile
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── shared/                  # Shared module — cross-cutting code
│       ├── src/
│       │   ├── databases/
│       │   │   └── SharedDatabase.ts
│       │   ├── SharedModule.ts  # Aggregates entities from every module
│       │   └── roles.yml        # Role definitions
│       ├── tests/
│       ├── .env.yml             # Environment configuration
│       ├── package.json
│       └── tsconfig.json
├── .commitlintrc.ts            # Conventional commit rules
├── .dockerignore
├── .gitignore
├── biome.jsonc                 # Formatter and linter config
├── bunfig.toml
├── nx.json
├── package.json                # Workspace root (scripts, workspaces, lint-staged)
├── README.md
└── tsconfig.json               # Root config with module path aliases
Each business domain you add — like Movie — becomes its own module alongside app and shared, registered automatically into AppModule.

Step 3: Build your first resource

With the app scaffolded, build the Movie domain. Choose the path that fits how you work — the CLI walks you through each artifact, while the AI prompt produces the whole slice in one pass.
Your project ships AI skills for every create command. Initialize them once, then give your agent a single prompt and it drives the module:create, entity:create, repository:create, and controller:create skills for you.Initialize the skills for your agent:
ooneex claude:init
Then copy this prompt into your agent:
Build a complete `Movie` domain in my Ooneex application using the
Ooneex CLI create skills. Follow the project's module conventions and
generate the matching tests for every artifact.

1. Module
   - Create a `movie` module and register it into the `app` destination.

2. Entity — `Movie`, mapped to the `movies` table, with these columns:
   - `title`        varchar(255), required, indexed
   - `slug`         varchar(255), required, unique
   - `description`  text, nullable
   - `releaseYear`  int, required
   - `durationMin`  int, nullable          (runtime in minutes)
   - `rating`       numeric(3,1), nullable (0.0–10.0)
   - `genre`        varchar(50), nullable
   - `isPublished`  boolean, default false
   Keep the generated audit columns (id, timestamps, soft delete).

3. Repository — `Movie`
   - Keep the generated CRUD methods.
   - Enable the `q` text search in `find` to match on `title`.
   - Add `findBySlug(slug: string)` returning a single movie or null.

4. Controllers under `/movies`, each thin, validated, and restricted to
   `ROLE_USER`:
   - `MovieList`    GET    /movies        list with pagination + `q` search
   - `MovieRead`    GET    /movies/:id    fetch one by id
   - `MovieCreate`  POST   /movies        validate the payload, persist
   - `MovieUpdate`  PUT    /movies/:id    partial update
   - `MovieDelete`  DELETE /movies/:id    soft delete

Wire payload, params, query, and response validation on each route, keep
the controllers delegating to the repository, and run the formatter,
linter, and tests when done.
The agent scaffolds each artifact with the CLI, fills in the columns, methods, routes, and validation, and writes the tests — producing the same module structure shown in the CLI tab.

Resulting module structure

Either path produces the same module — a complete vertical slice for the Movie domain:
modules/movie/
├── src/
│   ├── controllers/
│   │   └── MovieListController.ts   # Route config + handler
│   ├── entities/
│   │   └── MovieEntity.ts           # TypeORM entity → movies table
│   ├── repositories/
│   │   └── MovieRepository.ts       # CRUD over MovieEntity
│   └── MovieModule.ts               # Registers controllers, entities, …
├── tests/
│   ├── controllers/
│   ├── entities/
│   ├── repositories/
│   └── MovieModule.spec.ts
├── movie.yml                        # Module config
├── package.json
└── tsconfig.json

Next steps

CLI commands

Explore every generator — services, events, migrations, seeds, and more.

Controllers

Learn routing, validation, and role-based access in depth.

Entities

Model your data with TypeORM entities and the shared database.

Repositories

Query and persist data with the repository pattern.