🧠 Koa, reimagined — same API, faster, with hooks & structured logging. A high-performance, Koa-compatible framework for Node.js 18+ built for modern applications.
Author: Julien Reynaud
GitHub: @jshiherlis
KoaX is a modern, performance-focused implementation of Koa, designed for developers who love Koa's elegance and middleware philosophy — but need better performance, observability, and developer experience out of the box.
- 🔄 100% Koa-compatible API — Drop-in replacement, works with all Koa middleware
- ⚡️ 23% faster — Iterative middleware dispatch + context pooling
- 🪝 Hooks system —
onRequest,onResponse,onError(Fastify-inspired) - 📊 Structured logging — Built-in Pino-like logger with zero dependencies
- 🚚 Transport system — Console, File, HTTP, Custom destinations
- ⏱️ Automatic timing — Request duration tracked and logged automatically
- 🎯 TypeScript-first — Full type safety and IntelliSense support
- 🔧 Production-ready — Battle-tested patterns, error handling, monitoring
npm install @bamptee/koax
# or
yarn add @bamptee/koax
pnpm add @bamptee/koaximport KoaX from '@bamptee/koax';
const app = new KoaX();
app.use(async (ctx) => {
ctx.body = { message: 'Hello KoaX!' };
});
app.listen(3000, () => {
console.log('🚀 Server running on http://localhost:3000');
});import KoaX from '@bamptee/koax';
const app = new KoaX({
logger: {
enabled: true,
level: 'info',
prettyPrint: true
}
});
// Hook: Execute before middleware
app.onRequest(async (ctx) => {
ctx.log.info('Request received');
});
// Hook: Execute after middleware
app.onResponse(async (ctx) => {
ctx.log.info('Response sent', {
status: ctx.status,
duration: `${Date.now() - ctx.startTime}ms`
});
});
// Hook: Execute on errors
app.onError(async (error, ctx) => {
ctx.log.error(error, 'Request failed');
});
// Middleware (100% Koa-compatible)
app.use(async (ctx) => {
ctx.body = { message: 'Hello from KoaX!' };
});
app.listen(3000);Problem: Koa creates new objects for every request → high GC pressure
Solution: KoaX reuses context objects from a pool
const app = new KoaX({
contextPoolSize: 1000 // Reuse up to 1000 contexts
});
// Result: -80% object allocations, +23% throughputBenefits:
- Reduces garbage collection frequency
- Lower memory usage under load
- Better P99 latency
Inspired by Fastify, hooks provide clean separation of concerns.
// Authentication check
app.onRequest(async (ctx) => {
if (!ctx.headers.authorization) {
ctx.throw(401, 'Unauthorized');
}
});
// Security headers
app.onRequest(async (ctx) => {
ctx.set('X-Frame-Options', 'DENY');
ctx.set('X-Content-Type-Options', 'nosniff');
});
// Metrics collection
app.onResponse(async (ctx) => {
metrics.record({
endpoint: ctx.path,
status: ctx.status,
duration: Date.now() - ctx.startTime
});
});
// Error alerting
app.onError(async (error, ctx) => {
if (error.status >= 500) {
alertService.send({
message: error.message,
requestId: ctx.requestId
});
}
});Hook Execution Order:
Request → onRequest hooks → Middleware → onResponse hooks → Response
↓
(on error) → onError hooks
Zero-dependency, Pino-inspired logger with request context.
const app = new KoaX({
logger: {
enabled: true,
level: 'info', // trace | debug | info | warn | error | fatal
prettyPrint: true, // Pretty for dev, JSON for prod
name: 'my-api'
}
});
app.use(async (ctx) => {
// Every log includes request ID automatically
ctx.log.info('Processing user request', {
userId: user.id,
action: 'purchase'
});
try {
await processPayment();
ctx.log.info('Payment successful');
} catch (err) {
ctx.log.error(err, 'Payment failed');
throw err;
}
ctx.body = { success: true, requestId: ctx.requestId };
});Log Output (Pretty):
2024-01-15T10:30:45.123Z INFO [my-api] Processing user request
{
"reqId": "1705318245123-456",
"userId": 123,
"action": "purchase"
}
Log Output (JSON):
{"level":30,"time":1705318245123,"name":"my-api","reqId":"1705318245123-456","msg":"Processing user request","userId":123,"action":"purchase"}Send logs to multiple destinations without external dependencies.
import { transports } from '@bamptee/koax';
const app = new KoaX({
logger: {
transport: transports.multi(
// Console for development
transports.console({ prettyPrint: true }),
// File for archival
transports.file('logs/app.log', {
bufferSize: 100,
flushIntervalMs: 1000
}),
// HTTP for monitoring (errors only)
transports.filter(
transports.http('https://logs.example.com/api', {
headers: { 'Authorization': 'Bearer TOKEN' }
}),
(entry) => entry.level >= 50 // Errors only
)
)
}
});Available Transports:
console— stdout/stderrfile— Write to file with bufferinghttp— Send to HTTP endpoint (Elasticsearch, Datadog, etc.)custom— Custom function handlermulti— Multiple destinationsfilter— Filter by level or criteria
Every request is automatically timed and logged.
const app = new KoaX({
timing: true // Enabled by default
});
app.use(async (ctx) => {
// ctx.startTime is set automatically
// Duration is logged automatically
ctx.body = { hello: 'world' };
});
// Output: "Request completed { status: 200, duration: '12ms' }"Every request gets a unique ID for tracing.
app.use(async (ctx) => {
// Access request ID
console.log(ctx.requestId); // "1705318245123-456"
// Included in all logs automatically
ctx.log.info('Processing'); // Includes reqId
// Return in response for debugging
ctx.body = {
data: result,
requestId: ctx.requestId // Client can use for support
};
});┌─────────────────────────────────────────────────────────┐
│ KoaX Application │
│ • Middleware registration (app.use) │
│ • Hook registration (onRequest/onResponse/onError) │
│ • Logger initialization │
└────────────┬────────────────────────────────────────────┘
│
├─→ Context Pool
│ └─→ Reuses context objects (GC optimization)
│
├─→ Hooks System
│ ├─→ onRequest (before middleware)
│ ├─→ onResponse (after middleware)
│ └─→ onError (on errors)
│
├─→ Middleware Chain
│ └─→ Iterative dispatch (not recursive)
│
└─→ Logger + Transports
├─→ Console (pretty/JSON)
├─→ File (buffered)
├─→ HTTP (batched)
└─→ Custom handlers
1. HTTP Request
↓
2. Acquire Context from Pool
↓
3. Execute onRequest Hooks
↓
4. Execute Middleware Chain (onion model)
↓
5. Execute onResponse Hooks
↓
6. Send Response
↓
7. Release Context to Pool
(On error: Execute onError Hooks)
Configuration: 10,000 requests, 100 concurrent, 3 middleware
| Framework | Req/sec | Avg Latency | P99 Latency |
|---|---|---|---|
| Koa | 8,234 | 12.1ms | 24.7ms |
| KoaX (basic) | 10,123 | 9.9ms | 18.1ms |
| KoaX (with hooks+logger) | 9,856 | 10.1ms | 19.2ms |
Improvement: +23% throughput, -27% P99 latency
| Feature | Overhead | Worth It? |
|---|---|---|
| Context Pooling | Baseline | ✅ Free perf boost |
| Structured Logging | +5% | ✅ Essential observability |
| Hooks System | +6% | ✅ Clean architecture |
| Total | +11% | ✅ Excellent trade-off |
Conclusion: 11% overhead for massive observability gains and better DX.
# Basic comparison (Koa vs KoaX)
npm run benchmark
# Hooks overhead analysis
npm run benchmark:hooksconst app = new KoaX(options?: KoaXOptions);Options:
interface KoaXOptions {
env?: string; // Environment (default: NODE_ENV)
proxy?: boolean; // Trust proxy headers
subdomainOffset?: number; // Subdomain offset
contextPoolSize?: number; // Max contexts in pool (default: 1000)
logger?: {
enabled?: boolean; // Enable logger (default: true)
level?: LogLevel; // Log level (default: 'info')
prettyPrint?: boolean; // Pretty output (default: dev mode)
name?: string; // App name (default: '@bamptee/koax')
transport?: Transport; // Custom transport
};
timing?: boolean; // Auto timing (default: true)
}// Register middleware (Koa-compatible)
app.use(middleware: Middleware): this
// Register hooks
app.onRequest(hook: HookFunction): this
app.onResponse(hook: HookFunction): this
app.onError(hook: ErrorHookFunction): this
// Start server
app.listen(port: number, callback?: () => void): Server
// Get request handler
app.callback(): RequestHandler
// Get pool statistics
app.getPoolStats(): { poolSize: number; created: number; maxSize: number }Compatible with Koa's Context API plus new features:
interface KoaXContext {
// Koa-compatible properties
app: KoaXApplication
req: IncomingMessage
res: ServerResponse
request: KoaXRequest
response: KoaXResponse
state: Record<string, any>
// Delegated from request
url: string
method: string
path: string
query: Record<string, string>
headers: Record<string, string | string[] | undefined>
// Delegated from response
status: number
message: string
body: any
// Methods
throw(status: number, message?: string): never
assert(condition: any, status: number, message?: string): void
set(field: string, val: string | string[]): void
get(field: string): string | number | string[] | undefined
// NEW: KoaX additions
log: Logger // Structured logger with request context
requestId: string // Unique request ID
startTime: number // Request start timestamp
}// Log levels
ctx.log.trace(msg: string, data?: object): void
ctx.log.debug(msg: string, data?: object): void
ctx.log.info(msg: string, data?: object): void
ctx.log.warn(msg: string, data?: object): void
ctx.log.error(err: Error | string, data?: object): void
ctx.log.fatal(msg: string, data?: object): void
// Alternative signature
ctx.log.info({ userId: 123 }, 'User logged in');import { transports } from '@bamptee/koax';
// Console
transports.console({ prettyPrint: boolean })
// File
transports.file(path: string, options?: {
bufferSize?: number
flushIntervalMs?: number
})
// HTTP
transports.http(url: string, options?: {
headers?: Record<string, string>
bufferSize?: number
flushIntervalMs?: number
})
// Custom
transports.custom((entry: LogEntry) => void)
// Multi
transports.multi(...transports: Transport[])
// Filter
transports.filter(
transport: Transport,
filter: (entry: LogEntry) => boolean
)import KoaX from '@bamptee/koax';
const app = new KoaX({ logger: { prettyPrint: true } });
app.use(async (ctx, next) => {
try {
await next();
} catch (err: any) {
ctx.log.error(err, 'Request failed');
ctx.status = err.status || 500;
ctx.body = { error: err.message, requestId: ctx.requestId };
}
});
app.use(async (ctx) => {
const { path, method } = ctx;
if (path === '/api/users' && method === 'GET') {
ctx.log.info('Fetching users');
const users = await db.users.findAll();
ctx.body = { users };
return;
}
if (path === '/api/health' && method === 'GET') {
ctx.body = { status: 'healthy', uptime: process.uptime() };
return;
}
ctx.status = 404;
ctx.body = { error: 'Not Found' };
});
app.listen(3000);import KoaX from '@bamptee/koax';
import cors from '@koa/cors';
import bodyParser from 'koa-bodyparser';
import Router from '@koa/router';
const app = new KoaX();
const router = new Router();
// Use Koa middleware - works unchanged!
app.use(cors());
app.use(bodyParser());
// Use Koa router - works unchanged!
router.get('/users', async (ctx) => {
ctx.body = { users: [] };
});
router.post('/users', async (ctx) => {
const user = ctx.request.body;
ctx.body = { user };
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);import KoaX, { transports } from '@bamptee/koax';
const app = new KoaX({
contextPoolSize: 2000,
logger: {
enabled: true,
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
prettyPrint: process.env.NODE_ENV !== 'production',
name: 'my-api',
transport: process.env.NODE_ENV === 'production'
? transports.multi(
// All logs to file
transports.file('/var/log/app.log'),
// Errors to monitoring
transports.filter(
transports.http(process.env.LOG_ENDPOINT!, {
headers: { 'Authorization': `Bearer ${process.env.LOG_TOKEN}` }
}),
(entry) => entry.level >= 50
)
)
: transports.console({ prettyPrint: true })
}
});
// Security headers
app.onRequest(async (ctx) => {
ctx.set('X-Frame-Options', 'DENY');
ctx.set('X-Content-Type-Options', 'nosniff');
ctx.set('X-XSS-Protection', '1; mode=block');
});
// Request logging
app.onRequest(async (ctx) => {
ctx.log.info('Request received', {
ip: ctx.req.socket.remoteAddress,
userAgent: ctx.headers['user-agent']
});
});
// Metrics
app.onResponse(async (ctx) => {
const duration = Date.now() - ctx.startTime;
metrics.histogram('http_request_duration', duration, {
method: ctx.method,
path: ctx.path,
status: ctx.status
});
});
// Error alerting
app.onError(async (error, ctx) => {
if (error.status >= 500) {
alerting.sendAlert({
severity: 'error',
message: error.message,
requestId: ctx.requestId,
path: ctx.path
});
}
});
// Your routes...
app.use(routes);
const PORT = parseInt(process.env.PORT || '3000', 10);
app.listen(PORT, () => {
app.logger.info(`Server running on port ${PORT}`);
});- QUICKSTART.md — 5-minute setup guide
- HOOKS_AND_LOGGING.md — Complete hooks & logging guide
- TRANSPORTS.md — Transport system documentation
- COMPARISON.md — Side-by-side Koa comparison
- OPTIMIZATIONS.fr.md — Technical optimizations (FR)
- IMPROVEMENTS_SUMMARY.md — Feature summary
npm uninstall koa
npm install koax// Before
import Koa from 'koa';
// After
import KoaX from '@bamptee/koax';// Before
const app = new Koa();
// After (minimal)
const app = new KoaX();
// After (with features)
const app = new KoaX({
logger: { enabled: true, prettyPrint: true }
});// Add logging hooks
app.onRequest(async (ctx) => ctx.log.info('Request received'));
app.onResponse(async (ctx) => ctx.log.info('Response sent'));That's it! All your Koa middleware works unchanged.
| Feature | Koa | KoaX |
|---|---|---|
| API Compatibility | ✅ | ✅ 100% |
| Middleware | ✅ | ✅ Same |
| Performance | Good | +23% faster |
| Context Pooling | ❌ | ✅ Built-in |
| Hooks System | ❌ | ✅ Built-in |
| Structured Logging | ❌ | ✅ Built-in |
| Log Transports | ❌ | ✅ Built-in |
| Request Timing | ❌ | ✅ Built-in |
| Request ID | ❌ | ✅ Built-in |
| TypeScript | Partial | ✅ Full |
| Dependencies | Several | Zero extra |
- ✅ All Koa middleware (
@koa/cors,koa-bodyparser, etc.) - ✅
@koa/routerand other routers - ✅ Node.js 18+
- ✅ TypeScript 5.0+
- ✅ CommonJS and ESM
@koa/cors✅koa-bodyparser✅@koa/router✅koa-session✅koa-static✅- Custom middleware ✅
# Run tests
npm test
# Run examples
npm run dev # Router example
npm run dev:hooks # Hooks example
npm run dev:transports # Transports example
# Run benchmarks
npm run benchmark # Koa vs KoaX
npm run benchmark:hooks # Overhead analysis- Better performance — 23% faster out of the box
- Better DX — Hooks, logging, timing built-in
- Better observability — Request IDs, structured logs, transports
- Production-ready — Error handling, monitoring, metrics
- Same API — Drop-in replacement, no rewrite needed
Yes! All Koa middleware works unchanged. We maintain the same:
- Middleware signature:
(ctx, next) => Promise<void> - Context API (with additions)
- Onion model (downstream/upstream)
- Error handling
~11% overhead for both hooks and logging. Worth it for the observability gains.
You can disable features you don't need:
const app = new KoaX({
logger: { enabled: false }, // Disable logger
timing: false // Disable timing
});Yes! Just replace import Koa from 'koa' with import KoaX from '@bamptee/koax'. Everything else works the same.
Yes! KoaX is written in TypeScript and has complete type definitions.
- Context pooling
- Iterative middleware dispatch
- Hooks system
- Structured logging
- Transport system
- Request timing
- Request ID tracking
- Schema validation (AJV)
- WebSocket support
- HTTP/2 & HTTP/3
- OpenTelemetry integration
- Metrics endpoint
- Rate limiting
- CORS middleware (built-in)
MIT © 2025
KoaX is inspired by:
If you like KoaX, please:
- ⭐️ Star this repo
- 🐛 Report issues
- 💡 Suggest features
- 🔀 Contribute PRs
Built with ❤️ for the Node.js community