@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 Syntax | TypeScript Type | Example |
|---|---|---|
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;
}
}