Skip to content

Latest commit

 

History

History
504 lines (376 loc) · 13.4 KB

File metadata and controls

504 lines (376 loc) · 13.4 KB

Request

Request normalizes request access across runtimes and keeps handlers from depending directly on host-specific request primitives.

The Request class provides a generic wrapper for handling HTTP requests across different platforms with unified data access, cookie-backed session reads, and asynchronous body loading capabilities.

import { Request } from '@stackpress/ingest';

const req = new Request<ResourceType>({
  url: 'http://example.com/api',
  method: 'POST'
});
  1. Properties
  2. Methods
  3. Data Access
  4. Request Creation
  5. Body Loading
  6. Platform Compatibility
  7. Type Parameters
  8. Examples
  9. Type Safety

1. Properties

The following properties are available when instantiating a Request.

Property Type Description
data CallableNest Combined data from query, post, and additional data
headers CallableMap<string, string|string[]> Request headers
query CallableNest URL query parameters
post CallableNest POST body data
session CallableSession Session data
url URL Request URL object
method Method HTTP method (GET, POST, PUT, DELETE, etc.)
body Body|null Raw request body
loaded boolean Whether the body has been loaded
mimetype string Request body MIME type
resource R Original request resource
type string Type of body content
loader RequestLoader<R> Body loader function

2. Methods

The following methods are available when instantiating a Request.

2.1. Loading Request Body

The following example shows how to load the request body asynchronously for processing POST data and file uploads.

const req = new Request({
  url: 'http://example.com/api',
  method: 'POST'
});

await req.load(); // Loads body using the configured loader
console.log(req.body); // Access the loaded body
console.log(req.loaded); // true
console.log(req.post.get()); // Parsed POST body
console.log(req.data()); // Query + POST + additional data merged together

Returns

The Request instance to allow method chaining.

In normal adapter-driven request handling you usually do not call load() yourself. Both the HTTP and WHATWG adapters call it before the route lifecycle begins so handlers can read req.post and req.data without needing to think about the host runtime.

3. Data Access

The Request class provides multiple ways to access request data from different sources including query parameters, POST body, and session data.

These request data objects are all backed by the same nested callable data primitive. If you want the lower-level behavior behind req.query, req.post, and req.data, see Nest and Data Surfaces.

3.1. Combined Data Access

The data property combines query parameters, POST data, and additional context for unified access.

// URL: /users?page=1&limit=10
// POST body: { name: 'John', email: 'john@example.com' }
// Additional data: { userId: 123 }

const page = req.data('page');        // '1' (from query)
const name = req.data('name');        // 'John' (from POST)
const userId = req.data('userId');    // 123 (from additional data)

// Get all data
const allData = req.data();
// Returns: { page: '1', limit: '10', name: 'John', email: 'john@example.com', userId: 123 }

3.2. Query Parameters

Access URL query parameters directly for filtering and pagination.

// URL: /search?q=javascript&category=programming&page=2
const query = req.query.get('q');         // 'javascript'
const category = req.query.get('category'); // 'programming'
const page = req.query.get('page');       // '2'

// Get all query parameters
const allQuery = req.query.get();
// Returns: { q: 'javascript', category: 'programming', page: '2' }

3.3. POST Data

Access POST request body data after loading the request body.

// For POST requests with form data or JSON
await req.load(); // Load the body first

const name = req.post.get('name');
const email = req.post.get('email');

// Get all POST data
const allPost = req.post.get();

3.4. Headers

Access request headers for authentication, content negotiation, and metadata.

const contentType = req.headers.get('content-type');
const authorization = req.headers.get('authorization');
const userAgent = req.headers.get('user-agent');

// Check if header exists
if (req.headers.has('x-api-key')) {
  const apiKey = req.headers.get('x-api-key');
}

// Get all headers
const allHeaders = req.headers.entries();
for (const [name, value] of allHeaders) {
  console.log(`${name}: ${value}`);
}

3.5. Session Data

Access and modify session data for user state management.

// Get session data
const userId = req.session.get('userId');
const username = req.session.get('username');

// Set session data
req.session.set('lastVisit', new Date().toISOString());

// Check if session has data
if (req.session.has('isAuthenticated')) {
  // User is logged in
}

// Get all session data
const sessionData = req.session.get();

On the request side, session data is usually derived from incoming cookies. That keeps route code focused on reading state while the response side owns cookie revisions and persistence.

4. Request Creation

The Request class supports various initialization patterns for different use cases and platforms.

4.1. Basic Request Creation

Create a simple request with URL and method for basic HTTP operations.

import { Request } from '@stackpress/ingest';

const req = new Request({
  url: 'http://example.com/api/users',
  method: 'GET'
});

4.2. Request with Headers

Include custom headers for authentication and content negotiation.

const req = new Request({
  url: 'http://example.com/api/users',
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123'
  }
});

4.3. Request with Data

Initialize request with query parameters and POST data.

const req = new Request({
  url: 'http://example.com/api/users',
  method: 'POST',
  data: {
    name: 'John Doe',
    email: 'john@example.com'
  },
  query: {
    include: 'profile'
  }
});

4.4. Request with Session

Include session data for user context and state management.

const req = new Request({
  url: 'http://example.com/api/profile',
  method: 'GET',
  session: {
    userId: '123',
    username: 'john',
    role: 'admin'
  }
});

5. Body Loading

The Request class supports asynchronous body loading for different content types including JSON, form data, and file uploads.

Parsing is driven by the request content type. Ingest currently recognizes JSON, URL-encoded form data, and multipart form data. If a body uses another content type, the loader still caches the raw body string but req.post stays empty.

