Request & Response Transformation
Header manipulation, payload reshaping, protocol translation — client যা পাঠায় তা backend যা আশা করে তার সাথে মানিয়ে নেওয়া।
গল্পে বুঝি
একটা আন্তর্জাতিক অফিসের কাউন্টারে বসেন অনুবাদক-কেরানি ফাতিমা আল-ফিহরি। বিদেশি ভিজিটররা আসেন নিজেদের মতো করে লেখা দরখাস্ত নিয়ে — কেউ ইংরেজিতে, কেউ নিজের ছকে, সাথে আঁকিবুঁকি নোট আর অপ্রয়োজনীয় কাগজপত্র। ভেতরের ডিপার্টমেন্ট কিন্তু ঠিক নিজেদের ফর্ম্যাট ছাড়া কিছু গ্রহণ করে না — তাদের লাগে নির্দিষ্ট রেফারেন্স কোড, নির্দিষ্ট ঘরে বসানো তথ্য। ফাতিমা ভিজিটরের দরখাস্তটা নিয়ে নতুন করে লিখে দেন — যে রেফারেন্স কোডগুলো ভেতরের দপ্তরের দরকার সেগুলো বসিয়ে দেন, আর ভিজিটরের বাড়তি আঁকিবুঁকি ছেঁটে ফেলে দেন। ডিপার্টমেন্ট বুঝেই না যে ভিজিটর অন্য ভাষায় এসেছিল।
কিছুক্ষণ পর ভেতর থেকে জবাব আসে — কিন্তু সেটা ঠাসা অভ্যন্তরীণ পরিভাষা আর গোপন কোডে ভরা, যা ভিজিটরের বোঝার কথা না, বোঝানোও উচিত না। ফাতিমা আবার সেটা নতুন করে সাজিয়ে দেন — অভ্যন্তরীণ কোডগুলো লুকিয়ে ফেলেন, পরিভাষা সরল করেন, আর ভিজিটরের হাতে একটা পরিষ্কার-সহজ উত্তর তুলে দেন। ভিজিটর জানেই না ভেতরে কতগুলো আলাদা দপ্তর, কতরকম ছক — তার কাছে অভিজ্ঞতাটা শুরু থেকে শেষ পর্যন্ত ঝকঝকে।
এই ফাতিমাই হলো API gateway-র transformation স্তর। ভিজিটরের দরখাস্তকে ভেতরের ফর্মে লিখে দেওয়া — মানে request transformation: দরকারি header যোগ করা, অদরকারি header/field ছেঁটে ফেলা, body-র structure বদলে backend যা চায় সেই আকারে সাজানো। আর ভেতরের জবাবকে সরল করে, অভ্যন্তরীণ কোড লুকিয়ে ভিজিটরকে দেওয়া — মানে response transformation ও internal field hide করা। ভেতরের দপ্তরগুলো যতই আলাদা হোক, ভিজিটরের কাছে একটাই পরিষ্কার public API থাকে। বাস্তবে gateway ঠিক এটাই করে — client পাঠায় snake_case, backend চায় camelCase; client বলে REST, backend বলে gRPC; response থেকে internalFlags বা x-powered-by-র মতো জিনিস বাদ যায় — মাঝের স্তরটাই সব রূপান্তর সামলায়, দুই পাশের কেউ নিজের ধরন বদলায় না।
বাস্তব জীবনের উদাহরণ
একটা কূটনৈতিক মিটিংয়ের অনুবাদক — দুই পক্ষই নিজের ভাষায় সাবলীলভাবে কথা বলে, অনুবাদক রিয়েল টাইমে তাদের মধ্যে রূপান্তর করে দেয়। কোনো পক্ষই নিজের কাজের ধরন বদলায় না; মাঝের স্তরটাই রূপান্তর সামলায়।
Header Manipulation
সবচেয়ে সাধারণ transformation। forward করার আগে header যোগ, বাদ, বা নাম বদল করা:
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";
} 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
external path-কে internal path-এ ম্যাপ করা। client পরিষ্কার URL ব্যবহার করে; backend যা খুশি ব্যবহার করে:
# 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
request বা response body নতুন করে সাজানো। API version migrate করার সময় বা অমিল schema-র third-party service integrate করার সময় কাজে লাগে:
// 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 — client-এ ফেরত দেওয়ার আগে backend response মানিয়ে নেওয়া:
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.)
};
} Gateway-তে API Versioning
সার্ভিসে পুরনো code না রেখেই পুরনো API version চালু রাখা:
// 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 notice:
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
gateway-তে protocol-এর মধ্যে রূপান্তর করা — client REST বলে, backend 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
});
});
}); এতে আপনি client-দের REST ব্যবহার চালু রাখতে দিয়ে backend-গুলোকে ধাপে ধাপে gRPC-তে migrate করতে পারেন।
Kong Transformation Plugin
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