Request & Response Transformation
Header manipulation, payload reshaping, protocol translation — adapting what clients send to what backends expect.
Real-World Analogy
A translator at a diplomatic meeting — both parties speak fluently in their own language, the translator converts between them in real time. Neither side changes how they work; the middle layer handles the conversion.
Header Manipulation
The most common transformation. Add, remove, or rename headers before forwarding:
location /api/ {
proxy_pass http://notes;
# Add headers to backend request
proxy_set_header X-Request-ID $request_id;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
# Remove headers from client request before forwarding
proxy_set_header Authorization ""; # backend doesn't need raw JWT
# Remove headers from backend response before returning to client
proxy_hide_header X-Powered-By;
proxy_hide_header Server;
# Add headers to the response
add_header X-Gateway-Version "1.0";
add_header Strict-Transport-Security "max-age=31536000";
} In Node.js middleware:
function transformRequest(req: Request, _res: Response, next: NextFunction): void {
// Enrich with request ID for distributed tracing
req.headers['x-request-id'] = req.headers['x-request-id'] ?? crypto.randomUUID();
req.headers['x-forwarded-for'] = req.socket.remoteAddress;
// Strip sensitive client headers
delete req.headers['authorization']; // replaced by x-user-id from auth middleware
delete req.headers['cookie']; // don't forward cookies to APIs
next();
}
function transformResponse(req: Request, res: Response, next: NextFunction): void {
// Remove internal headers from response
res.on('finish', () => {
res.removeHeader('x-powered-by');
res.removeHeader('x-internal-service');
});
next();
} Path Rewriting
Map external paths to internal paths. Clients use clean URLs; backends use whatever they want:
# Strip /api/v1 prefix before forwarding
location /api/v1/users/ {
rewrite ^/api/v1/(.*)$ /$1 break;
proxy_pass http://user-service;
}
# GET /api/v1/users/123 → GET /users/123 on user-service // In Express gateway
app.use('/api/v1/users', (req, res, next) => {
// Rewrite path: /api/v1/users/123 → /123
req.url = req.url.replace(/^\/api\/v1\/users/, '');
proxy.web(req, res, { target: 'http://user-service:3001' });
}); Payload Transformation
Reshape request or response bodies. Useful when migrating API versions or integrating third-party services with mismatched schemas:
// v1 clients send snake_case; new backend expects camelCase
function transformV1Request(body: Record<string, unknown>): Record<string, unknown> {
return {
userId: body.user_id,
firstName: body.first_name,
lastName: body.last_name,
emailAddr: body.email_address,
};
}
// Middleware that transforms request body
app.use('/api/v1/', async (req, res, next) => {
if (req.method !== 'GET' && req.body) {
req.body = transformV1Request(req.body);
}
next();
}); Response transformation — adapt backend response before returning to client:
function transformUserResponse(backendResponse: BackendUser): ClientUser {
return {
id: backendResponse.userId,
first_name: backendResponse.firstName, // client expects snake_case
last_name: backendResponse.lastName,
email: backendResponse.emailAddr,
// Strip internal fields
// (no: internalFlags, createdBySystem, etc.)
};
} API Versioning at the Gateway
Keep old API versions alive without maintaining old code in services:
// Route v1 and v2 to different backends
const versionRoutes = {
'v1': 'http://api-v1:3000',
'v2': 'http://api-v2:3000',
};
// Version from URL: /api/v1/users
app.use('/api/:version/*', (req, res) => {
const version = req.params.version;
const target = versionRoutes[version];
if (!target) {
return res.status(404).json({ error: `API version ${version} not found` });
}
proxy.web(req, res, { target });
});
// Version from header: X-API-Version: 2
app.use('/api/', (req, res) => {
const version = req.headers['x-api-version'] ?? 'v1';
proxy.web(req, res, { target: versionRoutes[version] });
}); Deprecation notices:
function addDeprecationHeaders(req: Request, res: Response, next: NextFunction): void {
if (req.path.startsWith('/api/v1')) {
res.set('Deprecation', 'true');
res.set('Sunset', 'Sat, 31 Dec 2026 23:59:59 GMT');
res.set('Link', '</api/v2>; rel="successor-version"');
}
next();
} Protocol Translation
Convert between protocols at the gateway — clients speak REST, backends speak gRPC:
import * as grpc from '@grpc/grpc-js';
// Gateway translates REST → gRPC
app.get('/api/users/:id', async (req, res) => {
const client = new UserServiceClient(
'user-service:50051',
grpc.credentials.createInsecure(),
);
// REST request → gRPC call
client.getUser({ userId: req.params.id }, (err, response) => {
if (err) {
return res.status(503).json({ error: 'Service unavailable' });
}
// gRPC response → REST JSON response
res.json({
id: response.userId,
name: response.name,
email: response.email,
});
});
}); This lets you migrate backends to gRPC incrementally while clients keep using REST.
Kong Transformation Plugins
plugins:
# Add/remove/rename headers
- name: request-transformer
config:
add:
headers:
- "x-request-source:gateway"
remove:
headers:
- authorization
rename:
headers:
- "x-custom-id:x-user-id"
# Rewrite path
- name: request-transformer
config:
replace:
uri: "/$(uri_captures.version)/$(uri_captures.path)"
# Response transformation
- name: response-transformer
config:
remove:
headers:
- x-powered-by
- server