nestjs
NestJS Skill
Production patterns for NestJS 11 backend services using Fastify adapter, modular architecture, and Railway deployment.
When to Use This Skill
- Setting up a new NestJS backend project
- Creating REST API modules (controllers, services, DTOs)
- Configuring guards, pipes, interceptors, and filters
- Integrating Prisma with NestJS (PrismaModule/PrismaService)
- Deploying NestJS to Railway
Project Bootstrap
npm i -g @nestjs/cli
nest new project-name --package-manager npm --strict
cd project-name
npm install @nestjs/platform-fastify @nestjs/config @nestjs/swagger
npm install zod
npm uninstall @nestjs/platform-express @types/express
Project Structure
src/
main.ts # Bootstrap with Fastify, PORT binding
app.module.ts # Root module
app.controller.ts # Root health check
prisma/
prisma.module.ts # Global Prisma module
prisma.service.ts # Prisma client lifecycle
auth/
auth.module.ts
auth.controller.ts
auth.service.ts
auth.guard.ts
dto/
users/
users.module.ts
users.controller.ts
users.service.ts
dto/
create-user.dto.ts
update-user.dto.ts
common/
guards/
jwt-auth.guard.ts
pipes/
zod-validation.pipe.ts
interceptors/
transform.interceptor.ts
filters/
http-exception.filter.ts
decorators/
current-user.decorator.ts
Bootstrap (main.ts)
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
app.enableCors({
origin: process.env.FRONTEND_URL || 'http://localhost:3000',
credentials: true,
});
const config = new DocumentBuilder()
.setTitle('API')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);
const port = process.env.PORT || 4000;
await app.listen(port, '0.0.0.0');
console.log(`Server running on port ${port}`);
}
bootstrap();
Module Pattern
Root Module
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { PrismaModule } from './prisma/prisma.module';
import { AuthModule } from './auth/auth.module';
import { UsersModule } from './users/users.module';
import { HealthController } from './health/health.controller';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
PrismaModule,
AuthModule,
UsersModule,
],
controllers: [HealthController],
})
export class AppModule {}
Feature Module
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
Prisma Integration
PrismaService
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
PrismaModule
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
Using in Services
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
async findAll() {
return this.prisma.user.findMany();
}
async findOne(id: string) {
const user = await this.prisma.user.findUnique({ where: { id } });
if (!user) throw new NotFoundException(`User ${id} not found`);
return user;
}
async create(data: { email: string; name: string }) {
return this.prisma.user.create({ data });
}
async update(id: string, data: { name?: string }) {
await this.findOne(id);
return this.prisma.user.update({ where: { id }, data });
}
async remove(id: string) {
await this.findOne(id);
return this.prisma.user.delete({ where: { id } });
}
}
Controller Pattern
import { Controller, Get, Post, Put, Delete, Param, Body } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@ApiTags('users')
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
@ApiOperation({ summary: 'List all users' })
findAll() {
return this.usersService.findAll();
}
@Get(':id')
@ApiOperation({ summary: 'Get user by ID' })
findOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
@Post()
@ApiOperation({ summary: 'Create user' })
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
@Put(':id')
@ApiOperation({ summary: 'Update user' })
update(@Param('id') id: string, @Body() dto: UpdateUserDto) {
return this.usersService.update(id, dto);
}
@Delete(':id')
@ApiOperation({ summary: 'Delete user' })
remove(@Param('id') id: string) {
return this.usersService.remove(id);
}
}
DTO Validation with Zod
import { z } from 'zod';
import { PipeTransform, BadRequestException } from '@nestjs/common';
// Schema
export const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(255),
});
export type CreateUserDto = z.infer<typeof createUserSchema>;
// Zod Validation Pipe
export class ZodValidationPipe implements PipeTransform {
constructor(private schema: z.ZodSchema) {}
transform(value: unknown) {
const result = this.schema.safeParse(value);
if (!result.success) {
throw new BadRequestException(result.error.flatten());
}
return result.data;
}
}
// Usage in controller
@Post()
create(@Body(new ZodValidationPipe(createUserSchema)) dto: CreateUserDto) {
return this.usersService.create(dto);
}
Health Check
import { Controller, Get } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
@ApiTags('health')
@Controller('health')
export class HealthController {
@Get()
@ApiOperation({ summary: 'Health check' })
check() {
return { status: 'ok', timestamp: new Date().toISOString() };
}
}
Guards
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
@Injectable()
export class JwtAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new UnauthorizedException('No token provided');
}
// Verify token logic here
return true;
}
}
// Apply to controller or route
@UseGuards(JwtAuthGuard)
@Get('profile')
getProfile(@Req() req) {
return req.user;
}
Global Exception Filter
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
import { FastifyReply } from 'fastify';
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<FastifyReply>();
const status = exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message = exception instanceof HttpException
? exception.getResponse()
: 'Internal server error';
response.status(status).send({
statusCode: status,
message: typeof message === 'string' ? message : (message as any).message,
timestamp: new Date().toISOString(),
});
}
}
Environment Configuration
// .env
DATABASE_URL=postgres://postgres:postgres@localhost:5432/myapp
FRONTEND_URL=http://localhost:3000
JWT_SECRET=your-secret-here
PORT=4000
// Accessing in services
import { ConfigService } from '@nestjs/config';
@Injectable()
export class AuthService {
constructor(private config: ConfigService) {}
getJwtSecret() {
return this.config.getOrThrow<string>('JWT_SECRET');
}
}
Railway Deployment
railway.toml
[build]
builder = "nixpacks"
buildCommand = "npx prisma generate && npx prisma migrate deploy && npm run build"
[deploy]
startCommand = "node dist/main.js"
healthcheckPath = "/health"
healthcheckTimeout = 300
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 10
Required Environment Variables
| Variable | Source | Description |
|---|---|---|
DATABASE_URL |
Railway Postgres | Auto-wired from Postgres service |
PORT |
Railway | Auto-provided by Railway |
FRONTEND_URL |
Manual | Frontend domain for CORS |
JWT_SECRET |
Manual | Auth token signing key |
Port Binding
NestJS must listen on 0.0.0.0:$PORT for Railway:
await app.listen(process.env.PORT || 4000, '0.0.0.0');
Package Scripts
{
"scripts": {
"build": "nest build",
"start": "nest start",
"start:dev": "nest start --watch",
"start:prod": "node dist/main.js",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"db:generate": "prisma generate",
"db:migrate": "prisma migrate dev",
"db:migrate:deploy": "prisma migrate deploy",
"db:studio": "prisma studio",
"db:seed": "tsx prisma/seed.ts"
}
}
More from aussiegingersnap/cursor-skills
api-rest
REST API conventions for Next.js App Router with Zod validation and standardized error handling. This skill should be used when creating API routes, implementing CRUD operations, or establishing API patterns for a project.
12auth-better-auth
Better Auth integration for Next.js 16 with Drizzle adapter. This skill should be used when connecting to a Better Auth instance, configuring OAuth providers, or implementing protected routes with proxy.ts.
8ui-principles
Enforce a precise, minimal design system inspired by Linear, Notion, and Stripe. Use this skill when building dashboards, admin interfaces, or any UI that needs Jony Ive-level precision - clean, modern, minimalist with taste. Every pixel matters.
8feature-build
Orchestrator skill for the complete feature development lifecycle. Coordinates 5 phases - task selection, component design, build loop, analytics setup, and commit/documentation. Use when building any new feature or enhancement that requires multiple steps.
6railway
Railway deployment and infrastructure management skill. This skill should be used when deploying applications to Railway, managing Railway services, checking deployment status, viewing logs, configuring environment variables, or troubleshooting Railway deployments.
5skill-creator
Orchestrator skill for creating effective skills. Guides through 6 steps - understanding examples, planning contents with scope assessment, initialization, editing with contracts, packaging, and iteration. Use when creating a new skill or updating an existing skill.
5