Best Practices & Deployment
Prepare a NestJS API for production: Swagger documentation, security hardening, rate limiting, logging, project structure, and building and deploying the app.
On this page 13 sections
Introduction
A working API on your laptop is not the same as a production API. Production apps need documentation other developers can use, protection against common attacks and abuse, useful logs, and a reliable way to build and deploy. This final lesson brings together everything from the course into a production-ready setup.
- A scalable folder structure for real projects.
- How to generate interactive API docs with Swagger.
- Security essentials: helmet, CORS, validation and rate limiting.
- How to use Nest's built-in Logger.
- How to build and deploy a Nest app, including with Docker.
Recommended Project Structure
src/├── common/ # shared, cross-cutting code│ ├── decorators/ # @Public(), @Roles(), @CurrentUser()│ ├── filters/ # exception filters│ ├── guards/ # AuthGuard, RolesGuard│ └── interceptors/ # timing, transform├── config/ # registerAs() config namespaces├── auth/ # auth feature module├── users/ # users feature module│ ├── dto/│ ├── entities/│ ├── users.controller.ts│ ├── users.module.ts│ └── users.service.ts├── products/ # products feature module├── app.module.ts└── main.tsFeature folders keep everything about one domain together, while common/ holds reusable building blocks from lessons 6–10.
API Documentation with Swagger
The official @nestjs/swagger package generates OpenAPI documentation directly from your controllers and DTOs, plus an interactive page where anyone can try your endpoints:
npm install @nestjs/swagger// src/main.tsimport { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
const config = new DocumentBuilder() .setTitle('Shop API') .setDescription('Products, orders and users') .setVersion('1.0') .addBearerAuth() .build();
const documentFactory = () => SwaggerModule.createDocument(app, config);SwaggerModule.setup('docs', app, documentFactory);Visit http://localhost:3000/docs to see every endpoint. Decorate DTO properties with @ApiProperty() and routes with @ApiTags(), @ApiBearerAuth() and @ApiResponse() for richer documentation.
Security Hardening
| Measure | How | Covered In |
|---|---|---|
| Validate all input | Global ValidationPipe with whitelist and forbidNonWhitelisted | Lesson 6 |
| Authenticate by default | Global AuthGuard with @Public() opt-outs | Lesson 9 |
| Hash passwords | bcrypt — never store plain text | Lesson 9 |
| Hide sensitive fields | ClassSerializerInterceptor + @Exclude() | Lesson 10 |
| Secrets in environment | @nestjs/config with validation | Lesson 11 |
| Security headers | app.use(helmet()) | Lesson 7 |
| Restrict CORS | app.enableCors({ origin: [...] }) with your real frontend URLs | Lesson 7 |
| Limit request rates | @nestjs/throttler | Below |
Rate Limiting
Rate limiting stops a single client from flooding your API — especially important for login endpoints targeted by password-guessing attacks:
npm install @nestjs/throttler// src/app.module.tsimport { APP_GUARD } from '@nestjs/core';import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
@Module({ imports: [ ThrottlerModule.forRoot([{ ttl: 60000, limit: 100 }]), // 100 requests per minute ], providers: [{ provide: APP_GUARD, useClass: ThrottlerGuard }],})export class AppModule {}
// stricter limit on one route@Throttle({ default: { limit: 5, ttl: 60000 } })@Post('login')signIn() {}Clients that exceed the limit receive 429 Too Many Requests.
Logging
Replace console.log with Nest's built-in Logger, which adds timestamps, log levels and the source class name:
import { Injectable, Logger } from '@nestjs/common';
@Injectable()export class OrdersService { private readonly logger = new Logger(OrdersService.name);
placeOrder(userId: number) { this.logger.log(`Placing order for user ${userId}`); // ... this.logger.warn('Stock running low for product 42'); }}Click Run to see what this code prints.
For production systems that ship logs to tools like Datadog, Elasticsearch or CloudWatch, community packages such as nestjs-pino provide fast, structured JSON logging behind the same interface.
A Production main.ts
// src/main.tsimport { Logger, ValidationPipe } from '@nestjs/common';import { ConfigService } from '@nestjs/config';import { NestFactory } from '@nestjs/core';import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';import helmet from 'helmet';import { AppModule } from './app.module.js';
async function bootstrap() { const app = await NestFactory.create(AppModule); const config = app.get(ConfigService);
app.setGlobalPrefix('api'); app.use(helmet()); app.enableCors({ origin: config.get<string>('FRONTEND_URL') }); app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }), ); app.enableShutdownHooks(); // close DB connections cleanly on SIGTERM
if (config.get('NODE_ENV') !== 'production') { const docConfig = new DocumentBuilder().setTitle('Shop API').addBearerAuth().build(); SwaggerModule.setup('docs', app, () => SwaggerModule.createDocument(app, docConfig)); }
const port = config.get<number>('PORT', 3000); await app.listen(port); new Logger('Bootstrap').log(`API running on port ${port}`);}await bootstrap();Building & Deploying
Deploying a Nest app means compiling TypeScript to JavaScript and running the result with Node.js:
npm ci # clean install from package-lock.jsonnpm run build # compiles src/ into dist/npm run start:prod # runs: node dist/mainContainerizing with Docker makes the app run identically everywhere. A multi-stage Dockerfile keeps the final image small by leaving build tools behind:
# DockerfileFROM node:22-alpine AS buildWORKDIR /appCOPY package*.json ./RUN npm ciCOPY . .RUN npm run build
FROM node:22-alpineWORKDIR /appENV NODE_ENV=productionCOPY package*.json ./RUN npm ci --omit=devCOPY --from=build /app/dist ./distEXPOSE 3000CMD ["node", "dist/main"]| Platform | Notes |
|---|---|
| Render / Railway | Connect your Git repo; set the build command (npm run build) and start command (npm run start:prod). Free or cheap tiers for learning projects. |
| AWS, Google Cloud, Azure | Run the Docker image on a container service (ECS, Cloud Run, Azure Container Apps) for full control and scaling. |
| A Linux VPS | Run with a process manager like PM2 behind Nginx as a reverse proxy, or use Docker Compose. |
Run your tests, run database migrations, set every environment variable on the host (validated at startup thanks to lesson 11), and make sure synchronize is off.
Where to Go Next
GraphQL
Build GraphQL APIs with @nestjs/graphql and Apollo or Mercurius, using the same modules and DI.
Microservices
Split systems into services that talk over Redis, RabbitMQ, Kafka or gRPC with @nestjs/microservices.
WebSockets
Add real-time features with gateways from @nestjs/websockets.
Prisma
Try a schema-first ORM with a fully typed generated client as an alternative to TypeORM.
Caching & Queues
Speed up responses with @nestjs/cache-manager and process background jobs with BullMQ.
Scheduling
Run cron jobs and timed tasks with @nestjs/schedule.
Common Mistakes
- Running npm run start:dev (watch mode) in production instead of the compiled build.
- Exposing Swagger docs publicly in production without authentication.
- Allowing CORS from every origin (*) on an API that uses credentials.
- Shipping without rate limiting on login and registration endpoints.
- Deploying with synchronize: true against a production database.
Frequently Asked Questions
Yes, with an adapter (for example for AWS Lambda), though cold starts can be noticeable for large apps. Long-running containers are usually the simpler choice.
Only if profiling shows the HTTP layer is your bottleneck. Most APIs are limited by database queries, not by Express. Switching later is possible because Nest abstracts the platform.
A complete API — for example a task manager or mini e-commerce backend — with JWT auth, roles, validated DTOs, a MySQL/PostgreSQL database, Swagger docs, tests and a live deployment. It demonstrates every skill from this course.
Key Takeaways
- Organize code by feature modules plus a shared common/ folder.
- @nestjs/swagger generates interactive OpenAPI docs from controllers and DTOs.
- Combine validation, global auth, helmet, restricted CORS and @nestjs/throttler for security.
- Use Nest's Logger (or nestjs-pino) instead of console.log.
- Deploy the compiled dist/ build, ideally in a multi-stage Docker image.
Summary
Congratulations — you have gone from your first nest new command through modules, controllers, dependency injection, the full request pipeline, authentication, configuration, databases and testing, all the way to a production-ready deployment. The best way to make it stick is to build one complete API of your own, start to finish, and ship it.