Scaffold a production-ready NestJS 10+ API with TypeScript strict mode, modular architecture, validation, Swagger docs, and database integration.
62
72%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No known issues
Fix and improve this skill with Tessl
tessl review fix ./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.