Node.js Framework

NestJS

NestJS ist ein meinungsstarkes, von Angular inspiriertes Node.js Framework. Module, Controller und Provider bieten großen Teams eine gemeinsame Architektur und Dependency Injection direkt out-of-the-box.

advanced16 min readUpdated 16. Sept. 2026
cats.controller.ts
ts
// cats.controller.ts
import { Body, Controller, Get, Param, Post } from "@nestjs/common";
import { CatsService } from "./cats.service";
import { CreateCatDto } from "./dto/create-cat.dto";

@Controller("cats")
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get()
  findAll() {
    return this.catsService.findAll();
  }

  @Get(":id")
  findOne(@Param("id") id: string) {
    return this.catsService.findOne(id);
  }

  @Post()
  create(@Body() createCatDto: CreateCatDto) {
    return this.catsService.create(createCatDto);
  }
}
Veröffentlicht
2017
Läuft auf
Express oder Fastify
Sprache
TypeScript
Stil
Opinionated, modular
Kernidee
DI und Decorators
Version
11.x

Warum es wichtig ist

Warum Teams NestJS wählen

Struktur für große Teams

Module, Layer und Decorators geben allen die gleiche Orientierung. Neue Entwickler finden sofort, wo ein Feature implementiert ist, ohne die gesamte Codebasis lesen zu müssen.

Integrierte Dependency Injection

Ein echter Inversion-of-Control Container löst Konstruktoren auf, verwaltet Lebenszyklen und macht jede Abhängigkeit in Tests austauschbar.

Cross-cutting Concerns als Code

Guards, Interceptors, Pipes und Filter handhaben Authentifizierung, Logging, Validierung und Fehler deklarativ auf der richtigen Ebene.

Das Gesamtbild

Module, Provider, Controller

Ein Modul definiert die Feature-Grenze, ein Provider enthält die Logik und ein Controller wandelt HTTP-Anfragen in Methodenaufrufe um.

Module

Grenze

Jedes Feature ist ein Modul, das deklariert, was es importiert, bereitstellt und exportiert, sodass Abhängigkeiten explizit sind.

Provider

Inject

Services, Repositories und Factories sind Provider, die vom Container instanziiert und per Konstruktor injiziert werden.

Controller

Route

Dekorierte Klassen mappen HTTP-Methoden und Pfade auf Handler-Methoden, wobei Parameter durch Decorators extrahiert werden.

HTML5 auf einen Blick

Die NestJS Toolbox

Module

@Module gruppiert Controller und Provider zu einer Feature-Grenze.

Controller

@Controller und @Get verwandeln eine Klasse in einen Satz von Routen.

Provider

@Injectable Klassen werden vom DI-Container aufgelöst und geteilt.

DTOs

Typisierte Request-Shapes, die durch Pipes validiert werden, bevor der Handler ausgeführt wird.

Guards

Entscheiden anhand von Rollen oder Policies, ob eine Anfrage fortgesetzt werden darf.

Interceptors

Umschließen die Handler-Ausführung für Logging, Caching und Response-Mapping.

Der vollständige Leitfaden

NestJS: Alles was Sie wissen müssen

Was ist NestJS?

NestJS ist ein progressives Node.js Framework, das Struktur in serverseitiges JavaScript bringt. Während Express Ihnen lediglich Primitiven bereitstellt und Ihnen die Wahl überlässt, bietet NestJS eine fertige Architektur: Module, Controller, Provider, Decorators und einen Dependency Injection Container. Es lehnt sich stark an Angular sowie an Enterprise-Java-Frameworks an und läuft entweder auf Express oder Fastify.

Dieser „opinionated“ Ansatz ist das eigentliche Wertversprechen. In einem fünfköpfigen Team, das eine große API entwickelt, sind die schwierigsten Probleme selten HTTP-bezogen. Es geht vielmehr um Benennungen, Grenzen, Testing und die Beibehaltung konsistenter Patterns bei wachsender Codebasis. NestJS löst diese Probleme durch Konventionen, die man entweder akzeptiert oder ersetzt. Zudem liefert es das gesamte „Plumbing“ mit – Validierung, Guards, Interceptors, Konfiguration und Testing-Utilities –, sodass Sie diese nicht selbst zusammenbauen müssen.

Module definieren die Grenzen

Alles in NestJS ist in Modulen organisiert. Ein Modul ist eine Klasse, die mit @Module dekoriert ist und ihre Controller, ihre Provider sowie die Exporte für den Rest der Anwendung definiert.

