When we work on microservices, there are often a number of common concerns / functionalities that should be shared amongst different services.
These common functionality include authentication, monitoring, logging, rate-limiting, IP whitelisting, and request transformations.
Instead of having each service verify their own request guarantees, it makes sense to offload these functionalities to a central gateway / proxy. This way, your engineering team is focused on building actual features/services and less boilerplate.
Most of the functionality of a service should be delegated to a proxy.
This pattern is often called the API Gateway.
Today, we’ll be building a simple API gateway from scratch. Alternatively, you can use some existing open source / commercial gateways from this curated list.
A Minimum Viable Gateway
For simplicity, we’ll work on just two core features:
Routing: We want to specify which services to forward requests to when a request hits a particular route at our gateway.
Request Transformation: We want to intercept and transform incoming requests prior to forwarding, so we can add additional functionalities such as authentication, rate limits, caching, etc.
Let’s get started!
Your API Gateway is the last thing you want to be a bottleneck. Since it’s the single entry point to your fleet of microservices, it’d better be up when the requests come in. To achieve low response times and high throughput, we turn to OpenResty.
OpenResty turns the NGINX server into a powerful web app server, in which developers can use the
Luaprogramming language to script various existing nginx C modules and Lua modules and construct extremely high-performance web applications that are capable to handle 10K ~ 1000K+ connections in a single box.
With OpenResty, we can use
Lua to script NGINX to do things that were only possible with NGINX configuration files.
Here are some more nice things about OpenResty.
You’ll need to install OpenResty on your machine to get started.
If you’re not familiar with Nginx, the beginner’s guide may be helpful.
But I don’t know Lua!
It’s a scripting language with fairly friendly syntax, and you should be alright just knowing the basics.
Running OpenResty locally
I’ve created a barebones OpenResty project you can just clone and run: openresty-quickstart
localhost:8080 to see a greeting from nginx.
An OpenResty introduction
At this point, you should have some basic familiarity with NGINX’s configuration file structure. NGINX’s consists of modules which are controlled by directives - a DSL - specified in the configuration file. Learn more here and here.
Here’s an example of an nginx .conf file:
Openresty keeps the same structuring of the configuration files. You still create configuration files with simple and block level directives. Any nginx directive works with openresty in the same way as it would in a vanilla nginx application. In addition to that, OpenResty gives us additional directives which let us script behaviour with the
We’ll go through each one, explaining what they do.
content_by_lua directive lets us run arbitrary
Running NGINX with the above configuration will execute the lua code specified at the root URL. In this case, we display an HTML element.
For serious projects with more complex logic, we can use
Note that all four OpenResty directives listed above has a
_file version that accepts a
lua file path instead of a
lua code block.
init_by_lua directive lets us run initialization code as the nginx server is starting up. One use of this directive is for importing and defining libraries or modules that are used in our request handlers.
In the above snippet, we initialize a library and assign it to a global variable that our request handlers can use.
We also use this directive to define some configuration constants for our gateway.
You can use
init_by_lua_filefor better code organization.
rewrite_by_lua directive lets us ‘dynamically change the request URI using regular expressions, return redirects, and conditionally select configurations’.
In an API Gateway, this directive lets us route requests to its relevant destinations. For example, we can forward requests to
USER_MICROSERVICE_URL and forward requests to
You can read more about how NGINX rewrite rules here.
access_by_lua directive lets us defines access policies for specific locations/addresses.
Our API Gateway uses this for handling HTTP authentication and IP blacklisting/whitelisting.
You can read more about the NGINX access module here.
You’ve already seen the
ngx.say() method back in
say() is just one of the many methods defined on the
ngx package, which is made available globally for other directives to freely use.
What else does
ngx contain? Let’s take a look:
Lets you make requests to an internal URI. Returns the response. For example:
The above code captures the response to the
/by_file internal uri we’ve already defined somewhere in a
res contains the
body of the response.
You can also pass arguments and other options in the URI:
We can modify the contents of the request before forwarding it to a destination server.
ngx request object contains request attributes like so:
All of the above request attributes can be modified or decorated with additional information, depending on your use case.
We can modify the contents of the response before returning it to the client. For example, we can collate results from multiple services located at different internal addresses using
ngx.location.capture, and then send it back to the client.
ngx response object contains the following attributes:
There is no ngx.res.body() method where you can set the body before sending the response.
Openresty instead offers two methods in
ngx.print() any argument to the methods will be joined and sent as the res body.
It is also important to note that calling any one of these methods means that the response will be sent back to the client. So the response headers and the response status that you have prepared up to this point will be sent back to the client once you call
To be Continued!
In Part 2 of this series of blog posts on building API Gateways with Lua and NGINX, we’ll take a close look at how we can use the OpenResty directives we’ve seen to build a minimum viable API gateway.
Note: Due to time constraints and a change of priorities, part 2 is cancelled. I recommend having a look at this chapter of the NGINX cookbook
For simplicity, I’ve not used any Lua web frameworks for our gateway. However, for more complex production systems with more functionality it makes more sense to use Lapis instead. Lapis is a Lua web framework running on top of OpenResty. As a result, it ranks in the top 3 in recent performance benchmarks across all web frameworks.
“He had acquired his belief not by honestly earning it in patient investigation, but by stifling his doubts.” – William K Clifford, 1874
I plan to do some proper performance tests & benchmarking using Siege, comparing our gateway with an alternative implementation in Node. Check back on this post at a later time.
📬 Get updates straight to your inbox!
Subscribe to my newsletter to make sure you don't miss anything.