LearnAI ToolsCareerPractice BuildsPlayDevelopmentContact
Lesson 1422 min read

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.

What You Will Learn
  • 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.ts

Feature 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.ts
import { 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

MeasureHowCovered In
Validate all inputGlobal ValidationPipe with whitelist and forbidNonWhitelistedLesson 6
Authenticate by defaultGlobal AuthGuard with @Public() opt-outsLesson 9
Hash passwordsbcrypt — never store plain textLesson 9
Hide sensitive fieldsClassSerializerInterceptor + @Exclude()Lesson 10
Secrets in environment@nestjs/config with validationLesson 11
Security headersapp.use(helmet())Lesson 7
Restrict CORSapp.enableCors({ origin: [...] }) with your real frontend URLsLesson 7
Limit request rates@nestjs/throttlerBelow

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.ts
import { 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');
}
}
Console

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.ts
import { 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.json
npm run build # compiles src/ into dist/
npm run start:prod # runs: node dist/main

Containerizing with Docker makes the app run identically everywhere. A multi-stage Dockerfile keeps the final image small by leaving build tools behind:

# Dockerfile
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/main"]
PlatformNotes
Render / RailwayConnect 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, AzureRun the Docker image on a container service (ECS, Cloud Run, Azure Container Apps) for full control and scaling.
A Linux VPSRun with a process manager like PM2 behind Nginx as a reverse proxy, or use Docker Compose.
Before Every Deployment

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

Avoid These 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.

Course Complete!

You have finished the NestJS course. Put your skills to work by building and deploying a complete, tested API of your own.

Explore More Courses