Skip to main content

Routing & Parameters

HeliosJS provides powerful routing capabilities with intuitive decorators. Let's explore all the ways you can define routes and extract data from requests.

Route Decorators

HeliosJS supports all standard HTTP methods:

DecoratorMethodDescription
@Get(path)GetRetrieve data
@Post(path)PostCreate new resources
@Put(path)PutUpdate entire resources
@Patch(path)PatchUpdate partial resources
@Delete(path)DeleteRemove resources
@Options(path)OptionsGet allowed methods
@Head(path)HeadGet headers only
@Query(path)QueryRetrieve data using a request body
note

@Query(path) is the route decorator for the HTTP QUERY method. To read query string parameters from the URL, use the @QueryParam() parameter decorator.

Basic Routes

import { Controller, Get, Post, Put, Patch, Delete } from "@heliosjs/core";

@Controller("/users")
export class UserController {
@Get("/")
findAll() {
return [{ id: 1, name: "Alice" }];
}

@Post("/")
create(@Body() data: any) {
return { id: 2, ...data };
}

@Put("/:id")
update(@Param("id") id: string, @Body() data: any) {
return { id: parseInt(id), ...data };
}

@Patch("/:id")
partialUpdate(@Param("id") id: string, @Body() data: any) {
return { id: parseInt(id), ...data };
}

@Delete("/:id")
remove(@Param("id") id: string) {
return { deleted: true, id: parseInt(id) };
}
}

Route Parameters

URL Parameters (@Param)

Extract dynamic segments from the URL:

import { Controller, Get, Param } from "@heliosjs/core";

@Controller("/users")
export class UserController {
// Match: /users/123
@Get("/:id")
getUserById(@Param("id") id: string) {
return { userId: parseInt(id) };
}

// Match: /users/123/posts/456
@Get("/:userId/posts/:postId")
getUserPost(
@Param("userId") userId: string,
@Param("postId") postId: string,
) {
return {
userId: parseInt(userId),
postId: parseInt(postId),
};
}
}

Query Parameters (@QueryParam)

Extract query string parameters:

import { Controller, Get, QueryParam } from "@heliosjs/core";

@Controller("/users")
export class UserController {
// Match: /users?page=1&limit=10&search=john
@Get("/")
findAll(
@QueryParam("page") page?: string,
@QueryParam("limit") limit?: string,
@QueryParam("search") search?: string,
) {
return {
page: page ? parseInt(page) : 1,
limit: limit ? parseInt(limit) : 10,
search: search || "",
};
}

// Get all query parameters as an object
@Get("/filter")
filter(@QueryParam() query: any) {
return { filters: query };
}
}

QUERY Requests (@Query)

QUERY is a safe, idempotent HTTP method that carries a request body. Use it when a search filter is too large or too structured to fit in a URL query string, but the request must stay cacheable and side-effect free:

import { Controller, Query, Body } from "@heliosjs/core";

interface SearchFilter {
categories: string[];
priceRange: { min: number; max: number };
}

@Controller("/products")
export class ProductController {
@Query("/search")
search(@Body() filter: SearchFilter) {
return { matched: filter.categories };
}
}
warning

Parsing QUERY requires llhttp 9.2 or newer, which ships with Node.js 20.20 and every later release. Check process.versions.llhttp if you target an older patch release — below 9.2 the parser rejects the request before it reaches HeliosJS.

On AWS, QUERY passes through ALB, Lambda Function URLs, and CloudFront. API Gateway does not accept QUERY as a route method — declare the route as ANY and let HeliosJS dispatch it.

Request Body (@Body)

Extract the request body:

import { Controller, Post, Body } from "@heliosjs/core";

interface CreateUserDto {
name: string;
email: string;
age?: number;
}

@Controller("/users")
export class UserController {
@Post("/")
create(@Body() userData: CreateUserDto) {
// userData contains the parsed JSON body
return { id: 1, ...userData };
}

// Extract specific fields
@Post("/validate")
validate(@Body("email") email: string) {
return { email };
}
}

Request Headers (@Headers)

Extract HTTP headers:

import { Controller, Get, Headers } from "@heliosjs/core";

@Controller("/auth")
export class AuthController {
@Get("/profile")
getProfile(
@Headers("authorization") auth: string,
@Headers("user-agent") userAgent: string,
) {
return {
token: auth?.replace("Bearer ", ""),
userAgent,
};
}

// Get all headers
@Get("/headers")
getAllHeaders(@Headers() headers: any) {
return { headers };
}
}

Request Object (@Req)

Access the raw request object:

