The one-liner node.js proxy middleware for connect, express, next.js and more
92
Pending
Does it follow best practices?
Impact
92%
1.24xAverage score across 10 eval scenarios
Pending
The risk profile of this skill
Response interception and manipulation capabilities for modifying upstream responses before they reach the client.
Intercept and modify responses from upstream servers with automatic decompression support for common compression formats.
/**
* Creates a response interceptor function for modifying upstream responses
* @param interceptor - Function that receives the response buffer and can modify it
* @returns Response handler function for use with selfHandleResponse option
*/
function responseInterceptor<
TReq extends http.IncomingMessage = http.IncomingMessage,
TRes extends http.ServerResponse = http.ServerResponse,
>(interceptor: Interceptor<TReq, TRes>): (proxyRes: TReq, req: TReq, res: TRes) => Promise<void>;
/**
* Interceptor function interface for processing response buffers
*/
type Interceptor<TReq = http.IncomingMessage, TRes = http.ServerResponse> = (
buffer: Buffer,
proxyRes: TReq,
req: TReq,
res: TRes,
) => Promise<Buffer | string>;The response interceptor automatically handles:
Basic Response Interception Examples:
import { createProxyMiddleware, responseInterceptor } from "http-proxy-middleware";
// Basic JSON response modification
const jsonModifierProxy = createProxyMiddleware({
target: "http://api.example.com",
selfHandleResponse: true, // Required for response interception
on: {
proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
// Parse JSON response
const response = JSON.parse(responseBuffer.toString("utf8"));
// Add metadata to all responses
response._metadata = {
proxiedAt: new Date().toISOString(),
originalHost: proxyRes.headers?.host,
requestId: req.headers["x-request-id"],
};
// Return modified JSON
return JSON.stringify(response);
}),
},
});
// HTML content modification
const htmlModifierProxy = createProxyMiddleware({
target: "http://web.example.com",
selfHandleResponse: true,
on: {
proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
const contentType = proxyRes.headers?.["content-type"];
// Only modify HTML responses
if (contentType?.includes("text/html")) {
let html = responseBuffer.toString("utf8");
// Inject custom analytics script
const analyticsScript = '<script src="/analytics.js"></script>';
html = html.replace("</head>", `${analyticsScript}</head>`);
// Add custom header
html = html.replace("<body>", '<body><div class="proxy-notice">Content proxied</div>');
return html;
}
// Return unmodified for non-HTML content
return responseBuffer;
}),
},
});Complex response manipulation scenarios including conditional processing, format transformations, and multi-step processing.
Advanced Processing Examples:
// API response transformation and caching
const apiTransformProxy = createProxyMiddleware({
target: "http://legacy-api.example.com",
selfHandleResponse: true,
on: {
proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
const contentType = proxyRes.headers?.["content-type"];
const statusCode = proxyRes.statusCode;
// Only process successful JSON responses
if (statusCode === 200 && contentType?.includes("application/json")) {
const data = JSON.parse(responseBuffer.toString("utf8"));
// Transform legacy API format to modern format
const transformedData = {
success: true,
data: transformLegacyFormat(data),
pagination: data.pagination ? {
page: data.pagination.current_page,
totalPages: data.pagination.total_pages,
totalItems: data.pagination.total_items,
hasNext: data.pagination.has_next,
hasPrev: data.pagination.has_prev,
} : undefined,
meta: {
apiVersion: "v2",
transformedAt: new Date().toISOString(),
originalFormat: "legacy",
},
};
// Cache the transformed response
await cacheResponse(req.url, transformedData);
// Set modern content type
res.setHeader("Content-Type", "application/json; charset=utf-8");
res.setHeader("X-API-Version", "v2");
res.setHeader("X-Transformed", "true");
return JSON.stringify(transformedData);
}
return responseBuffer;
}),
},
});
// Multi-format response handling with error transformation
const multiFormatProxy = createProxyMiddleware({
target: "http://api.example.com",
selfHandleResponse: true,
on: {
proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
const contentType = proxyRes.headers?.["content-type"];
const statusCode = proxyRes.statusCode || 200;
const accept = req.headers.accept;
let responseData;
// Parse response based on content type
if (contentType?.includes("application/json")) {
responseData = JSON.parse(responseBuffer.toString("utf8"));
} else if (contentType?.includes("application/xml")) {
responseData = await parseXML(responseBuffer.toString("utf8"));
} else {
responseData = { raw: responseBuffer.toString("utf8") };
}
// Handle error responses consistently
if (statusCode >= 400) {
const errorResponse = {
error: {
code: statusCode,
message: responseData.message || responseData.error || "Unknown error",
details: responseData.details || responseData,
timestamp: new Date().toISOString(),
path: req.url,
method: req.method,
},
};
res.setHeader("Content-Type", "application/json");
return JSON.stringify(errorResponse);
}
// Transform response based on client preferences
if (accept?.includes("application/xml") && !contentType?.includes("xml")) {
// Convert JSON to XML if client prefers XML
const xmlResponse = convertToXML(responseData);
res.setHeader("Content-Type", "application/xml");
return xmlResponse;
}
if (accept?.includes("text/csv") && Array.isArray(responseData)) {
// Convert array data to CSV
const csvResponse = convertToCSV(responseData);
res.setHeader("Content-Type", "text/csv");
res.setHeader("Content-Disposition", 'attachment; filename="data.csv"');
return csvResponse;
}
// Default JSON response
res.setHeader("Content-Type", "application/json");
return JSON.stringify(responseData);
}),
},
});Security-focused response processing including content sanitization, sensitive data filtering, and security header injection.
Security Processing Examples:
// Sensitive data filtering and security headers
const securityFilterProxy = createProxyMiddleware({
target: "http://internal-api.example.com",
selfHandleResponse: true,
on: {
proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
const contentType = proxyRes.headers?.["content-type"];
// Add security headers
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader("X-Frame-Options", "DENY");
res.setHeader("X-XSS-Protection", "1; mode=block");
res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
if (contentType?.includes("application/json")) {
const data = JSON.parse(responseBuffer.toString("utf8"));
// Remove sensitive fields based on user permissions
const userRole = req.headers["x-user-role"];
const sanitizedData = sanitizeResponse(data, userRole);
// Remove potentially sensitive patterns
const responseStr = JSON.stringify(sanitizedData);
const cleanResponse = responseStr
.replace(/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g, "****-****-****-****") // Credit cards
.replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, "[email protected]") // Email addresses
.replace(/\b\d{3}-\d{2}-\d{4}\b/g, "***-**-****"); // SSN pattern
return cleanResponse;
}
// Sanitize HTML content
if (contentType?.includes("text/html")) {
const html = responseBuffer.toString("utf8");
// Remove potentially dangerous script tags and attributes
const sanitizedHtml = sanitizeHtml(html, {
allowedTags: ['p', 'div', 'span', 'a', 'strong', 'em', 'ul', 'ol', 'li'],
allowedAttributes: {
a: ['href', 'title'],
div: ['class'],
span: ['class'],
},
});
return sanitizedHtml;
}
return responseBuffer;
}),
},
});
// Response validation and schema enforcement
const validationProxy = createProxyMiddleware({
target: "http://api.example.com",
selfHandleResponse: true,
on: {
proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
const contentType = proxyRes.headers?.["content-type"];
const statusCode = proxyRes.statusCode || 200;
if (statusCode === 200 && contentType?.includes("application/json")) {
const data = JSON.parse(responseBuffer.toString("utf8"));
// Validate response against expected schema
const endpoint = req.url?.split("?")[0];
const schema = getResponseSchema(endpoint);
if (schema) {
const validation = validateResponse(data, schema);
if (!validation.valid) {
// Log validation errors
console.error("Response validation failed:", validation.errors);
// Return error response
res.statusCode = 502;
res.setHeader("Content-Type", "application/json");
return JSON.stringify({
error: {
code: 502,
message: "Invalid response format from upstream server",
details: validation.errors,
},
});
}
}
// Add validation metadata
const validatedResponse = {
...data,
_validation: {
validated: true,
schema: schema?.name,
timestamp: new Date().toISOString(),
},
};
return JSON.stringify(validatedResponse);
}
return responseBuffer;
}),
},
});Response processing focused on performance optimization, caching, and content compression.
Performance Optimization Examples:
// Response caching and optimization
const cachingProxy = createProxyMiddleware({
target: "http://api.example.com",
selfHandleResponse: true,
on: {
proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
const cacheKey = generateCacheKey(req);
const contentType = proxyRes.headers?.["content-type"];
const statusCode = proxyRes.statusCode || 200;
// Cache successful responses
if (statusCode === 200 && shouldCache(req, proxyRes)) {
await setCache(cacheKey, responseBuffer, {
ttl: getCacheTTL(req),
contentType,
headers: proxyRes.headers,
});
// Add cache headers
res.setHeader("X-Cache", "MISS");
res.setHeader("X-Cache-Key", cacheKey);
res.setHeader("Cache-Control", `public, max-age=${getCacheTTL(req)}`);
}
// Optimize JSON responses
if (contentType?.includes("application/json") && statusCode === 200) {
const data = JSON.parse(responseBuffer.toString("utf8"));
// Remove null values and optimize structure
const optimizedData = optimizeJsonResponse(data);
// Compress if client supports it
const acceptEncoding = req.headers["accept-encoding"];
if (acceptEncoding?.includes("gzip")) {
const compressed = await gzipCompress(JSON.stringify(optimizedData));
res.setHeader("Content-Encoding", "gzip");
return compressed;
}
return JSON.stringify(optimizedData);
}
return responseBuffer;
}),
},
});
// Streaming response processing for large responses
const streamingProxy = createProxyMiddleware({
target: "http://large-data-api.example.com",
selfHandleResponse: true,
on: {
proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
const contentLength = parseInt(proxyRes.headers?.["content-length"] || "0");
// For large responses, process in chunks
if (contentLength > 1024 * 1024) { // > 1MB
const contentType = proxyRes.headers?.["content-type"];
if (contentType?.includes("application/json")) {
// Stream JSON processing for large datasets
const data = JSON.parse(responseBuffer.toString("utf8"));
if (Array.isArray(data)) {
// Process array items in chunks
const processedItems = await processLargeArray(data, {
chunkSize: 1000,
processor: (chunk) => chunk.map(item => transformLargeItem(item)),
});
return JSON.stringify(processedItems);
}
}
}
return responseBuffer;
}),
},
});docs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10