API Routing
Prerequisites
API routing requires the @antelopejs/interface-api package. Install it in your module and import from @antelopejs/interface-api.
npm install @antelopejs/interface-api
yarn add @antelopejs/interface-api
pnpm add @antelopejs/interface-api
All decorators, classes, and functions referenced in this guide come from this single package. No additional dependencies are needed.
Creating Controllers
Controllers group related routes under a shared base path. The Controller function creates a base class at a specific URL location. Extend it to define your controller. Each method inside the class becomes a route handler when decorated with an HTTP method decorator.
import { Controller, Get, Post, Put, Delete, Parameter, JSONBody } from "@antelopejs/interface-api";
class UserController extends Controller("/api/users") {
@Get("list")
async listUsers() {
return { users: [] };
}
@Get("get")
async getUser(@Parameter("id") id: string) {
return { id, name: "Alice" };
}
@Post("create")
async createUser(@JSONBody() body: { name: string; email: string }) {
return { id: "new-id", ...body };
}
@Put("update")
async updateUser(@Parameter("id") id: string, @JSONBody() body: any) {
return { id, ...body };
}
@Delete("delete")
async deleteUser(@Parameter("id") id: string) {
return { success: true };
}
}
The framework automatically serializes return values from handler methods as JSON responses. The Controller function is not a decorator. It returns a class that you extend with extends Controller("/path"). The decorator on each parameter determines whether the value comes from the URL path or the query string.
HTTP Responses
The HTTPResult class provides control over status codes and response headers. Without HTTPResult, all successful responses return status 200. Use HTTPResult when you need a different status code or custom headers.
import { Controller, Post, JSONBody, HTTPResult } from "@antelopejs/interface-api";
class UserController extends Controller("/api/users") {
@Post("create")
async createUser(@JSONBody() body: any) {
const user = await saveUser(body);
return new HTTPResult(201, user);
}
}
The first argument is the HTTP status code and the second is the response body. This pattern works well for 201 Created, 204 No Content, and other non-200 responses.
Route Prefixes and Postfixes
The @Prefix and @Postfix decorators are method decorators that run before or after the main handler for a specific route. They are useful for authentication checks, input validation, response modification, or logging.
@Prefix(method, location?, priority?) runs before the main handler. @Postfix(method, location?, priority?) runs after the main handler.
import { Controller, Get, Prefix, Postfix, Result, HTTPResult, HandlerPriority } from "@antelopejs/interface-api";
class ApiController extends Controller("/api") {
@Prefix("get", "status")
checkAccess() {
// Runs before the main handler for GET /api/status
// Return an HTTPResult to short-circuit the request
}
@Get("status")
async handler() {
return { version: 1 };
}
@Postfix("get", "status")
addHeaders(@Result() response: HTTPResult) {
// Runs after the main handler for GET /api/status
response.addHeader("X-API-Version", "1.0");
}
}
The method argument is the HTTP method to match (e.g., "get", "post", or "*" for all methods). The location argument specifies the route path relative to the controller. The optional priority argument controls execution order when multiple prefix or postfix handlers match the same route.
Handler Priority
The HandlerPriority enum controls the order in which route handlers run when multiple handlers match the same path. The enum defines five levels and is not a decorator.
import { HandlerPriority } from "@antelopejs/interface-api";
// HandlerPriority.HIGHEST = 0
// HandlerPriority.HIGH = 1
// HandlerPriority.NORMAL = 2
// HandlerPriority.LOW = 3
// HandlerPriority.LOWEST = 4
Handlers with a lower numeric value run first. The default priority for all handlers is NORMAL. Set the priority through route configuration when registering routes manually.
Parameter Decorators
Parameter decorators extract specific parts of the incoming HTTP request and inject them as method arguments. Each decorator targets a different part of the request.
| Decorator | Description |
|---|---|
@Parameter(name, source?) | Single value from route parameter, query string, or header |
@MultiParameter(name, source?) | Multiple values from query string or header |
@JSONBody() | Parsed JSON request body |
@RawBody() | Raw request body as Buffer |
@Context() | Full RequestContext object |
@Result() | Response HTTPResult object (typically used in postfix handlers) |
@WriteStream(type?) | Writable stream for long-running or chunked responses |
@Connection() | WebSocket connection object (for @WebsocketHandler routes) |
@Transform(fn) | Apply a transformation function to a parameter value |
Request Context
The @Context() decorator provides access to the full request context, including the raw request object, response object, headers, and other low-level details.
import { Controller, Get, Context, type RequestContext } from "@antelopejs/interface-api";
class DebugController extends Controller("/api/debug") {
@Get("info")
async handler(@Context() ctx: RequestContext) {
return {
method: ctx.rawRequest.method,
url: ctx.url.toString(),
headers: ctx.rawRequest.headers,
};
}
}
The RequestContext interface provides rawRequest (the Node.js IncomingMessage), rawResponse (the ServerResponse), url (a parsed URL object), routeParameters, body, and response (the HTTPResult that will be sent). Use @Context() when the higher-level parameter decorators do not provide enough information. Most route handlers only need @Parameter() and @JSONBody().
Streaming Responses
The @WriteStream() decorator injects a writable stream for sending long-running or chunked responses. Streaming is useful for server-sent events, file downloads, or progress updates.
import { Controller, Get, WriteStream } from "@antelopejs/interface-api";
import { PassThrough } from "node:stream";
class StreamController extends Controller("/api/stream") {
@Get("data")
async streamData(@WriteStream() stream: PassThrough) {
for (let i = 0; i < 10; i++) {
stream.write(`data: chunk ${i}\n\n`);
}
stream.end();
}
}
The stream remains open until you explicitly call end(). The client receives data as it is written.
WebSocket Handlers
The @WebsocketHandler decorator marks a method as a WebSocket handler. Use the @Connection() decorator to receive the WebSocket connection object.
import { Controller, WebsocketHandler, Connection } from "@antelopejs/interface-api";
class WebSocketController extends Controller("/ws") {
@WebsocketHandler("connect")
async handleConnection(@Connection() conn: any) {
conn.on("message", (msg: string) => {
conn.send(`Echo: ${msg}`);
});
}
}
WebSocket handlers operate independently from HTTP route handlers. They manage persistent connections and bidirectional communication.
Manual Route Registration
The RegisterRoute() function registers routes programmatically without decorators. This approach works well when routes need to be generated dynamically or configured at runtime.
import { RegisterRoute } from "@antelopejs/interface-api";
RegisterRoute({
mode: "handler",
method: "get",
location: "/api/health",
callback: async () => {
return { status: "ok" };
},
parameters: [],
properties: {},
proto: {},
});
The RegisterRoute function takes a RouteHandler object with these required fields: mode ("handler", "prefix", "postfix", "monitor", or "websocket"), method (HTTP method), location (full route path), callback (handler function), parameters (computed parameter array), properties (computed properties), and proto (controller prototype). An optional priority field accepts a HandlerPriority value. Manual registration gives you full control over route configuration. Use this approach when you need to generate routes from external configuration or runtime data.
@Patch decorator. For PATCH requests, use @Route with custom configuration.Starting the Server
The API module starts the HTTP server automatically by default. The server port and other settings are controlled through the module's configuration in antelope.config.ts.
To take manual control over when the server starts, set autoListen to false in the API module's configuration and call Listen() yourself:
export default defineConfig({
modules: {
"api": {
config: { autoListen: false }
}
}
});
import { Listen } from "@antelopejs/interface-api";
export async function start() {
await Listen();
}
autoListen behavior. Disable it only when you need to delay server startup until other resources are ready.