import { Controller, Get, Req, Request } from "@heliosjs/core";
import { IncomingMessage } from "http";

@Controller("/request")
export class RequestController {
@Get("/info")
getRequestInfo(@Req() req: Request) {
return {
method: req.method,
url: req.url,
headers: req.headers,
httpVersion: req.httpVersion,
};
}
}

Response Object (@Res)

Access the raw response object for manual control:

import { Controller, Get, Res, Response } from "@heliosjs/core";

@Controller("/response")
export class ResponseController {
@Get("/custom")
sendCustomResponse(@Res() res: Response) {
res.statusCode = 201;
res.setHeader("X-Custom-Header", "Hello");
}
}

Request Fingerprint (@Fingerprint)

Inject a stable, hashed fingerprint derived from request attributes (client IP, User-Agent, headers). The value is computed lazily and cached per request:

import { Controller, Get, Fingerprint } from "@heliosjs/core";

@Controller("/session")
export class SessionController {
@Get("/")
current(@Fingerprint() fp: string) {
return { fingerprint: fp };
}
}

See Fingerprint Decorator for configuration (components, secret/HMAC, custom compute) and the companion @UseFingerprint() decorator.

Combined Example

Here's a complete controller showing all parameter types:

import {
Controller,
Get,
Post,
Put,
Delete,
Param,
QueryParam,
Body,
Headers,
Req,
Res,
} from "@heliosjs/core";

interface Product {
id: number;
name: string;
price: number;
}

@Controller("/products")
export class ProductController {
private products: Product[] = [];
private nextId = 1;

// Get /products?category=electronics&minPrice=10
@Get("/")
findAll(
@QueryParam("category") category?: string,
@QueryParam("minPrice") minPrice?: string,
) {
let filtered = [...this.products];

if (category) {
filtered = filtered.filter((p) => p.name.includes(category));
}

if (minPrice) {
const price = parseInt(minPrice);
filtered = filtered.filter((p) => p.price >= price);
}

return filtered;
}

// Get /products/123
@Get("/:id")
findOne(@Param("id") id: string) {
const product = this.products.find((p) => p.id === parseInt(id));
if (!product) {
throw new Error("Product not found");
}
return product;
}

// Post /products
@Post("/")
create(
@Body() productData: Omit<Product, "id">,
@Headers("authorization") auth: string,
) {
// Check authentication
if (!auth) {
throw new Error("Unauthorized");
}

const newProduct: Product = {
id: this.nextId++,
...productData,
};

this.products.push(newProduct);
return newProduct;
}

// Put /products/123
@Put("/:id")
update(@Param("id") id: string, @Body() productData: Partial<Product>) {
const productId = parseInt(id);
const index = this.products.findIndex((p) => p.id === productId);

if (index === -1) {
throw new Error("Product not found");
}

this.products[index] = { ...this.products[index], ...productData };
return this.products[index];
}

// Delete /products/123
@Delete("/:id")
remove(@Param("id") id: string, @Req() req: any, @Res() res: any) {
const productId = parseInt(id);
const index = this.products.findIndex((p) => p.id === productId);

if (index === -1) {
res.statusCode = 404;
return { error: "Product not found" };
}

this.products.splice(index, 1);
return { deleted: true, id: productId };
}
}

Parameter Summary

DecoratorSourceUsage
@Param(name?)URL path/users/:id 12; @Param('id')
@QueryParam(name?)Query string?page=1 12; @QueryParam('page')
@Body(name?)Request bodyJSON payload
@Headers(name?)HTTP headersAuthorization header
@Files(name?)Multipart data
@Req()Raw requestFull Incoming Message object
@Res()Raw responseFull Server Response object
@Fingerprint()ComputedHashed request fingerprint

Multipart Form Data (@Files)

Extract multipart form data from requests, optionally by field name:

import { Controller, Post, Files } from "@heliosjs/core";

@Controller("/upload")
export class UploadController {
@Post("/")
uploadFile(@Files("file") file: any) {
// file contains the uploaded file data
return { uploaded: true, file };
}
}
DecoratorSourceUsage
@Files(name?)Multipart form dataExtract multipart field by name or all multipart data

This corresponds to the @Multipart parameter decorator used in the codebase as @Files.

Route Priority

Routes are matched in the order they are defined. More specific routes should come before general ones:

@Controller("/users")
export class UserController {
// Specific route first
@Get("/profile")
getProfile() {
return { page: "profile" };
}

// General route last
@Get("/:id")
getUserById(@Param("id") id: string) {
return { userId: id };
}
}