Introduction
An overview of the RawStack API component.
The API is the core backend application for the system. It is responsible for authentication, user-facing domain logic, persistence, validation, and publishing integration events. It is built as a NestJS application with a modular structure, and it uses CQRS and Domain-Driven Design (DDD) to organise code by domain and responsibility rather than by transport or framework concerns. The current top-level modules in src are auth, user, and common.
The codebase is best understood as a modular monolith. Modules are separated internally, but they still run in a single deployable service and share core infrastructure such as NestJS, Prisma, and the database.
Tech specification
The API currently uses:
- TypeScript
- NestJS
- PostgreSQL
- Prisma
- Redis
- Zod via
nestjs-zod @nestjs/cqrs- JWT-based authentication
- Argon2 for password hashing
- AWS EventBridge
- Docker
These dependencies are visible directly in apps/api/package.json, including @nestjs/cqrs, @aws-sdk/client-eventbridge, ioredis, @prisma/client, nestjs-zod, passport-jwt, and argon2.
Architecture
The API is a REST API with a modular structure. It uses Domain-Driven Design (DDD) and CQRS to organise code around domain responsibilities rather than transport or framework concerns. The goal is clarity and maintainability, not strict adherence to every pattern.
CQRS
CQRS separates operations that change state from operations that read data. Commands represent writes, while queries represent reads.
- Commands handle writes—they change the state of the system (e.g., creating or updating data).
- Queries handle reads—they retrieve and return data without modifying it.
In this API, CQRS is used in the application layer to keep write logic and read logic distinct. The codebase does not implement separate read and write databases or a fully separate read model, but it does use command handlers and query handlers to make use cases easier to reason about and evolve.
Queries are optimised for speed and rely heavily on Redis caching, while commands are optimised for correctness and rely on the domain layer for business rules and validation.
Domain-Driven Design
Domain-Driven Design (DDD) is an approach to structuring software around the business domain rather than around frameworks, databases, or transport layers. In practice, that means defining clear domain boundaries and keeping business logic separate from infrastructure concerns such as HTTP, persistence, and external integrations.
DDD introduces a few core concepts that are useful in this codebase:
- Bounded Contexts define clear domain boundaries, each with its own model and business rules.
- Models and Value Objects represent domain concepts and data with business meaning.
- Aggregates group related domain objects and help enforce consistency rules.
- Repositories provide access to domain objects without coupling business logic to persistence details.
- Domain Services hold business logic that does not naturally belong to a single model.
In this API, DDD is applied pragmatically rather than dogmatically. NestJS modules provide the main domain boundaries, and each module is split into application, domain, and infrastructure layers. The aim is to keep the code aligned with the problem domain as the system evolves.
DDD and CQRS together
DDD and CQRS complement each other in this codebase. DDD provides the structure for modelling domain concepts and business rules, while CQRS shapes the application layer by separating state-changing use cases from read-only ones.
Together, they help keep controllers thin, business logic focused, and module internals easier to evolve over time.
How are DDD and CQRS implemented here?
This is not a “full DDD” or “full CQRS” implementation in the strict sense. The RawStack API uses both ideas where they help keep the codebase clear and maintainable.
Business logic is organised around domain concepts rather than controllers or ORM models. Within each NestJS module, the code is split into application, domain, and infrastructure layers. Command handlers orchestrate domain services and query handlers build responses, while the domain layer owns the core rules and behaviour.
Event-driven
The API is event-driven. It includes both an internal event bus and an external event bus. The external bus, powered by EventBridge, allows the API to publish domain events that can be consumed by other services such as the Notification service. The internal bus, provided by @nestjs/cqrs, allows for decoupled communication between different parts of the application, such as between domains and aggregates.
Directory structure
NestJS provides the module system, but the important structure in this API is the internal split used inside each module.
Each domain module generally looks like this:
application: commands, queries, handlers, DTOs, response buildersdomain: models, repository contracts, domain servicesinfrastructure: controllers/actions, persistence implementations, framework integrations, external providers
The current top-level layout under src is:
/src
/auth
/application
/domain
/infrastructure
auth.module.ts
/user
/application
/domain
/infrastructure
user.module.ts
/common
/application
/domain
/infrastructure
common.module.ts
app.module.ts
cli.module.ts
cli.ts
main.tsApplication layer
The application layer contains use-case orchestration.
Typical contents include:
- commands and command handlers
- queries and query handlers
- DTOs
- response builders
This layer should coordinate work, not own core business rules. In practice, handlers usually delegate to domain services or repositories and then shape output through response builders. UserModule and AuthModule both register command handlers and query handlers explicitly.
Domain layer
The domain layer contains the business model.
Typical contents include:
- entities or models
- repository interfaces
- domain services
- domain events
- value-like types and contracts
The important rule is that this layer should describe business behaviour without depending on HTTP or Prisma details.
Infrastructure layer
The infrastructure layer contains framework and integration code.
Typical contents include:
- HTTP actions/controllers
- Prisma repository implementations
- security and auth strategy wiring
- cache implementations
- event bus adaptors
- external service integrations
HTTP endpoints are named actions rather than controllers, for example CreateUserAction, GetUserAction, and CreateTokenAction.
The API CLI
The API includes a CLI for running one-off scripts and maintenance tasks. It is built with NestJS's built-in CLI utilities and is defined in cli.ts and cli.module.ts. The CLI shares the same NestJS application context as the main API, so it has access to all modules, services, and infrastructure. This allows for easy reuse of domain logic and database access in scripts without needing to set up separate database connections or configurations.
To run a CLI command, use:
npm run rawstack <command>Currently available commands include:
npm run rawstack user:create— creates a new user with an email and password and optionally admin role