Interceptors
Learn how to use interceptors to modify, monitor, or add custom logic to requests and responses in your API.
What Are Interceptors?
Interceptors are powerful hooks that allow you to intercept and modify:
- Incoming requests before they are processed
- Outgoing responses before they are returned to the client
This enables you to add custom logic such as:
- Authentication and authorization
- Request validation and transformation
- Response modification
- Logging and monitoring
- Error handling
- Data customization
Types of Interceptors
api.vision supports two main types of interceptors:
Request Interceptors
Execute before the request is processed. Can modify the request or short-circuit the response.
Use cases: Authentication, request validation, request transformation
Response Interceptors
Execute after the response is generated but before it's sent to the client.
Use cases: Response transformation, adding headers, error handling
Creating Interceptors
Interceptors can be defined in your API configuration or through the dashboard:
Request Interceptor Example
{
"interceptors": {
"request": [
{
"name": "authCheck",
"path": "**", // Apply to all paths
"script": "
// Check for API key
const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== 'your-secret-key') {
return {
status: 401,
body: {
error: 'Unauthorized',
message: 'Invalid or missing API key'
}
};
}
// Add user info to the request for downstream use
req.locals.user = { id: 42, role: 'admin' };
// Continue with the request
return null;
"
}
]
}
}
Response Interceptor Example
{
"interceptors": {
"response": [
{
"name": "addHeaders",
"path": "**", // Apply to all paths
"script": "
// Add custom headers to all responses
res.headers['x-api-version'] = '1.0.0';
res.headers['x-response-time'] = `${Date.now() - req.startTime}ms`;
// Log the response
console.log(`Response to ${req.method} ${req.path}: ${res.status}`);
// Return the modified response
return res;
"
}
]
}
}
Interceptor Context
Interceptors have access to a rich context with these objects:
Object | Available In | Description |
---|---|---|
req | Both | The request object with method, path, headers, query params, body |
res | Response only | The response object with status, headers, body |
config | Both | The API configuration |
store | Both | Persistent storage for the API |
utils | Both | Utility functions for common operations |
The Request Object
req = {
method: 'GET', // HTTP method
path: '/users/42', // Path without query string
pathParams: { id: '42' }, // Path parameters
query: { fields: 'name,email' }, // Query parameters
headers: { ... }, // Request headers (lowercase keys)
body: { ... }, // Request body (parsed if JSON)
cookies: { ... }, // Request cookies
ip: '192.168.1.1', // Client IP address
startTime: 1618497730000, // Request start time (timestamp)
locals: { ... } // Local variables for this request
}
The Response Object
res = {
status: 200, // HTTP status code
headers: { ... }, // Response headers
body: { ... }, // Response body
cookies: { ... }, // Response cookies to set
locals: { ... } // Local variables for this response
}
Common Interceptor Use Cases
Authentication and Authorization
// JWT Authentication Interceptor
{
"name": "jwtAuth",
"path": "**",
"script": "
// Skip auth for login endpoint
if (req.path === '/login') return null;
// Get token from Authorization header
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return { status: 401, body: { error: 'Unauthorized' } };
}
const token = authHeader.split(' ')[1];
try {
// Verify JWT (in a real app, use a proper JWT library)
const user = utils.jwt.verify(token, 'your-secret-key');
// Add user to request context
req.locals.user = user;
// Check authorization for admin-only endpoints
if (req.path.startsWith('/admin') && user.role !== 'admin') {
return { status: 403, body: { error: 'Forbidden' } };
}
return null; // Continue processing the request
} catch (err) {
return { status: 401, body: { error: 'Invalid token' } };
}
"
}
Request Transformation
// Request Transformation Interceptor
{
"name": "transformRequest",
"path": "/products",
"methods": ["POST", "PUT", "PATCH"],
"script": "
// Convert snake_case to camelCase in request body
if (req.body && typeof req.body === 'object') {
req.body = utils.transformKeys(req.body, utils.snakeToCamel);
}
// Add timestamp to request
if (req.method === 'POST') {
req.body.createdAt = new Date().toISOString();
}
req.body.updatedAt = new Date().toISOString();
return null; // Continue with the modified request
"
}
Response Transformation
// Response Transformation Interceptor
{
"name": "transformResponse",
"path": "/products/**",
"methods": ["GET"],
"script": "
// Convert camelCase to snake_case in response body
if (res.body) {
if (Array.isArray(res.body)) {
res.body = res.body.map(item => utils.transformKeys(item, utils.camelToSnake));
} else {
res.body = utils.transformKeys(res.body, utils.camelToSnake);
}
}
// Add API version header
res.headers['x-api-version'] = '1.0.0';
return res;
"
}
Error Handling
// Error Handling Interceptor
{
"name": "errorHandler",
"path": "**",
"script": "
// Skip if the response is already set and is not an error
if (res && res.status < 400) return res;
// Standardize error format
if (res && res.status >= 400) {
const originalBody = res.body;
res.body = {
status: res.status,
error: utils.getHttpStatusText(res.status),
message: originalBody?.message || originalBody?.error || 'An error occurred',
path: req.path,
timestamp: new Date().toISOString()
};
// Log the error
console.error(`Error in ${req.method} ${req.path}: ${res.status} ${res.body.message}`);
return res;
}
return null;
"
}
Conditional Logic and Dynamic Responses
// Dynamic Response Interceptor
{
"name": "dynamicResponse",
"path": "/weather",
"methods": ["GET"],
"script": "
// Get location from query params
const location = req.query.location || 'default';
// Simulate different weather based on location
let weather;
if (location.toLowerCase().includes('seattle')) {
weather = { condition: 'rainy', temperature: 12, unit: 'C' };
} else if (location.toLowerCase().includes('desert')) {
weather = { condition: 'sunny', temperature: 35, unit: 'C' };
} else if (location.toLowerCase().includes('mountain')) {
weather = { condition: 'snowy', temperature: -5, unit: 'C' };
} else {
weather = { condition: 'partly cloudy', temperature: 22, unit: 'C' };
}
// Return custom response immediately
return {
status: 200,
body: {
location,
weather,
forecast: ['similar', 'similar', 'improving', 'better', 'nice'],
lastUpdated: new Date().toISOString()
}
};
"
}
Advanced Interceptor Features
Path Matching
Interceptors use glob pattern matching for the path property:
Pattern | Description | Examples |
---|---|---|
** | Matches any path | /users, /products/123, etc. |
/users/* | Matches one level under /users | /users/123, /users/profile, etc. |
/users/** | Matches any path under /users | /users/123, /users/123/posts, etc. |
/users/:id | Matches paths with named parameter | /users/123, /users/abc, etc. |
Interceptor Order and Priority
When multiple interceptors match a request, they execute in order of:
- Global interceptors (path = "**")
- More specific path matches (from least to most specific)
- Order in the configuration (first to last)
You can explicitly set priority to override the default order:
{
"name": "highPriorityInterceptor",
"path": "/users",
"priority": 100, // Higher priority executes first
"script": "..."
}
Async Operations
Interceptors support async operations using async/await:
{
"name": "externalApiInterceptor",
"path": "/weather/:city",
"script": "
// Simulate fetching data from external API
async function getWeatherData(city) {
// In a real interceptor, this would call an actual API
await utils.sleep(500); // Simulate network delay
return {
city,
temperature: Math.floor(Math.random() * 30) + 5,
condition: ['sunny', 'cloudy', 'rainy', 'snowy'][Math.floor(Math.random() * 4)]
};
}
// Execute the async function and return the result
const city = req.pathParams.city;
const weatherData = await getWeatherData(city);
return {
status: 200,
body: weatherData
};
"
}
Best Practices
- Keep interceptors focused on a single responsibility
- Use descriptive names that reflect what the interceptor does
- Add comments to explain complex logic
- Be careful with performance in interceptors that run on every request
- Use try/catch blocks to handle errors gracefully
- Test interceptors thoroughly with different inputs
- Consider security implications, especially when modifying authentication or authorization logic