@ttoss/http-server
Lightweight HTTP server built on Koa for the ttoss ecosystem.
Installation
pnpm add @ttoss/http-server
Quick Start
import { App, Router, bodyParser, cors, serve } from '@ttoss/http-server';
const app = new App();
app.use(cors());
app.use(bodyParser());
const router = new Router();
router.get('/health', (ctx) => {
ctx.body = { status: 'ok' };
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
Health Check Endpoint
Add a health check endpoint with a single line:
import { App, addHealthCheck } from '@ttoss/http-server';
const app = new App();
addHealthCheck({ app });
// or with custom path: addHealthCheck({ app, path: '/healthz' });
app.listen(3000);
// GET /health returns { status: 'ok' }
Core Features
Static File Serving
Serve static files from a directory using the serve middleware:
import { App, serve } from '@ttoss/http-server';
const app = new App();
// Serve files from the 'public' directory
app.use(serve('./public'));
app.listen(3000);
// Files in ./public are now accessible at http://localhost:3000
Advanced Options:
// With custom options
app.use(
serve('./public', {
maxage: 3600000, // Cache files for 1 hour (in milliseconds)
index: 'index.html', // Default file to serve for directories
hidden: false, // Don't serve hidden files
gzip: true, // Enable gzip compression
})
);
Combining with Routes:
import { App, Router, serve } from '@ttoss/http-server';
const app = new App();
const router = new Router();
// Define API routes first
router.get('/api/users', (ctx) => {
ctx.body = [{ id: 1, name: 'John' }];
});
app.use(router.routes());
// Static files are served after API routes
app.use(serve('./public'));
app.listen(3000);
Route Parameters
router.get('/users/:id', (ctx) => {
const { id } = ctx.params;
ctx.body = { userId: id };
});
Request Body Parsing
JSON and form-urlencoded data are automatically parsed when using bodyParser():
router.post('/users', (ctx) => {
const userData = ctx.request.body;
ctx.body = { created: userData };
});
File Uploads
import { multer } from '@ttoss/http-server';
import type { MulterFile } from '@ttoss/http-server';
const upload = multer();
router.post('/upload', upload.single('file'), (ctx) => {
const file = ctx.file as MulterFile | undefined;
ctx.body = {
filename: file?.originalname,
size: file?.size,
};
});
Error Handling
app.use(async (ctx, next) => {
try {
await next();
} catch (error) {
ctx.status = error.status || 500;
ctx.body = { error: error.message };
}
});
An app with its own error envelope usually recognizes only its own error class,
so a deliberate ctx.throw(401, 'Unauthorized', { headers }) from a library
middleware — authMiddleware, createMcpRouter's auth — becomes a 500 with
the WWW-Authenticate header stripped. For MCP that header is the whole
RFC 9728 discovery chain: the client
gets an opaque server error where it expected the pointer to the authorization
server, so OAuth discovery never starts.
toHttpError normalizes a thrown value into { status, message, headers } when
it is a deliberate, exposable 4xx, and undefined otherwise, so a genuine bug
still becomes a 500 with nothing leaked. applyHttpErrorHeaders copies the
headers the thrower attached onto the response — without it the status is right
but discovery is still broken.
import { App, applyHttpErrorHeaders, toHttpError } from '@ttoss/http-server';
app.use(async (ctx, next) => {
try {
await next();
} catch (error) {
if (error instanceof ApiError) {
writeError(ctx, error);
return;
}
const httpError = toHttpError(error);
if (httpError) {
applyHttpErrorHeaders({ ctx, error });
writeError(ctx, new ApiError(httpError.status, httpError.message));
return;
}
console.error('Unhandled request error:', error);
writeError(ctx, new ApiError(500, 'internal_error'));
}
});
OAuth
Authentication lives in @ttoss/http-server-auth — authMiddleware (verify Bearer tokens, including an oauth strategy) and oauthServer() (issue tokens), a thin Koa layer over the runner-agnostic engine in @ttoss/auth-core. This base runner stays auth-free. See the OAuth Authorization Server guideline.
API Reference
All exports are re-exported from established Koa ecosystem packages:
App- Koa applicationRouter- Koa router for routingbodyParser- Koa body parser for JSON/form parsingcors- Koa CORS for cross-origin requestsmulter- Koa multer for file uploadsserve- Koa static for serving static filesaddHealthCheck({ app, path? })- Adds a health endpoint (defaults to/health) returning{ status: 'ok' }toHttpError(error)- Normalizes a deliberate, exposable 4xx into{ status, message, headers };undefinedfor anything elseapplyHttpErrorHeaders({ ctx, error })- Copies headers attached to a thrown error onto the response (e.g.WWW-Authenticate)NormalizedHttpError(type) - Return shape oftoHttpErrorMulterFile(type) - File type for uploaded filesRouterContext<StateT, ContextT>(type) - Generic Koa router context for type-safe route handlers