Context
The ShokupanContext object provides a rich API for handling requests and responses. It’s passed to every route handler and middleware function.
Context Properties
Section titled “Context Properties”Request Information
Section titled “Request Information”app.get('/info', (ctx) => { return { // HTTP method method: ctx.method, // 'GET', 'POST', etc.
// Request path path: ctx.path, // '/info'
// Full URL url: ctx.url, // 'http://localhost:3000/info?q=test'
// Path parameters params: ctx.params, // { id: '123' } from /users/:id
// Query parameters (URLSearchParams) query: ctx.query, // URLSearchParams object
// Headers (Headers object) headers: ctx.headers,
// Client IP address (string) ip: ctx.ip,
// Host (string) host: ctx.host, // localhost:3000
// Hostname (string) hostname: ctx.hostname, // localhost
// Protocol (string) protocol: ctx.protocol, // http
// Secure context (boolean) secure: ctx.secure, // false
// Origin (string) origin: ctx.origin // http://localhost:3000 };});Request Object
Section titled “Request Object”Access the raw Request object:
app.post('/upload', async (ctx) => { // Raw Request object const request = ctx.req;
// Use Request methods const formData = await request.formData(); const blob = await request.blob();
return { uploaded: true };});Share data across middleware and handlers:
app.use(async (ctx, next) => { ctx.state.requestId = crypto.randomUUID(); ctx.state.startTime = Date.now(); return next();});
app.get('/', (ctx) => { return { requestId: ctx.state.requestId, duration: Date.now() - ctx.state.startTime };});Reading Request Data
Section titled “Reading Request Data”Query Parameters
Section titled “Query Parameters”app.get('/search', (ctx) => { // Get single value const q = ctx.query.get('q');
// Get with default const page = ctx.query.get('page') || '1';
// Get all values for a key const tags = ctx.query.getAll('tag');
// Check if exists const hasFilter = ctx.query.has('filter');
return { q, page, tags, hasFilter };});
// GET /search?q=test&page=2&tag=news&tag=techPath Parameters
Section titled “Path Parameters”app.get('/users/:userId/posts/:postId', (ctx) => { const { userId, postId } = ctx.params;
return { user: userId, post: postId };});
// GET /users/123/posts/456// params = { userId: '123', postId: '456' }Request Body
Section titled “Request Body”The body() method automatically parses JSON and form data:
app.post('/users', async (ctx) => { // Auto-parsed based on Content-Type const data = await ctx.body();
return { created: data };});For specific formats:
app.post('/data', async (ctx) => { // JSON const json = await ctx.req.json();
// Text const text = await ctx.req.text();
// Form data const form = await ctx.req.formData();
// Binary const blob = await ctx.req.blob(); const buffer = await ctx.req.arrayBuffer();
return { received: true };});Headers
Section titled “Headers”app.get('/headers', (ctx) => { // Get single header const auth = ctx.headers.get('authorization'); const userAgent = ctx.headers.get('user-agent');
// Check if exists const hasAuth = ctx.headers.has('authorization');
// Get all headers const allHeaders = Object.fromEntries(ctx.headers.entries());
return { auth, userAgent, hasAuth, allHeaders };});Cookies
Section titled “Cookies”app.get('/cookies', (ctx) => { const cookieHeader = ctx.headers.get('cookie');
// Parse cookies manually or use a plugin const cookies = Object.fromEntries( cookieHeader?.split(';').map(c => { const [key, val] = c.trim().split('='); return [key, val]; }) || [] );
return { cookies };});Sending Responses
Section titled “Sending Responses”JSON Response
Section titled “JSON Response”app.get('/json', (ctx) => { // Implicit JSON (most common) return { message: 'Hello' };
// Explicit JSON with status return ctx.json({ message: 'Created' }, 201);
// Add headers ctx.set('X-Custom', 'value'); return ctx.json({ data: 'value' });});Text Response
Section titled “Text Response”app.get('/text', (ctx) => { return ctx.text('Hello, World!');
// With status return ctx.text('Not Found', 404);});HTML Response
Section titled “HTML Response”JSX Response
Section titled “JSX Response”Render JSX elements directly:
app.get('/jsx', (ctx) => { return ctx.jsx(<div>Hello, World!</div>);
// With props return ctx.jsx(<MyComponent name="Alice" />);});To use JSX, ensure you have configured a JSX renderer in your ShokupanConfig (if not using the default) or are using a transpiler that supports it.
File Response
Section titled “File Response”app.get('/download', (ctx) => { return ctx.file('./path/to/file.pdf', { type: 'application/pdf' });});Redirect
Section titled “Redirect”app.get('/old', (ctx) => { return ctx.redirect('/new');
// Permanent redirect return ctx.redirect('/new', 301);
// Temporary redirect (default) return ctx.redirect('/new', 302);});Status Code
Section titled “Status Code”app.delete('/users/:id', (ctx) => { // No content return ctx.status(204);
// Or with send return ctx.send(null, { status: 204 });});Custom Response
Section titled “Custom Response”Return a Response object directly:
app.get('/custom', (ctx) => { return new Response('Custom response', { status: 200, headers: { 'Content-Type': 'text/plain', 'X-Custom-Header': 'value' } });});Response Headers
Section titled “Response Headers”Set Headers
Section titled “Set Headers”app.get('/headers', (ctx) => { // Set single header ctx.set('X-Custom-Header', 'value');
// Set multiple headers ctx.set('X-Version', '1.0'); ctx.set('X-Powered-By', 'Shokupan');
return { message: 'Check headers' };});Set Cookies
Section titled “Set Cookies”app.get('/set-cookie', (ctx) => { ctx.setCookie('sessionId', 'abc123', { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 3600, // 1 hour path: '/', domain: 'example.com' });
return { message: 'Cookie set' };});Streaming Responses
Section titled “Streaming Responses”Shokupan provides powerful streaming capabilities for efficient data transfer, real-time updates, and Server-Sent Events (SSE).
Generic Streaming (ctx.stream())
Section titled “Generic Streaming (ctx.stream())”Stream binary or text data with full control:
app.get('/stream', (ctx) => { return ctx.stream(async (stream) => { // Write binary data await stream.write(new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]));
// Write string data (auto-encoded to UTF-8) await stream.write("Hello World");
// Sleep for delayed writes await stream.sleep(1000);
// Pipe another ReadableStream const externalStream = await fetchDataStream(); await stream.pipe(externalStream);
// Handle client disconnect stream.onAbort(() => { console.log('Client disconnected'); cleanup(); }); });});Error Handling:
app.get('/stream-safe', (ctx) => { return ctx.stream( async (stream) => { await stream.write("Starting..."); throw new Error("Something went wrong"); }, (err, stream) => { // Custom error handler console.error('Stream error:', err); // Optionally write error message } );});Text Streaming (ctx.streamText())
Section titled “Text Streaming (ctx.streamText())”Optimized for text streaming with proper headers:
app.get('/logs', (ctx) => { return ctx.streamText(async (stream) => { // Write without newline await stream.write("Log: ");
// Write with newline await stream.writeln("Application started"); await stream.writeln("Loading modules...");
// Delayed output await stream.sleep(1000); await stream.writeln("Ready!"); });});Automatically sets:
Content-Type: text/plain; charset=utf-8Transfer-Encoding: chunkedX-Content-Type-Options: nosniff
Real-time Log Streaming:
app.get('/tail-logs', (ctx) => { return ctx.streamText(async (stream) => { const logWatcher = watchLogFile('./app.log');
stream.onAbort(() => { logWatcher.close(); });
for await (const line of logWatcher) { await stream.writeln(line); } });});Server-Sent Events (ctx.streamSSE())
Section titled “Server-Sent Events (ctx.streamSSE())”Perfect for real-time updates and live data:
app.get('/events', (ctx) => { return ctx.streamSSE(async (stream) => { let id = 0;
while (true) { await stream.writeSSE({ event: 'time-update', id: String(id++), data: new Date().toISOString(), retry: 5000 // Reconnect after 5s });
await stream.sleep(1000); } });});Automatically sets:
Content-Type: text/event-streamCache-Control: no-cacheConnection: keep-alive
Live Updates Example:
app.get('/stock-prices', (ctx) => { return ctx.streamSSE(async (stream) => { const subscription = subscribeToStockUpdates();
stream.onAbort(() => { subscription.unsubscribe(); });
for await (const update of subscription) { await stream.writeSSE({ event: 'price-update', data: JSON.stringify({ symbol: update.symbol, price: update.price, change: update.change }), id: update.id }); } });});Client-side (Browser):
const eventSource = new EventSource('/stock-prices');
eventSource.addEventListener('price-update', (event) => { const data = JSON.parse(event.data); console.log(`${data.symbol}: $${data.price}`);});
eventSource.onerror = () => { console.error('Connection lost, reconnecting...');};Piping Streams (ctx.pipe())
Section titled “Piping Streams (ctx.pipe())”Pipe external ReadableStreams directly:
app.get('/video/:id', async (ctx) => { const videoId = ctx.params.id; const videoStream = await getVideoStream(videoId);
return ctx.pipe(videoStream, { status: 200, headers: { 'Content-Type': 'video/mp4', 'Accept-Ranges': 'bytes' } });});Proxy Example:
app.get('/proxy/:url', async (ctx) => { const targetUrl = decodeURIComponent(ctx.params.url); const response = await fetch(targetUrl);
return ctx.pipe(response.body!, { headers: { 'Content-Type': response.headers.get('Content-Type') || 'application/octet-stream' } });});AI/LLM Streaming
Section titled “AI/LLM Streaming”Stream AI responses in real-time:
app.post('/ai/chat', async (ctx) => { const { message } = await ctx.body();
return ctx.streamText(async (stream) => { const aiStream = await callOpenAI(message);
for await (const chunk of aiStream) { await stream.write(chunk.content); } });});Performance Tips
Section titled “Performance Tips”-
Use appropriate method:
ctx.stream()for binary data or custom headersctx.streamText()for text/logsctx.streamSSE()for real-time eventsctx.pipe()for external streams
-
Handle cleanup:
stream.onAbort(() => {// Close connections// Release resources// Stop background tasks}); -
Error handling:
return ctx.streamText(async (stream) => { /* ... */ },(err, stream) => {console.error('Stream error:', err);}); -
Backpressure: Streams automatically handle backpressure - no manual management needed!
Advanced Features
Section titled “Advanced Features”Client IP
Section titled “Client IP”app.get('/ip', (ctx) => { return { ip: ctx.ip };});Response Builder
Section titled “Response Builder”Access the response builder:
app.get('/response', (ctx) => { const response = ctx.response;
// Build custom response response.headers.set('X-Custom', 'value'); response.status = 201;
return { data: 'value' };});Session (with Session Plugin)
Section titled “Session (with Session Plugin)”import { Session } from 'shokupan';
app.use(Session({ secret: 'secret' }));
app.get('/login', (ctx) => { ctx.session.user = { id: '123', name: 'Alice' }; return { message: 'Logged in' };});
app.get('/profile', (ctx) => { if (!ctx.session.user) { return ctx.json({ error: 'Not authenticated' }, 401); } return ctx.session.user;});Type Safety
Section titled “Type Safety”Add types to your context:
import { ShokupanContext } from 'shokupan';
interface User { id: string; name: string;}
interface MyContext { user?: User;}
app.get('/typed', (ctx: ShokupanContext<MyContext>) => { // ctx.state.user is typed as User | undefined return { user: ctx.state.user };});Next Steps
Section titled “Next Steps”- Routing - Learn about routing patterns
- Middleware - Create custom middleware
- API Reference - Complete Context API reference