5.1. JSON Body

Handle JSON request bodies for API endpoints.

const req = new Request({
  url: 'http://example.com/api/users',
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'John', email: 'john@example.com' })
});

await req.load();
console.log(req.mimetype); // 'application/json'
console.log(req.post.get()); // { name: 'John', email: 'john@example.com' }

5.2. Form Data

Process multipart form data for file uploads and complex forms.

const req = new Request({
  url: 'http://example.com/api/upload',
  method: 'POST',
  headers: { 'Content-Type': 'multipart/form-data' },
  // body would be set by the platform (browser FormData, etc.)
});

await req.load();
console.log(req.post.get('username'));
console.log(req.post.get('file')); // File data

5.3. URL Encoded Data

Handle URL-encoded form submissions.

const req = new Request({
  url: 'http://example.com/api/login',
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: 'username=john&password=secret'
});

await req.load();
console.log(req.post.get('username')); // 'john'
console.log(req.post.get('password')); // 'secret'

6. Platform Compatibility

The Request class provides cross-platform compatibility for Node.js HTTP and WHATWG Fetch environments.

6.1. Node.js HTTP

Integration with Node.js HTTP server for traditional server applications.

import { createServer } from 'node:http';
import { Request } from '@stackpress/ingest';

createServer((incomingMessage, serverResponse) => {
  const req = new Request({ resource: incomingMessage });
  
  console.log(req.method); // GET, POST, etc.
  console.log(req.url.pathname); // /api/users
  console.log(req.headers.get('user-agent'));
});

6.2. WHATWG Fetch (Serverless)

Support for serverless environments like Vercel, Netlify, and Cloudflare Workers.

// Vercel, Netlify, etc.
export default async function handler(request: Request) {
  const req = new Request({ resource: request });
  
  console.log(req.method);
  console.log(req.url.pathname);
  console.log(req.headers.get('authorization'));
  
  return new Response('OK');
}

7. Type Parameters

The Request class accepts one generic type parameter for type-safe resource handling.

Parameter Default Description
R any Request resource type (e.g., IncomingMessage, Request)
import type { IncomingMessage } from 'node:http';

// Node.js HTTP request
const nodeReq = new Request<IncomingMessage>({
  resource: incomingMessage
});

// WHATWG Fetch request
const fetchReq = new Request<globalThis.Request>({
  resource: request
});

// Custom request type
interface CustomRequest {
  customProperty: string;
}

const customReq = new Request<CustomRequest>({
  resource: { customProperty: 'value' }
});

8. Examples

The following examples demonstrate common Request usage patterns and best practices for real-world applications.

8.1. Route Handler with Request Processing

import { server } from '@stackpress/ingest/http';

const app = server();

app.post('/api/users', async ({ req, res }) => {
  // Load request body
  await req.load();
  
  // Access different data sources
  const userData = req.post.get(); // POST body data
  const includeProfile = req.query.get('include'); // Query parameter
  const currentUser = req.session.get('userId'); // Session data
  
  // Validate required fields
  if (!userData.name || !userData.email) {
    res.setError('Name and email are required', {
      name: !userData.name ? 'Name is required' : undefined,
      email: !userData.email ? 'Email is required' : undefined
    }, [], 400);
    return;
  }
  
  // Create user
  const newUser = {
    id: Date.now(),
    ...userData,
    createdBy: currentUser
  };
  
  res.setJSON({ user: newUser }, 201);
});

8.2. Authentication Middleware

app.on('request', async ({ req, res }) => {
  // Skip authentication for public routes
  if (req.url.pathname.startsWith('/public')) {
    return true;
  }
  
  // Check for authorization header
  const authHeader = req.headers.get('authorization');
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    res.setError('Authentication required', {}, [], 401);
    return false;
  }
  
  // Extract and validate token
  const token = authHeader.substring(7);
  try {
    const user = await validateToken(token);
    
    // Store user in request data for later use
    req.data.set('user', user);
    req.session.set('userId', user.id);
    
    return true; // Continue processing
  } catch (error) {
    res.setError('Invalid token', {}, [], 401);
    return false;
  }
}, 10); // High priority

async function validateToken(token: string) {
  // Token validation logic
  return { id: 1, username: 'john', role: 'admin' };
}

8.3. File Upload Handling

app.post('/api/upload', async ({ req, res }) => {
  await req.load();
  
  // Check content type
  if (!req.mimetype.startsWith('multipart/form-data')) {
    res.setError('Multipart form data required', {}, [], 400);
    return;
  }
  
  // Access uploaded files
  const files = req.post.get('files');
  const description = req.post.get('description');
  
  if (!files || !Array.isArray(files)) {
    res.setError('No files uploaded', {}, [], 400);
    return;
  }
  
  // Process files
  const uploadedFiles = [];
  for (const file of files) {
    const savedFile = await saveFile(file);
    uploadedFiles.push(savedFile);
  }
  
  res.setJSON({
    message: 'Files uploaded successfully',
    files: uploadedFiles,
    description
  });
});

9. Type Safety

The Request class supports TypeScript generics for type-safe resource handling:

import type { IncomingMessage } from 'node:http';

// Node.js HTTP request
const nodeReq = new Request<IncomingMessage>({
  resource: incomingMessage
});

// WHATWG Fetch request
const fetchReq = new Request<globalThis.Request>({
  resource: request
});

// Custom request type
interface CustomRequest {
  customProperty: string;
}

const customReq = new Request<CustomRequest>({
  resource: { customProperty: 'value' }
});

The Request class provides a unified interface for handling HTTP requests across different platforms while maintaining type safety and providing convenient data access methods.