CRUD operations aren't always enough. A client needs the "current user", the "latest deployment", the "default payment method" — and this isn't filtering, it's convenient shortcuts. A separate class of requests is domain commands: confirm an order, cancel it, ship it. There are two tools for these: alias segments and action endpoints.
Alias segments: shortcuts instead of an identifier
me — the current user
When a profile can be viewed both by the user themselves and by an administrator with someone else's ID, it's convenient to use a single route GET /users/:id. The string 'me' in this case works as an alias: NestJS passes it into the id parameter, the controller checks the value and substitutes the ID from the token.
@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);
}
}
The question that helps you understand whether you need me: "can an administrator hit this same endpoint with someone else's ID?"
- Yes → a single route
GET /users/:id, withmeas an alias in the parameter. - No → a separate singleton resource
/profilewithout an alias.
A common mistake is to set up @Controller('me') as a separate top-level controller. Don't do that: me is an alias inside UsersController, not a standalone resource.
Another mistake is using me where the endpoint already belongs to the current user by the token context:
// Wrong: /users/me/orders is redundant, orders are already bound to the token
@Get('users/me/orders')
// Right: an orders controller, the ID comes from the token
@Controller('orders')
@Get()
findAll(@CurrentUser() user: Actor) {
return this.ordersService.findByCustomer(user.id);
}
latest, current, next — temporal and ordinal aliases
Here the order in which routes are declared matters. In NestJS a string route must come before a parametric one — otherwise 'latest' will be handled as the value of :id.
@Controller('deployments')
@ApiTags('Deployments')
export class DeploymentsController {
@Get('latest') // declared BEFORE ':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);
}
}
A similar construction for other aliases:
@Get('current') // GET /api/v1/subscriptions/current
@Get('next') // GET /api/v1/invoices/next
@Get('previous') // GET /api/v1/billing-periods/previous
default, primary, active — logical aliases
When there's a "main" one among the resources by some business attribute, the alias names it explicitly:
@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) {}
}
Likewise: 'primary', 'active', 'draft'.
Action endpoints: domain commands
Some operations can't be reduced to CRUD. Confirming an order isn't just changing the status field — it's a domain event with business rules. For such operations you use action endpoints: @Post with a verb in the path segment.
@Controller('orders')
@ApiTags('Orders')
export class OrdersController {
@Post(':id/confirm')
@HttpCode(200)
@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);
}
}
A few rules for action endpoints:
- Segment name — a verb in the infinitive:
confirm,cancel,ship,refund. Not a noun (confirmation), not a participle (confirmed). - Method — always
@Post. Not@Put, not@Patch. - Response code —
@HttpCode(200). The action was performed, but no resource was created, so 201 doesn't fit. - Request body — in
@Body(). If there are no parameters, an empty body is acceptable. operationId— camelCase, belongs to the tag of the parent resource.
An example DTO with action parameters:
export class ShipOrderDto {
@IsString()
trackingNumber: string;
@IsString()
carrier: string;
}
When action, and when PATCH
A simple criterion: are there business rules or a state change through a state machine?
// Changing description — no business rules → PATCH
@Patch(':id')
update(@Param('id') id: string, @Body() dto: UpdateOrderDto) {}
// Changing status through a state machine → Action
@Post(':id/confirm')
@HttpCode(200)
confirm(@Param('id') id: string) {}
An action makes the semantics visible in the logs: POST /orders/123/confirm is clearer than PATCH /orders/123 with { status: 'CONFIRMED' } in the body.
In short
me— an alias in the':id'parameter, only when the endpoint is accessible both "by your own ID" and "by someone else's" (for example, for an administrator).- A separate
@Controller('me')is the wrong approach; the alias lives insideUsersController. - String routes (
'latest','default') are declared before the parametric ones (':id') — otherwise NestJS won't find them. - An action endpoint is
@Post(':id/verb')+@HttpCode(200). The@Patchmethod stays for updating fields without business rules. - The action segment name is a verb in the infinitive; nouns and participles are a common mistake.
Further reading
- URL and resources — path format, controller decorators.
- JSON and response format — response structure for an action.
- OpenAPI and antipatterns —
operationIdfor an action. - Versioning — actions in v2.