Skip to content

Context API ​

The Context object is passed to every route handler and middleware, providing access to the request, response helpers, and shared state.

Context Properties ​

ctx.request ​

The standard Web API Request object:

typescript
app.get('/example', (ctx) => {
  const method = ctx.request.method;        // HTTP method
  const url = ctx.request.url;              // Full URL
  const headers = ctx.request.headers;      // Headers object
  
  return json({ method, url });
});

ctx.params ​

Route parameters extracted from the URL:

typescript
app.get('/users/:id/posts/:postId', (ctx) => {
  const { id, postId } = ctx.params;
  return json({ userId: id, postId });
});

ctx.url ​

Parsed URL object:

typescript
app.get('/search', (ctx) => {
  const pathname = ctx.url.pathname;                    // /search
  const query = ctx.url.searchParams.get('q');          // Query param
  const host = ctx.url.host;                            // example.com
  const protocol = ctx.url.protocol;                    // https:
  
  return json({ pathname, query, host, protocol });
});

ctx.state ​

Shared state object for passing data between middlewares:

typescript
// Middleware sets state
app.use(async (ctx, next) => {
  ctx.state.user = await getCurrentUser(ctx);
  await next();
});

// Handler accesses state
app.get('/profile', (ctx) => {
  const user = ctx.state.user;
  return json({ user });
});

Request Methods ​

Reading JSON ​

typescript
app.post('/api/data', async (ctx) => {
  const body = await ctx.request.json();
  return json({ received: body });
});

Reading Text ​

typescript
app.post('/webhook', async (ctx) => {
  const text = await ctx.request.text();
  return json({ received: text });
});

Reading Form Data ​

typescript
app.post('/upload', async (ctx) => {
  const formData = await ctx.request.formData();
  const file = formData.get('file');
  const name = formData.get('name');
  
  return json({ filename: file?.name, name });
});

Reading Blob ​

typescript
app.post('/binary', async (ctx) => {
  const blob = await ctx.request.blob();
  return json({ size: blob.size, type: blob.type });
});

Reading ArrayBuffer ​

typescript
app.post('/raw', async (ctx) => {
  const buffer = await ctx.request.arrayBuffer();
  return json({ byteLength: buffer.byteLength });
});

Headers ​

Reading Headers ​

typescript
app.get('/info', (ctx) => {
  const userAgent = ctx.request.headers.get('User-Agent');
  const auth = ctx.request.headers.get('Authorization');
  const contentType = ctx.request.headers.get('Content-Type');
  
  return json({ userAgent, auth, contentType });
});

Checking Headers ​

typescript
app.post('/api/data', async (ctx) => {
  const contentType = ctx.request.headers.get('Content-Type');
  
  if (!contentType?.includes('application/json')) {
    return json({
      error: 'Content-Type must be application/json'
    }, { status: 415 });
  }
  
  const data = await ctx.request.json();
  return json({ data });
});

Response Helpers ​

JSON Response ​

typescript
import { json } from '@curisjs/core';

app.get('/api/users', (ctx) => {
  return json({ users: [] });
});

// With status code
app.post('/api/users', (ctx) => {
  return json({ created: true }, { status: 201 });
});

// With custom headers
app.get('/api/data', (ctx) => {
  return json({ data: 'value' }, {
    headers: {
      'X-Custom-Header': 'value',
      'Cache-Control': 'no-cache'
    }
  });
});

Text Response ​

typescript
import { text } from '@curisjs/core';

app.get('/robots.txt', (ctx) => {
  return text('User-agent: *\nDisallow: /admin');
});

HTML Response ​

typescript
import { html } from '@curisjs/core';

app.get('/', (ctx) => {
  return html('<h1>Welcome</h1>');
});

app.get('/page', (ctx) => {
  return html(`
    <!DOCTYPE html>
    <html>
      <head><title>Page</title></head>
      <body><h1>Hello</h1></body>
    </html>
  `);
});

Redirect ​

typescript
import { redirect } from '@curisjs/core';

app.get('/old-url', (ctx) => {
  return redirect('/new-url', 301); // Permanent redirect
});

app.get('/temp', (ctx) => {
  return redirect('/new', 302); // Temporary redirect (default)
});

// Other redirect codes: 303, 307, 308

Custom Response ​

typescript
app.get('/custom', (ctx) => {
  return new Response('Custom response', {
    status: 200,
    statusText: 'OK',
    headers: {
      'Content-Type': 'text/plain',
      'X-Custom': 'value'
    }
  });
});

Stream Response ​

typescript
import { stream } from '@curisjs/core';

app.get('/stream', (ctx) => {
  const readable = new ReadableStream({
    start(controller) {
      controller.enqueue(new TextEncoder().encode('chunk 1\n'));
      controller.enqueue(new TextEncoder().encode('chunk 2\n'));
      controller.close();
    }
  });
  
  return stream(() => readable);
});

Server-Sent Events ​

typescript
import { sse } from '@curisjs/core';

app.get('/events', (ctx) => {
  const stream = new ReadableStream({
    start(controller) {
      const encoder = new TextEncoder();
      
      setInterval(() => {
        const data = `data: ${JSON.stringify({ time: Date.now() })}\n\n`;
        controller.enqueue(encoder.encode(data));
      }, 1000);
    }
  });
  
  return sse(() => stream);
});

Query Parameters ​

Single Value ​

typescript
app.get('/search', (ctx) => {
  const query = ctx.url.searchParams.get('q');
  
  if (!query) {
    return json({ error: 'Query parameter required' }, { status: 400 });
  }
  
  return json({ query });
});

