Core — Application & Router

The Application class is the central entry point for Katal. It wires together the router, middleware pipeline, DI container, and every optional subsystem (queue, cache, events, health, …).


Application

Constructor

import { Application } from "katal";

const app = new Application({
  port:       3000,       // default: 3000
  host:       "0.0.0.0", // default: "localhost"
  cors:       false,      // enable built-in CORS pre-flight (basic)
  bodyParser: true,       // auto-parse JSON / form bodies
});
Option Type Default Description
port number 3000 Listening port
host string "localhost" Binding host
cors boolean false Enable minimal CORS headers
bodyParser boolean true Auto-parse JSON request bodies

Server Lifecycle

// Start listening
await app.listen();

// Graceful shutdown
await app.stop();

Get the Router

const router = app.getRouter();
router.get("/ping", PingController);

Routing

See the full routing reference in HTTP — Controllers. A quick overview:

const router = app.getRouter();

// HTTP verbs
router.get("/users",        ListController);
router.post("/users",       CreateController);
router.put("/users/:id",    ReplaceController);
router.patch("/users/:id",  UpdateController);
router.delete("/users/:id", DeleteController);

// Route with named middleware
router.get("/me", MeController, { middleware: ["auth"] });

// Route with inline validation
router.post("/items", CreateItemController, {
  validation: { name: { required: true, type: "string" as const } },
});

Route Groups

router.group("/api/v1", (r) => {
  r.group("/orders", (r) => {
    r.get("/",      ListOrdersController);
    r.post("/",     CreateOrderController, { middleware: ["auth"] });
    r.get("/:id",   GetOrderController);
  });
});

Groups are nestable. Middleware defined on a group applies to all nested routes.


Middleware

Global Middleware

Applied to every request:

import type { Middleware } from "katal";

const logger: Middleware = async (ctx, next) => {
  const t = Date.now();
  const res = await next(ctx);
  console.log(`${ctx.request.method} ${ctx.url.pathname} ${Date.now() - t}ms`);
  return res;
};

app.use(logger);

Named Middleware

Register once, reference by name on routes:

import { CorsMiddleware, createAuthMiddleware, Auth } from "katal";

const auth = new Auth({ secret: process.env.JWT_SECRET! });

app.middleware("auth",  createAuthMiddleware(auth));
app.middleware("cors",  new CorsMiddleware({ origins: "*" }));
app.middleware("admin", adminOnlyMiddleware);

Use on a specific route:

router.get("/admin/dashboard", AdminController, {
  middleware: ["auth", "admin"],
});

Middleware as Function

A Middleware is simply an async function that receives (ctx, next):

type Middleware = (ctx: RequestContext, next: (ctx: RequestContext) => Promise<Response>) => Promise<Response>;

Return a Response directly to short-circuit the pipeline. Call next(ctx) to proceed.


Built-in Middleware

Name Import Description
CorsMiddleware katal CORS headers + pre-flight
RateLimitMiddleware katal Sliding-window rate limiter
createAuthMiddleware katal JWT authentication
createOptionalAuthMiddleware katal JWT auth — user may be null
createAuthorizationMiddleware katal/middleware Policy gate (can:<name>)
Request observability app.useRequestObservability() Correlation IDs + access log

Application Methods Reference

DI Container

app.singleton("db", () => new Database());       // shared instance
app.bind("mailer", () => new Mailer());           // new instance per resolve
app.instance("config", configObject);             // register existing value

const db = app.resolve<Database>("db");

Middleware

app.use(globalMiddlewareFn);
app.middleware("name", middlewareFn);

Config

app.configure<MailConfig>("mail", {
  schema: { host: String, port: Number },
  defaults: { port: 587 },
});

const mail = app.getConfig<MailConfig>("mail");

Queue

app.useInMemoryQueue();          // explicit in-memory queue
app.useQueue(myAdapter);         // custom adapter
const queue = app.getQueue();    // lazy-init queue
const driver = app.getQueueDriver(); // "memory" | "custom"

Cache

app.useCacheStore(myRedisStore); // plug in a custom CacheStore

const value = await app.cacheGet<string>("key");
await app.cacheSet("key", "value", 60_000);     // TTL in ms
await app.cacheDelete("key");
const result = await app.cacheRemember("key", () => compute(), 60_000);

Events

app.on("user.created", async (event) => { /* ... */ });
app.once("app.ready",  handler);
app.off("user.created", handler);

await app.emit("user.created", { id: 1 });
app.emitSync("metrics.tick", {});

Health Checks

app.addHealthCheck("database", async () => {
  await db.ping();
  return { status: "ok" };
});

app.enableHealthEndpoints();
// exposes GET /health, /health/ready, /health/live

OpenAPI

app.exposeOpenApi("/openapi.json");
// Generates from registered routes + validation schemas

Authorization

app.definePolicy("can-edit-post", async (ctx) => {
  return ctx.user?.id === ctx.params.authorId;
});

// or bulk:
app.definePolicies({
  "admin": async (ctx) => ctx.user?.role === "admin",
});

Plugins & Providers

app.registerPlugin(myPlugin, { option: "value" });
app.registerProvider(new DatabaseProvider());
app.registerProviders([new CacheProvider(), new MailProvider()]);

Request Observability

app.useRequestObservability({
  includeBody: false,
  logLevel: "info",
});

See Also

const mail = app.configure('mail', { host: { required: true, type: 'string' }, port: { required: true, type: 'number', min: 1, max: 65535 }, secure: { required: true, type: 'boolean' } }, { source: { host: 'smtp.example.com' }, envPrefix: 'MAIL_' });

const resolved = app.getConfig('mail');


### Request Handling
```typescript
import { Controller } from 'katal';

class UserController extends Controller {
    async handle(request: Request) {
        const data = await request.json();
        return this.json({ success: true, data });
    }
}

app.getRouter().post('/users', UserController);

Configuration

The Application accepts the following configuration options:

interface AppConfig {
    port: number;      // Default: 3000
    host: string;      // Default: "localhost"
    cors: boolean;     // Default: false
    bodyParser: boolean; // Default: true
}

Request Context

Each route handler receives a context object:

interface RequestContext {
    request: Request;
    id: string;                     // Request ID
    state: Record<string, unknown>; // Shared per-request state
    params: Record<string, string>;  // URL parameters
    query: Record<string, string>;   // Query string parameters
    body: any;                       // Parsed request body
    user?: User;                     // Authenticated user (if available)
}

Error Handling

The framework includes built-in RFC-style Problem Details responses (application/problem+json) for framework-level errors such as 404, 422, and 500:

{
  "type": "https://httpstatuses.com/500",
  "title": "Internal Server Error",
  "status": 500,
  "detail": "...",
  "instance": "http://localhost:3000/users",
  "traceId": "..."
}

Server Lifecycle

// Boot without starting server
await app.boot();

// Start server
await app.listen(3000, 'localhost');

// Stop server
await app.stop();