@Module({
  imports: [DatabaseModule],
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}

Über imports werden andere Module eingebunden, deren exportierte Provider benötigt werden. providers sind die Klassen, die dieses Modul besitzt. exports ist die öffentliche API: Nur das, was ein Modul exportiert, kann an anderer Stelle injiziert werden. Dadurch werden Abhängigkeiten explizit – wenn OrdersModule CatsService benötigt, muss es CatsModule importieren, und der compiler-ähnliche Container meldet es Ihnen, wenn etwas nicht aufgelöst werden kann.

Das Root-AppModule importiert jedes Feature-Modul. Feature-Module können in Shared- oder Core-Module gruppiert werden, und @Global() markiert ein Modul, das überall verfügbar sein soll, ohne dass es wiederholt importiert werden muss. Verwenden Sie globale Module sparsam für echte querschnittliche Infrastruktur wie Konfiguration oder Logging.

Controller: Routing über Decorators

Ein Controller bildet HTTP-Requests auf Methoden ab. Der Class-Decorator legt den Basis-Pfad fest, während Method-Decorators das Verb und den Unterpfad definieren.

@Controller("cats")
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get()
  findAll() {
    return this.catsService.findAll();
  }

  @Get(":id")
  findOne(@Param("id") id: string) {
    return this.catsService.findOne(id);
  }

  @Post()
  create(@Body() dto: CreateCatDto) {
    return this.catsService.create(dto);
  }
}

Parameter-Decorators übernehmen die Extraktion: @Param, @Query, @Body, @Headers, @Req und @Res. Der Handler gibt einen einfachen Wert oder ein Promise zurück, welches NestJS mit dem entsprechenden Status als JSON serialisiert. Wirft man eine HttpException wie zum Beispiel NotFoundException, wandelt das Framework diese in eine korrekte Error-Response um.

Controller sollten “thin” bleiben. Sie übersetzen HTTP-Anfragen in einen Methodenaufruf und mehr nicht. Die gesamte Logik gehört in Provider; so bleibt der Controller testbar und die HTTP-Belange isoliert.

Provider und Dependency Injection

Ein Provider ist jede Klasse, die injiziert werden kann. Markiere sie mit @Injectable() und deklariere sie in einem Modul, und der Container erledigt den Rest.

@Injectable()
export class CatsService {
  constructor(private readonly db: DatabaseService) {}

  findAll() {
    return this.db.query("SELECT * FROM cats");
  }
}

Die Injection erfolgt über den Konstruktor und anhand des Typs. Der Container liest die ausgegebenen Metadaten, löst jeden Parameter auf und übergibt die Instanz. Wenn der Wert keine Klasse ist, kannst du auch eine Injection per Token vornehmen, indem du @Inject("CONFIG") zusammen mit einem passenden Provider verwendest.

Provider können durch Factories, Werte und Aliase angepasst werden. Ein Factory-Provider ist der übliche Weg, um Dinge zu injizieren, die ein asynchrones Setup benötigen, wie zum Beispiel eine Datenbankverbindung:

@Module({
  providers: [
    {
      provide: "DATABASE",
      useFactory: async (config: ConfigService) => {
        return createPool(config.getOrThrow("DATABASE_URL"));
      },
      inject: [ConfigService],
    },
  ],
  exports: ["DATABASE"],
})
export class DatabaseModule {}

Auch der Scope ist entscheidend. Der Standard-Singleton-Scope teilt eine einzige Instanz über die gesamte App hinweg, was für zustandslose Services ideal ist. Der REQUEST Scope erstellt eine neue Instanz pro Request und ist nützlich für request-spezifische Kontexte, kostet jedoch Performance, da er den gesamten Dependency-Graph nach oben durchläuft.

DTOs, Pipes und Validierung

Die Validierung von Inputs ist ein zentrales Anliegen. Ein DTO (Data Transfer Object) ist eine Klasse, die die Struktur eines Requests beschreibt und mit class-validator-Regeln dekoriert wird.

export class CreateCatDto {
  @IsString()
  @MinLength(1)
  name: string;

  @IsInt()
  @Min(0)
  age: number;
}

Eine Pipe transformiert oder validiert den Wert, bevor er den Handler erreicht. Aktivieren Sie die integrierte ValidationPipe global, und jedes dekorierte DTO wird automatisch geprüft:

app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
    transform: true,
  }),
);

whitelist entfernt unbekannte Eigenschaften, forbidNonWhitelisted lehnt diese komplett ab und transform konvertiert Payloads in tatsächliche DTO-Instanzen, sodass die Typen zur Laufzeit real sind. Ungültige Inputs erzeugen eine strukturierte 400, ohne dass eine einzige Zeile Validierungscode im Handler geschrieben werden muss. Pipes können zudem pro Parameter oder pro Controller mittels @UsePipes angewendet werden.

Guards, Interceptors und Filter

Diese drei Bausteine decken die meisten querschnittlichen Belange (cross-cutting concerns) ab und werden in einer festgelegten Reihenfolge ausgeführt.

