A layered backend
How a request travels, and what each layer decorates it with.
Documented design
- A controller calls a use case, which injects a repository interface by a string token, and a TypeORM repository implements that interface on top of a shared base repository. Domain models, enums and the error catalogs live in the domain layer.
- Cross-cutting behaviour is applied per controller with decorators: authentication, a permission guard, a validation pipe, an interceptor that wraps results in a standard envelope with the duration, and an interceptor that writes an API log record to MongoDB.
- Authentication calls an external identity service to validate each token; the service issues no tokens itself. Permissions are codes of the form module and action, in 22 enum groups with 206 codes. A guard accepts a list as OR, and a nested list as AND.
- A request-scoped transaction interceptor, applied on 33 controllers, opens a transaction, hands its entity manager to the repositories through the request, and commits or rolls back at the end.
- Entities share a base with created, updated and deleted timestamps and actor columns, and delete is soft. A shared pagination helper defaults to 15 rows and caps at 100.
Design rationale
- Request-scoped transactions let a use case change several tables atomically without passing a transaction object through every call.
- Interfaces between use cases and repositories keep business rules free of query code.
Controller
Auth, permission, validation, envelope
Use case
Business rules
Repository interface
Injected by token
TypeORM repository
Inside the request's transaction
- Active controllers
- 53
- Use case files
- 70
- DTO files
- 343