WhatwgAdapter isolates WHATWG runtime specifics at the boundary so the rest of the application can keep using the same server and handler model.
WHATWG Fetch API adapter that bridges standard Request and Response objects with the Ingest framework, enabling seamless integration with serverless environments and modern web standards.
import WhatwgAdapter from '@stackpress/ingest/whatwg/Adapter';
// Static usage
const response = await WhatwgAdapter.plug(context, request);
// Instance usage
const adapter = new WhatwgAdapter(context, request);
const response = await adapter.plug();- Plugging WHATWG Requests
- Methods
- Request Processing Flow
- Body Loading
- Response Dispatching
- Serverless Integration Examples
- Static Functions
- Advanced Usage
- Best Practices
The following example shows how to handle WHATWG requests using the static plug method for serverless environments.
import { server } from '@stackpress/ingest/whatwg';
const app = server();
// Basic usage
export default async function handler(request: Request) {
return await WhatwgAdapter.plug(app, request);
}
// With custom action
export async function customHandler(request: Request) {
return await WhatwgAdapter.plug(app, request, 'custom-handler');
}
// With action function
export async function functionHandler(request: Request) {
return await WhatwgAdapter.plug(app, request, async ({ res }) => {
res.setJSON({ message: 'Custom handler' });
});
}Parameters
| Parameter | Type | Description |
|---|---|---|
context |
WhatwgServer<C> |
Server context instance |
request |
Request |
WHATWG Request object |
action |
string|WhatwgAction<C> |
Custom action name or function (optional) |
Returns
A promise that resolves to a WHATWG Response object.
The following methods are available when instantiating a WhatwgAdapter.
The following example shows how to process WHATWG requests through the adapter with flexible action handling.
const adapter = new WhatwgAdapter(context, request);
// Process with automatic route detection
const response = await adapter.plug();
// Process with custom action
const response2 = await adapter.plug('user-login');
// Process with action function
const response3 = await adapter.plug(async ({ res }) => {
const users = await getUsers();
res.setResults(users);
});Parameters
| Parameter | Type | Description |
|---|---|---|
action |
string|WhatwgAction<C> |
Custom action name or function (optional) |
Returns
A promise that resolves to a WHATWG Response object.
The following example shows how WHATWG requests are converted to Ingest Request objects.
const adapter = new WhatwgAdapter(context, request);
const ingestRequest = adapter.request();
// Access request properties
console.log(ingestRequest.method); // 'GET', 'POST', etc.
console.log(ingestRequest.url); // URL object
console.log(ingestRequest.headers); // Request headers
console.log(ingestRequest.query); // Query parameters
console.log(ingestRequest.session); // Session data from cookiesReturns
An Ingest Request object configured for the WHATWG request.
The following example shows how WHATWG responses are created for Ingest processing.
const adapter = new WhatwgAdapter(context, request);
const ingestResponse = adapter.response();
// Set response data
ingestResponse.setJSON({ message: 'Hello World' });
ingestResponse.setHTML('<h1>Hello World</h1>');
ingestResponse.setError('Not found', {}, [], 404);
// Dispatch to WHATWG response
const whatwgResponse = await ingestResponse.dispatch();Returns
An Ingest Response object configured for WHATWG output.
WhatwgAdapter follows a structured request processing flow for reliable WHATWG request handling.
Convert WHATWG Request to Ingest Request with comprehensive data extraction.
// Convert WHATWG Request to Ingest Request
const request = adapter.request();
// - Extracts HTTP method, URL, headers
// - Parses cookies into session data
// - Converts query parameters to nested object
// - Sets up body loader for POST dataCreate Ingest Response for WHATWG Response with proper configuration.
// Create Ingest Response for WHATWG Response
const response = adapter.response();
// - Configures response dispatcher
// - Sets up cookie handling
// - Prepares header managementLoad request body asynchronously with WHATWG-specific handling.
// Load request body asynchronously
await request.load();
// - Reads POST data from the WHATWG request body
// - Parses JSON, URL-encoded, and multipart form bodies
// - Merges parsed data into request.post and request.dataLike the HTTP adapter, plug() calls request.load() before Route.emit(...), so handlers work against the same loaded request shape regardless of runtime.
Execute route through Route.emit with complete lifecycle management.
// Execute route through Route.emit
await Route.emit(event, request, response, context);
// - Runs request lifecycle (prepare, process, shutdown)
// - Executes route handlers
// - Handles errors and 404sCreate WHATWG Response with proper headers and content.
// Create WHATWG Response
const whatwgResponse = await response.dispatch();
// - Creates new Response object
// - Sets status code and message
// - Writes cookies to Set-Cookie headers
// - Sends response headers and bodyWhatwgAdapter provides robust body loading for WHATWG requests with native API support.
Parse request bodies automatically based on content type using WHATWG APIs.
// Body is automatically loaded and parsed
await request.load();
// Access parsed data
const formData = request.post.get(); // Form data
const jsonData = request.data.get(); // Combined data
const rawBody = request.body; // Raw body stringThe WHATWG loader follows the same content-type rules as the HTTP loader: JSON, URL-encoded, and multipart inputs are parsed into request.post; unsupported types leave request.post empty while still caching the raw body text.
Handle different content types with appropriate parsing strategies.
// Form data (application/x-www-form-urlencoded)
// Content-Type: application/x-www-form-urlencoded
// Body: name=John&email=john@example.com
// Result: { name: 'John', email: 'john@example.com' }
// JSON data (application/json)
// Content-Type: application/json
// Body: {"name":"John","email":"john@example.com"}
// Result: { name: 'John', email: 'john@example.com' }
// FormData (multipart/form-data)
// Handles file uploads and form fieldsUse WHATWG Request body methods for different data types.
// WHATWG Request body loading
const bodyText = await request.text();
const bodyJson = await request.json();
const bodyFormData = await request.formData();
const bodyArrayBuffer = await request.arrayBuffer();One runtime detail worth knowing is that the WHATWG loader does not currently enforce a request size limit the way the HTTP loader can. If strict body caps matter, that policy still has to live above or around the adapter today.
WhatwgAdapter handles various response types and formats with WHATWG Response creation.
The dispatch path mirrors the HTTP adapter closely, but the final target is a WHATWG Response. Strings, buffers, Uint8Array, and ReadableStream values are written directly, Node Readable streams are converted, and plain objects or arrays become structured JSON payloads.
If a route or hook already set res.resource to a native WHATWG response, the adapter returns that resource directly instead of rebuilding it. That gives advanced handlers a way to drop down to runtime-native behavior without changing the rest of the application model.
app.get('/native-response', ({ res }) => {
res.resource = new Response('already built', {
status: 200,
headers: { 'Content-Type': 'text/plain' }
});
});Handle different response types with appropriate WHATWG Response creation.
// String responses
response.setHTML('<h1>Hello World</h1>');
// Creates Response with text/html content-type
// JSON responses
response.setJSON({ message: 'Success' });
// Creates Response with application/json content-type
// Buffer responses
response.body = Buffer.from('binary data');
// Creates Response with binary data
// Stream responses
response.body = new ReadableStream({...});
// Creates Response with streaming body
// Object responses (automatic JSON)
response.body = { users: [...] };
// Automatically serializes to JSON ResponseManage session cookies with automatic serialization for serverless environments.
// Session cookies are automatically handled
response.session.set('user_id', '123');
response.session.set('preferences', JSON.stringify(prefs));
// Cookies are written as Set-Cookie headers
// Set-Cookie: user_id=123; Path=/
// Set-Cookie: preferences={"theme":"dark"}; Path=/Set custom headers for security, caching, and API versioning.
// Custom headers
response.headers.set('X-API-Version', '1.0');
response.headers.set('Cache-Control', 'no-cache');
// Security headers
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');The following examples demonstrate WhatwgAdapter integration with popular serverless platforms.
// api/users.ts
import { server } from '@stackpress/ingest/whatwg';
import WhatwgAdapter from '@stackpress/ingest/whatwg/Adapter';
const app = server();
app.get('/api/users', async ({ res }) => {
const users = await getUsers();
res.setResults(users);
});
export default async function handler(request: Request) {
return await WhatwgAdapter.plug(app, request);
}
async function getUsers() {
return [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }];
}// netlify/functions/api.ts
import { server } from '@stackpress/ingest/whatwg';
import WhatwgAdapter from '@stackpress/ingest/whatwg/Adapter';
const app = server();
app.all('/*', async ({ req, res }) => {
// Handle all routes
const path = req.url.pathname;
res.setJSON({ path, method: req.method });
});
export default async function handler(request: Request) {
return await WhatwgAdapter.plug(app, request);
}// worker.ts
import { server } from '@stackpress/ingest/whatwg';
import WhatwgAdapter from '@stackpress/ingest/whatwg/Adapter';
const app = server();
app.get('/', async ({ res }) => {
res.setHTML('<h1>Hello from Cloudflare Workers!</h1>');
});
export default {
async fetch(request: Request): Promise<Response> {
return await WhatwgAdapter.plug(app, request);
}
};// main.ts
import { server } from '@stackpress/ingest/whatwg';
import WhatwgAdapter from '@stackpress/ingest/whatwg/Adapter';
const app = server();
app.get('/api/hello', async ({ res }) => {
res.setJSON({ message: 'Hello from Deno!' });
});
Deno.serve(async (request: Request) => {
return await WhatwgAdapter.plug(app, request);
});// lambda.ts
import { server } from '@stackpress/ingest/whatwg';
import WhatwgAdapter from '@stackpress/ingest/whatwg/Adapter';
const app = server();
app.get('/api/stream', async ({ res }) => {
const stream = new ReadableStream({
start(controller) {
controller.enqueue('chunk 1\n');
controller.enqueue('chunk 2\n');
controller.close();
}
});
res.body = stream;
});
export const handler = async (event: any) => {
const request = new Request(event.requestContext.http.sourceIp, {
method: event.requestContext.http.method,
headers: event.headers,
body: event.body
});
return await WhatwgAdapter.plug(app, request);
};WhatwgAdapter provides utility functions for request and response handling with WHATWG-specific implementations.
The following example shows how to create a custom body loader for WHATWG requests.
import { loader } from '@stackpress/ingest/whatwg/Adapter';
// Create loader for WHATWG Request
const bodyLoader = loader(request);
// Use with Ingest request
ingestRequest.loader = bodyLoader;
await ingestRequest.load();Parameters
| Parameter | Type | Description |
|---|---|---|
resource |
Request |
WHATWG Request object |
Returns
A loader function that reads and parses the request body.
The following example shows how to create a custom response dispatcher for WHATWG responses.
import { dispatcher } from '@stackpress/ingest/whatwg/Adapter';
// Create dispatcher with cookie options
const responseDispatcher = dispatcher({
path: '/',
httpOnly: true,
secure: true,
sameSite: 'strict'
});
// Use with response
response.dispatcher = responseDispatcher;
const whatwgResponse = await response.dispatch();Parameters
| Parameter | Type | Description |
|---|---|---|
options |
CookieOptions |
Cookie configuration options |
Returns
A dispatcher function that creates a WHATWG Response object.
const app = server();
// Custom request preprocessing
app.on('request', async (req, res) => {
// Add request ID
req.data.set('requestId', crypto.randomUUID());
// Parse custom headers
const apiKey = req.headers.get('x-api-key');
if (apiKey) {
req.data.set('apiKey', apiKey);
}
return true;
});
// Route with custom processing
app.get('/api/data', async ({ req, res }) => {
const requestId = req.data('requestId');
const apiKey = req.data('apiKey');
res.setJSON({ requestId, authenticated: !!apiKey });
});app.get('/api/stream', async ({ res }) => {
const stream = new ReadableStream({
async start(controller) {
for (let i = 0; i < 10; i++) {
controller.enqueue(`data chunk ${i}\n`);
await new Promise(resolve => setTimeout(resolve, 100));
}
controller.close();
}
});
res.body = stream;
res.headers.set('Content-Type', 'text/plain');
res.headers.set('Transfer-Encoding', 'chunked');
});app.on('error', async ({ req, res }) => {
// Custom error formatting for APIs
if (req.url.pathname.startsWith('/api/')) {
res.setJSON({
error: res.error,
code: res.code,
timestamp: new Date().toISOString(),
path: req.url.pathname
});
} else {
// HTML error pages for web routes
res.setHTML(`
<h1>Error ${res.code}</h1>
<p>${res.error}</p>
`);
}
return true;
});The following best practices ensure optimal performance and security in serverless environments.
Detect and adapt to different serverless environments for optimal performance.
const app = server();
// Detect serverless environment
app.on('request', async ({ req, res }) => {
const isVercel = process.env.VERCEL === '1';
const isNetlify = process.env.NETLIFY === 'true';
const isCloudflare = typeof caches !== 'undefined';
req.data.set('environment', {
isVercel,
isNetlify,
isCloudflare,
isServerless: isVercel || isNetlify || isCloudflare
});
return true;
});Implement proper CORS handling for cross-origin requests.
app.on('request', async ({ req, res }) => {
// Handle CORS preflight
if (req.method === 'OPTIONS') {
res.headers.set('Access-Control-Allow-Origin', '*');
res.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.headers.set('Access-Control-Max-Age', '86400');
res.code = 204;
return false; // Skip further processing
}
// Set CORS headers for all requests
res.headers.set('Access-Control-Allow-Origin', '*');
return true;
});Optimize responses for serverless environments with appropriate caching.
app.on('response', async ({ req, res }) => {
// Add caching headers
if (req.method === 'GET' && res.code === 200) {
res.headers.set('Cache-Control', 'public, max-age=3600');
res.headers.set('ETag', `"${Date.now()}"`);
}
// Compression hint
res.headers.set('Vary', 'Accept-Encoding');
return true;
});Implement security headers appropriate for serverless deployments.
app.on('response', async (req, res) => {
// Security headers for web responses
if (!req.url.pathname.startsWith('/api/')) {
res.headers.set('X-Frame-Options', 'DENY');
res.headers.set('X-Content-Type-Options', 'nosniff');
res.headers.set('X-XSS-Protection', '1; mode=block');
res.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
}
return true;
});