Project Structure

ForgeStack is an Nx monorepo containing the backend services, the shared backend libraries, the frontend, and all infrastructure-as-code.

Top-level layout

.
├── backend/
│   ├── services/
│   │   └── service-1/        NestJS API (auth, notifications, …)
│   └── libs/
│       ├── common/           CQRS, DDD bases, auth, outbox/inbox, otel
│       ├── kafka/            Kafka producer/consumer + event listener
│       ├── mongodb/          Mongo client, base repository, transactions
│       └── redis/            Redis client (cache + pub/sub)
├── frontend/
│   └── public-page/          Next.js 16 app (landing, auth, console, docs)
├── infra/
│   ├── compose/              docker-compose.dev.yml / .prod.yml
│   ├── caddy/                Caddyfile (reverse proxy + TLS)
│   ├── monitoring/           prometheus, grafana, loki, tempo, promtail
│   ├── mongo/                replica-set startup script
│   ├── env/                  *.env.prod.example templates
│   └── scripts/              deploy + server-provision scripts
└── eslint/
    └── rules/                custom DDD/CQRS architectural lint rules

Inside a service

service-1 is organised by bounded context. Each context owns the full hexagonal stack:

backend/services/service-1/src/
├── bounded-contexts/
│   ├── auth/
│   │   ├── domain/
│   │   │   ├── aggregates/          user/, email-verification/, …
│   │   │   ├── value-objects/       email.vo.ts, password.vo.ts, …
│   │   │   └── services/            domain services (ports)
│   │   ├── application/
│   │   │   ├── commands/            <name>.command.ts + .command-handler.ts
│   │   │   ├── queries/             <name>.query.ts + .query-handler.ts
│   │   │   └── domain-event-handlers/
│   │   ├── infrastructure/
│   │   │   ├── repositories/        mongodb/, in-memory/
│   │   │   └── services/            external adapters (e.g. google-oauth)
│   │   ├── interfaces/
│   │   │   ├── controllers/         HTTP endpoints
│   │   │   └── integration-events/  Kafka subscribers
│   │   └── auth.module.ts
│   ├── notifications/
│   └── shared/
├── app.module.ts
└── app.config.ts

Each command lives in its own folder next to its handler — for example application/commands/register-user/register-user.command.ts and register-user.command-handler.ts. The cqrs-handler-collocation lint rule enforces that a handler imports its command/query from the same directory.

Shared libraries

The backend libs are the reusable spine every service builds on:

LibraryWhat it provides
@libs/nestjs-commonCQRS base classes, SharedAggregate, value-object bases, auth (JWT), outbox & inbox, integration-event bases, transactions, logging, tracing, metrics
@libs/nestjs-kafkaKafka producer/consumer, the integration-event publisher and listener
@libs/nestjs-mongodbMongo client module, Base_MongoRepository, transaction support
@libs/nestjs-redisRedis client (database, publisher, subscriber)

TypeScript path aliases

Defined in tsconfig.base.json, so imports stay clean and refactor-safe:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@libs/nestjs-common": ["backend/libs/common/src"],
      "@libs/nestjs-kafka": ["backend/libs/kafka/src"],
      "@libs/nestjs-mongodb": ["backend/libs/mongodb/src"],
      "@libs/nestjs-redis": ["backend/libs/redis/src"],
      "@bc/*": ["backend/services/service-1/src/bounded-contexts/*"]
    }
  }
}

Usage:

import { Base_CommandHandler } from '@libs/nestjs-common';
import { Base_MongoRepository } from '@libs/nestjs-mongodb';
import { User } from '@bc/auth/domain/aggregates/user/user.aggregate';

The @bc/* alias is also what the no-cross-bc-imports rule keys off: an import like @bc/notifications/... from inside the auth context is flagged at lint time.

The frontend

frontend/public-page/
├── app/
│   ├── layout.tsx            root layout (fonts, i18n, providers)
│   ├── page.tsx              landing
│   ├── login/ register/ …    auth pages
│   ├── dashboard/            authenticated console (sidebar + topbar)
│   ├── docs/                 this documentation (MDX)
│   └── shared/ui/            design-system primitives
├── i18n/                     next-intl config
├── messages/                 translation catalogues (en + 7 locales)
└── lib/                      brand config, utils

See Frontend for the conventions.

Naming conventions

File suffixes are part of the contract — the lint rules use them to apply the right base-class and naming checks:

SuffixMust…
*.aggregate.tsextend SharedAggregate
*.vo.tsimplement the value-object interface
*.command.ts / *.command-handler.tspair up; handler extends Base_CommandHandler(Command)
*.query.ts / *.query-handler.tspair up; handler extends Base_QueryHandler(Query)<Response>()
*.domain-event.tsextend Base_DomainEvent
*.integration-event.tsextend Base_IntegrationEvent

Class names follow the same shape — handlers end in _CommandHandler, _QueryHandler, etc. (enforced by handler-naming).