Creating a simple API Gateway in ASP.NET Core
In a previous article of mine, JWT Authentication for Microservices in .NET, I have looked into creating an authentication microservice. This would be used to validate the identity of the user’s identity before performing any action in other components of the system.
Another component that is crucial to this process is the API gateway — basically, a middleware system that routes the incoming requests to the responsible microservice, but also performs the identity validation before doing so.
There are many frameworks that can be used for setting up an API gateway, such as Ocelot, in .NET Core, or Netflix Zuul, in Java. However, in this article I will go over the process of creating a simple API gateway from scratch, in .NET Core.
Creating the project
The first step is to create a new ASP.NET Core Web Application project is Visual Studio. Since this project will only act as middleware, choose Empty as the template.
This will generate a new project with two classes: Startup and Program. The most important part of these, for our purposes, is the Configure method from Startup. This is where we can handle the incoming HTTP requests and choose what to send as a response. In the Configure method, you will probably find this already existing code:
Designing the Router
Since the Configure method is where we want to handle the incoming requests, let’s try to design the way we would want it to work:
First of all, we want a Router class. This class will hold all the existing routes, perform the validation and send the requests to wherever it is necessary. To keep the code cleaner, we will get the routes from a JSON file. So, after a request comes to the API gateway, it will be sent to the router, which does whatever it is necessary, and returns the response. Finally, we set that as the response that the client would get.
Before starting the Router code, let’s sketch the routes.json file. What we need in this file is a list of routes, each containing an API gateway endpoint and a destination URI. We can also add a field that would tell the Router whether to authenticate the user before sending requests to that URI or not. We also need to specify which service to use for authentication. So, this is how this file could look like:
Creating the Destination class
Now we also know that each Route should have an endpoint and a destination, and that each Destination should have a uri and a requiresAuthentication field.
Since the endpoint is simply a string, let’s design the Destination class. I will add the two fields, two constructors for convenience purposes, and a private constructor without parameters, for future JSON deserialization:
It would also be nice to have a SendRequest method in this class, so each Destination object is responsible for its requests. This method will take the HttpRequest object that represents the incoming requests, get all the necessary details from it, and send it to the destination URI. So, first of all, we need a CreateDestinationUri method, that combines the microservice’s base URI with the endpoint and query string coming from the client:
Now, we can create the SendRequest method, that will send the request to the correct URI and get the response back:
Creating the JSON parser
Before moving to the Router class, we need to create a way to deserialize the JSON file where we keep all the routes. I will create a helper class for this, that will contain two methods: one for reading a JSON file into an object, and one for deserializing a JSON object.
Creating the Router class
One last thing before getting into the Router class is to create a simple Route model:
Now, let’s add the properties and the constructor to the Router class:
I am using the dynamic type to read the JSON file and the deserialize it into the object’s properties.
Everything is now ready for the main functionality of the API gateway: the routing and the identity validation, which will happen in the RouteRequest method. We need to extract the base endpoint from the request object (so, if the endpoint is /movies/add, we want to get the /movies/ part, since that defines our route). After that, we need to check if we have that route registered. If the answer is yes, then we check the user identity and route the request; otherwise, we return an error message. I have also created a ConstructErrorMessage method for convenience.
For the authentication part, I chose to extract the token from the request header and send it as a query parameter. You can choose to keep it as a header, which means that the authentication microservice will have to extract it.
You can now test the API gateway by filling in the routes.json file with services that you already created, and check if the responses are correct. If you do not have any services, an easy testing method is to route one of the endpoints to a website such as google.com.
Creating a basic API gateway does not require much effort, but it also doesn’t offer much in terms of functionality. If you need features such as load balancing, you might want to look into already existing frameworks or platforms that offer routing services.
You can find all the code, ready to use, on my Github.