Understanding API Gateways: Roles, Implementation, and a Node.js Example
This article explains what API gateways are, why they are essential in micro‑service architectures, outlines the cross‑cutting concerns they address such as authentication, transport security, load balancing, request routing, dependency resolution and data transformation, and provides a practical Node.js implementation with code samples.
In this part of the micro‑service series we discuss API gateways and how they help solve important problems in a micro‑service architecture, building on the issues described in the first article.
What Is an API Gateway and Why Use It?
All service‑based architectures share several cross‑cutting concerns, and micro‑services are no exception. Because micro‑services are developed in isolation, higher‑level layers of the software stack handle these concerns, and the API gateway is one such layer.
Authentication
Transport security
Load balancing
Request routing (including fault tolerance and service discovery)
Dependency resolution
Transport transformation
Authentication
Most gateways perform some form of authentication for each request (or a batch of requests). Based on service‑specific rules the gateway either forwards the request to the appropriate micro‑service or returns an error code. When forwarding, the gateway often adds authentication information to the request so that downstream services can apply user‑specific logic.
Security
Many gateways act as a single entry point for public APIs. In this role they handle transport security, for example by terminating SSL: a secure TLS connection is established between the client and the gateway, and the gateway forwards the request to internal services over a non‑SSL channel.
“Many gateways serve as a single entry point for public APIs.”
Load Balancing
Under high load the gateway can distribute requests among micro‑service instances according to custom logic, taking into account each service’s scaling limits. For services that run multiple instances behind different internal endpoints, the gateway can route requests to those endpoints or even trigger dynamic instantiation.
Request Routing
The gateway can provide custom routing logic even under normal load. In large architectures, internal endpoints are added or removed as teams create or retire micro‑services. The gateway can cooperate with a service‑registry/discovery mechanism or a database that describes how each request should be dispatched, enabling flexible routing, fail‑over to backup services, and graceful handling of service failures.
Dependency Resolution
Because micro‑services often need to call many other services to accomplish a task, the gateway can expose “virtual” endpoints that internally route to multiple micro‑services, reducing chatter and improving performance.
Transport Transformation
Micro‑services may return data in formats that are inconvenient for clients. The gateway performs the necessary transformations so that clients can interact with the backend services transparently.
API Gateway Example
Below is a simple Node.js gateway that handles HTTP requests, forwards them to appropriate internal endpoints, and performs required transport transformations. It addresses the concerns listed above.
Authentication (Example)
The example uses JWT for authentication. A single endpoint (/login) validates credentials stored in a MongoDB database and issues a token whose access is limited by role.
/*
* Simple login: returns a JWT if login data is valid.
*/
function doLogin(req, res) {
getData(req).then(function(data) {
try {
var loginData = JSON.parse(data);
User.findOne({ username: loginData.username }, function(err, user) {
if (err) { logger.error(err); send401(res); return; }
if (user.password === loginData.password) {
var token = jwt.sign({ jti: uuid.v4(), roles: user.roles }, secretKey, { subject: user.username, issuer: issuerStr });
res.writeHeader(200, { 'Content-Length': token.length, 'Content-Type': "text/plain" });
res.write(token);
res.end();
} else {
send401(res);
}
}, 'users');
} catch (err) {
logger.error(err); send401(res);
}
}, function(err) { logger.error(err); send401(res); });
}
/*
* Authentication validation using JWT. Strategy: find existing user.
*/
function validateAuth(data, callback) {
if (!data) { callback(null); return; }
data = data.split(" ");
if (data[0] !== "Bearer" || !data[1]) { callback(null); return; }
var token = data[1];
try {
var payload = jwt.verify(token, secretKey);
if (!payload.jti || revokedTokens[payload.jti]) {
logger.debug('Revoked token, access denied: ' + payload.jti);
callback(null);
} else {
callback({ jwt: payload });
}
} catch (err) {
logger.error(err);
callback(null);
}
}Disclaimer: The code shown is for illustration only and is not production‑ready. Do not copy‑paste blindly.
Transport Security
Transport security is handled via TLS: all public requests first hit a reverse Nginx proxy with a sample certificate before being forwarded internally.
Load Balancing
Load balancing is performed by Nginx; see the example configuration for details.
Dynamic Routing, Data Aggregation and Fault Handling
Requests are dynamically scheduled based on configuration stored in a database, supporting both HTTP and AMQP protocols. The gateway can aggregate responses from multiple internal services into a single JSON payload, a pattern used by Netflix for performance and by Falcor for easy multi‑source data fetching.
Failures of internal requests are handled by logging the error and returning reduced information to the client.
/*
* Parses the request and dispatches multiple concurrent requests to each
* internal endpoint. Results are aggregated and returned.
*/
function serviceDispatch(req, res) {
var parsedUrl = url.parse(req.url);
Service.findOne({ url: parsedUrl.pathname }, function(err, service) {
if (err) { logger.error(err); send500(res); return; }
var authorized = roleCheck(req.context.authPayload.jwt, service);
if (!authorized) { send401(res); return; }
// Fanout all requests to all related endpoints.
var promises = [];
service.endpoints.forEach(function(endpoint) {
logger.debug(sprintf('Dispatching request from public endpoint %s to internal endpoint %s (%s)', req.url, endpoint.url, endpoint.type));
switch (endpoint.type) {
case 'http-get':
case 'http-post':
promises.push(httpPromise(req, endpoint.url, endpoint.type === 'http-get'));
break;
case 'amqp':
promises.push(amqpPromise(req, endpoint.url));
break;
default:
logger.error('Unknown endpoint type: ' + endpoint.type);
}
});
Q.allSettled(promises).then(function(results) {
var responseData = {};
results.forEach(function(result) {
if (result.state === 'fulfilled') {
responseData = _.extend(responseData, result.value);
} else {
logger.error(result.reason.message);
}
});
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(responseData));
});
}, 'services');
}Role Check
var User = userDb.model('User', new mongoose.Schema({ username: String, password: String, roles: [String] }));
var Service = servicesDb.model('Service', new mongoose.Schema({ name: String, url: String, endpoints: [new mongoose.Schema({ type: String, url: String })], authorizedRoles: [String] }));
function roleCheck(jwt_, service) {
var intersection = _.intersection(jwt_.roles, service.authorizedRoles);
return intersection.length === service.authorizedRoles.length;
}Transport and Data Transformation
The gateway performs transport transformation to convert between HTTP and AMQP requests.
Logging
Logging is centralized: all logs are published to the console and an internal message bus, where other services can react.
Get the full source code.
Narrative: How Webtask and Auth0 Implement These Patterns
Webtasks are themselves micro‑services that run behind the gateway. The gateway handles authentication, dynamic routing, and centralized logging for them.
Authentication: Auth0 issues tokens; webtasks validate those tokens, establishing a trust relationship.
Real‑time logging: Webtasks implement a stateless, elastic ZeroMQ architecture that runs across the cluster.
Dynamic routing: A custom Node.js proxy uses CoreOS etcd as a pub‑sub mechanism to route webtasks appropriately.
Conclusion
API gateways are a crucial component of any micro‑service‑based architecture. They provide a convenient, uniform way to handle cross‑cutting concerns such as authentication, load balancing, dependency resolution, data transformation, and dynamic request routing, allowing individual micro‑services to focus on their specific business logic and accelerate development.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architects Research Society
A daily treasure trove for architects, expanding your view and depth. We share enterprise, business, application, data, technology, and security architecture, discuss frameworks, planning, governance, standards, and implementation, and explore emerging styles such as microservices, event‑driven, micro‑frontend, big data, data warehousing, IoT, and AI architecture.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
