Context Extensions
Extend the request context with custom properties and services
Context extensions allow you to attach custom properties, services, and utilities to the request context, making them available in all route handlers.
Why Extend Context?
Common use cases include:
- Database connections
- Authentication state
- Logger instances
- Configuration
- Utility functions
Basic Extension
Use the extend() method to add properties:
import { server } from "kitojs";
interface Extends {
user: { id: string; name: string };
}
const app = server().extend<Extends>(ctx => {
ctx.user = { id: "1", name: "Neo" };
});
app.get("/", ({ res, user }) => {
res.json({ user });
});Type-Safe Extensions
Extensions are fully type-safe:
interface Extensions {
db: Database;
config: AppConfig;
logger: Logger;
}
const app = server()
.extend<Extensions>(ctx => {
ctx.db = db;
ctx.config = config;
ctx.logger = logger;
});
app.get("/", ({ db, config, logger }) => {
// TypeScript knows about all extensions
db.query("..."); // ✅ Type-safe
config.apiKey; // ✅ Type-safe
logger.info("..."); // ✅ Type-safe
});Return Style Extension
Return an object instead of mutating context:
const app = server().extend<{ db: Database }>(_ => {
return {
db: createDatabaseConnection()
};
});Multiple Extensions
Chain multiple extensions:
const app = server()
.extend<{ db: Database }>(ctx => {
ctx.db = db;
})
.extend<{ cache: Cache }>(ctx => {
ctx.cache = cache;
})
.extend<{ logger: Logger }>(ctx => {
ctx.logger = logger;
});
app.get("/", ({ db, cache, logger }) => {
// All extensions are available
db.query("...");
cache.get("...");
logger.info("...");
});Per-Request Extensions
Create request-specific data:
const app = server()
.extend<{ requestId: string }>(ctx => {
ctx.requestId = crypto.randomUUID();
});
app.get("/", ({ requestId, logger, res }) => {
logger.info(`Request ID: ${requestId}`);
res.send("OK");
});Service Container
Create a service container pattern:
interface Services {
userService: {
getById: (id: string) => Promise<User>;
create: (data: UserData) => Promise<User>;
};
emailService: {
send: (to: string, subject: string, body: string) => Promise<void>;
};
}
const app = server().extend<Services>(ctx => ({
userService: {
getById: async (id) => ctx.db.query("SELECT * FROM users WHERE id = ?", [id]),
create: async (data) => ctx.db.query("INSERT INTO users...", data)
},
emailService: {
send: async (to, subject, body) => sendEmail(to, subject, body)
}
}));
app.post("/users", async ctx => {
const { userService, emailService, req, res } = ctx;
const user = await userService.create(req.body);
await emailService.send(user.email, "Welcome", "...");
res.json(user);
});Combining with Validation
Use extensions with validated data:
const userSchema = schema({
body: t.object({
name: t.str(),
email: t.str().email()
})
});
app.post("/users", async ({ db, req, res }) => {
// Both validation and extensions work together
const user = await db.query(
"INSERT INTO users (name, email) VALUES (?, ?)",
[req.body.name, req.body.email]
);
res.json(user);
}, userSchema); 💡 Tip
Extensions are created once per request, so they’re perfect for request-scoped services and state.
ℹ️ Note
Extensions run before route handlers but after middleware. Plan your architecture accordingly.