Skip to main content

NestJS

NestJS ist ein Framework für serverseitige Node.js-Anwendungen. Es basiert auf TypeScript und kombiniert Konzepte aus Angular, Express und objektorientierten Designmustern.

Hauptmerkmale

  • TypeScript Unterstützung
  • Modulare Architektur
  • Dependency Injection
  • Decorator-basiert
  • REST, GraphQL, WebSockets
  • Integration mit TypeORM

Installation

npm install -g @nestjs/cli
nest new mein-projekt
cd mein-projekt
npm run start:dev

Projektstruktur

src/
├── app.module.ts
├── app.controller.ts
├── app.service.ts
└── main.ts

Module

Module gruppieren zusammengehörige Teile der Applikation.

import { Module } from "@nestjs/common";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";

@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}

Controller

Controller nehmen HTTP-Anfragen entgegen und geben Antworten zurück.

import {
Controller,
Get,
Post,
Body,
Param,
Delete,
Put,
} from "@nestjs/common";
import { UsersService } from "./users.service";

@Controller("users")
export class UsersController {
constructor(private readonly usersService: UsersService) {}

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

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

@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}

@Put(":id")
update(@Param("id") id: string, @Body() updateUserDto: UpdateUserDto) {
return this.usersService.update(+id, updateUserDto);
}

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

Services

Services enthalten die Geschäftslogik. Controller rufen Services auf.

import { Injectable, NotFoundException } from "@nestjs/common";

@Injectable()
export class UsersService {
private users = [{ id: 1, name: "Arlind", email: "arlind@bekjiri.ch" }];

findAll() {
return this.users;
}

findOne(id: number) {
const user = this.users.find((u) => u.id === id);
if (!user) throw new NotFoundException(`User ${id} nicht gefunden`);
return user;
}

create(dto: CreateUserDto) {
const user = { id: Date.now(), ...dto };
this.users.push(user);
return user;
}

update(id: number, dto: UpdateUserDto) {
const user = this.findOne(id);
Object.assign(user, dto);
return user;
}

remove(id: number) {
const index = this.users.findIndex((u) => u.id === id);
this.users.splice(index, 1);
}
}

DTOs

DTOs definieren die Form der Daten die rein- und rauskommen.

export class CreateUserDto {
name: string;
email: string;
age: number;
}

export class UpdateUserDto {
name?: string;
email?: string;
age?: number;
}

Validation mit class-validator

npm install class-validator class-transformer
import { IsString, IsEmail, IsInt, Min } from "class-validator";

export class CreateUserDto {
@IsString()
name: string;

@IsEmail()
email: string;

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

In main.ts die globale Validierung aktivieren:

import { ValidationPipe } from "@nestjs/common";

async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}

Mit TypeORM kombinieren

npm install @nestjs/typeorm typeorm pg
// app.module.ts
import { TypeOrmModule } from "@nestjs/typeorm";

@Module({
imports: [
TypeOrmModule.forRoot({
type: "postgres",
host: "localhost",
port: 5432,
username: "postgres",
password: "passwort",
database: "meine_app",
entities: [__dirname + "/**/*.entity{.ts,.js}"],
synchronize: true,
}),
],
})
export class AppModule {}

Entity definieren

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";

@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;

@Column()
name: string;

@Column({ unique: true })
email: string;
}

Repository im Service verwenden

import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";

@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}

findAll() {
return this.usersRepository.find();
}

findOne(id: number) {
return this.usersRepository.findOneBy({ id });
}

create(dto: CreateUserDto) {
const user = this.usersRepository.create(dto);
return this.usersRepository.save(user);
}

async remove(id: number) {
await this.usersRepository.delete(id);
}
}

Ressourcen mit CLI generieren

nest generate module users
nest generate controller users
nest generate service users
nest generate resource users

Guards

import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";

@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
return request.headers.authorization === "mein-token";
}
}

@UseGuards(AuthGuard)
@Get("geschuetzt")
geschuetzt() {
return "Nur mit Token erreichbar";
}

Pipes

@Get(":id")
findOne(@Param("id", ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}

Interceptors

import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from "@nestjs/common";
import { Observable, tap } from "rxjs";

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

Exceptions

import {
NotFoundException,
BadRequestException,
UnauthorizedException,
ForbiddenException,
} from "@nestjs/common";

throw new NotFoundException("User nicht gefunden");
throw new BadRequestException("Ungültige Eingabe");
throw new UnauthorizedException("Nicht eingeloggt");
throw new ForbiddenException("Keine Berechtigung");

Best Practices

  • Geschäftslogik in Services, nicht in Controllern
  • DTOs für Ein- und Ausgaben verwenden
  • synchronize: true nur in der Entwicklung, in Produktion Migrationen
  • Module nach Feature aufteilen
  • ValidationPipe global aktivieren

Ressourcen