Skip to content

NestJS Reference: Modules, Controllers, Services, Guards, TypeORM & Testing

NestJS is a Node.js framework built on top of Express (or Fastify) with a strong opinion about structure. It uses TypeScript and decorators to enforce a module/controller/service architecture similar to Angular. The key concepts: Modules group related features, Controllers handle HTTP routing, Services contain business logic and are injected via Dependency Injection. NestJS is the dominant choice when teams want Angular-style structure for their API layer.

1. Modules, Controllers & Services

@Module, @Controller, @Injectable, and the basic request lifecycle
// npm install @nestjs/core @nestjs/common reflect-metadata rxjs
// npm install -g @nestjs/cli
// nest new my-api

// users.module.ts — groups all user-related components:
import { Module } from "@nestjs/common";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";

@Module({
  controllers: [UsersController],
  providers:   [UsersService],
  exports:     [UsersService],  // expose to other modules that import UsersModule
})
export class UsersModule {}

// users.controller.ts — handles HTTP routing:
import { Controller, Get, Post, Body, Param, Delete, HttpCode, HttpStatus } from "@nestjs/common";
import { UsersService } from "./users.service";
import { CreateUserDto } from "./dto/create-user.dto";

@Controller("users")   // route prefix: /users
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get()
  findAll() {
    return this.usersService.findAll();
  }

  @Get(":id")
  findOne(@Param("id") id: string) {
    return this.usersService.findOne(+id);
  }

  @Post()
  @HttpCode(HttpStatus.CREATED)
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }

  @Delete(":id")
  @HttpCode(HttpStatus.NO_CONTENT)
  remove(@Param("id") id: string) {
    return this.usersService.remove(+id);
  }
}

// users.service.ts — business logic:
import { Injectable, NotFoundException } from "@nestjs/common";

@Injectable()
export class UsersService {
  private users = [];

  findAll() { return this.users; }

  findOne(id: number) {
    const user = this.users.find(u => u.id === id);
    if (!user) throw new NotFoundException(`User #${id} not found`);
    return user;
  }

  create(dto: CreateUserDto) { /* ... */ }
  remove(id: number) { /* ... */ }
}

2. Pipes, DTOs & Validation

class-validator DTOs, ValidationPipe, ParseIntPipe, and transformation
// npm install class-validator class-transformer

// create-user.dto.ts — defines and validates request shape:
import { IsEmail, IsString, MinLength, IsOptional, IsEnum } from "class-validator";
import { Transform } from "class-transformer";

export class CreateUserDto {
  @IsString()
  @MinLength(2)
  name: string;

  @IsEmail()
  @Transform(({ value }) => value.toLowerCase())  // normalize on intake
  email: string;

  @IsOptional()
  @IsEnum(["admin", "user", "moderator"])
  role?: string;
}

// Enable globally in main.ts:
import { ValidationPipe } from "@nestjs/common";
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,        // strip unknown properties (never trust client)
    forbidNonWhitelisted: true,  // throw 400 if unknown fields present
    transform: true,        // auto-transform payload to DTO class instances
    transformOptions: { enableImplicitConversion: true },
  }));
  await app.listen(3000);
}

// ParseIntPipe — transform route param to number:
@Get(":id")
findOne(@Param("id", ParseIntPipe) id: number) {
  return this.usersService.findOne(id);
}

// Other built-in pipes:
// ParseUUIDPipe — validates UUID format
// ParseBoolPipe — "true"/"false" strings → boolean
// DefaultValuePipe — provide fallback for optional params

// Partial update DTO — all fields optional:
import { PartialType } from "@nestjs/mapped-types";
export class UpdateUserDto extends PartialType(CreateUserDto) {}

3. Guards, Interceptors & Middleware

JwtAuthGuard, RolesGuard, response transformation interceptors, and logging middleware
// Guard — controls access (runs before route handler):
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";

@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(private jwtService: JwtService) {}

  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const token = request.headers.authorization?.split(" ")[1];
    if (!token) return false;
    try {
      request.user = this.jwtService.verify(token, { secret: process.env.JWT_SECRET });
      return true;
    } catch { return false; }
  }
}

// Apply guard to route or controller:
@UseGuards(JwtAuthGuard)
@Get("profile")
getProfile(@Request() req) { return req.user; }

