Creates a new Context instance from HTTP request information.
The constructor validates all input parameters and sets up the context for request processing. Invalid parameters will throw descriptive errors.
Validation Rules:
Security Features:
The HTTP method (GET, POST, PUT, DELETE, etc.)
The URL object containing pathname and searchParams
The request headers
Optional
body: BufferOptional request body as Buffer
// Create context from request
const url = new URL('https://api.example.com/users/123?page=1&limit=10');
const headers = new Headers({
'content-type': 'application/json',
'authorization': 'Bearer token123'
});
const body = Buffer.from('{"name":"John","age":30}');
const ctx = new Context('POST', url, headers, body);
// Access validated properties
console.log(ctx.method); // 'POST'
console.log(ctx.path); // '/users/123'
console.log(ctx.requestBody()); // { name: 'John', age: 30 }
console.log(ctx.queryParams.get('page')); // '1'
// Error cases
try {
new Context('INVALID', url, headers); // Throws: Invalid HTTP method
} catch (error) {
console.error(error.message);
}
try {
const longPathUrl = new URL('https://example.com/' + 'a'.repeat(2049));
new Context('GET', longPathUrl, headers); // Throws: Path too long
} catch (error) {
console.error(error.message);
}
Custom data container for storing request-scoped state.
This object allows middleware and handlers to share data throughout the request lifecycle. Common use cases include:
Note: This data is request-scoped and will be garbage collected after the request completes.
V8 optimization: Object.create(null) eliminates prototype chain for faster property access and cleaner object shapes.
// Authentication middleware
const authMiddleware = new Middleware(async (ctx) => {
const token = ctx.requestHeaders.get('authorization');
if (token) {
ctx.data.user = await validateToken(token);
ctx.data.isAuthenticated = true;
}
});
// Handler using stored data
const userHandler = (ctx) => {
if (ctx.data.isAuthenticated) {
ctx.json({ user: ctx.data.user, message: 'Welcome!' });
} else {
ctx.notFound('Please login');
}
};
// Caching parsed data
const cacheMiddleware = new Middleware((ctx) => {
if (!ctx.data.parsedBody) {
ctx.data.parsedBody = ctx.requestBody();
}
});
// Logging middleware
const loggingMiddleware = new Middleware((ctx) => {
ctx.data.startTime = Date.now();
ctx.data.requestId = generateRequestId();
});
Collection of errors that occurred during the request lifecycle.
This array stores any errors that occur during middleware execution, route handler execution, or after-callback execution. Errors are collected instead of immediately thrown, allowing the complete request lifecycle (including after callbacks like logging) to run.
Error Collection Flow:
Use Cases:
// Middleware can check for and consume errors
const loggingMiddleware = new Middleware((ctx) => {
if (ctx.errors.length > 0) {
console.error(`Request failed with ${ctx.errors.length} error(s):`);
ctx.errors.forEach((error, index) => {
console.error(`Error ${index + 1}:`, error.message);
});
// Consume the errors after logging them
ctx.errors.length = 0;
}
});
// Handlers can add custom errors
const validationHandler = (ctx) => {
const data = ctx.requestBody();
if (!data.email) {
ctx.errors.push(new Error('Email is required'));
}
if (!data.password) {
ctx.errors.push(new Error('Password is required'));
}
};
Named path parameters extracted from the URL path.
This object contains key-value pairs where keys are parameter names
(without the :
prefix) and values are the actual path segments.
Path parameters are typically set by the router when matching routes
with parameter placeholders.
Note: All values are strings, even if they represent numbers. Convert to appropriate types as needed in your handlers.
// Route: /users/:id/posts/:postId
// URL: /users/123/posts/456
console.log(ctx.pathParams.id); // "123"
console.log(ctx.pathParams.postId); // "456"
// Route: /products/:category/:productId
// URL: /products/electronics/laptop-123
console.log(ctx.pathParams.category); // "electronics"
console.log(ctx.pathParams.productId); // "laptop-123"
// Convert string to number if needed
const userId = parseInt(ctx.pathParams.id, 10);
const postId = Number(ctx.pathParams.postId);
The body content to return in the HTTP response.
Can be a string, Buffer, or null. The content type should be set via the responseHeaders to ensure proper interpretation by the client.
Note: For large responses, consider using streams instead of setting the entire body in memory.
// Set string response body
ctx.responseBody = "Hello, World!";
// Set Buffer response body (for binary data)
ctx.responseBody = Buffer.from([0x89, 0x50, 0x4E, 0x47]); // PNG header
// Set JSON response body
ctx.responseBody = JSON.stringify({ success: true, data: [] });
// Clear response body
ctx.responseBody = null;
Flag indicating whether the response has been finalized.
When set to true
, this prevents further processing of the request:
This flag is typically set by middleware that needs to short-circuit the request processing (e.g., authentication failures, rate limiting).
// Authentication middleware
const authMiddleware = new Middleware((ctx) => {
const token = ctx.requestHeaders.get('authorization');
if (!token) {
ctx.responseStatusCode = 401;
ctx.responseBody = 'Unauthorized';
ctx.responseEnded = true; // Stop processing
return;
}
// Continue processing...
});
// Rate limiting middleware
const rateLimitMiddleware = new Middleware((ctx) => {
if (isRateLimited(ctx)) {
ctx.responseStatusCode = 429;
ctx.responseBody = 'Too Many Requests';
ctx.responseEnded = true; // Stop processing
return;
}
});
HTTP Headers to send in the response.
A mutable Headers object that allows setting response headers. All header keys are automatically normalized to lowercase for consistency.
Common Headers: Content-Type, Content-Length, Cache-Control, Set-Cookie, Location (for redirects), etc.
// Set response headers
ctx.responseHeaders.set('content-type', 'application/json');
ctx.responseHeaders.set('cache-control', 'no-cache');
ctx.responseHeaders.set('x-custom-header', 'value');
// Set multiple headers
ctx.responseHeaders.set('access-control-allow-origin', '*');
ctx.responseHeaders.set('access-control-allow-methods', 'GET, POST, PUT');
// Check if header is set
if (!ctx.responseHeaders.has('content-type')) {
ctx.responseHeaders.set('content-type', 'text/plain');
}
The HTTP status code to return in the response.
Defaults to 200 (OK). Common status codes:
// Set success status codes
ctx.responseStatusCode = 200; // OK
ctx.responseStatusCode = 201; // Created
ctx.responseStatusCode = 204; // No Content
// Set error status codes
ctx.responseStatusCode = 400; // Bad Request
ctx.responseStatusCode = 401; // Unauthorized
ctx.responseStatusCode = 404; // Not Found
ctx.responseStatusCode = 500; // Internal Server Error
// Use with response helpers
ctx.notFound(); // Sets 404
ctx.error(); // Sets 500
The HTTP method of the request in uppercase.
Returns the normalized HTTP method (GET, POST, PUT, DELETE, etc.) as a string. The method is validated during construction to ensure it's a valid HTTP method.
The URL path of the request.
Returns the normalized path from the request URL. The path is validated during construction with security limits to prevent DoS attacks:
Note: Trailing slashes are preserved as they can be significant for routing purposes.
// Access the request path
console.log(ctx.path); // "/users/123/posts"
// Path-based routing logic
if (ctx.path.startsWith('/api/')) {
// Handle API routes
} else if (ctx.path.startsWith('/admin/')) {
// Handle admin routes
}
// Path validation
if (ctx.path.length > 100) {
ctx.error('Path too long');
return;
}
Query parameters parsed from the URL.
Returns a URLSearchParams object containing all query string parameters. This provides a standard interface for accessing and manipulating query parameters with built-in URL encoding/decoding.
Note: While the URLSearchParams object is returned directly, modifications to query parameters after construction are generally discouraged as they may affect middleware behavior and request integrity.
// Access query parameters
const page = ctx.queryParams.get('page'); // "1"
const limit = ctx.queryParams.get('limit'); // "10"
// Check if parameter exists
if (ctx.queryParams.has('search')) {
const searchTerm = ctx.queryParams.get('search');
// Handle search functionality
}
// Get all values for a parameter (if multiple)
const tags = ctx.queryParams.getAll('tag'); // ["js", "api", "web"]
// Iterate over all parameters
for (const [key, value] of ctx.queryParams.entries()) {
console.log(`${key}: ${value}`);
}
// URL: /users?page=1&limit=10&search=john&tag=js&tag=api
// Results in:
// page: "1"
// limit: "10"
// search: "john"
// tag: "js" (first occurrence)
// tag: "api" (second occurrence)
HTTP Headers of the request.
Returns the complete Headers object containing all request headers. All header keys are normalized to lowercase for consistent access.
Note: While the Headers object is returned directly, modifications to request headers after construction are generally discouraged as they may affect middleware behavior and request integrity.
The request headers object
// Access request headers
const contentType = ctx.requestHeaders.get('content-type');
const authToken = ctx.requestHeaders.get('authorization');
// Check if header exists
if (ctx.requestHeaders.has('x-api-key')) {
// Handle API key authentication
}
// Iterate over all headers
for (const [key, value] of ctx.requestHeaders.entries()) {
console.log(`${key}: ${value}`);
}
Adds a middleware instance to the after-callback queue.
The middleware will be executed after the main handler in FIFO order. After-callbacks are typically used for logging, metrics, response transformation, and cleanup operations.
Execution Order: Middleware added first will be executed first.
The middleware instance to add
// Add response logging middleware
const loggingMiddleware = new Middleware((ctx) => {
const duration = Date.now() - ctx.data.startTime;
console.log(`${ctx.method} ${ctx.path} - ${ctx.responseStatusCode} - ${duration}ms`);
});
ctx.addAfterCallback(loggingMiddleware);
// Add response transformation middleware
const transformMiddleware = new Middleware((ctx) => {
if (ctx.responseHeaders.get('content-type')?.includes('application/json')) {
const body = JSON.parse(ctx.responseBody);
body.timestamp = new Date().toISOString();
ctx.responseBody = JSON.stringify(body);
}
});
ctx.addAfterCallback(transformMiddleware);
// Execute all after callbacks
await ctx.runAfterCallbacks();
Adds multiple middleware instances to the after-callback queue.
Middleware are added in the order provided and will be executed
in FIFO order when runAfterCallbacks()
is called.
Performance Note: This is more efficient than calling addAfterCallback()
multiple times as it avoids repeated array operations.
Array of middleware instances to add
// Create multiple after-callbacks
const loggingMiddleware = new Middleware((ctx) => {
// Logging logic
});
const metricsMiddleware = new Middleware((ctx) => {
// Metrics collection
});
const cleanupMiddleware = new Middleware((ctx) => {
// Cleanup operations
});
// Add all after-callbacks at once
ctx.addAfterCallbacks([loggingMiddleware, metricsMiddleware, cleanupMiddleware]);
// Execute in order: logging -> metrics -> cleanup
await ctx.runAfterCallbacks();
Adds a middleware instance to the before-callback queue.
The middleware will be executed before the main handler in FIFO order.
If the responseEnded
flag is already set to true
, the middleware
will not be executed when runBeforeCallbacks()
is called.
Execution Order: Middleware added first will be executed first.
The middleware instance to add
// Add authentication middleware
const authMiddleware = new Middleware(async (ctx) => {
const token = ctx.requestHeaders.get('authorization');
if (!token) {
ctx.notFound('Unauthorized');
return;
}
ctx.data.user = await validateToken(token);
});
ctx.addBeforeCallback(authMiddleware);
// Add logging middleware
const loggingMiddleware = new Middleware((ctx) => {
console.log(`${ctx.method} ${ctx.path} - ${new Date().toISOString()}`);
});
ctx.addBeforeCallback(loggingMiddleware);
// Execute all before callbacks
await ctx.runBeforeCallbacks();
Adds multiple middleware instances to the before-callback queue.
Middleware are added in the order provided and will be executed
in FIFO order when runBeforeCallbacks()
is called.
Performance Note: This is more efficient than calling addBeforeCallback()
multiple times as it avoids repeated array operations.
Array of middleware instances to add
// Create multiple middleware
const authMiddleware = new Middleware(async (ctx) => {
// Authentication logic
});
const loggingMiddleware = new Middleware((ctx) => {
// Logging logic
});
const corsMiddleware = new Middleware((ctx) => {
// CORS logic
});
// Add all middleware at once
ctx.addBeforeCallbacks([corsMiddleware, loggingMiddleware, authMiddleware]);
// Execute in order: cors -> logging -> auth
await ctx.runBeforeCallbacks();
Sends a 500 Internal Server Error response.
This is a convenience method for sending 500 error responses. It automatically sets the status code to 500, content-type to text/plain, and calculates the content-length for proper HTTP compliance.
Note: Falsy values (null, undefined, 0, false) are converted to empty strings to ensure consistent behavior.
Optional
message: string = MESSAGES.INTERNAL_SERVER_ERROROptional custom error message
The Context instance for method chaining
// Send default 500 response
ctx.error();
// Result: 500 Internal Server Error, Content-Type: text/plain
// Body: "Internal Server Error"
// Send custom 500 message
ctx.error('Database connection failed');
// Result: 500 Internal Server Error, Content-Type: text/plain
// Body: "Database connection failed"
// Send empty 500 response
ctx.error('');
// Result: 500 Internal Server Error, Content-Type: text/plain
// Body: "" (empty string)
// Method chaining
ctx.error('Service temporarily unavailable')
.responseHeaders.set('retry-after', '60');
// Common usage in error handling
try {
const result = await riskyOperation();
ctx.json(result);
} catch (err) {
console.error('Operation failed:', err);
return ctx.error('Operation failed');
}
// In middleware error handling
const errorMiddleware = new Middleware(async (ctx) => {
try {
// Some operation that might fail
} catch (err) {
ctx.error('Middleware processing failed');
ctx.responseEnded = true;
}
});
Sends a 200 OK response with text/html content type.
This is a convenience method for sending HTML responses. It automatically sets the content-type header and calculates the content-length for proper HTTP compliance.
Note: Falsy values (null, undefined, 0, false) are converted to empty strings to ensure consistent behavior.
The HTML content to send
The Context instance for method chaining
// Send HTML response
ctx.html('<h1>Welcome</h1><p>Hello, World!</p>');
// Result: 200 OK, Content-Type: text/html, Content-Length: 35
// Send empty HTML response
ctx.html('');
// Result: 200 OK, Content-Type: text/html, Content-Length: 0
// Method chaining with additional headers
ctx.html('<html><body>Success</body></html>')
.responseHeaders.set('cache-control', 'no-cache');
Checks if the current response represents a "not found" condition.
This helper method determines if the request resulted in a 404 Not Found response, which is useful for conditional logic in middleware (like logging) that needs to handle 404s differently from other responses.
Use Cases:
True if the response status code is 404
// In logging middleware
const loggingMiddleware = new Middleware((ctx) => {
if (ctx.isNotFound()) {
console.log(`⚠️ 404 Not Found: ${ctx.method} ${ctx.path}`);
} else if (ctx.errors.length > 0) {
console.log(`❌ Error: ${ctx.method} ${ctx.path}`);
} else {
console.log(`✅ Success: ${ctx.method} ${ctx.path}`);
}
});
// In analytics middleware
const analyticsMiddleware = new Middleware((ctx) => {
if (ctx.isNotFound()) {
trackMissingRoute(ctx.path);
}
});
// In custom error page middleware
const errorPageMiddleware = new Middleware((ctx) => {
if (ctx.isNotFound()) {
ctx.html('<h1>Page Not Found</h1><p>The requested page does not exist.</p>');
}
});
Sends a 200 OK response with application/javascript content type.
This is a convenience method for sending JavaScript responses. It automatically sets the content-type header and calculates the content-length for proper HTTP compliance.
Note: Falsy values (null, undefined, 0, false) are converted to empty strings to ensure consistent behavior.
The JavaScript code to send
The Context instance for method chaining
// Send JavaScript response
ctx.js('console.log("Hello from server!");');
// Result: 200 OK, Content-Type: application/javascript, Content-Length: 32
// Send JSONP response
const callback = ctx.queryParams.get('callback') || 'callback';
const data = { message: 'Hello World' };
ctx.js(`${callback}(${JSON.stringify(data)});`);
// Send module response
ctx.js('export const version = "1.0.0";');
// Method chaining
ctx.js('alert("Success!");')
.responseHeaders.set('cache-control', 'no-cache');
Sends a 200 OK response with application/json content type.
This is a convenience method for sending JSON responses. It automatically serializes the data to JSON, sets the content-type header, and calculates the content-length for proper HTTP compliance.
Error Handling: If the data contains circular references or other non-serializable values, JSON.stringify() will throw a TypeError.
Note: Undefined values in objects are omitted from the JSON output as per JSON specification.
The data to serialize as JSON
The Context instance for method chaining
// Send simple JSON object
ctx.json({ success: true, message: 'Hello World' });
// Result: 200 OK, Content-Type: application/json
// Body: {"success":true,"message":"Hello World"}
// Send JSON array
ctx.json([1, 2, 3, 4, 5]);
// Result: 200 OK, Content-Type: application/json
// Body: [1,2,3,4,5]
// Send complex nested object
ctx.json({
user: {
id: 123,
name: 'John Doe',
email: 'john@example.com'
},
metadata: {
timestamp: new Date().toISOString(),
version: '1.0.0'
}
});
// Handle undefined values (omitted from JSON)
ctx.json({ name: 'John', age: undefined, active: true });
// Result: {"name":"John","active":true}
// Method chaining
ctx.json({ status: 'success' })
.responseHeaders.set('x-api-version', '1.0');
// Error case - circular reference
const obj = { name: 'test' };
obj.self = obj;
try {
ctx.json(obj); // Throws TypeError
} catch (error) {
console.error('JSON serialization error:', error.message);
}
Sends a 404 Not Found response.
This is a convenience method for sending 404 error responses. It automatically sets the status code to 404, content-type to text/plain, and calculates the content-length for proper HTTP compliance.
Note: Falsy values (null, undefined, 0, false) are converted to empty strings to ensure consistent behavior.
Optional
message: string = MESSAGES.NOT_FOUNDOptional custom error message
The Context instance for method chaining
// Send default 404 response
ctx.notFound();
// Result: 404 Not Found, Content-Type: text/plain
// Body: "Not Found"
// Send custom 404 message
ctx.notFound('User not found');
// Result: 404 Not Found, Content-Type: text/plain
// Body: "User not found"
// Send empty 404 response
ctx.notFound('');
// Result: 404 Not Found, Content-Type: text/plain
// Body: "" (empty string)
// Method chaining
ctx.notFound('Resource not available')
.responseHeaders.set('x-error-code', 'RESOURCE_404');
// Common usage in route handlers
const user = await getUserById(ctx.pathParams.id);
if (!user) {
return ctx.notFound('User not found');
}
Redirects the client to a different URL.
This method sets up an HTTP redirect response by setting the Location header and an appropriate status code. The client will automatically follow the redirect to the new URL.
Common Status Codes:
The URL to redirect to (can be relative or absolute)
Optional
status: number = 302The HTTP status code for the redirect
The Context instance for method chaining
// Temporary redirect (default)
ctx.redirect('/new-page');
// Result: 302 Found, Location: /new-page
// Permanent redirect
ctx.redirect('/new-location', 301);
// Result: 301 Moved Permanently, Location: /new-location
// External redirect
ctx.redirect('https://example.com');
// Result: 302 Found, Location: https://example.com
// POST redirect (See Other)
if (ctx.method === 'POST') {
ctx.redirect('/success', 303);
// Result: 303 See Other, Location: /success
}
// Method chaining
ctx.redirect('/dashboard')
.responseHeaders.set('x-redirect-reason', 'authentication');
Parses and returns the request body based on the content-type header.
This method automatically handles different content types:
application/json
: Parses as JSON object/arrayapplication/x-www-form-urlencoded
: Parses as plain objectPerformance Note: Parsing occurs on each call. For high-performance scenarios, consider caching the result if the body is accessed multiple times.
Error Handling: JSON parsing errors are thrown as SyntaxError. Form data parsing is lenient and handles malformed input gracefully.
The parsed body or null if no body exists
// JSON body
// Content-Type: application/json
// Body: {"name":"John","age":30}
const body = ctx.requestBody();
console.log(body.name); // "John"
console.log(body.age); // 30
// Form data body
// Content-Type: application/x-www-form-urlencoded
// Body: name=John&age=30&active=true
const formData = ctx.requestBody();
console.log(formData.name); // "John"
console.log(formData.age); // "30" (string)
console.log(formData.active); // "true" (string)
// Raw body (other content types)
// Content-Type: application/octet-stream
// Body: [binary data]
const rawBody = ctx.requestBody();
console.log(Buffer.isBuffer(rawBody)); // true
console.log(rawBody.toString()); // string representation
// No body
const emptyBody = ctx.requestBody();
console.log(emptyBody); // null
Executes all after-callbacks in FIFO order.
This method processes each middleware in the queue sequentially, waiting for each to complete before proceeding to the next. Middleware are removed from the queue as they are executed (consumed).
Execution Flow:
responseEnded
becomes true
Dynamic Middleware: Middleware can add more middleware during execution, enabling complex conditional logic and dynamic response processing.
Error Handling: If any middleware throws an error, execution stops and the error is propagated to the caller.
Resolves when all after-callbacks complete
// Basic usage
await ctx.runAfterCallbacks();
// With error handling
try {
await ctx.runAfterCallbacks();
} catch (error) {
console.error('After-callback error:', error);
// Note: Response may already be sent, so error handling is limited
}
// Dynamic after-callback example
const conditionalMiddleware = new Middleware((ctx) => {
if (ctx.responseStatusCode >= 400) {
// Add error logging middleware dynamically
ctx.addAfterCallback(new Middleware((ctx) => {
console.error('Error response:', ctx.responseStatusCode, ctx.responseBody);
}));
}
});
ctx.addAfterCallback(conditionalMiddleware);
await ctx.runAfterCallbacks(); // May execute additional middleware
Executes all before-callbacks in FIFO order.
This method processes each middleware in the queue sequentially, waiting for each to complete before proceeding to the next. Middleware are removed from the queue as they are executed (consumed).
Execution Flow:
responseEnded
becomes true
Dynamic Middleware: Middleware can add more middleware during execution, enabling complex conditional logic and dynamic request processing.
Error Handling: If any middleware throws an error, execution stops and the error is propagated to the caller.
Resolves when all before-callbacks complete
// Basic usage
await ctx.runBeforeCallbacks();
// With error handling
try {
await ctx.runBeforeCallbacks();
} catch (error) {
console.error('Middleware error:', error);
ctx.error('Internal server error');
}
// Dynamic middleware example
const conditionalMiddleware = new Middleware((ctx) => {
if (ctx.requestHeaders.get('x-debug')) {
// Add debug middleware dynamically
ctx.addBeforeCallback(new Middleware((ctx) => {
console.log('Debug info:', ctx.data);
}));
}
});
ctx.addBeforeCallback(conditionalMiddleware);
await ctx.runBeforeCallbacks(); // May execute additional middleware
Sends a 200 OK response with text/plain content type.
This is a convenience method for sending plain text responses. It automatically sets the content-type header and calculates the content-length for proper HTTP compliance.
Note: Falsy values (null, undefined, 0, false) are converted to empty strings to ensure consistent behavior.
The text content to send
The Context instance for method chaining
// Send simple text response
ctx.text('Hello, World!');
// Result: 200 OK, Content-Type: text/plain, Content-Length: 13
// Send empty text response
ctx.text('');
// Result: 200 OK, Content-Type: text/plain, Content-Length: 0
// Handle falsy values
ctx.text(null); // Sends empty string
ctx.text(0); // Sends empty string
// Method chaining
ctx.text('Success')
.responseHeaders.set('x-custom', 'value');
Sends a 200 OK response with application/xml content type.
This is a convenience method for sending XML responses. It automatically sets the content-type header and calculates the content-length for proper HTTP compliance.
Note: Falsy values (null, undefined, 0, false) are converted to empty strings to ensure consistent behavior.
The XML content to send
The Context instance for method chaining
// Send XML response
ctx.xml('<root><item>value</item></root>');
// Result: 200 OK, Content-Type: application/xml, Content-Length: 28
// Send RSS feed
const rss = `<?xml version="1.0"?>
<rss version="2.0">
<channel>
<title>My Feed</title>
<item><title>Article 1</title></item>
</channel>
</rss>`;
ctx.xml(rss);
// Method chaining
ctx.xml('<response>success</response>')
.responseHeaders.set('x-api-version', '1.0');
Context - The core HTTP request/response lifecycle abstraction.
This class provides a unified interface for handling HTTP requests and responses in an isomorphic manner - working both on the server and in the browser. It encapsulates all request data (headers, body, query params, path params) and provides convenient methods for building responses.
Key Features
Design Philosophy
The Context class follows a mutable-by-design approach for performance reasons. Instead of creating new instances for each middleware step, the same context object is passed through the entire request lifecycle. This reduces memory allocation and garbage collection pressure in high-throughput scenarios.
Security Considerations
Example
Example