1. NestJS Setup — Module, Controller, Service & Dependency Injection
NestJS ist das führende TypeScript-Backend-Framework für Node.js und bringt Angular-ähnliche Architektur in die Server-Welt. Claude Code versteht die Dekorator-basierte Struktur nativ und generiert saubere, testbare Module in Sekunden.
Projekt anlegen
Mit nest new entsteht eine vollständige Projektstruktur inklusive Webpack, ESLint und Jest:
# NestJS CLI global installieren
npm install -g @nestjs/cli
# Neues Projekt erstellen
nest new enterprise-api --package-manager npm
# In Projektverzeichnis wechseln
cd enterprise-api
# Ressource generieren (Module + Controller + Service in einem)
nest generate resource users --no-spec
nest generate resource products --no-spec
nest generate resource orders --no-spec
Projektstruktur verstehen
NestJS erzeugt eine klare, skalierbare Struktur die Claude Code sofort erkennt und erweitert:
src/
├── app.module.ts # Root-Modul
├── main.ts # Bootstrap
├── users/
│ ├── users.module.ts
│ ├── users.controller.ts
│ ├── users.service.ts
│ └── dto/
│ ├── create-user.dto.ts
│ └── update-user.dto.ts
└── shared/
├── filters/
├── guards/
└── interceptors/
Root-Modul und Dependency Injection
Das AppModule verbindet alle Feature-Module. NestJS' IoC-Container löst Abhängigkeiten automatisch auf — @Injectable() macht jeden Service injizierbar:
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { UsersModule } from './users/users.module';
import { ProductsModule } from './products/products.module';
import { PrismaModule } from './prisma/prisma.module';
import { AuthModule } from './auth/auth.module';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
PrismaModule,
AuthModule,
UsersModule,
ProductsModule,
],
})
export class AppModule {}
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { EmailService } from '../shared/email.service';
@Module({
controllers: [UsersController],
providers: [UsersService, EmailService],
exports: [UsersService], // für andere Module verfügbar
})
export class UsersModule {}
// Service mit @Injectable() — wird vom IoC-Container verwaltet
@Injectable()
export class UsersService {
constructor(
private readonly prisma: PrismaService,
private readonly email: EmailService,
private readonly config: ConfigService,
) {}
async findAll(): Promise<User[]> {
return this.prisma.user.findMany({
where: { isActive: true },
select: { id: true, email: true, name: true, role: true },
});
}
}
2. REST-API — Controller, Decorators und DTOs mit class-validator
NestJS-Controller definieren API-Endpunkte über Dekoratoren. Claude Code generiert typsichere DTOs mit class-validator-Annotationen, die Request-Bodies automatisch validieren.
Controller mit allen HTTP-Methoden
import {
Controller, Get, Post, Put, Delete, Patch,
Param, Body, Query, ParseIntPipe,
HttpCode, HttpStatus, UseGuards,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { PaginationDto } from '../shared/dto/pagination.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { RolesGuard } from '../auth/guards/roles.guard';
import { Roles } from '../auth/decorators/roles.decorator';
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard)
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll(@Query() pagination: PaginationDto) {
return this.usersService.findAll(pagination);
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
@Post()
@Roles('admin')
@HttpCode(HttpStatus.CREATED)
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Put(':id')
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateUserDto: UpdateUserDto,
) {
return this.usersService.update(id, updateUserDto);
}
@Delete(':id')
@Roles('admin')
@HttpCode(HttpStatus.NO_CONTENT)
remove(@Param('id', ParseIntPipe) id: number) {
return this.usersService.remove(id);
}
}
DTOs mit class-validator
Data Transfer Objects validieren eingehende Daten automatisch. Claude Code generiert vollständige DTOs inklusive aller Validierungsregeln:
import {
IsEmail, IsString, IsEnum, IsOptional,
MinLength, MaxLength, IsStrongPassword,
} from 'class-validator';
import { Transform } from 'class-transformer';
export enum UserRole {
ADMIN = 'admin',
USER = 'user',
MODERATOR = 'moderator',
}
export class CreateUserDto {
@IsEmail({}, { message: 'Ungültige E-Mail-Adresse' })
@Transform(({ value }) => value.toLowerCase().trim())
email: string;
@IsString()
@MinLength(2)
@MaxLength(100)
name: string;
@IsStrongPassword({
minLength: 8,
minUppercase: 1,
minNumbers: 1,
minSymbols: 1,
})
password: string;
@IsEnum(UserRole)
@IsOptional()
role?: UserRole = UserRole.USER;
}
// main.ts — globale Validierungspipeline aktivieren
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // Unbekannte Felder entfernen
forbidNonWhitelisted: true, // Fehler bei unbekannten Feldern
transform: true, // Typen automatisch casten
}));
await app.listen(3000);
}
transform: true castet NestJS URL-Parameter automatisch zum korrekten Typ. @Param('id', ParseIntPipe) wird dann redundant — bleibt aber als explizite Dokumentation sinnvoll.
3. Guards und Interceptors — Auth, Logging und Response Transformation
Guards entscheiden ob ein Request weiterverarbeitet wird. Interceptors transformieren Requests und Responses. Claude Code erzeugt beide Patterns inklusive JWT-Integration und strukturierter Logs.
JwtAuthGuard
import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
return super.canActivate(context);
}
handleRequest(err: any, user: any) {
if (err || !user) {
throw err || new UnauthorizedException('Nicht authentifiziert');
}
return user;
}
}
// Roles Guard — prüft Benutzerrolle
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) return true;
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some(role => user.roles?.includes(role));
}
}
Logging und Transform Interceptors
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<any> {
const req = context.switchToHttp().getRequest();
const { method, url, ip } = req;
const start = Date.now();
return next.handle().pipe(
tap({
next: () => {
const ms = Date.now() - start;
this.logger.log(`${method} ${url} — ${ms}ms [${ip}]`);
},
error: (err) => {
const ms = Date.now() - start;
this.logger.error(`${method} ${url} — ${ms}ms — ${err.message}`);
},
}),
);
}
}
// TransformInterceptor — standardisiertes API-Response-Format
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, ApiResponse<T>> {
intercept(_ctx: ExecutionContext, next: CallHandler): Observable<ApiResponse<T>> {
return next.handle().pipe(
map(data => ({
success: true,
data,
timestamp: new Date().toISOString(),
})),
);
}
}
app.useGlobalInterceptors(new LoggingInterceptor(), new TransformInterceptor()) — alle Routen werden automatisch geloggt und in ein einheitliches Format gewrappt.
4. Prisma-Integration — PrismaService, PrismaModule und Transactions
Prisma ist das bevorzugte ORM für NestJS-Projekte 2026. Claude Code versteht das Prisma-Schema und generiert typsichere Service-Methoden inklusive Transaction-Handling.
PrismaModule erstellen
npm install @prisma/client prisma
npx prisma init --datasource-provider postgresql
# Schema definieren
npx prisma migrate dev --name init
npx prisma generate
import { Injectable, OnModuleInit, OnModuleDestroy, Logger } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
private readonly logger = new Logger(PrismaService.name);
async onModuleInit() {
await this.$connect();
this.logger.log('Datenbankverbindung hergestellt');
}
async onModuleDestroy() {
await this.$disconnect();
this.logger.log('Datenbankverbindung getrennt');
}
// Transaktion-Helper für komplexe Operationen
async executeTransaction<T>(
fn: (prisma: Omit<PrismaClient, symbol>) => Promise<T>
): Promise<T> {
return this.$transaction(fn);
}
}
// PrismaModule — global verfügbar
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
Service mit Prisma und Transactions
@Injectable()
export class OrdersService {
constructor(private readonly prisma: PrismaService) {}
// Bestellung + Inventar-Update in einer Transaktion
async createOrder(dto: CreateOrderDto): Promise<Order> {
return this.prisma.executeTransaction(async (tx) => {
// 1. Verfügbarkeit prüfen
const product = await tx.product.findUniqueOrThrow({
where: { id: dto.productId },
});
if (product.stock < dto.quantity) {
throw new BadRequestException('Nicht genug auf Lager');
}
// 2. Bestellung erstellen
const order = await tx.order.create({
data: {
userId: dto.userId,
productId: dto.productId,
quantity: dto.quantity,
total: product.price * dto.quantity,
status: 'PENDING',
},
});
// 3. Inventar aktualisieren
await tx.product.update({
where: { id: dto.productId },
data: { stock: { decrement: dto.quantity } },
});
return order;
});
}
async findAll(userId: number, pagination: PaginationDto) {
const [orders, total] = await this.prisma.$transaction([
this.prisma.order.findMany({
where: { userId },
skip: (pagination.page - 1) * pagination.limit,
take: pagination.limit,
include: { product: true },
orderBy: { createdAt: 'desc' },
}),
this.prisma.order.count({ where: { userId } }),
]);
return { orders, total, pages: Math.ceil(total / pagination.limit) };
}
}
5. Exception-Filter — HTTP-Fehler, Custom Exceptions und globale Handler
NestJS Exception Filter fangen Fehler ab und transformieren sie in strukturierte HTTP-Responses. Claude Code generiert vollständige Filter-Hierarchien inklusive Custom Exception Classes.
Globaler Exception Filter
import {
ExceptionFilter, Catch, ArgumentsHost,
HttpException, HttpStatus, Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(HttpExceptionFilter.name);
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
const exceptionResponse = exception.getResponse();
const errorBody = {
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
method: request.method,
message: typeof exceptionResponse === 'string'
? exceptionResponse
: (exceptionResponse as any).message,
};
if (status >= 500) {
this.logger.error(JSON.stringify(errorBody), exception.stack);
} else {
this.logger.warn(JSON.stringify(errorBody));
}
response.status(status).json(errorBody);
}
}
// Custom Domain Exceptions
export class UserNotFoundException extends HttpException {
constructor(id: number) {
super({ message: `Benutzer mit ID ${id} nicht gefunden`, error: 'USER_NOT_FOUND' }, HttpStatus.NOT_FOUND);
}
}
export class InsufficientStockException extends HttpException {
constructor(productId: number, available: number) {
super({
message: `Produkt ${productId}: Nur noch ${available} verfügbar`,
error: 'INSUFFICIENT_STOCK',
}, HttpStatus.CONFLICT);
}
}
// Prisma-spezifischer Filter für Datenbankfehler
@Catch(PrismaClientKnownRequestError)
export class PrismaExceptionFilter implements ExceptionFilter {
catch(exception: PrismaClientKnownRequestError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
switch (exception.code) {
case 'P2002': // Unique constraint
response.status(409).json({ error: 'DUPLICATE_ENTRY', field: exception.meta?.target });
break;
case 'P2025': // Record not found
response.status(404).json({ error: 'NOT_FOUND' });
break;
default:
response.status(500).json({ error: 'DATABASE_ERROR' });
}
}
}
6. Microservices — MessagePattern, ClientProxy, TCP/Redis und CQRS
NestJS unterstützt verschiedene Microservice-Transporter nativ. Claude Code generiert Message Handler, ClientProxy-Konfigurationen und CQRS-Grundstrukturen für event-driven Architekturen.
Microservice Bootstrap
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
// TCP Microservice
const tcpApp = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options: { host: '0.0.0.0', port: 3001 },
},
);
// Redis Microservice (für Event-driven)
const redisApp = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.REDIS,
options: {
host: process.env.REDIS_HOST || 'localhost',
port: 6379,
retryAttempts: 5,
retryDelay: 1000,
},
},
);
await Promise.all([tcpApp.listen(), redisApp.listen()]);
}
MessagePattern Handler und ClientProxy
import { Controller, Inject } from '@nestjs/common';
import { MessagePattern, EventPattern, Payload, ClientProxy } from '@nestjs/microservices';
import { firstValueFrom } from 'rxjs';
@Controller()
export class NotificationsController {
// MessagePattern — Request/Response
@MessagePattern({ cmd: 'send_email' })
async sendEmail(@Payload() data: EmailPayload) {
const { to, subject, template, context } = data;
await this.mailService.send({ to, subject, template, context });
return { sent: true, to, timestamp: new Date() };
}
// EventPattern — Fire and Forget
@EventPattern('user.registered')
async onUserRegistered(@Payload() event: UserRegisteredEvent) {
await this.mailService.sendWelcome(event.email);
await this.analyticsService.track('signup', { userId: event.userId });
}
}
// Client in einem anderen Service nutzen
@Injectable()
export class UsersService {
constructor(
@Inject('NOTIFICATIONS_SERVICE')
private readonly client: ClientProxy,
) {}
async registerUser(dto: CreateUserDto) {
const user = await this.prisma.user.create({ data: dto });
// Asynchrones Event senden
this.client.emit('user.registered', { userId: user.id, email: user.email });
// Synchrone Anfrage mit Antwort
const result = await firstValueFrom(
this.client.send({ cmd: 'send_email' }, {
to: user.email,
subject: 'Willkommen bei SpockyMagicAI!',
template: 'welcome',
})
);
return { user, emailSent: result.sent };
}
}
CQRS-Grundlagen mit @nestjs/cqrs
import { CommandHandler, ICommandHandler, EventBus } from '@nestjs/cqrs';
// Command — Intention ausdrücken
export class CreateUserCommand {
constructor(public readonly dto: CreateUserDto) {}
}
// Command Handler — Business-Logik kapseln
@CommandHandler(CreateUserCommand)
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
constructor(
private readonly prisma: PrismaService,
private readonly eventBus: EventBus,
) {}
async execute({ dto }: CreateUserCommand) {
const user = await this.prisma.user.create({
data: { ...dto, password: await hash(dto.password, 12) },
});
this.eventBus.publish(new UserCreatedEvent(user.id, user.email));
return user;
}
}
// Query — Leseoperation
export class GetUserQuery {
constructor(public readonly id: number) {}
}
@QueryHandler(GetUserQuery)
export class GetUserHandler implements IQueryHandler<GetUserQuery> {
async execute({ id }: GetUserQuery) {
return this.prisma.user.findUniqueOrThrow({ where: { id } });
}
}
// Im Controller — CommandBus und QueryBus nutzen
@Post()
async create(@Body() dto: CreateUserDto) {
return this.commandBus.execute(new CreateUserCommand(dto));
}