Урок 04

Контроллеры

Что такое контроллер

Контроллер — отвечает за обработку HTTP-запросов. Принимает запрос → вызывает сервис → возвращает ответ.

Создание

nest g controller users

Что создаст:

import { Controller } from '@nestjs/common';

@Controller('users')
export class UsersController {}

'users' — префикс URL.

Базовый контроллер

import { Controller, Get } from '@nestjs/common';

@Controller('users')
export class UsersController {
    @Get()
    findAll(): string {
        return 'Все пользователи';
    }
}

GET /users — вернёт строку.

HTTP-методы

import {
    Controller, Get, Post, Put, Patch, Delete,
} from '@nestjs/common';

@Controller('users')
export class UsersController {
    @Get()
    findAll() { return []; }

    @Get(':id')
    findOne(@Param('id') id: string) { return { id }; }

    @Post()
    create(@Body() data: any) { return data; }

    @Put(':id')
    update(@Param('id') id: string, @Body() data: any) { return data; }

    @Patch(':id')
    partialUpdate(@Param('id') id: string, @Body() data: any) { return data; }

    @Delete(':id')
    remove(@Param('id') id: string) { return { deleted: id }; }
}

Полный CRUD.

Декораторы параметров

@Param() — параметры маршрута:

@Get(':id')
findOne(@Param('id') id: string) {}

@Get(':userId/posts/:postId')
findPost(
    @Param('userId') userId: string,
    @Param('postId') postId: string,
) {}

@Get(':id')
findAll(@Param() params: { id: string }) {}

@Query() — query-параметры:

@Get()
findAll(
    @Query('page') page: string,
    @Query('limit') limit: string,
) {}

@Get()
findAll(@Query() query: { page: string; limit: string }) {}

@Body() — тело запроса:

@Post()
create(@Body() data: CreateUserDto) {}

@Post()
create(@Body('email') email: string) {}

@Headers() — заголовки:

@Get()
findAll(@Headers('authorization') auth: string) {}

@Get()
findAll(@Headers() headers: Record<string, string>) {}

@Req(), @Res() — объекты запроса/ответа:

@Get()
findAll(@Req() req: Request) {
    return { ip: req.ip };
}

⚠️ @Res() отключает встроенную обработку — используй осторожно.

Динамические маршруты

Параметры:

@Get(':id')
findOne(@Param('id') id: string) {}

@Get(':userId/posts')
findUserPosts(@Param('userId') userId: string) {}

Опциональные:

@Get('search')
search(@Query('q') q?: string) {}

Wildcard:

@Get('files/*')
getFile(@Req() req: Request) {}

Статусы

@HttpCode():

@Post()
@HttpCode(201)
create() {}

@Delete(':id')
@HttpCode(204)
remove() {}

По умолчанию:

  • GET → 200.
  • POST → 201.
  • DELETE → 200.
  • PUT/PATCH → 200.

Заголовки ответа

@Get()
@Header('Cache-Control', 'none')
findAll() {}

Redirect

@Get('old')
@Redirect('/new', 301)
redirect() {}

Route-specific middleware

Middleware — через модуль. Guards / Interceptors / Pipes — декораторы.

@Get()
@UseGuards(AuthGuard)
@UseInterceptors(LoggingInterceptor)
@UsePipes(ValidationPipe)
findAll() {}

Контроллер + сервис

Правильная структура:

@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() dto: CreateUserDto) {
        return this.usersService.create(dto);
    }

    @Patch(':id')
    update(@Param('id') id: string, @Body() dto: UpdateUserDto) {
        return this.usersService.update(+id, dto);
    }

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

Контроллер тонкий — только HTTP. Логика — в сервисе.

Группировка маршрутов

Префикс:

@Controller('api/v1/users')
export class UsersController {}

Глобальный префикс:

main.ts:

app.setGlobalPrefix('api/v1');

Все роуты будут с префиксом.

Версионирование

URI-версия:

app.enableVersioning({
    type: VersioningType.URI,
    defaultVersion: '1',
});

В контроллере:

@Controller({ path: 'users', version: '2' })

URL: /v2/users.

Валидация параметров

@Param('id', ParseIntPipe):

@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
    // id — уже number
}

@Query('page', DefaultValuePipe(1), ParseIntPipe):

@Get()
findAll(
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
) {}

Ошибка → 400 Bad Request автоматически.

Валидация тела

Через DTO + ValidationPipe:

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

CreateUserDto:

export class CreateUserDto {
    @IsEmail()
    email: string;

    @IsString()
    @MinLength(2)
    name: string;
}

Валидация — на уровне Pipe.

Async-контроллеры

@Get()
async findAll(): Promise<User[]> {
    return await this.usersService.findAll();
}

Автоматически ждёт промис.

Обработка ошибок

Бросай исключения:

@Get(':id')
findOne(@Param('id') id: string) {
    const user = this.usersService.findOne(+id);

    if (!user) {
        throw new NotFoundException(`Пользователь ${id} не найден`);
    }

    return user;
}

Встроенные исключения:

  • BadRequestException — 400.
  • UnauthorizedException — 401.
  • ForbiddenException — 403.
  • NotFoundException — 404.
  • ConflictException — 409.
  • InternalServerErrorException — 500.

NestJS сам преобразует в JSON.

Формат ошибки

{
    "statusCode": 404,
    "message": "Пользователь 1 не найден",
    "error": "Not Found"
}

Практика: полный CRUD

users.controller.ts:

import {
    Controller, Get, Post, Body, Patch, Param,
    Delete, HttpCode, ParseIntPipe, NotFoundException,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

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

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

    @Get(':id')
    findOne(@Param('id', ParseIntPipe) id: number) {
        const user = this.usersService.findOne(id);

        if (!user) {
            throw new NotFoundException(`Пользователь ${id} не найден`);
        }

        return user;
    }

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

    @Patch(':id')
    update(
        @Param('id', ParseIntPipe) id: number,
        @Body() dto: UpdateUserDto,
    ) {
        return this.usersService.update(id, dto);
    }

    @Delete(':id')
    @HttpCode(204)
    remove(@Param('id', ParseIntPipe) id: number) {
        return this.usersService.remove(id);
    }
}

Проверка:

curl http://localhost:3000/users
curl http://localhost:3000/users/1
curl -X POST http://localhost:3000/users \
    -H 'Content-Type: application/json' \
    -d '{"name":"Иван","email":"ivan@example.com"}'

Полезные декораторы

  • @Controller(path) — контроллер.
  • @Get, @Post, @Put, @Patch, @Delete, @Options, @Head — HTTP-методы.
  • @Param, @Query, @Body, @Headers, @Req, @Res, @Session — параметры.
  • @HttpCode, @Header, @Redirect — ответ.
  • @UseGuards, @UseInterceptors, @UsePipes, @UseFilters — обвязка.
  • @SetMetadata — метаданные.

Итог

  • @Controller — контроллер.
  • @Get, @Post, @Patch, @Delete — методы.
  • @Param, @Query, @Body, @Headers — входные данные.
  • @HttpCode, @Header, @Redirect — ответ.
  • ParseIntPipe — валидация параметров.
  • Исключения — NotFoundException и др.
  • Тонкий контроллер — логика в сервисе.
  • Префиксы и версии — через main.ts и @Controller.

Дальше — сервисы и DI.