Skip to content

Context API

The CloudwerkContext object provides access to bindings, utilities, and request information throughout your application.

There are three ways to access context in Cloudwerk:

  1. Importable helpers (recommended) - Import params, request, get, set directly
  2. getContext() - Call to get the full context object
  3. context parameter (loaders only) - Receive context as a function parameter

All three methods work anywhere within a request: route handlers, loaders, pages, layouts, and middleware.

// Importable helpers - work everywhere (recommended)
import { params, request, get } from '@cloudwerk/core/context'
export async function GET() {
const userId = params.id
const user = get<User>('user')
return json({ userId, user })
}
// In a loader - importable helpers or context parameter both work
import { params, get } from '@cloudwerk/core/context'
export async function loader({ context }: LoaderArgs) {
// Option 1: Use importable helpers (recommended)
const userId = params.id
const user = get<User>('user')
// Option 2: Use context parameter
const userId2 = context.params.id
return { userId, user };
}
// getContext() - also works everywhere
import { getContext, json } from '@cloudwerk/core'
export async function GET() {
const ctx = getContext()
return json({ userId: ctx.params.id })
}
// In middleware
import { set } from '@cloudwerk/core/context'
export const middleware: Middleware = async (request, next) => {
set('user', await validateSession(request))
return next()
};

Import context helpers directly from @cloudwerk/core/context. These work in route handlers, loaders, pages, layouts, and middleware - anywhere within a request:

import { params, request, get, set, getRequestId } from '@cloudwerk/core/context'
export async function GET() {
const userId = params.id
const authHeader = request.headers.get('Authorization')
const user = get<User>('user') // From middleware
return json({ userId, requestId: getRequestId() })
}
ExportTypeDescription
paramsRecord<string, string>Route parameters from dynamic segments
requestRequestCurrent request object
envRecord<string, unknown>Environment bindings
executionCtxExecutionContextFor waitUntil() background tasks
getRequestId()() => stringGet unique request ID for tracing
get<T>(key)(key: string) => T | undefinedGet middleware data
set<T>(key, value)(key: string, value: T) => voidSet data for downstream handlers

Access route parameters:

import { params } from '@cloudwerk/core/context'
// For route /users/[id]/posts/[postId]
export async function GET() {
const { id, postId } = params
return json({ userId: id, postId })
}

Access request data:

import { request } from '@cloudwerk/core/context'
export async function POST() {
const body = await request.json()
const contentType = request.headers.get('Content-Type')
return json({ received: body })
}

Background tasks with executionCtx:

import { executionCtx, request } from '@cloudwerk/core/context'
import { json } from '@cloudwerk/core'
export async function POST() {
const data = await request.json()
// Fire-and-forget background task
executionCtx.waitUntil(
sendAnalytics({ event: 'data_submitted', data })
)
return json({ success: true })
}

Share data between middleware and handlers:

// middleware.ts
import { set } from '@cloudwerk/core/context'
export const middleware: Middleware = async (request, next) => {
const user = await validateSession(request)
set('user', user)
return next()
}
// route.ts
import { get } from '@cloudwerk/core/context'
import { json } from '@cloudwerk/core'
export async function GET() {
const user = get<User>('user')
if (!user) {
return new Response('Unauthorized', { status: 401 })
}
return json({ user })
}

Access to all Cloudflare bindings:

interface CloudwerkContext {
env: {
DB: D1Database; // D1 binding
KV: KVNamespace; // KV binding
R2: R2Bucket; // R2 binding
MY_QUEUE: Queue; // Queue binding
DURABLE_OBJECT: DurableObjectNamespace;
[key: string]: unknown; // Custom bindings
};
}

Query builder for D1 database:

// Select
const users = await context.db
.selectFrom('users')
.where('status', '=', 'active')
.orderBy('created_at', 'desc')
.limit(10)
.execute();
// Insert
const user = await context.db
.insertInto('users')
.values({ email: '[email protected]', name: 'John' })
.returning(['id', 'email'])
.executeTakeFirst();
// Update
await context.db
.updateTable('users')
.set({ name: 'Jane' })
.where('id', '=', userId)
.execute();
// Delete
await context.db
.deleteFrom('users')
.where('id', '=', userId)
.execute();

KV namespace helper:

// Get value
const value = await context.kv.get('key');
const jsonValue = await context.kv.get<User>('key', 'json');
// Set value
await context.kv.put('key', 'value');
await context.kv.put('key', JSON.stringify(data), {
expirationTtl: 3600,
});
// Delete
await context.kv.delete('key');

