CtrlK
BlogDocsLog inGet started
Tessl Logo

nestjs-project-starter

Scaffold a production-ready NestJS 10+ API with TypeScript strict mode, modular architecture, validation, Swagger docs, and database integration.

78

Quality

72%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Optimize this skill with Tessl

npx tessl skill review --optimize ./backend-node/nestjs-project-starter/SKILL.md
SKILL.md
Quality
Evals
Security

NestJS Project Starter

Scaffold a production-ready NestJS 10+ API with TypeScript strict mode, modular architecture, validation, Swagger docs, and database integration.

Prerequisites

  • Node.js >= 20.x
  • npm >= 10.x (or pnpm >= 9.x)
  • TypeScript >= 5.3
  • PostgreSQL (if using TypeORM/Prisma)

Scaffold Command

npx @nestjs/cli@latest new <project-name> --strict --package-manager pnpm
cd <project-name>
pnpm add @nestjs/config @nestjs/swagger class-validator class-transformer
pnpm add -D @types/node

Optional — TypeORM

pnpm add @nestjs/typeorm typeorm pg

Optional — Prisma

pnpm add @prisma/client
pnpm add -D prisma
npx prisma init

Project Structure

src/
  app.module.ts              # Root module — imports all feature modules
  main.ts                    # Bootstrap, Swagger setup, global pipes
  common/
    decorators/              # Custom decorators (e.g. @CurrentUser)
    filters/                 # Exception filters (HttpExceptionFilter)
    guards/                  # Auth guards (JwtAuthGuard, RolesGuard)
    interceptors/            # Logging, transform, timeout interceptors
    pipes/                   # Custom validation pipes
    dto/                     # Shared DTOs (PaginationDto, etc.)
  config/
    app.config.ts            # Typed config via @nestjs/config registerAs
    database.config.ts
  modules/
    users/
      users.module.ts
      users.controller.ts
      users.service.ts
      dto/
        create-user.dto.ts
        update-user.dto.ts
      entities/
        user.entity.ts       # TypeORM entity or Prisma model reference
      users.controller.spec.ts
      users.service.spec.ts
    auth/
      auth.module.ts
      auth.controller.ts
      auth.service.ts
      guards/
        jwt-auth.guard.ts
      strategies/
        jwt.strategy.ts
test/
  app.e2e-spec.ts
.env.example                    # Required env vars template

Key Conventions

  • One module per domain feature — never dump everything in app.module.ts
  • DTOs for every request body — validated via class-validator decorators
  • Entities live next to their module, not in a shared folder
  • Services contain business logic; controllers are thin routing layers
  • Use @nestjs/config with registerAs for typed, namespaced configuration
  • Enable strict and strictNullChecks in tsconfig.json
  • Global validation pipe set once in main.ts, not per-controller
  • Guards for authentication, Interceptors for cross-cutting concerns, Pipes for transformation/validation

Essential Patterns

Bootstrap with Swagger and Global Pipes — main.ts

import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Global validation — strips unknown properties, transforms payloads to DTO instances
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
      transformOptions: { enableImplicitConversion: true },
    }),
  );

  // Swagger
  const config = new DocumentBuilder()
    .setTitle('API')
    .setVersion('1.0')
    .addBearerAuth()
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api/docs', app, document);

  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

Typed Configuration — config/app.config.ts

import { registerAs } from '@nestjs/config';

export const appConfig = registerAs('app', () => ({
  port: parseInt(process.env.PORT ?? '3000', 10),
  environment: process.env.NODE_ENV ?? 'development',
  jwtSecret: process.env.JWT_SECRET,
}));

// Usage in a service:
// constructor(@Inject(appConfig.KEY) private config: ConfigType<typeof appConfig>) {}

DTO with Validation and Swagger — dto/create-user.dto.ts

import { IsEmail, IsString, MinLength } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class CreateUserDto {
  @ApiProperty({ example: 'user@example.com' })
  @IsEmail()
  email: string;

  @ApiProperty({ minLength: 8 })
  @IsString()
  @MinLength(8)
  password: string;

  @ApiProperty({ example: 'Jane Doe' })
  @IsString()
  name: string;
}

Controller — users.controller.ts

