Path Parameters
Path parameters are segments of the URL that carry variable values — for example the id in /customers/42. OPRA extracts, coerces, and validates them before your handler runs, exactly like query parameters.
Declaring a path parameter
Define the placeholder in the operation's path option using :name syntax, then declare the parameter with .PathParam():
import { HttpController, HttpOperation } from '@opra/common';
import { HttpContext } from '@opra/http';
@HttpController({ path: 'customers' })
export class CustomersController {
@(HttpOperation.GET({ path: ':id' })
.PathParam('id', { type: 'integer' })
.Response(200, { type: Customer }))
async getOne(ctx: HttpContext) {
const { id } = ctx.pathParams;
// id: number (coerced from the URL string)
}
@(HttpOperation.DELETE({ path: ':id' })
.PathParam('id', { type: 'integer' })
.Response(204))
async delete(ctx: HttpContext) {
const { id } = ctx.pathParams;
}
}
Path parameters are always required. A URL cannot match a route unless every placeholder is filled, so OPRA marks path parameters required by default — you cannot make them optional.
Controller-level path parameters
When a path parameter appears in every operation of a controller (for example a resource key like :id), declare it once on the controller instead of repeating it on each operation:
@HttpController({ path: 'customers/:id' })
@HttpController.PathParam('id', { type: 'integer' })
export class CustomerController {
@(HttpOperation.GET()
.Response(200, { type: Customer }))
async getOne(ctx: HttpContext) {
const { id } = ctx.pathParams; // available on every operation
}
@(HttpOperation.PATCH()
.Response(200, { type: Customer }))
async update(ctx: HttpContext) {
const { id } = ctx.pathParams;
}
@(HttpOperation.DELETE()
.Response(204))
async delete(ctx: HttpContext) {
const { id } = ctx.pathParams;
}
}
KeyParam — resource identifier shorthand
@HttpController.KeyParam() is a convenience decorator for the common case of a single primary-key parameter. It does two things at once:
- Appends
@:nameto the controller path (e.g.customers→customers/:id) - Registers the parameter with
keyParam: trueso OPRA knows it is the entity identifier
@HttpController({ path: 'customers' })
@HttpController.KeyParam('id', { type: 'integer' })
export class CustomerController {
// Effective path: customers/:id
// ctx.pathParams.id is automatically available as a number
@(HttpOperation.GET()
.Response(200, { type: Customer }))
async getOne(ctx: HttpContext) {
const { id } = ctx.pathParams;
}
@(HttpOperation.PATCH()
.Response(200, { type: Customer }))
async update(ctx: HttpContext) {
const { id } = ctx.pathParams;
}
}
Use KeyParam when the controller represents a single resource addressed by one primary key. Use PathParam for all other cases, including composite keys or nested resources.
Options
| Option | Type | Default | Description |
|---|---|---|---|
type | string | Type | — | OPRA type name ('string', 'integer', 'uuid', …) or a class. Controls coercion and validation. |
required | boolean | true | Always true for path params — cannot be overridden. |
default | any | — | Not applicable for path parameters. |
deprecated | boolean | string | — | Marks the parameter deprecated in the API schema. |
parser | (v: any) => any | — | Custom post-processing function applied after type coercion. |
Type coercion
Path parameter values arrive as strings from the URL. OPRA coerces them to the declared type before calling your handler:
@(HttpOperation.GET({ path: ':id' })
.PathParam('id', { type: 'integer' })
.Response(200, { type: Customer }))
async getOne(ctx: HttpContext) {
const { id } = ctx.pathParams;
// /customers/42 → id === 42 (number)
// /customers/abc → 400 Bad Request (not a valid integer)
}
@(HttpOperation.GET({ path: ':slug' })
.PathParam('slug', { type: 'string' })
.Response(200, { type: Article }))
async getBySlug(ctx: HttpContext) {
const { slug } = ctx.pathParams;
// /articles/hello-world → slug === 'hello-world'
}
Validation
If the value cannot be coerced to the declared type, OPRA rejects the request with a 400 BadRequestError before calling your handler:
@(HttpOperation.GET({ path: ':id' })
.PathParam('id', 'uuid') // shorthand: second arg can be a type name string
.Response(200, { type: Customer }))
async getOne(ctx: HttpContext) {
const { id } = ctx.pathParams;
// /customers/not-a-uuid → 400 Bad Request
// /customers/550e8400-e29b-... → id is the UUID string
}
Multiple path parameters
Declare each segment independently:
@HttpController({ path: 'orgs/:orgId/projects/:projectId/tasks' })
@HttpController.PathParam('orgId', { type: 'integer' })
@HttpController.PathParam('projectId', { type: 'integer' })
export class TasksController {
@(HttpOperation.GET()
.Response(200, { type: ArrayType(Task) }))
async list(ctx: HttpContext) {
const { orgId, projectId } = ctx.pathParams;
// Both are numbers, both were validated before reaching here
}
}
Reading parameters from context
Decoded path parameters are available on ctx.pathParams:
const { id, slug } = ctx.pathParams;
To access the raw, unparsed path segment, read ctx.request.params directly (this is the raw Express params object, before OPRA coercion):
const rawId = ctx.request.params['id']; // always a string