Опирается на правила:
R-ALIAS-1..3,R-ACT-1..4и X-коды из REST API Style Guide → раздел Alias и Action-эндпоинты.
Важно знать
me— только когда эндпоинт принимает и свой, и чужой ID (admin scope).- Граница
me: «может ли супер-админ обратиться по другому ID?» Да →meнужен.- Alias-параметр
':id'в NestJS может принять строку'me'— маршруты сортируются по специфичности.- Временные alias (
latest,current) — отдельный@Get('latest')до@Get(':id').- Action —
@Post(':id/confirm')+@HttpCode(200); имя — глагол в инфинитиве.@ApiOperation({ operationId })action-метода относится к тегу родительского ресурса./api/v1/meбезusers/префикса — запрещён.
CRUD не покрывает все доменные операции. NestJS реализует alias через порядок объявления маршрутов и action-эндпоинты через @Post с вложенным сегментом.
Alias-сегменты
me
R-ALIAS-1: в эндпоинтах, где admin может обратиться по чужому ID.
@Controller('users')
@ApiTags('Users')
export class UsersController {
@Get(':id')
@ApiOperation({ operationId: 'getUser', summary: 'Get user by id or alias' })
findOne(@Param('id') id: string, @CurrentUser() actor: Actor) {
const resolvedId = id === 'me' ? actor.id : id;
return this.usersService.findOne(resolvedId);
}
}
Маршрут @Get(':id') принимает и 550e8400-... и строку 'me'. Логика разрешения alias — в use case, не в контроллере.
Граничный тест: «может ли супер-админ обратиться по другому ID к этому же эндпоинту?»
- Да →
GET /users/:idиGET /users/me— один маршрут, alias в параметре. - Нет → singleton (
/profile),meизбыточен.
Временные и порядковые alias
R-ALIAS-2: отдельный маршрут до параметрического.
@Controller('deployments')
@ApiTags('Deployments')
export class DeploymentsController {
@Get('latest') // ✓ — объявлен ДО ':id', иначе NestJS матчит 'latest' как id
@ApiOperation({ operationId: 'getLatestDeployment', summary: 'Get latest deployment' })
findLatest() {
return this.deploymentsService.findLatest();
}
@Get(':id')
@ApiOperation({ operationId: 'getDeployment', summary: 'Get deployment by id' })
findOne(@Param('id') id: string) {
return this.deploymentsService.findOne(id);
}
}
Порядок объявления — критичен в NestJS: строковый маршрут ('latest') должен идти перед параметрическим (':id').
Другие примеры:
@Get('current') // GET /api/v1/subscriptions/current
@Get('next') // GET /api/v1/invoices/next
@Get('previous') // GET /api/v1/billing-periods/previous
Логические alias
R-ALIAS-3: по бизнес-признаку.
@Controller('payment-methods')
@ApiTags('PaymentMethods')
export class PaymentMethodsController {
@Get('default')
@ApiOperation({ operationId: 'getDefaultPaymentMethod', summary: 'Get default payment method' })
findDefault(@CurrentUser() user: Actor) {
return this.paymentMethodsService.findDefault(user.id);
}
@Get(':id')
findOne(@Param('id') id: string) {}
}
Аналогично: 'primary', 'active', 'draft'.
Запреты для me
R-ALIAS-X1: me где не нужен.
// ✗ — orders уже из контекста токена
@Get('users/me/orders')
// ✓ — заказы текущего пользователя
@Controller('orders')
@Get()
findAll(@CurrentUser() user: Actor) {
return this.ordersService.findByCustomer(user.id);
}
R-ALIAS-X2: me без users/:
// ✗ отдельный контроллер /api/v1/me
@Controller('me')
export class MeController {}
// ✓ — alias внутри UsersController
@Controller('users')
@Get(':id')
findOne(@Param('id') id: string) { /* 'me' через alias */ }
Action-эндпоинты
R-ACT-1..4:
@Controller('orders')
@ApiTags('Orders')
export class OrdersController {
@Post(':id/confirm')
@HttpCode(200) // action возвращает 200, не 201
@ApiOperation({ operationId: 'confirmOrder', summary: 'Confirm order' })
confirm(@Param('id') id: string) {
return this.ordersService.confirm(id);
}
@Post(':id/cancel')
@HttpCode(200)
@ApiOperation({ operationId: 'cancelOrder', summary: 'Cancel order' })
cancel(
@Param('id') id: string,
@Body() dto: CancelOrderDto,
) {
return this.ordersService.cancel(id, dto.reason);
}
@Post(':id/ship')
@HttpCode(200)
@ApiOperation({ operationId: 'shipOrder', summary: 'Ship order' })
ship(
@Param('id') id: string,
@Body() dto: ShipOrderDto,
) {
return this.ordersService.ship(id, dto);
}
}
Параметры action-эндпоинта:
- Имя — глагол в инфинитиве (
confirm,cancel,ship,refund). - Метод — всегда
@Post+@HttpCode(200). - Параметры — в
@Body(). Если параметров нет — пустое тело допустимо. operationIdв camelCase относится к тегу родителя (Orders).
ShipOrderDto с параметрами действия:
export class ShipOrderDto {
@IsString()
trackingNumber: string;
@IsString()
carrier: string;
}
Когда action vs PATCH
// Меняем description — нет бизнес-правил → PATCH
@Patch(':id')
update(@Param('id') id: string, @Body() dto: UpdateOrderDto) {}
// Меняем status через state-machine → Action
@Post(':id/confirm')
@HttpCode(200)
confirm(@Param('id') id: string) {}
Action делает семантику явной в логах: POST /orders/123/confirm вместо PATCH /orders/123.
Что запрещено
| Антипаттерн | Правило | Что взамен |
|---|---|---|
me где endpoint singleton | R-ALIAS-X1 | singleton без alias |
@Controller('me') без users/ | R-ALIAS-X2 | alias в UsersController |
@Get(':id') до @Get('latest') | R-ALIAS-2 | строковый маршрут первым |
@Post(':id/confirmation') существительное | R-ACT-X1 | 'confirm' |
@Post(':id/confirmed') причастие | R-ACT-X1 | 'confirm' |
@Put(':id/confirm') | R-ACT-X2 | @Post |
@Patch(':id') { status: 'CONFIRMED' } для команды | R-ACT-1 | @Post(':id/confirm') |
Action без @HttpCode(200) | R-RSP-6 | @HttpCode(200) обязателен |
Куда дальше
- URL и ресурсы — формат пути, декораторы контроллеров.
- JSON и формат ответов — 200 + обновлённый ресурс для action.
- OpenAPI и антипаттерны —
operationIdдля action. - Версионирование — action в v2.
- REST API Style Guide (нормативно) — формулировки.