Node.js & Backend

NestJS mit Claude Code: Enterprise Node.js 2026

Module, Controller, Guards, Interceptors, Prisma und Microservices — der vollständige Enterprise-Guide.

📅 6. Mai 2026 ⏱ 12 min Lesezeit 🏷 NestJS · TypeScript · Enterprise
← Zurück zum Blog

Was du in diesem Guide lernst

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:

bashTerminal
# 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:

textProjektstruktur
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:

typescriptsrc/app.module.ts
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 {}
typescriptsrc/users/users.module.ts
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 },
    });
  }
}
Claude Code Prompt-Tip "Generiere ein NestJS UsersModule mit PrismaService, ConfigService und EmailService — alle via Constructor Injection. Nutze @Injectable() scope DEFAULT."

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

typescriptsrc/users/users.controller.ts
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:

typescriptsrc/users/dto/create-user.dto.ts
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);
}
Tip: ValidationPipe mit transform: true Mit 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

typescriptsrc/auth/guards/jwt-auth.guard.ts
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

typescriptsrc/shared/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<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(),
      })),
    );
  }
}
Global registrieren in main.ts 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

bashTerminal
npm install @prisma/client prisma
npx prisma init --datasource-provider postgresql

# Schema definieren
npx prisma migrate dev --name init
npx prisma generate
typescriptsrc/prisma/prisma.service.ts
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

typescriptsrc/orders/orders.service.ts
@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

typescriptsrc/shared/filters/http-exception.filter.ts
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' });
    }
  }
}
Claude Code Workflow "Erstelle einen globalen ExceptionFilter der HttpException, PrismaClientKnownRequestError und unbekannte Fehler abfängt — strukturiertes JSON-Format mit statusCode, timestamp, path und message."

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

typescriptsrc/main.microservice.ts
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

typescriptsrc/notifications/notifications.controller.ts
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

typescriptsrc/users/commands/create-user.command.ts
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));
}
CQRS Vorteile Command/Query Responsibility Segregation trennt Schreib- und Leseoperationen. Ideal für komplexe Domains mit vielen Business-Regeln. Claude Code generiert vollständige CQRS-Stacks inklusive Event Sourcing auf Anfrage.

NestJS Enterprise-Apps mit Claude Code

Starte deinen kostenlosen Trial und lass Claude Code deine NestJS-Architektur automatisch aufbauen — Module, Guards, Prisma-Schema und Microservices inklusive.

Kostenlos testen →
NestJS TypeScript Node.js Prisma Microservices CQRS Guards Interceptors REST-API Claude Code Enterprise