import {
  Controller, Get, Post, Body, Param, Patch, Delete,
  ParseUUIDPipe, HttpCode, HttpStatus, UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';

@ApiTags('users')
@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  @HttpCode(HttpStatus.CREATED)
  @ApiOperation({ summary: 'Create a user' })
  create(@Body() dto: CreateUserDto) {
    return this.usersService.create(dto);
  }

  @Get(':id')
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth()
  findOne(@Param('id', ParseUUIDPipe) id: string) {
    return this.usersService.findOne(id);
  }

  @Patch(':id')
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth()
  update(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: UpdateUserDto,
  ) {
    return this.usersService.update(id, dto);
  }

  @Delete(':id')
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth()
  @HttpCode(HttpStatus.NO_CONTENT)
  remove(@Param('id', ParseUUIDPipe) id: string) {
    return this.usersService.remove(id);
  }
}

Service — users.service.ts

import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly usersRepo: Repository<User>,
  ) {}

  async create(dto: CreateUserDto): Promise<User> {
    const user = this.usersRepo.create(dto);
    return this.usersRepo.save(user);
  }

  async findOne(id: string): Promise<User> {
    const user = await this.usersRepo.findOneBy({ id });
    if (!user) throw new NotFoundException(`User ${id} not found`);
    return user;
  }

  async update(id: string, dto: UpdateUserDto): Promise<User> {
    const user = await this.findOne(id);
    Object.assign(user, dto);
    return this.usersRepo.save(user);
  }

  async remove(id: string): Promise<void> {
    const user = await this.findOne(id);
    await this.usersRepo.remove(user);
  }
}

TypeORM Entity — entities/user.entity.ts

import {
  Entity, PrimaryGeneratedColumn, Column,
  CreateDateColumn, UpdateDateColumn,
} from 'typeorm';

@Entity('users')
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

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

  @Column()
  name: string;

  @Column({ select: false })
  password: string;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
}

Guard — guards/jwt-auth.guard.ts

import { Injectable, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  canActivate(context: ExecutionContext) {
    return super.canActivate(context);
  }
}

Interceptor — interceptors/logging.interceptor.ts

import {
  Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger,
} from '@nestjs/common';
import { Observable, tap } from 'rxjs';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  private readonly logger = new Logger(LoggingInterceptor.name);

  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const req = context.switchToHttp().getRequest();
    const { method, url } = req;
    const start = Date.now();

    return next.handle().pipe(
      tap(() => {
        this.logger.log(`${method} ${url} — ${Date.now() - start}ms`);
      }),
    );
  }
}

Exception Filter — filters/http-exception.filter.ts

import {
  ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Logger,
} from '@nestjs/common';
import { Response } from 'express';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  private readonly logger = new Logger(AllExceptionsFilter.name);

  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const res = ctx.getResponse<Response>();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const message =
      exception instanceof HttpException
        ? exception.getResponse()
        : 'Internal server error';

    this.logger.error(exception);

    res.status(status).json({
      statusCode: status,
      ...(typeof message === 'string' ? { message } : (message as object)),
      timestamp: new Date().toISOString(),
    });
  }
}

Module Wiring — users.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { User } from './entities/user.entity';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

First Steps After Scaffold

  1. Copy .env.example to .env and fill in values
  2. Install dependencies: pnpm install
  3. Start dev server: pnpm start:dev
  4. Test the API: curl http://localhost:3000/api/docs

Common Commands

# Development
pnpm start:dev

# Production build
pnpm build && pnpm start:prod

# Unit tests
pnpm test

# E2E tests
pnpm test:e2e

# Test coverage
pnpm test:cov

# Lint
pnpm lint

# Generate a new resource (module + controller + service + DTOs)
npx nest g resource modules/<name> --no-spec

# Database migrations (TypeORM)
npx typeorm migration:generate src/migrations/<name> -d src/data-source.ts
npx typeorm migration:run -d src/data-source.ts

# Prisma
npx prisma migrate dev --name <name>
npx prisma generate
npx prisma studio

Integration Notes

  • Auth: Pair with @nestjs/passport + @nestjs/jwt for JWT authentication. Passport strategies live inside the auth module.
  • Database: TypeORM for Active Record / Data Mapper patterns; Prisma for type-safe query builder. Pick one per project, not both.
  • Caching: Use @nestjs/cache-manager with Redis for production caching.
  • Queues: @nestjs/bullmq for background jobs (email sending, report generation).
  • WebSockets: @nestjs/websockets + @nestjs/platform-socket.io for real-time features.
  • Testing: Use @nestjs/testing Test.createTestingModule to build isolated test modules with mocked providers.
  • Docker: Use multi-stage build — node:20-alpine for build, node:20-alpine for runtime with only dist/ and node_modules/.
Repository
achreftlili/deep-dev-skills
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.