Guards werden vor dem Handler ausgeführt und entscheiden, ob die Anfrage fortgesetzt wird.

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get<string[]>("roles", context.getHandler());
    if (!roles) return true;
    const { user } = context.switchToHttp().getRequest();
    return roles.includes(user?.role);
  }
}

Interceptors umschließen den Handler-Aufruf und können das Ergebnis transformieren, die Zeit messen oder Caching hinzufügen.

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const start = Date.now();
    return next.handle().pipe(
      tap(() => console.log(`took ${Date.now() - start}ms`)),
    );
  }
}

Exception Filter fangen geworfene Fehler ab und formatieren die Antwort. Ohne einen eigenen Filter nutzt NestJS einen sinnvollen Standard; ein benutzerdefinierter Filter sorgt dafür, dass die Fehlerstrukturen über die gesamte API hinweg konsistent bleiben.

@Catch(HttpException)
export class HttpErrorFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const response = host.switchToHttp().getResponse();
    const status = exception.getStatus();
    response.status(status).json({
      statusCode: status,
      message: exception.message,
      timestamp: new Date().toISOString(),
    });
  }
}

Registriere sie global, pro Controller oder pro Route. Globale Guards, Interceptors und Filter sind der Weg, wie eine ausgereifte NestJS-App Authentifizierung, Observability und Fehlerbehandlung zentralisiert.

Konfiguration und Umgebung

Das @nestjs/config Paket kapselt Umgebungsvariablen und macht sie injizierbar. Importiere es einmalig als globales Modul.

@Module({
  imports: [ConfigModule.forRoot({ isGlobal: true })],
})
export class AppModule {}

Injiziere anschließend ConfigService überall dort, wo es benötigt wird:

@Injectable()
export class DatabaseService {
  constructor(private readonly config: ConfigService) {}

  connect() {
    const url = this.config.getOrThrow<string>("DATABASE_URL");
    return createPool(url);
  }
}

Verwende für erforderliche Werte vorzugsweise getOrThrow, damit eine fehlende Variable bereits beim Start und nicht erst bei der ersten Anfrage zu einem Fehler führt. Kombiniere dies mit einem Validierungsschema, sodass die Konfiguration einmalig geprüft wird, und halte Secrets vollständig aus dem Repository fern.

Testen mit TestingModule

Die Testing-Utilities von NestJS erstellen einen echten Container in Isolation. Dabei deklarieren Sie nur die zu testenden Provider und überschreiben deren Abhängigkeiten anschließend mit Mocks.

describe("CatsController", () => {
  let controller: CatsController;
  let service: CatsService;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      controllers: [CatsController],
      providers: [CatsService],
    }).compile();

    controller = module.get(CatsController);
    service = module.get(CatsService);
  });

  it("returns all cats", () => {
    jest.spyOn(service, "findAll").mockReturnValue([]);
    expect(controller.findAll()).toEqual([]);
  });
});

Für End-to-End-Tests bauen Sie die vollständige App auf und verwenden supertest gegen app.getHttpServer(). Da derselbe DI-Container genutzt wird wie in der Produktion, prüfen diese Tests Guards, Pipes und Filter exakt so, wie sie im Deployment ausgeführt werden.

Best Practices

  • Halten Sie Controller schlank und lagern Sie die gesamte Logik in injectable Services aus.
  • Ziehen Sie Modulgrenzen um Features und exportieren Sie nur das, was andere Module benötigen.
  • Validieren Sie jede Anfrage mit einem DTO und einem globalen ValidationPipe.
  • Nutzen Sie Guards für die Authentifizierung und Autorisierung sowie Interceptors für die Gestaltung von Antworten.
  • Registrieren Sie globale Exception Filter, damit Fehlerantworten ein einheitliches Format haben.
  • Injizieren Sie Konfigurationen, anstatt process.env direkt auszulesen.
  • Bevorzugen Sie den Singleton Scope; nutzen Sie den Request Scope nur, wenn es wirklich notwendig ist.
  • Testen Sie mit TestingModule und überschreiben Sie Abhängigkeiten, anstatt echte Services aufzurufen.

Häufige Fehler

  • Provider mit new instanziieren, anstatt sie zu injizieren, was den Container beschädigt.
  • Vergessen, einen Provider zu einem Modul hinzuzufügen, und anschließend einen “Nest can’t resolve dependencies”-Fehler suchen.
  • Ein Modul importieren, aber vergessen, den Provider, den es teilen soll, zu export.
  • Business-Logik in Controllern platzieren, wodurch diese nicht mehr unit-testbar sind.
  • Überall Request Scope verwenden und so unbemerkt die Performance verschlechtern.
  • @Body() ohne ein DTO und eine Validation Pipe vertrauen.
  • Die Fehlerformatierung an verschiedenen Stellen verteilen, anstatt sie zentral in einem Filter zu bündeln.
  • Module als bloße Ordner betrachten, anstatt sie als Dependency Boundaries zu behandeln.

