Backend JSRuntime GuidesServerless Workers
Serverless Platform Patterns
Platform-ready auth and backend SDK patterns for Lambda-style and edge-worker runtimes.
Use one runtime-agnostic handler shape and adapt platform event details at the edge of your app.
This pattern covers both auth modes for fully serverless backend deployments:
- JWT/session auth for user-scoped routes
- API key/OAuth protected auth for machine routes
Shared auth handlers
import { authenticateRequest, gateway, users, WachtAuthError } from '@wacht/backend';
export async function handleUserRead(request: Request): Promise<Response> {
try {
const { auth } = await authenticateRequest(request);
await auth.protect({ permission: 'user:read' });
return Response.json(await users.listUsers({ limit: 20 }));
} catch (error) {
if (error instanceof WachtAuthError) {
return Response.json({ error: error.code, message: error.message }, { status: error.status });
}
return Response.json({ error: 'internal_error' }, { status: 500 });
}
}
export async function handleMachineUserRead(request: Request): Promise<Response> {
const decision = await gateway.checkPrincipalAuthz({
principalType: 'api_key',
principalValue: request.headers.get('x-api-key') ?? '',
resource: '/machine/users',
method: 'GET',
requiredPermissions: ['user:read'],
});
if (!decision.allowed) {
return new Response(JSON.stringify({ error: 'forbidden' }), { status: 403 });
}
return Response.json(await users.listUsers({ limit: 20 }));
}Route dispatcher (both auth modes)
import { handleMachineUserRead, handleUserRead } from './users';
export async function routeRequest(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/admin/users' && request.method === 'GET') {
return handleUserRead(request);
}
if (url.pathname === '/machine/users' && request.method === 'GET') {
return handleMachineUserRead(request);
}
return new Response(JSON.stringify({ error: 'not_found' }), { status: 404 });
}AWS Lambda adapter (JWT + machine)
import { initClient } from '@wacht/backend';
import type { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from 'aws-lambda';
import { routeRequest } from '../handlers/router';
export async function handler(event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2> {
initClient({
apiKey: process.env.WACHT_API_KEY!,
baseUrl: process.env.WACHT_BACKEND_API_URL,
});
const request = new Request(`https://lambda.local${event.rawPath}`, {
method: event.requestContext.http.method,
headers: event.headers as HeadersInit,
});
const response = await routeRequest(request);
return {
statusCode: response.status,
headers: Object.fromEntries(response.headers.entries()),
body: await response.text(),
};
}Edge adapter (JWT + machine)
import { initClient } from '@wacht/backend';
import { routeRequest } from '../handlers/router';
export default async function handler(request: Request): Promise<Response> {
initClient({
apiKey: process.env.WACHT_API_KEY!,
baseUrl: process.env.WACHT_BACKEND_API_URL,
});
return routeRequest(request);
}publishableKey is optional when WACHT_PUBLISHABLE_KEY is already configured in env.