Skip to main content

@Route

The @Route() decorator defines a REST API endpoint on a backend method. It maps HTTP methods and URL patterns to TypeScript methods with typed, auto-parsed parameters.

Package: io-oidis-localhost

import { Route } from "@io-oidis-localhost/.../Primitives/Decorators.js";

Syntax​

@Route("METHOD /path?queryParam=type(default)")
public async MethodName($queryParam : type) : Promise<ReturnType> { ... }

HTTP Methods​

@Route("GET /api/items")
@Route("POST /api/items")
@Route("PUT /api/items/{id}")
@Route("DELETE /api/items/{id}")
@Route("PATCH /api/items/{id}")

Parameter Types​

Query Parameters​

Typed with optional defaults in parentheses:

@Route("GET /api/search?query=string()&page=int(1)&size=int(20)&active=boolean(true)")
public async Search($query : string, $page : number, $size : number, $active : boolean) {
// $query = required (no default)
// $page = 1 if not provided
// $size = 20 if not provided
// $active = true if not provided
}
Type SyntaxTypeScript TypeExample
int()number?count=int() — required integer
int(0)number?offset=int(0) — default 0
float(1.5)number?rate=float(1.5) — default 1.5
boolean(false)boolean?active=boolean(false) — default false
string()string?name=string() — required string
string(test)string?mode=string(test) — default "test"

Path Parameters​

Use {paramName} in the URL path:

@Route("GET /api/groups/{groupId}/members/{memberId}")
public async GetMember($groupId : string, $memberId : string) { ... }

// Matches: GET /api/groups/abc123/members/def456
// $groupId = "abc123", $memberId = "def456"

Hash Parameters​

Use #{paramName} for fragment-based routing:

@Route("GET /view#{section}")
public async ViewSection($section : string) { ... }

// Matches: GET /view#settings
// $section = "settings"

Multiple Routes​

A single method can have multiple @Route decorators:

@Route("GET /api/v1/users")
@Route("GET /api/v2/users")
@Route("GET /api/users")
public async ListUsers() : Promise<IModelListResult> {
// All three URLs invoke this method
}

Registration​

@Route creates a function methodName_route_Route() and registers it in globalThis.oidisAPIRoutes. No manual routing table is needed — discovery is automatic at class load time.

Authorization​

@Route methods go through the same AuthManager authorization as @Extern methods. Register them in your AuthManager constructor using generateAuthMethodsFrom or addSystemPermissions.

Independence from @Extern​

@Route is completely independent of @Extern. A method can have:

  • Only @Route — REST-only endpoint (no LCP access)
  • Only @Extern — LCP-only endpoint (no REST access)
  • Both @Route + @Extern — Dual access via REST and LCP
// REST only
@Route("GET /api/health")
public async HealthCheck() : Promise<{ status: string }> {
return { status: "ok" };
}

// Both REST and LCP
@Extern()
@Route("GET /api/stats")
public async GetStats() : Promise<IStats> {
return await this.computeStats();
}

Complete Example​

export class ProductAPI extends BaseConnector {

@Route("GET /api/products?category=string()&minPrice=float(0)&maxPrice=float(999999)&page=int(1)&size=int(25)")
public async ListProducts(
$category : string,
$minPrice : number,
$maxPrice : number,
$page : number,
$size : number
) : Promise<IModelListResult> {
// Query keys are the stored (camelCase) field names, not the PascalCase model properties
const filter : any = {deleted: {$ne: true}};
if (!ObjectValidator.IsEmptyOrNull($category)) {
filter.category = $category;
}
filter.price = {$gte: $minPrice, $lte: $maxPrice};

const offset : number = ($page - 1) * $size;
const size : number = await Product.Model().countDocuments(filter);
const records : Product[] = await Product.Find(filter, {limit: $size, offset});

return {data: await Promise.all(records.map(($record : Product) : Promise<any> => $record.ToJSON())), limit: $size, offset, size};
}

@Route("GET /api/products/{id}")
public async GetProduct($id : string) : Promise<IProduct> {
return await Product.FindById($id);
}

@Extern()
@Route("POST /api/products")
public async CreateProduct($data : IProductInput) : Promise<string> {
const product : Product = new Product();
product.Name = $data.name; // copy fields explicitly, never sync a client payload into a model
product.Price = $data.price;
await product.Save();
return product.Id;
}
}

See also: @Extern | @Resolver | Routing