Wie geht es weiter?

NestJS ist der strukturierteste Weg, um ein Node.js Backend zu bauen, und basiert direkt auf Express oder Fastify. Lesen Sie diese Guides, um die HTTP-Layer zu verstehen, die Sie hier abstrahieren. Fundierte TypeScript-Kenntnisse sind essenziell, da Decorators und Dependency Injection stark auf Typen setzen, und die Node.js basics erklären die Runtime, auf der alles läuft. Bauen Sie anschließend ein Feature-Modul komplett end-to-end: Controller, Service, DTO und Test.

In der Praxis

Die vier Dateien eines Features

Ein Controller routet, ein Service enthält die Logik, ein Modul verbindet sie und ein DTO definiert sowie validiert den Input.

cats.controller.ts
import {
  Body, Controller, Delete, Get, Param, Post,
} from "@nestjs/common";
import { CatsService } from "./cats.service";
import { CreateCatDto } from "./dto/create-cat.dto";

@Controller("cats")
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get()
  findAll() {
    return this.catsService.findAll();
  }

  @Get(":id")
  findOne(@Param("id") id: string) {
    return this.catsService.findOne(id);
  }

  @Post()
  create(@Body() dto: CreateCatDto) {
    return this.catsService.create(dto);
  }

  @Delete(":id")
  remove(@Param("id") id: string) {
    return this.catsService.remove(id);
  }
}

Abhängigkeiten beziehen

Constructor Injection erlaubt es dem Container, den Service zu erstellen und zu teilen, und ermöglicht es Tests, einen Mock zu verwenden. Die manuelle Instanziierung macht den Sinn des Frameworks zunichte.

Bevorzugt
@Controller("cats")
export class CatsController {
  constructor(private readonly catsService: CatsService) {}
}
Vermeiden
@Controller("cats")
export class CatsController {
  // Bypasses DI, breaks the lifecycle and
  // cannot be replaced in a test.
  private catsService = new CatsService();
}

Input validieren

Ein DTO mit class-validator Decorators plus eine globale ValidationPipe lehnt fehlerhafte Anfragen bereits vor dem Handler ab und liefert konsistente 400-Antworten.

Bevorzugt
export class CreateCatDto {
  @IsString()
  @MinLength(1)
  name: string;

  @IsInt()
  age: number;
}

@Post()
create(@Body() dto: CreateCatDto) {
  return this.catsService.create(dto);
}
Vermeiden
@Post()
create(@Body() body: any) {
  if (typeof body.name !== "string") {
    throw new BadRequestException();
  }
  // every handler repeats the same checks
}

Abwägungen

Ist NestJS der richtige Standard?

NestJS tauscht Freiheit gegen Struktur. Die Konventionen zahlen sich aus, wenn die Codebasis wächst, können sich aber bei kleinen Projekten schwerfällig anfühlen.

Strengths

  • Struktur vom ersten Tag an

    Module, Controller und Provider geben jedem Feature einen festen Platz, sodass ein wachsendes Team eine gemeinsame Architektur nutzt, statt ständig neue zu erfinden.

  • Integrierte Dependency Injection

    Ein echter Container verwaltet Lebenszyklen und macht jede Abhängigkeit austauschbar, was Services ohne globalen State testbar hält.

  • Batteries included

    Validierung, Guards, Interceptors, Konfiguration und Testing-Utilities sind im Framework enthalten, sodass man deutlich weniger selbst bauen muss.

Trade-offs

  • Viel Boilerplate

    Decorators, Module und DTOs führen zu mehr Dateien und Konzepten, bevor der erste Endpoint funktioniert, wodurch kleine Services überkonstruiert wirken können.

  • Übernahme der Framework-Meinung

    NestJS gibt vor, wie Code organisiert wird. Gegen diese Konventionen zu arbeiten, ist kostspieliger, als ein leichteres Framework zu wählen.

  • TypeScript wird vorausgesetzt

    Die Decorators und die Dependency Injection basieren auf Typen und Metadaten, weshalb ein reines JavaScript-Team eine steile Lernkurve vor sich hat.

Häufig gestellte Fragen

Häufig gestellte Fragen

Keep learning

Related topics from the roadmap.

$ Lernen Sie jetzt

Bereit, NestJS zu lernen?

Unser interaktives Tutorial führt Sie Schritt für Schritt durch NestJS — mit Quizzen und echtem Code, den Sie im Browser ausführen können.