// GET /search?q=test -> { query: "test" }

Multiple Values ​

typescript
app.get('/filter', (ctx) => {
  const tags = ctx.url.searchParams.getAll('tag');
  return json({ tags });
});

// GET /filter?tag=js&tag=ts -> { tags: ["js", "ts"] }

With Defaults ​

typescript
app.get('/list', (ctx) => {
  const page = parseInt(ctx.url.searchParams.get('page') || '1');
  const limit = parseInt(ctx.url.searchParams.get('limit') || '10');
  
  return json({ page, limit });
});

// GET /list -> { page: 1, limit: 10 }
// GET /list?page=2&limit=20 -> { page: 2, limit: 20 }

All Parameters ​

typescript
app.get('/debug', (ctx) => {
  const params = Object.fromEntries(ctx.url.searchParams);
  return json({ params });
});

// GET /debug?a=1&b=2&c=3 -> { params: { a: "1", b: "2", c: "3" } }

Cookies ​

Reading Cookies ​

typescript
app.get('/profile', (ctx) => {
  const cookieHeader = ctx.request.headers.get('Cookie');
  
  // Parse cookies manually
  const cookies = Object.fromEntries(
    cookieHeader?.split(';').map(c => {
      const [key, ...values] = c.trim().split('=');
      return [key, values.join('=')];
    }) || []
  );
  
  const sessionId = cookies.session;
  return json({ sessionId });
});

Setting Cookies ​

typescript
app.get('/login', (ctx) => {
  return json({ success: true }, {
    headers: {
      'Set-Cookie': 'session=abc123; HttpOnly; Secure; SameSite=Strict'
    }
  });
});

// Multiple cookies
app.get('/auth', (ctx) => {
  return json({ success: true }, {
    headers: {
      'Set-Cookie': [
        'session=abc123; HttpOnly; Secure',
        'user=john; Max-Age=3600'
      ]
    }
  });
});

State Management ​

Share data between middlewares and route handlers:

typescript
// Authentication middleware
app.use(async (ctx, next) => {
  const token = ctx.request.headers.get('Authorization');
  
  if (token) {
    const user = await verifyToken(token);
    ctx.state.user = user;
    ctx.state.authenticated = true;
  } else {
    ctx.state.authenticated = false;
  }
  
  await next();
});

// Protected route
app.get('/api/profile', (ctx) => {
  if (!ctx.state.authenticated) {
    return json({ error: 'Unauthorized' }, { status: 401 });
  }
  
  return json({ user: ctx.state.user });
});

Typed State ​

typescript
interface AppState {
  user?: User;
  authenticated: boolean;
  requestId: string;
}

// In middleware
app.use(async (ctx, next) => {
  const state = ctx.state as AppState;
  state.requestId = crypto.randomUUID();
  await next();
});

// In handler
app.get('/example', (ctx) => {
  const state = ctx.state as AppState;
  return json({ requestId: state.requestId });
});

Request Information ​

Method ​

typescript
app.all('/resource', (ctx) => {
  const method = ctx.request.method; // GET, POST, PUT, DELETE, etc.
  return json({ method });
});

URL Information ​

typescript
app.get('/info', (ctx) => {
  return json({
    href: ctx.url.href,           // Full URL
    origin: ctx.url.origin,       // https://example.com
    protocol: ctx.url.protocol,   // https:
    host: ctx.url.host,           // example.com:3000
    hostname: ctx.url.hostname,   // example.com
    port: ctx.url.port,           // 3000
    pathname: ctx.url.pathname,   // /info
    search: ctx.url.search,       // ?key=value
    hash: ctx.url.hash            // #section
  });
});

Client IP ​

typescript
app.get('/ip', (ctx) => {
  // Check for proxy headers first
  const ip = 
    ctx.request.headers.get('X-Forwarded-For')?.split(',')[0] ||
    ctx.request.headers.get('X-Real-IP') ||
    'unknown';
  
  return json({ ip });
});

Complete Example ​

typescript
import { createApp, json, text, redirect } from '@curisjs/core';

const app = createApp();

// Middleware - add request ID
app.use(async (ctx, next) => {
  ctx.state.requestId = crypto.randomUUID();
  await next();
});

// Middleware - authentication
app.use(async (ctx, next) => {
  const token = ctx.request.headers.get('Authorization');
  ctx.state.user = token ? await verifyToken(token) : null;
  await next();
});

// Home
app.get('/', (ctx) => {
  return json({
    message: 'Welcome',
    requestId: ctx.state.requestId
  });
});

// Get user profile
app.get('/api/profile', (ctx) => {
  if (!ctx.state.user) {
    return json({ error: 'Unauthorized' }, { status: 401 });
  }
  
  return json({ user: ctx.state.user });
});

// Create resource
app.post('/api/posts', async (ctx) => {
  if (!ctx.state.user) {
    return json({ error: 'Unauthorized' }, { status: 401 });
  }
  
  const body = await ctx.request.json();
  const post = await database.create({
    ...body,
    authorId: ctx.state.user.id
  });
  
  return json({ post }, { status: 201 });
});

// Search with query params
app.get('/api/search', (ctx) => {
  const query = ctx.url.searchParams.get('q') || '';
  const page = parseInt(ctx.url.searchParams.get('page') || '1');
  const limit = parseInt(ctx.url.searchParams.get('limit') || '10');
  
  return json({ query, page, limit, results: [] });
});

app.listen(3000);

Released under the MIT License.