R2 bucket helper:

// Get object
const object = await context.r2.get('path/to/file.txt');
if (object) {
const text = await object.text();
}
// Put object
await context.r2.put('path/to/file.txt', content, {
httpMetadata: { contentType: 'text/plain' },
});
// Delete
await context.r2.delete('path/to/file.txt');

Queue producers:

// Send message
await context.queues.MY_QUEUE.send({ type: 'email', to: '[email protected]' });
// Send batch
await context.queues.MY_QUEUE.sendBatch([
{ body: { type: 'email', to: '[email protected]' } },
{ body: { type: 'email', to: '[email protected]' } },
]);

Authentication utilities:

interface AuthContext {
// Get current user (null if not authenticated)
getUser(): Promise<User | null>;
// Require user (throws RedirectError if not authenticated)
requireUser(): Promise<User>;
// Session management
createSession(data: SessionData): Promise<void>;
destroySession(): Promise<void>;
getSession(): Promise<Session | null>;
// OAuth helpers
getOAuthUrl(provider: string): string;
exchangeOAuthCode(provider: string, code: string): Promise<OAuthTokens>;
getOAuthProfile(provider: string, tokens: OAuthTokens): Promise<OAuthProfile>;
}

Usage:

// Get current user
const user = await context.auth.getUser();
if (!user) {
throw new RedirectError('/login');
}
// Or use requireUser
const user = await context.auth.requireUser(); // Throws if not authenticated
// Create session
await context.auth.createSession({
userId: user.id,
email: user.email,
});
// Destroy session (logout)
await context.auth.destroySession();

The incoming Request object:

// Access request properties
const url = new URL(context.request.url);
const method = context.request.method;
const headers = context.request.headers;
// Parse body
const json = await context.request.json();
const formData = await context.request.formData();
const text = await context.request.text();

Extend request lifetime for background tasks:

export async function POST(request: Request, { context }: CloudwerkHandlerContext) {
// Respond immediately
const response = json({ success: true });
// Continue processing in background
context.waitUntil(
sendAnalyticsEvent({ type: 'api_call', endpoint: '/api/users' })
);
return response;
}

Cloudflare-specific request properties:

interface CfProperties {
asn: number; // ASN of the incoming request
asOrganization: string; // Organization name
city: string; // City
colo: string; // Cloudflare data center
continent: string; // Continent code
country: string; // Country code
latitude: string; // Latitude
longitude: string; // Longitude
postalCode: string; // Postal code
region: string; // Region/state
regionCode: string; // Region code
timezone: string; // Timezone
tlsVersion: string; // TLS version
tlsCipher: string; // TLS cipher
}
// Usage
const { country, city, timezone } = context.cf;
console.log(`Request from ${city}, ${country} (${timezone})`);

Create JSON response:

export async function GET(request: Request, { context }: CloudwerkHandlerContext) {
return context.json({ message: 'Hello' });
return context.json({ error: 'Not found' }, { status: 404 });
return context.json(data, {
status: 201,
headers: { 'X-Custom': 'value' },
});
}

Create redirect response:

return context.redirect('/dashboard');
return context.redirect('/login', 302); // Temporary redirect
return context.redirect('https://example.com', 301); // Permanent redirect

Create HTML response:

return context.html('<h1>Hello, World!</h1>');
return context.html(htmlContent, { status: 200 });

Create text response:

return context.text('Hello, World!');
return context.text('Not found', { status: 404 });

Create streaming response:

const stream = new ReadableStream({
start(controller) {
controller.enqueue('Hello, ');
controller.enqueue('World!');
controller.close();
},
});
return context.stream(stream, {
headers: { 'Content-Type': 'text/plain' },
});
interface CloudwerkContext {
env: Env;
db: DatabaseClient;
kv: KVHelper;
r2: R2Helper;
queues: Record<string, Queue>;
auth: AuthContext;
request: Request;
waitUntil: (promise: Promise<unknown>) => void;
cf: CfProperties;
// Response helpers
json<T>(data: T, init?: ResponseInit): Response;
redirect(url: string, status?: number): Response;
html(content: string, init?: ResponseInit): Response;
text(content: string, init?: ResponseInit): Response;
stream(stream: ReadableStream, init?: ResponseInit): Response;
}
interface CloudwerkHandlerContext {
params: Record<string, string>;
context: CloudwerkContext;
}
interface LoaderArgs {
request: Request;
params: Record<string, string>;
context: CloudwerkContext;
}