Adding a Service
Adding a NestJS service is one command. Name it whatever you want. billing
is used as an example on this guide:
npx nx g @forgestack/tools:service billing # generate new service called "billing"
npm i # link the new package into the workspaceThe generator takes two flags:
--dry-run— print everything the generator would create or edit without writing any of it.--expose=false— to generate an internal service that shouldn't be reachable from the browser.
The generated service contains a starter bounded context called example with
one of every building block a bounded context is made of:
- An aggregate —
Greeting, which raises a domain event (GreetingCreated_DomainEvent) when created. - A value object —
GreetingMessage, with validation. - A command —
CreateGreeting_Commandand itsCreateGreeting_CommandHandler. - A query —
ListGreetings_Queryand itsListGreetings_QueryHandler. - Controllers that dispatch them through
the buses —
CreateGreeting_Controller(POST /api/v1/greetings) andListGreetings_Controller(GET /api/v1/greetings). - A domain-event handler —
GreetingCreated_PublishIntegrationEvent_DomainEventHandler, which turns the internal event into a publicGreetingCreated_IntegrationEventand publishes it through the outbox for other services to consume. - An integration-event handler
—
UserCreated_IntegrationEventHandler, consuming another service'suser.createdintegration event from Kafka — the new service greets every registered user out of the box. - Repositories —
Greeting_MongodbRepositoryandGreeting_InMemoryRepository, behind the sameGreeting_Repositoryport interface. - Tests for every handler type, all passing from the first run.
Rename or get rid of the example context once you start building real ones.
Beyond the code, the generator wires the service into the dev and prod compose files, adds the Caddy host and creates the env files.
What the generator sets up (and where to edit it)
| Piece | Where | Notes |
|---|---|---|
| Service code | backend/services/<name>/ | Configs, jest, and the example starter bounded context |
| Local config | backend/services/<name>/.env (+ .env.example) | SERVICE_NAME, KAFKA_SERVICE_ID and the Mongo database are unique per service |
| Dev container | infra/compose/docker-compose.dev.yml | One small block to deploy the service locally |
| Prod container | infra/compose/docker-compose.prod.yml | One small service block to deploy in prod; image tag forgestack-<name>:v1 |
| Prod config | infra/env/<name>.env.prod (+ .example) | Secrets are placeholders — fill them in before deploying |
| Public hostname | infra/caddy/Caddyfile | <name>.<domain> → <name>:3000 to expose it through Caddy |
That table is the complete list — everything else (builds, deploys, type-checks, lint, metrics, logs, traces) discovers the new service automatically.
After generating
- Dev: start the stack (
npm run dev), ornpm run dev:restart <name> caddyif it's already running — Caddy re-reads its config only on restart.GET /api/v1/greetingson the new hostname confirms the wiring. - Prod: fill in
infra/env/<name>.env.prod, point<name>.<your-domain>DNS at the server, and use.npm run prod:updateto deploy it (make sure to include Caddy in the deployment).
To call the new service from the website, see Calling Backend Services.