Getting Started with Katal¶
This guide walks you from zero to a fully working Katal application with authentication, validation, and middleware.
Prerequisites¶
- Bun ≥ 1.0 installed
- Basic TypeScript familiarity
1. Create a Project¶
mkdir my-api && cd my-api
bun init -y
bun add katal
Add tsconfig.json:
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"outDir": "./dist"
}
}
2. Hello World¶
Create src/index.ts:
import { Application, Controller } from "katal";
import type { RequestContext } from "katal";
const app = new Application({ port: 3000 });
const router = app.getRouter();
class RootController extends Controller {
async handle(_ctx: RequestContext) {
return this.success({ message: "Hello from Katal!" });
}
}
router.get("/", RootController);
await app.listen();
console.log("Running on http://localhost:3000");
bun run src/index.ts
3. Add a Route with Path Parameters¶
class UserController extends Controller {
async handle(ctx: RequestContext) {
const id = ctx.params.id;
return this.success({ userId: id });
}
}
router.get("/users/:id", UserController);
4. Parse a Request Body¶
class CreateUserController extends Controller {
async handle(ctx: RequestContext) {
const body = ctx.body as { name: string; email: string };
// create user logic...
return this.success({ created: body }, 201);
}
}
router.post("/users", CreateUserController);
5. Add Input Validation¶
Katal provides schema-based validation directly on your controller.
import { Controller, Validator } from "katal";
import type { RequestContext } from "katal";
class CreateUserController extends Controller {
protected schema = {
name: { required: true, type: "string" as const, min: 2 },
email: { required: true, type: "string" as const, pattern: /.+@.+\..+/ },
age: { required: false, type: "number" as const, min: 13 },
};
async handle(ctx: RequestContext) {
// ctx.body is type-safe once schema passes
const { name, email } = ctx.body as { name: string; email: string };
return this.success({ name, email }, 201);
}
}
Validation errors return HTTP 422 automatically:
{
"error": "Validation failed",
"details": [
{ "field": "email", "message": "email is required" }
]
}
6. Add a Middleware¶
import type { Middleware } from "katal";
const logger: Middleware = async (ctx, next) => {
const start = Date.now();
const response = await next(ctx);
console.log(`${ctx.request.method} ${ctx.url.pathname} — ${Date.now() - start}ms`);
return response;
};
app.use(logger);
7. Add CORS¶
import { CorsMiddleware } from "katal";
app.middleware("cors", new CorsMiddleware({
origins: ["https://my-frontend.com"],
methods: ["GET", "POST"],
}));
app.use("cors");
8. Add JWT Authentication¶
import { Auth, createAuthMiddleware } from "katal";
const auth = new Auth({ secret: process.env.JWT_SECRET! });
app.middleware("auth", createAuthMiddleware(auth));
// Issue a token on login
class LoginController extends Controller {
async handle(ctx: RequestContext) {
const { email, password } = ctx.body as { email: string; password: string };
// validate credentials...
const token = auth.generateToken({ id: "1", email });
return this.success({ token });
}
}
// Protect a route
router.get("/profile", ProfileController, { middleware: ["auth"] });
9. Add Rate Limiting¶
import { RateLimitMiddleware } from "katal";
app.useRateLimiting({
windowMs: 60_000, // 1 minute
max: 100, // 100 requests per window
});
10. Organise with Route Groups¶
router.group("/api/v1", (r) => {
r.group("/users", (r) => {
r.get("/", ListUsersController);
r.get("/:id", GetUserController);
r.post("/", CreateUserController, { middleware: ["auth"] });
r.put("/:id", UpdateUserController, { middleware: ["auth"] });
r.delete("/:id", DeleteUserController, { middleware: ["auth"] });
});
r.group("/posts", (r) => {
r.get("/", ListPostsController);
r.post("/", CreatePostController, { middleware: ["auth"] });
});
});
11. Health Checks¶
app.addHealthCheck("database", async () => {
// attempt a query
return { status: "ok" };
});
app.enableHealthEndpoints(); // exposes /health, /health/ready, /health/live
12. Full Working Example¶
import {
Application,
Controller,
Auth,
createAuthMiddleware,
CorsMiddleware,
RateLimitMiddleware,
} from "katal";
import type { RequestContext } from "katal";
// --- App setup ---
const app = new Application({ port: 3000 });
const router = app.getRouter();
const auth = new Auth({ secret: process.env.JWT_SECRET ?? "dev-secret" });
// --- Middleware ---
app.middleware("cors", new CorsMiddleware({ origins: "*" }));
app.middleware("auth", createAuthMiddleware(auth));
app.use("cors");
app.useRateLimiting({ windowMs: 60_000, max: 200 });
// --- Health ---
app.enableHealthEndpoints();
// --- Controllers ---
class PingController extends Controller {
async handle(_ctx: RequestContext) {
return this.success({ pong: true });
}
}
class LoginController extends Controller {
protected schema = {
email: { required: true, type: "string" as const },
password: { required: true, type: "string" as const },
};
async handle(ctx: RequestContext) {
const { email } = ctx.body as { email: string; password: string };
const token = auth.generateToken({ id: "1", email });
return this.success({ token });
}
}
class MeController extends Controller {
async handle(ctx: RequestContext) {
return this.success({ user: ctx.user });
}
}
// --- Routes ---
router.get("/ping", PingController);
router.post("/login", LoginController);
router.get("/me", MeController, { middleware: ["auth"] });
// --- Start ---
await app.listen();
Next Steps¶
- Read the Core guide for the full Application + Router API
- Define reusable base controllers in HTTP
- Set up a job queue: Queue
- Write integration tests with the Testing Kit