// Custom decorator — extract current user:
export const CurrentUser = createParamDecorator(
  (_, ctx: ExecutionContext) => ctx.switchToHttp().getRequest().user
);
@Get("profile")
getProfile(@CurrentUser() user: User) { return user; }

// Interceptor — transform response, add logging:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from "@nestjs/common";
import { Observable } from "rxjs";
import { map, tap } from "rxjs/operators";

@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      map(data => ({ data, timestamp: new Date().toISOString() }))
    );
  }
}

// Middleware — runs before guards (like Express middleware):
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(`${req.method} ${req.url}`);
    next();
  }
}
// Apply in module:
configure(consumer: MiddlewareConsumer) {
  consumer.apply(LoggerMiddleware).forRoutes("*");
}

4. Database with TypeORM or Prisma

@nestjs/typeorm entity setup, repository pattern, and @nestjs/config for env vars
// npm install @nestjs/typeorm typeorm pg @nestjs/config

// app.module.ts — wire up database + config:
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { ConfigModule, ConfigService } from "@nestjs/config";

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),  // process.env accessible everywhere
    TypeOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        type: "postgres",
        host:     config.get("DB_HOST"),
        port:     config.get<number>("DB_PORT"),
        username: config.get("DB_USER"),
        password: config.get("DB_PASS"),
        database: config.get("DB_NAME"),
        entities:    [__dirname + "/**/*.entity{.ts,.js}"],
        synchronize: config.get("NODE_ENV") !== "production",  // NEVER in prod
        autoLoadEntities: true,
      }),
    }),
  ],
})
export class AppModule {}

// user.entity.ts:
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from "typeorm";

@Entity("users")
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  email: string;

  @Column()
  name: string;

  @Column({ default: "user" })
  role: string;

  @CreateDateColumn()
  createdAt: Date;
}

// users.module.ts — register entity:
@Module({ imports: [TypeOrmModule.forFeature([User])], ... })
export class UsersModule {}

// users.service.ts — use Repository:
@Injectable()
export class UsersService {
  constructor(@InjectRepository(User) private repo: Repository<User>) {}

  findAll() { return this.repo.find(); }
  findOne(id: number) { return this.repo.findOneBy({ id }); }
  create(dto: CreateUserDto) { return this.repo.save(this.repo.create(dto)); }
  async update(id: number, dto: UpdateUserDto) {
    await this.repo.update(id, dto);
    return this.findOne(id);
  }
  remove(id: number) { return this.repo.delete(id); }
}

5. Exception Filters, Testing & CLI

Custom exception filters, unit testing services, e2e testing, and nest CLI commands
// Global exception filter — consistent error response shape:
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from "@nestjs/common";

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx      = host.switchToHttp();
    const response = ctx.getResponse();
    const status   = exception.getStatus();
    const message  = exception.getResponse();
    response.status(status).json({
      statusCode: status,
      message:    typeof message === "string" ? message : (message as any).message,
      timestamp:  new Date().toISOString(),
    });
  }
}
// main.ts: app.useGlobalFilters(new HttpExceptionFilter());

// Unit test a service (no HTTP layer):
describe("UsersService", () => {
  let service: UsersService;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [
        UsersService,
        { provide: getRepositoryToken(User), useValue: {
          find: jest.fn(), findOneBy: jest.fn(), save: jest.fn(), delete: jest.fn(),
        }},
      ],
    }).compile();
    service = module.get<UsersService>(UsersService);
  });

  it("should throw NotFoundException for missing user", async () => {
    jest.spyOn(service, "findOne").mockResolvedValue(null);
    await expect(service.findOne(999)).rejects.toThrow(NotFoundException);
  });
});

// nest CLI:
nest new my-api                     # scaffold project
nest g module users                 # generate module
nest g controller users --no-spec   # controller without spec file
nest g service users                # injectable service
nest g guard auth/jwt-auth          # guard
nest g filter http-exception        # exception filter
nest g pipe validation              # custom pipe
nest build                          # compile to dist/
nest start --watch                  # dev mode (ts-node-dev)

Track Node.js and framework releases at ReleaseRun. Related: Express Reference | FastAPI Reference | TypeScript Reference | Nodejs EOL Tracker

🔍 Free tool: npm Package Health Checker — check NestJS packages — @nestjs/core, @nestjs/typeorm, passport — for known CVEs and active maintenance.

Founded

2023 in London, UK

Contact

hello@releaserun.com