Authoring styles
Flare HTTP handlers come in two shapes: inline functions on host.http and class-based controllers. Both share the same pipeline.
Flare registers HTTP handlers in two styles. Most apps use both: inline functions for one-off routes, and ControllerBase subclasses when several routes share a prefix, contract, services, or state tokens.
| Style | Registration | Best for |
|---|---|---|
| Inline function | host.http.get(path, handler) or host.http.get(path, options, handler) | Single routes, quick endpoints, functional middleware hooks |
| Controller class | host.http.controller(prefix, Cls) with @Get, @Post, … decorators | Resource groups, shared static contract, CRUD surfaces |
Middleware and error handlers follow the same split: builder callbacks on host.http.before / after / finally / error, or classes extending MiddlewareBase / ErrorHandlerBase registered with host.http.use / host.http.error.
Inline handlers
Section titled “Inline handlers”Inline route handlers are functions with signature (ctx, scope) => HandlerResult. They receive a FlareHttpContext as ctx and a handler scope as scope.
Declare services with a named inject map (inject: { db: DbService }, read scope.db). Declare route, query, and body shapes as inline descriptor fields on the route options (route, query, body) and read parsed values from scope.input.
import { FlareHost, FlareResponse } from "@flare-ts/core";import { node } from "@flare-ts/core/node";import { int } from "@flare-ts/lib/schema";
const host = new FlareHost(node);
host.http.get("/ping", () => new FlareResponse(200, { ok: true }));
host.http.get("/users/:id", { route: { id: int } }, (_ctx, scope) => { return new FlareResponse(200, { id: scope.input.route.id });});
const app = host.build();app.run();See Inline route handlers for the full HttpRouteOptions surface.
Controller classes
Section titled “Controller classes”Controllers group routes under one mount prefix. Each class declares static deps, static state, and optionally static contract (an httpContract token). Handler methods use decorators from @flare-ts/core/decorators and read validated input with this.ctx.extract(Contract.entry).
import { ControllerBase, FlareHost, httpContract } from "@flare-ts/core";import { Get } from "@flare-ts/core/decorators";import { node } from "@flare-ts/core/node";import { int } from "@flare-ts/lib/schema";
const UsersContract = httpContract({ show: { route: { id: int } },});
class UsersController extends ControllerBase { public static override deps = []; public static override state = []; public static override contract = UsersContract;
@Get("/:id") show() { const { route } = this.ctx.extract(UsersContract.show); return this.ok({ id: route.id }); }}
const host = new FlareHost(node);host.http.controller("/users", UsersController);
const app = host.build();app.run();See Controller classes for static members, decorators, and response helpers.
Contracts on either style
Section titled “Contracts on either style”HTTP contracts attach two ways:
- Inline routes: descriptor fields on the route options (
route,query,body,response,maxBodyBytes,signedCookies), or a brandedcontracttoken (not both). - Controllers:
static contract = httpContract({ methodName: descriptor, … })andthis.ctx.extract(Contract.methodName)inside each handler.
Middleware and errors
Section titled “Middleware and errors”| Concern | Inline | Class |
|---|---|---|
| Middleware | host.http.before(fn) or host.http.before(options, fn) | host.http.use(MiddlewareCls) |
| Error handling | host.http.error(fn) or host.http.error(options, fn) | host.http.error(ErrorHandlerCls) |
Group-scoped registration uses the same APIs on the group builder inside host.http.group. See Middleware and Error handling.
When to pick which
Section titled “When to pick which”Use inline handlers when the route is small, standalone, or you are prototyping. Use a controller when you have multiple methods on one resource, a shared httpContract, or you want this.ok / this.notFound helpers without constructing FlareResponse yourself.
Both styles compile to dedicated per-route execution functions at build(). The choice is about structure and compile-time guardrails (static deps, static state, contract keys matched to method names).
Without a shared surface for this, the controller shape is something each codebase reinvents: a base class, a way to map methods to routes, a set of response helpers, and the standing hope that a route written as a class runs the same middleware and validation as one written as a function. Here both shapes are first-class over one pipeline, so a class handler and an inline handler with the same contract behave identically, and promoting a route from a function to a controller method is a change of structure rather than of runtime behavior.
Related
Section titled “Related”- Tutorial: Your first app: end-to-end controller style
- Routing: groups, path rules, and registration surface
- Named inject map: inline DI in 0.3