js

Complete NestJS Production API Guide: PostgreSQL, Prisma, Authentication, Testing & Docker Deployment

Learn to build production-ready REST APIs with NestJS, Prisma & PostgreSQL. Complete guide covering authentication, testing, Docker deployment & more.

Complete NestJS Production API Guide: PostgreSQL, Prisma, Authentication, Testing & Docker Deployment

I’ve been thinking a lot about building robust backend systems lately. The challenge isn’t just making something work—it’s about creating APIs that scale, stay secure, and handle real traffic. That’s why I want to share this complete guide to building production-ready REST APIs using NestJS, Prisma, and PostgreSQL.

Let me show you how to build something that doesn’t just work on your local machine but stands up to real-world demands.

Setting up the foundation is crucial. I start with a clean NestJS project structure that separates concerns right from the beginning. Here’s how I organize the core modules:

// src/books/books.module.ts
@Module({
  imports: [PrismaModule],
  controllers: [BooksController],
  providers: [BooksService],
  exports: [BooksService],
})
export class BooksModule {}

Have you ever wondered how to handle database operations cleanly? Prisma makes this incredibly straightforward with its type-safe queries. Here’s a sample service method that handles creating a new book:

// src/books/books.service.ts
async create(createBookDto: CreateBookDto, userId: string) {
  return this.prisma.book.create({
    data: {
      ...createBookDto,
      userId,
      status: BookStatus.DRAFT,
    },
    include: {
      author: true,
      categories: true,
    },
  });
}

Authentication is where many APIs stumble. I implement JWT-based auth with role guards that ensure only authorized users access specific endpoints. The beauty of NestJS guards makes this elegant:

// src/common/guards/roles.guard.ts
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.get<Role[]>(
      'roles',
      context.getHandler(),
    );
    if (!requiredRoles) return true;
    
    const { user } = context.switchToHttp().getRequest();
    return requiredRoles.some((role) => user.role === role);
  }
}

What happens when things go wrong? Proper error handling separates amateur projects from professional ones. I create custom exception filters that return consistent error responses:

// src/common/filters/http-exception.filter.ts
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const status = exception.getStatus();
    const exceptionResponse = exception.getResponse();

    response.status(status).json({
      success: false,
      timestamp: new Date().toISOString(),
      ...(typeof exceptionResponse === 'string' 
        ? { message: exceptionResponse }
        : exceptionResponse
      ),
    });
  }
}

Testing isn’t optional—it’s essential. I write both unit tests for individual services and integration tests for API endpoints. Here’s how I test the books service:

// test/books.service.spec.ts
describe('BooksService', () => {
  let service: BooksService;
  let prisma: PrismaService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [BooksService, PrismaService],
    }).compile();

    service = module.get<BooksService>(BooksService);
    prisma = module.get<PrismaService>(PrismaService);
  });

  it('should create a book', async () => {
    const createBookDto = {
      title: 'Test Book',
      authorId: 'test-author-id',
    };
    
    jest.spyOn(prisma.book, 'create').mockResolvedValue({
      id: 'test-id',
      ...createBookDto,
      userId: 'test-user-id',
      status: BookStatus.DRAFT,
      createdAt: new Date(),
      updatedAt: new Date(),
    });

    const result = await service.create(createBookDto, 'test-user-id');
    expect(result.title).toBe('Test Book');
  });
});

Deployment with Docker ensures consistency across environments. My Dockerfile optimizes for production with multi-stage builds:

# Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:18-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/main"]

Monitoring and logging complete the production readiness. I integrate Winston for structured logging that helps debug issues in production:

// src/common/logger/winston.logger.ts
export const winstonLogger = {
  logger: WinstonModule.createLogger({
    level: 'info',
    format: format.combine(
      format.timestamp(),
      format.json(),
    ),
    transports: [
      new transports.File({ filename: 'error.log', level: 'error' }),
      new transports.File({ filename: 'combined.log' }),
    ],
  }),
};

Building APIs that withstand production demands requires attention to detail at every layer. From database design to deployment strategies, each piece matters. The combination of NestJS’s structure, Prisma’s type safety, and PostgreSQL’s reliability creates a foundation you can build upon with confidence.

What aspects of API development do you find most challenging? I’d love to hear about your experiences and questions. If this guide helped you, please share it with others who might benefit, and don’t hesitate to leave comments about your own implementation approaches.

Keywords: NestJS REST API, Prisma ORM PostgreSQL, JWT authentication NestJS, TypeScript REST API development, NestJS testing deployment guide, production ready API tutorial, NestJS Prisma integration, PostgreSQL database design patterns, Docker NestJS deployment, REST API best practices



Similar Posts
Blog Image
How to Build Scalable Event-Driven Architecture with NestJS, RabbitMQ, and MongoDB

Learn to build scalable event-driven architecture using NestJS, RabbitMQ & MongoDB. Master microservices, CQRS patterns & production deployment strategies.

Blog Image
High-Performance GraphQL APIs: Apollo Server 4, DataLoader, and Redis Caching Complete Guide

Learn to build high-performance GraphQL APIs with Apollo Server 4, DataLoader batching, and Redis caching. Master N+1 query optimization and production deployment.

Blog Image
Next.js Prisma Integration Guide: Build Type-Safe Full-Stack Apps with Modern ORM

Learn how to integrate Next.js with Prisma ORM for type-safe, scalable web applications. Build data-driven apps with seamless database operations and improved developer productivity.

Blog Image
Complete Guide to Integrating Next.js with Prisma ORM: Build Type-Safe Full-Stack Applications

Learn to seamlessly integrate Next.js with Prisma ORM for type-safe full-stack development. Master database operations, migrations, and server-side rendering. Start building better apps today.

Blog Image
Complete Guide to Integrating Next.js with Prisma ORM for Type-Safe Full-Stack Applications

Learn how to integrate Next.js with Prisma ORM for type-safe, full-stack applications. Complete guide with setup, API routes, and best practices.

Blog Image
Build Production-Ready GraphQL APIs with NestJS, Prisma and Redis: Complete Tutorial 2024

Build scalable GraphQL APIs with NestJS, Prisma & Redis. Learn authentication, real-time subscriptions, caching, testing & Docker deployment. Complete production guide.