Опирается на правила:
R-PRIN-1..4,R-URL-1..3,R-RES-1..3,R-MTH-1..6,R-NEST-1..4из REST API Style Guide → раздел URL и ресурсы.
Важно знать
- 4 принципа: предсказуемость, единообразие, читаемость, стабильность.
- URL — публичный контракт: изменение = breaking change, требует
v2.- HATEOAS-ссылки в теле запрещены — навигация описывается в OpenAPI.
setGlobalPrefix('api')+VersioningType.URI→/api/v1/...из коробки.- NestJS не редиректит trailing slash — не объявлять пути со слешем.
- Коллекции — множественное число (
/orders), singleton — единственное (/profile).- HTTP-методы через декораторы;
@HttpCode(204)на@Delete,@HttpCode(200)на action@Post.- Максимум 2 уровня вложенности. Глубже — flat resource с filter.
REST URL — первая документация API. Разработчик угадывает GET /orders/:id/items по аналогии с GET /orders. NestJS реализует правила через декораторы контроллеров и глобальную конфигурацию.
Глобальная конфигурация
R-VER-1..3 и R-URL-3:
// main.ts
import { VersioningType } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api');
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: '1',
});
await app.listen(3000);
}
Результат: все контроллеры автоматически получают префикс /api/v1/.... Служебные эндпоинты — вне этой цепочки:
@Controller('health')
export class HealthController {
@Get()
check() { return { status: 'ok' }; }
}
// → GET /health (без /api, без версии)
Формат пути
R-URL-1..3:
@Controller('order-items') // ✓ kebab-case, строчные
export class OrderItemsController {}
@Controller('OrderItems') // ✗ заглавные
@Controller('order_items') // ✗ snake_case
@Controller('orderItems') // ✗ camelCase
NestJS не добавляет trailing slash сам — не объявлять пути вида 'orders/'.
@Get(':id') // ✓ /api/v1/orders/:id
@Get(':id/') // ✗ trailing slash
@Get('list.json') // ✗ расширение
@Get('getAll') // ✗ глагол
Ресурсы
R-RES-1..3:
@Controller('orders') // ✓ коллекция — множественное
export class OrdersController {}
@Controller('products') // ✓
export class ProductsController {}
@Controller('order') // ✗ единственное для коллекции
export class OrderController {}
Singleton-ресурс (один на пользователя):
@Controller('profile') // ✓ singleton — единственное
export class ProfileController {
@Get() // GET /api/v1/profile
get() {}
}
Имя ресурса — доменный термин из Ubiquitous Language:
Order→/orders, не/purchases,/transactions.DeliveryAddress→/delivery-addresses, не/shipping-info.Customer→/customers, не/users,/clients.
HTTP-методы
R-MTH-1..6 через декораторы:
@Controller('orders')
export class OrdersController {
@Get() // GET /api/v1/orders → 200
findAll() {}
@Get(':id') // GET /api/v1/orders/:id → 200
findOne(@Param('id') id: string) {}
@Post() // POST /api/v1/orders → 201 (дефолт NestJS)
create(@Body() dto: CreateOrderDto) {}
@Put(':id') // PUT /api/v1/orders/:id → 200
replace(@Param('id') id: string, @Body() dto: ReplaceOrderDto) {}
@Patch(':id') // PATCH /api/v1/orders/:id → 200
update(@Param('id') id: string, @Body() dto: UpdateOrderDto) {}
@Delete(':id')
@HttpCode(204) // DELETE → 204 No Content (дефолт NestJS — 200, нужен @HttpCode)
remove(@Param('id') id: string) {}
}
| Декоратор | Метод | Дефолт NestJS | @HttpCode нужен? |
|---|---|---|---|
@Get | GET | 200 | нет |
@Post создание | POST | 201 | нет |
@Put / @Patch | PUT/PATCH | 200 | нет |
@Delete | DELETE | 200 | да, 204 |
@Post action | POST | 201 | да, 200 |
R-MTH-X1 — GET без побочных эффектов:
@Get(':id/cancel') // ✗ GET с side-effect
cancel() {}
@Post(':id/cancel') // ✓
@HttpCode(200)
cancel() {}
Вложенность
R-NEST-1..4:
@Controller('orders')
export class OrdersController {
@Get(':orderId/items') // ✓ 2 уровня
getItems(@Param('orderId') orderId: string) {}
@Get(':orderId/items/:itemId') // ✓ 2 уровня + id
getItem(
@Param('orderId') orderId: string,
@Param('itemId') itemId: string,
) {}
}
Path-параметры в коде именуются уникально (orderId, itemId) — требование Swagger/Redoc. В дизайн-документации используется {id}. Подробнее — OpenAPI и антипаттерны, правило R-OAS-3.
Глубже двух уровней — flat resource с filter:
// ✗ 3 уровня
@Get(':userId/orders/:orderId/items/:itemId')
// ✓ flat с filter
@Get() // GET /api/v1/items?orderId=...
findAll(@Query('orderId') orderId: string) {}
ID в теле вместо пути — запрещён:
@Put(':id') // ✓
replace(@Param('id') id: string) {}
@Put() // ✗ — id в body вместо path
replace(@Body() dto: { id: string }) {}
Что запрещено
| Антипаттерн | Правило | Что взамен |
|---|---|---|
HATEOAS _links в теле ответа | R-PRIN-X1 | OpenAPI описывает навигацию |
@Controller('OrderItems') | R-URL-X1 | 'order-items' |
@Get('orders/') trailing slash | R-URL-X2 | без слеша |
@Get('orders.json') | R-URL-X3 | Accept: application/json |
@Get('getOrders') глагол | R-URL-X4 | @Get() |
@Controller('order') для коллекции | R-RES-X1 | 'orders' |
@Get(':id') — GET с side-effect | R-MTH-X1 | @Post(':id/action') |
| Вложенность более 2 уровней | R-NEST-X1 | flat с filter |
ID в body вместо @Param | R-NEST-X2 | path-параметр |
Куда дальше
- Версионирование —
enableVersioning,/api/v1→/api/v2. - Alias и Action-эндпоинты —
me,latest, доменные команды в NestJS. - Query-параметры — DTO-классы,
@Query, пагинация. - OpenAPI и антипаттерны —
operationId, именование path-параметров. - REST API Style Guide (нормативно) — формулировки правил.