Routing & Handlers
Every endpoint in Moost starts with an HTTP method decorator and a route path. This page covers how to define routes, use parameters, and control which methods your handlers respond to.
HTTP method decorators
Moost provides a decorator for each HTTP method:
| Decorator | HTTP Method |
|---|---|
@Get(path?) | GET |
@Post(path?) | POST |
@Put(path?) | PUT |
@Delete(path?) | DELETE |
@Patch(path?) | PATCH |
@All(path?) | All methods |
@Upgrade(path?) | UPGRADE (WebSocket handshake) |
All decorators are imported from @moostjs/event-http.
@Upgrade(path?) (equivalently @HttpMethod('UPGRADE', ...)) registers a WebSocket-upgrade handler. See the WebSocket docs for completing the handshake.
import { Get, Post, Put, Delete, Patch } from '@moostjs/event-http'
import { Controller, Param } from 'moost'
@Controller('users')
export class UserController {
@Get('')
list() {
return [] // GET /users
}
@Get(':id')
find(@Param('id') id: string) {
return { id } // GET /users/123
}
@Post('')
create() {
return { created: true } // POST /users
}
@Delete(':id')
remove(@Param('id') id: string) {
return { deleted: id } // DELETE /users/123
}
}Path defaults
The path argument is optional. When omitted, the method name becomes the path:
@Get()
getUsers() { /* GET /getUsers */ }
@Get('')
root() { /* GET / (root of controller) */ }
@Get('list')
getUsers() { /* GET /list */ }TIP
Use @Get('') (empty string) to handle the controller's root path. Omitting the argument entirely uses the method name as the path segment.
HEAD, OPTIONS, and custom methods
For HTTP methods without a convenience decorator, use @HttpMethod:
import { HttpMethod } from '@moostjs/event-http'
@HttpMethod('HEAD', 'health')
healthCheck() { /* HEAD /health */ }
@HttpMethod('OPTIONS', '')
cors() { /* OPTIONS / */ }Route parameters
Dynamic segments in a route are prefixed with :. Use @Param('name') to extract them.
@Get('users/:id')
getUser(@Param('id') id: string) {
return { id }
}Multiple parameters
Parameters can be separated by slashes or hyphens:
// Slash-separated: /flights/SFO/LAX
@Get('flights/:from/:to')
getFlight(
@Param('from') from: string,
@Param('to') to: string,
) { /* ... */ }
// Hyphen-separated: /dates/2024-01-15
@Get('dates/:year-:month-:day')
getDate(
@Param('year') year: string,
@Param('month') month: string,
@Param('day') day: string,
) { /* ... */ }Regex-constrained parameters
Restrict a parameter's shape with a regex pattern in parentheses:
// Only matches two-digit hours and minutes: /time/09h30m
@Get('time/:hours(\\d{2})h:minutes(\\d{2})m')
getTime(
@Param('hours') hours: string,
@Param('minutes') minutes: string,
) { /* ... */ }Repeated parameters (arrays)
When the same parameter name appears multiple times, the value becomes an array:
// /rgb/255/128/0 → color = ['255', '128', '0']
@Get('rgb/:color/:color/:color')
getRgb(@Param('color') color: string[]) { /* ... */ }All parameters at once
Use @Params() to get every route parameter as an object:
@Get('asset/:type/:type/:id')
getAsset(@Params() params: { type: string[], id: string }) {
return params
}Wildcards
An asterisk (*) matches zero or more characters, including / — @Get('*') on a controller prefixed static matches everything under /static/, however deep.
@Controller('static')
export class StaticController {
// Matches /static/anything/here
@Get('*')
handleAll(@Param('*') path: string) { /* ... */ }
// Matches /static/bundle.js, /static/vendor.js
@Get('*.js')
handleJS(@Param('*') path: string) { /* ... */ }
// Multiple wildcards → array
@Get('*/test/*')
handleTest(@Param('*') paths: string[]) { /* ... */ }
// Regex on wildcard: only digits
@Get('*(\\d+)')
handleNumbers(@Param('*') path: string) { /* ... */ }
}Query parameters
Query strings (?key=value) are not part of the route path. Use @Query to extract them:
import { Get, Query } from '@moostjs/event-http'
@Get('search')
search(
@Query('q') query: string,
@Query('page') page: string,
) {
return { query, page }
}
// GET /search?q=moost&page=2 → { query: 'moost', page: '2' }Use @Query() without arguments to get all query parameters as an object:
@Get('search')
search(@Query() params: Record<string, string | string[]>) {
return params
}INFO
Query values arrive as strings (or string arrays). Use pipes to transform them to numbers or other types.
Gotchas for @Query()
- Keys ending in
[]become arrays:?tags[]=a&tags[]=b→{ 'tags[]': ['a', 'b'] }. - Repeating a plain key (
?q=a&q=b) triggers an automatic400 Duplicate keyresponse. - When there are no query parameters at all,
@Query()resolves toundefined, not{}.
Handler return values
Whatever your handler returns (or resolves to) becomes the response body — strings go out as text/plain, objects as application/json, Node Readable streams are piped. See Responses & Errors for the full return-type table, status codes, headers, and cookies.
Async handlers work the same way — return a promise:
@Get('data')
async getData() {
const data = await fetchFromDb()
return data
}