If you are building a small application with one API, you probably don't need an API Gateway.
But as your application grows, you may end up with multiple APIs:
- User API
- Product API
- Order API
- Payment API
- Notification API
Now imagine a frontend application that needs information from all these APIs.
Instead of the frontend calling every service directly, we can introduce an API Gateway.
In this article, we'll learn what an API Gateway is and how to build one using Ocelot with ASP.NET Core.
What is an API Gateway?
Let's start with the basic question.
What problem does an API Gateway solve?
Suppose we have three APIs:
User API → http://localhost:5001
Product API → http://localhost:5002
Order API → http://localhost:5003
Without an API Gateway, our frontend might communicate directly with all three APIs:
┌─────────────┐
│ Frontend │
└──────┬──────┘
│
┌─────────┼─────────┐
↓ ↓ ↓
User API Product API Order API
This can become difficult to manage.
The frontend needs to know:
- Where each API is located
- Which port each API uses
- Which endpoint belongs to which service
- How authentication works for each service
- How to handle failures
An API Gateway provides a single entry point.
┌─────────────┐
│ Frontend │
└──────┬──────┘
│
↓
┌─────────────┐
│ API Gateway │
└──────┬──────┘
│
┌─────────┼─────────┐
↓ ↓ ↓
User API Product API Order API
Now the frontend only needs to communicate with the Gateway.
What is an API Gateway?
An API Gateway is a server that sits between clients and backend services.
The client sends a request to the Gateway, and the Gateway forwards that request to the appropriate backend service.
For example:
Client
│
│ GET /products
↓
API Gateway
│
│ forwards request
↓
Product API
The Product API might actually be running at:
http://localhost:5002/api/products
But the client doesn't need to know that.
It can simply call:
http://localhost:5000/products
The Gateway takes care of the routing.
What is Ocelot?
Now that we understand API Gateways, let's talk about Ocelot.
Ocelot is an API Gateway framework designed for .NET applications.
It helps us implement common API Gateway functionality such as:
- Request routing
- Authentication and authorization integration
- Load balancing
- Rate limiting
- Request aggregation
- Service discovery integration
- Middleware-based processing
For a beginner, the most important feature to understand first is routing.
In simple terms:
Ocelot receives a request and decides which backend API should receive it.
A Simple Example
Let's say we have the following services.
User API
http://localhost:5001
Product API
http://localhost:5002
API Gateway
http://localhost:5000
We want clients to call:
GET http://localhost:5000/users
and have Ocelot forward the request to:
GET http://localhost:5001/users
Similarly:
GET http://localhost:5000/products
should be forwarded to:
GET http://localhost:5002/products
The architecture looks like this:
Client
│
│
▼
┌─────────────────┐
│ API Gateway │
│ Ocelot │
│ localhost:5000 │
└────────┬────────┘
│
┌────────┴────────┐
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│ User API │ │Product API │
│ :5001 │ │ :5002 │
└───────────┘ └───────────┘
Why Do We Need an API Gateway?
You might be wondering:
Why can't my frontend just call the APIs directly?
It can.
An API Gateway isn't mandatory.
But it becomes useful when you have multiple backend services.
Here are some common benefits.
1. Single Entry Point
Instead of exposing multiple APIs to clients:
User API
Product API
Order API
Payment API
you expose one endpoint:
API Gateway
The client only needs to know about the Gateway.
2. Hide Internal Services
Suppose your Product API is running internally at:
http://product-service:5002
You probably don't want clients to know about your internal infrastructure.
The Gateway can expose:
GET /products
while internally forwarding it to:
http://product-service:5002/products
3. Centralized Authentication
Authentication can be handled at the Gateway level.
For example:
Client
│
│ JWT Token
↓
API Gateway
│
│ Validate/authorize request
↓
Product API
This can reduce the amount of repeated gateway-level logic across services.
However, in a production system, backend services should still enforce their own authorization and trust boundaries rather than assuming that the Gateway alone provides security.
4. Centralized Rate Limiting
Suppose a client sends thousands of requests.
The Gateway can apply rate-limiting policies before requests reach your backend services.
Client
│
│ 10,000 requests
↓
API Gateway
│
│ Rate limit
↓
Backend APIs
This can help protect your services.
Creating an Ocelot API Gateway
Let's build a simple example.
We'll assume you already have the .NET SDK installed.
You can check it with:
dotnet --version
Step 1: Create an ASP.NET Core Project
Create a new Web API project:
dotnet new webapi -n ApiGateway
Move into the project:
cd ApiGateway
Step 2: Install Ocelot
Install the Ocelot NuGet package:
dotnet add package Ocelot
After installing it, restore the packages:
dotnet restore
Step 3: Create the Ocelot Configuration File
Ocelot needs to know where requests should be sent.
We'll create a file called:
ocelot.json
Add it to the project root.
For example:
ApiGateway
│
├── Program.cs
├── ApiGateway.csproj
├── appsettings.json
└── ocelot.json
Step 4: Configure a Route
Let's assume our Product API is running at:
http://localhost:5002
We want the Gateway to expose:
/products
Create the following configuration:
{
"Routes": [
{
"DownstreamPathTemplate": "/api/products",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5002
}
],
"UpstreamPathTemplate": "/products",
"UpstreamHttpMethod": [ "GET" ]
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5000"
}
}
Don't worry if this looks confusing.
Let's understand each property.
Understanding Ocelot Configuration
UpstreamPathTemplate
"UpstreamPathTemplate": "/products"
Upstream means the request coming into the Gateway.
So the client calls:
GET /products
DownstreamPathTemplate
"DownstreamPathTemplate": "/api/products"
Downstream means the request going from the Gateway to your backend service.
So Ocelot forwards:
/products
to:
/api/products
DownstreamHostAndPorts
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5002
}
]
This tells Ocelot where the backend API is located.
In this example:
localhost:5002
DownstreamScheme
"DownstreamScheme": "http"
This specifies the protocol used to communicate with the downstream service.
For example:
http
or:
https
UpstreamHttpMethod
"UpstreamHttpMethod": [ "GET" ]
This tells Ocelot that this route accepts a GET request.
For example:
GET /products
will match the route.
Step 5: Configure Program.cs
Now we need to tell ASP.NET Core to use Ocelot.
For a minimal hosting model, your Program.cs can look like this:
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile(
"ocelot.json",
optional: false,
reloadOnChange: true);
builder.Services.AddOcelot(builder.Configuration);
var app = builder.Build();
await app.UseOcelot();
app.Run();
That's the basic Ocelot setup.
Let's understand what is happening.
What Does AddOcelot Do?
This line:
builder.Services.AddOcelot(builder.Configuration);
registers Ocelot with the ASP.NET Core dependency injection system.
It tells the application:
I want to use Ocelot as part of this application.
What Does UseOcelot Do?
This line:
await app.UseOcelot();
adds Ocelot's middleware to the request pipeline.
When a request arrives, Ocelot gets the opportunity to process it.
For example:
GET /products
│
▼
Ocelot Middleware
│
▼
http://localhost:5002/api/products
Step 6: Make Sure ocelot.json Is Copied
Depending on your project configuration, make sure ocelot.json is available when the application runs.
You can add this to your .csproj file if needed:
<ItemGroup>
<None Update="ocelot.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
This ensures the configuration file is copied to the output directory.
Step 7: Run the Gateway
Start the Gateway:
dotnet run
Let's assume it starts on:
http://localhost:5000
Now call:
GET http://localhost:5000/products
Ocelot receives the request.
It looks at ocelot.json.
It finds this route:
/products
Then it forwards the request to:
http://localhost:5002/api/products
The flow is:
GET /products
│
▼
┌──────────────┐
│ API Gateway │
│ Ocelot │
└──────┬───────┘
│
│ forwards
▼
┌──────────────────────┐
│ Product API │
│ /api/products │
│ localhost:5002 │
└──────────────────────┘
The response then travels back through the Gateway to the client.
Adding More APIs
Now let's say we have an Order API running on:
http://localhost:5003
We can add another route.
Our ocelot.json could look like this:
{
"Routes": [
{
"DownstreamPathTemplate": "/api/products",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5002
}
],
"UpstreamPathTemplate": "/products",
"UpstreamHttpMethod": [ "GET" ]
},
{
"DownstreamPathTemplate": "/api/orders",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5003
}
],
"UpstreamPathTemplate": "/orders",
"UpstreamHttpMethod": [ "GET" ]
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5000"
}
}
Now we have:
GET /products
↓
Product API :5002
GET /orders
↓
Order API :5003
The frontend still communicates with only:
API Gateway :5000
POST, PUT and DELETE Routes
Ocelot isn't limited to GET requests.
For example, suppose we want to create a product.
We can configure:
{
"DownstreamPathTemplate": "/api/products",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5002
}
],
"UpstreamPathTemplate": "/products",
"UpstreamHttpMethod": [ "POST" ]
}
Now:
POST /products
can be forwarded to:
POST /api/products
Similarly, you can configure routes for:
GET
POST
PUT
PATCH
DELETE
depending on what your backend API supports.
Understanding Upstream vs Downstream
This is one of the most important concepts to understand when learning Ocelot.
Remember:
Upstream
Request coming into the Gateway.
Client → Gateway
Example:
/products
Downstream
Request going from the Gateway to the service.
Gateway → Product API
Example:
/api/products
Think about it like this:
UPSTREAM
Client ─────────────────────► Gateway
│
│
▼
DOWNSTREAM
Product API
If you remember only one thing about Ocelot routing, remember:
Upstream = client-facing route
Downstream = backend service route
Routing with Route Parameters
You can also use route parameters.
Suppose the Product API has:
GET /api/products/10
We can expose:
GET /products/10
using:
{
"DownstreamPathTemplate": "/api/products/{productId}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5002
}
],
"UpstreamPathTemplate": "/products/{productId}",
"UpstreamHttpMethod": [ "GET" ]
}
Now:
GET /products/10
gets forwarded as:
GET /api/products/10
The {productId} value is passed through.
API Gateway with Authentication
Another common requirement is authentication.
For example, your application may use JWT authentication.
The request could look like:
Client
│
│ Authorization: Bearer <token>
▼
API Gateway
│
│ authenticated request
▼
Product API
Ocelot can integrate with ASP.NET Core authentication and authorization.
The exact configuration depends on your authentication provider and Ocelot version, but conceptually the Gateway can protect routes such as:
/products
/orders
/payments
while leaving public routes such as:
/login
/register
available without authentication.
API Gateway Is More Than Just Routing
At first, it is useful to think of Ocelot as a router.
But an API Gateway can become an important part of your architecture.
For example:
Client
│
▼
┌───────────────┐
│ API Gateway │
│ Ocelot │
└───────┬───────┘
│
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
User API Product API Order API
The Gateway can provide capabilities around:
- Routing
- Authentication
- Authorization
- Rate limiting
- Load balancing
- Logging
- Monitoring
- Request transformation
- Service discovery
But you don't necessarily need all of these features on day one.
Start with routing.
API Gateway vs Reverse Proxy
As a beginner, you may hear another term: reverse proxy.
They are related, but not exactly the same concept.
A reverse proxy generally receives requests and forwards them to backend servers.
An API Gateway is more focused on APIs and can provide additional API-specific capabilities such as:
- Authentication
- Authorization
- Rate limiting
- Request transformation
- API composition
- Service discovery
Ocelot is commonly used as an API Gateway in .NET-based architectures.
API Gateway vs Load Balancer
These concepts can also overlap.
A load balancer primarily distributes traffic across multiple backend instances.
For example:
Load Balancer
/ | \
/ | \
API 1 API 2 API 3
An API Gateway can also perform load balancing in appropriate architectures, but its responsibilities can be broader.
For example:
API Gateway
│
┌─────────────┼─────────────┐
▼ ▼ ▼
Routing Security Rate Limit
│
▼
Backend APIs
So don't think of an API Gateway as simply another name for a load balancer.
A Real-World Example
Imagine an e-commerce application.
We have:
User Service
Product Service
Order Service
Payment Service
Notification Service
Without a Gateway:
Mobile App
│
├── User Service
├── Product Service
├── Order Service
├── Payment Service
└── Notification Service
With an API Gateway:
Mobile App
│
▼
┌─────────────┐
│ API Gateway │
│ Ocelot │
└──────┬──────┘
│
┌──────────────┼───────────────┐
│ │ │
▼ ▼ ▼
User Service Product Service Order Service
│
▼
Payment Service
The mobile application doesn't need to understand the internal architecture.
It only needs to know the Gateway.
For example:
GET /users/10
GET /products
GET /orders/100
POST /orders
Ocelot decides where those requests should go.
Common Beginner Mistakes
Mistake 1: Confusing Upstream and Downstream
Remember:
Upstream = Client → Gateway
Downstream = Gateway → Service
Mistake 2: Thinking the Gateway Contains Business Logic
The API Gateway generally shouldn't contain your application's core business logic.
For example, calculating an order's business rules should normally belong to the Order service, not the Gateway.
The Gateway should primarily coordinate communication and cross-cutting concerns.
Mistake 3: Exposing Every Internal API
One purpose of a Gateway is to provide a controlled entry point.
You don't necessarily need to expose every internal service directly to external clients.
Mistake 4: Putting Everything in the Gateway
An API Gateway can become a bottleneck if you keep adding business logic and application-specific responsibilities to it.
A good rule is:
Keep the Gateway focused on traffic management and cross-cutting concerns.
When Should You Use an API Gateway?
You may want an API Gateway when:
- You have multiple backend services.
- You are building a microservices architecture.
- Multiple clients need to access your services.
- You want a single public API endpoint.
- You need centralized API-level concerns such as routing or rate limiting.
- You want to hide internal service locations from clients.
For a simple application with one Web API, introducing an API Gateway may add unnecessary complexity.
Ocelot Request Flow
Let's summarize everything with one diagram.
Client
│
│ GET /products
▼
┌──────────────────┐
│ API Gateway │
│ Ocelot │
└────────┬─────────┘
│
│ Reads ocelot.json
│
▼
┌──────────────────┐
│ Route Matching │
└────────┬─────────┘
│
│ /products
▼
┌──────────────────┐
│ Product API │
│ :5002 │
│ /api/products │
└────────┬─────────┘
│
│ Response
▼
┌──────────────────┐
│ API Gateway │
└────────┬─────────┘
│
▼
Client
This is the fundamental idea behind Ocelot.
Complete Minimal Example
For reference, our basic Gateway consists of two important files.
Program.cs
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile(
"ocelot.json",
optional: false,
reloadOnChange: true);
builder.Services.AddOcelot(builder.Configuration);
var app = builder.Build();
await app.UseOcelot();
app.Run();
ocelot.json
{
"Routes": [
{
"DownstreamPathTemplate": "/api/products",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5002
}
],
"UpstreamPathTemplate": "/products",
"UpstreamHttpMethod": [ "GET" ]
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5000"
}
}
That's enough to understand the basic Ocelot Gateway concept.
Conclusion
An API Gateway is essentially a single entry point between clients and backend services.
Instead of your frontend communicating directly with many services:
Frontend → User API
Frontend → Product API
Frontend → Order API
you can use:
Frontend
│
▼
API Gateway
│
├── User API
├── Product API
└── Order API
Ocelot makes it relatively straightforward to implement this pattern in ASP.NET Core.
The most important concepts to remember are:
- API Gateway = single entry point for clients.
- Ocelot = a .NET API Gateway framework.
- Upstream = the route exposed by the Gateway.
- Downstream = the backend service that receives the request.
-
ocelot.json= where Ocelot routing configuration is defined. -
AddOcelot()= registers Ocelot. -
UseOcelot()= adds Ocelot to the request pipeline.
Once you understand routing, you can move on to more advanced topics such as JWT authentication, authorization, rate limiting, load balancing, service discovery, request aggregation, Docker, and Kubernetes.
The key idea is simple:
The client talks to the Gateway. The Gateway talks to the services.
Top comments (0)