Scaffold a production-ready NestJS 10+ API with TypeScript strict mode, modular architecture, validation, Swagger docs, and database integration.
78
72%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Optimize this skill with Tessl
npx tessl skill review --optimize ./backend-node/nestjs-project-starter/SKILL.mdScaffold a production-ready NestJS 10+ API with TypeScript strict mode, modular architecture, validation, Swagger docs, and database integration.
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/nodepnpm add @nestjs/typeorm typeorm pgpnpm add @prisma/client
pnpm add -D prisma
npx prisma initsrc/
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 templateapp.module.tsclass-validator decorators@nestjs/config with registerAs for typed, namespaced configurationstrict and strictNullChecks in tsconfig.jsonmain.ts, not per-controllermain.tsimport { 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();config/app.config.tsimport { 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/create-user.dto.tsimport { 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;
}users.controller.tsimport {
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);
}
}users.service.tsimport { 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);
}
}entities/user.entity.tsimport {
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;
}guards/jwt-auth.guard.tsimport { Injectable, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
return super.canActivate(context);
}
}interceptors/logging.interceptor.tsimport {
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`);
}),
);
}
}filters/http-exception.filter.tsimport {
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(),
});
}
}users.module.tsimport { 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 {}.env.example to .env and fill in valuespnpm installpnpm start:devcurl http://localhost:3000/api/docs# 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@nestjs/passport + @nestjs/jwt for JWT authentication. Passport strategies live inside the auth module.@nestjs/cache-manager with Redis for production caching.@nestjs/bullmq for background jobs (email sending, report generation).@nestjs/websockets + @nestjs/platform-socket.io for real-time features.@nestjs/testing Test.createTestingModule to build isolated test modules with mocked providers.node:20-alpine for build, node:20-alpine for runtime with only dist/ and node_modules/.181fcbc
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.