Routing
Shokupan supports Express-style routing with a clean, intuitive API. Routes can be defined using HTTP method helpers or the generic add() method.
Basic Routes
Section titled “Basic Routes”Define routes for different HTTP methods:
import { Shokupan } from 'shokupan';
const app = new Shokupan();
// GET requestapp.get('/users', (ctx) => { return { users: ['Alice', 'Bob'] };});
// POST requestapp.post('/users', async (ctx) => { const body = await ctx.body(); return { created: body };});
// PUT, PATCH, DELETEapp.put('/users/:id', (ctx) => ({ updated: ctx.params.id }));app.patch('/users/:id', (ctx) => ({ patched: ctx.params.id }));app.delete('/users/:id', (ctx) => ({ deleted: ctx.params.id }));
// HEAD, OPTIONSapp.head('/users', (ctx) => ctx.status(200));app.options('/users', (ctx) => ctx.status(200));
// Match all HTTP methodsapp.all('/webhook', (ctx) => ({method: ctx.method}));Path Parameters
Section titled “Path Parameters”Extract dynamic values from the URL path:
// Single parameterapp.get('/users/:id', (ctx) => { const userId = ctx.params.id; return { id: userId, name: 'Alice' };});
// Multiple parametersapp.get('/posts/:postId/comments/:commentId', (ctx) => { return { postId: ctx.params.postId, commentId: ctx.params.commentId };});
// Optional segments with wildcardsapp.get('/files/*', (ctx) => { // Matches /files/anything/here return { path: ctx.path };});Query Strings
Section titled “Query Strings”Access query parameters using the query property:
app.get('/search', (ctx) => { const query = ctx.query.get('q'); const page = ctx.query.get('page') || '1'; const limit = ctx.query.get('limit') || '10';
return { query, page: parseInt(page), limit: parseInt(limit), results: [] };});
// GET /search?q=shokupan&page=2&limit=20Routers
Section titled “Routers”Group related routes using ShokupanRouter:
import { ShokupanRouter } from 'shokupan';
const apiRouter = new ShokupanRouter();
apiRouter.get('/users', (ctx) => ({ users: [] }));apiRouter.get('/posts', (ctx) => ({ posts: [] }));apiRouter.get('/comments', (ctx) => ({ comments: [] }));
// Mount router under /api prefixapp.mount('/api', apiRouter);
// Available at:// GET /api/users// GET /api/posts// GET /api/commentsNested Routers
Section titled “Nested Routers”Routers can be nested for better organization:
const v1Router = new ShokupanRouter();const v2Router = new ShokupanRouter();
v1Router.get('/users', (ctx) => ({ version: 1, users: [] }));v2Router.get('/users', (ctx) => ({ version: 2, users: [] }));
app.mount('/api/v1', v1Router);app.mount('/api/v2', v2Router);Route Handlers
Section titled “Route Handlers”Handlers receive a context object and can return various response types:
// Return JSON (most common)app.get('/json', (ctx) => { return { message: 'Hello' };});
// Return textapp.get('/text', (ctx) => { return ctx.text('Hello, World!');});
// Return HTMLapp.get('/html', (ctx) => { return ctx.html('<h1>Hello</h1>');});
// Return Response directlyapp.get('/custom', (ctx) => { return new Response('Custom', { status: 200, headers: { 'X-Custom': 'value' } });});
// Async handlersapp.get('/async', async (ctx) => { const data = await fetchFromDatabase(); return { data };});Multiple Handlers (Middleware Chain)
Section titled “Multiple Handlers (Middleware Chain)”Pass multiple handlers to create a middleware chain:
const authenticate = async (ctx, next) => { if (!ctx.headers.get('authorization')) { return ctx.json({ error: 'Unauthorized' }, 401); } return next();};
const authorize = async (ctx, next) => { // Check permissions return next();};
app.get('/protected', authenticate, authorize, (ctx) => { return { secret: 'data' }; });Route Groups
Section titled “Route Groups”Apply middleware to multiple routes:
const authRouter = new ShokupanRouter();
// This middleware applies to all routes in this routerauthRouter.use(authenticate);
authRouter.get('/profile', (ctx) => ({ profile: {} }));authRouter.get('/settings', (ctx) => ({ settings: {} }));authRouter.post('/logout', (ctx) => ({ message: 'Logged out' }));
app.mount('/auth', authRouter);Route Order
Section titled “Route Order”Routes are matched in the order they are defined:
// ✅ Specific route firstapp.get('/users/me', (ctx) => { return { current: 'user' };});
app.get('/users/:id', (ctx) => { return { id: ctx.params.id };});
// ❌ Wrong order - /users/me would never matchapp.get('/users/:id', (ctx) => { return { id: ctx.params.id };});
app.get('/users/me', (ctx) => { return { current: 'user' }; // Never reached!});OpenAPI Metadata
Section titled “OpenAPI Metadata”Add OpenAPI documentation to your routes:
app.get('/users/:id', { summary: 'Get user by ID', description: 'Retrieves a single user', tags: ['Users'], parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'User found', content: { 'application/json': { schema: { type: 'object', properties: { id: { type: 'string' }, name: { type: 'string' } } } } } } }}, (ctx) => { return { id: ctx.params.id, name: 'Alice' };});Next Steps
Section titled “Next Steps”- Controllers - Use decorators for organized routing
- Middleware - Add cross-cutting concerns
- Context API - Full context reference