Nest REST Backend Template: the backend foundation I use to start new projects
A production-oriented NestJS backend template: Fastify, Drizzle ORM, PostgreSQL, CQRS, RBAC/CASL, gRPC auth service, JWT/cookies, S3 file storage, BullMQ, i18n, captcha, healthchecks, Docker and observability.
nest-rest-backend-template is my personal backend template, the one I use as a starting point for new projects. It is not a “Nest CLI plus UsersController” starter. I built it as a foundation for product systems that almost always need the same baseline: users, roles, authentication, files, queues, mail, notifications, healthchecks, Docker, configuration, migrations and a clear architectural map.
I did not want to reconnect Fastify, Pino, Swagger, CSRF, S3, Redis, BullMQ, Drizzle, health endpoints and admin seeding from scratch every time. I also did not want to copy pieces from older projects and spend days reconciling differences. So the template became not boilerplate for the sake of boilerplate, but a way to start projects from an engineering baseline I trust.
The main idea is simple: a new project should not start from chaos. It should start from a structure where business logic, infrastructure and growth already have a place.
What Is Inside
The template is built as a Nest CLI monorepo with two deployable apps:
apps/backend- the main REST API on NestJS/Fastify;apps/auth-service- a separate gRPC service for credential verification, token issuance, refresh rotation and password flows;libs/- shared contracts, auth types, password service, Drizzle auth schema and a common config loader.
The main backend includes several modules I often need as a base:
users- users, roles, permissions and the auth HTTP surface;file- S3/MinIO-backed file storage with file versions;mail- emails through Handlebars templates and a BullMQ queue;notification- in-app notifications with asynchronous dispatch;captcha- custom SVG captcha with a pre-generated Redis pool and S3 asset storage;admin- dashboard, access logs and system settings;health- liveness/readiness endpoints;migration- boot-time seeding for roles, permissions, admin user and templates.
This is a lot for a template, but in real backend projects these pieces often appear within the first few weeks. The difference is that here they are not taped to the first feature module. They have clear ownership from the beginning.
Architecture
Feature modules follow a DDD/CQRS-style structure:
domain/
application/
infrastructure/
presentation/
Controllers stay thin: they receive DTOs, pass validation and dispatch commands or queries through CommandBus/QueryBus. Decisions happen in handlers, domain rules stay close to entities/domain services, and persistence is hidden behind repository ports.
For example, UsersModule does not depend directly on storage implementation. It defines abstractions such as UserRepository, RolePermissionRepository and MagicLinkTokenRepository, while Drizzle implementations are wired through providers. The same pattern is used for auth-service communication: the application layer knows about AuthGatewayPort, while gRPC is isolated inside AuthGrpcGatewayAdapter.
I like this style for one practical reason: when a project grows, it already has a map. A new endpoint does not have to become another random piece of service code. It has a place: command, query, handler, repository, mapper, DTO.
Why Auth Became a Separate Service
The most important evolution of the template is the separate auth-service.
At first, authentication could have lived inside the main backend, as it usually does in NestJS projects. But Argon2id password verification is CPU-bound. When many login attempts happen at once, the main Node.js process can become worse at serving every endpoint, even though the real pressure comes only from the auth flow.
That is why credential verification, refresh-token rotation, logout, password reset/change and token issuance were moved to a gRPC service. The REST contract did not change for clients: /auth/login, /auth/refresh and /auth/logout still live on the main backend. Internally, the HTTP handler calls AuthGatewayPort, receives a structured result and translates it back into HTTP exceptions, cookies, domain events and i18n messages.
I like this decision because it does not break the external API, but isolates the heavy part. If a login surge creates pressure, auth-service degrades first, not the whole product.
Security and Auth Flow
Authentication is not implemented as “issue JWT and forget”.
Important details:
- access and refresh tokens are signed separately;
- refresh tokens are persisted and can be revoked server-side;
- refresh rotation creates a new token pair and revokes the previous refresh token;
- password change can invalidate old sessions through token versioning;
- cookie delivery supports
HYBRID,COOKIES_ONLYandRESPONSE_ONLYmodes; - CSRF guard uses a double-submit cookie pattern for unsafe methods;
- brute-force protection tracks failed attempts, requests captcha after several failures and temporarily locks the account after more attempts;
- magic-link login and password reset are separate flows.
RBAC is implemented with CASL. Roles and permissions are seeded on startup: Administrator, Manager, User, Public. Routes are protected declaratively through @Policy(action, subject) and guards, while the permission matrix lives as data rather than scattered if (user.role === ...) checks.
This is not enterprise complexity for its own sake. It is the set of things that almost always has to be added later if the product lives long enough.
Files, Mail, Notifications and Captcha
I gave infrastructure modules their own shape because they often become a source of technical debt.
Files are not stored in the application container. Metadata and versions live in the database, while binary content goes to S3-compatible storage. Locally this is MinIO. The backend streams files back to clients instead of exposing storage internals directly.
The mail module uses Handlebars templates stored in PostgreSQL and seeded at startup. Delivery happens through a BullMQ queue and each email keeps its status. This is much better than sending mail directly inside a request handler and hoping SMTP responds quickly.
The notification module follows a similar idea: in-app notifications are domain entities, while dispatch happens asynchronously. Modules do not call each other directly. For example, UserCreatedEvent can be handled by both the mail module and notification module independently.
Captcha is a separate piece. Instead of depending on a third-party captcha provider, the template has its own SVG text captcha. Images are pre-generated in batches, stored through an S3-backed pool and tracked in Redis. Answers are stored through HMAC hashes, not plaintext. This is a bit heavier than synchronous generation, but it avoids putting captcha rendering on the user-facing request path.
Bootstrap and Deployment Shape
main.ts contains useful plumbing rather than decoration:
- Fastify adapter;
- request id propagation via
X-Request-Id; - CORS allowlist;
- Helmet/CSP;
- cookie and multipart plugins;
- global
/api/v1prefix; - Swagger with optional Basic Auth protection;
- global validation pipe;
- i18n-aware validation exceptions;
- language/logger/transform interceptors;
- global exception filter;
- startup warning when external monitoring is disabled.
Docker Compose runs a full local environment: PostgreSQL, Redis, MinIO, Mailpit, auth-service and backend. Services have healthchecks, MinIO-init creates the bucket and enables versioning, and the backend waits for infrastructure and auth-service readiness.
For me this matters because the template should run like a small production-shaped system, not only as pnpm start:dev on a local machine.
Observability and Supportability
Logging is built on Pino. Development can use pretty output, while production can use JSON. Optional transports exist for file logs, Graylog/GELF and Elasticsearch. Sensitive fields are redacted before logs reach transports: Authorization, Cookie, password, accessToken, refreshToken and related values should not leak through observability.
Health endpoints are split into:
/health- readiness plus memory thresholds;/health/ready- dependency readiness;/health/live- process liveness without external dependencies.
There is also an optional docker-compose.zabbix.yaml stack. It does not make the project magically production-ready, but it sets the right direction: a service should be observable, not only runnable.
Testing
The template has separate Jest configurations for unit, integration and e2e tests. Unit tests live near source files and mock abstract repositories/services. The e2e harness boots the real AppModule and Fastify, with a health-check e2e spec already in place.
I do not treat this part as perfect. Integration/e2e coverage is more of a prepared harness than a full test suite. This is an honest area for future improvement: the infrastructure exists, but each real project still needs to add its own critical-path tests.
Result
The main value of the project is not that it “has everything”. Its value is that it removes repeated friction from the beginning of backend development.
When I start a new product, I do not have to rebuild these pieces again:
- auth;
- roles and permissions;
- config loader;
- Docker Compose;
- migrations;
- admin seeding;
- S3 files;
- queues;
- mail;
- i18n;
- healthchecks;
- structured logging;
- Swagger;
- request ids;
- basic test harness.
I can move faster toward the actual domain: orders, documents, auctions, terminals, marketplace logic or admin workflows.
This template is my way of not starting from a blank page every time. It does not replace architectural thinking, but it gives me a strong starting position: the system is already split into clear layers, infrastructure is already wired, and the first business modules can be built on top of a foundation I trust.