When building a web application or API, you will often hear the word middleware. It may sound complicated at first, but the idea behind middleware is actually very simple.

Middleware is software or code that runs between an incoming request and the main application logic.

It can inspect the request, modify it, allow it to continue, or stop it completely. Middleware can also work with the response before it is sent back to the user.

In simple words:

Middleware is code that sits in the middle of the request and response pipeline.

Understanding Middleware with a Simple Example

Imagine you are entering an office building.

Before you can meet someone inside, you may need to:

  1. Enter through the main gate.

  2. Show your ID to security.

  3. Register at reception.

  4. Get permission to enter a particular floor.

  5. Finally meet the person you came to see.

A web application works in a similar way.

When a request reaches your application, it may need to pass through several checks before reaching the actual API or controller.

For example:

User
  ↓
HTTP Request
  ↓
Logging Middleware
  ↓
Authentication Middleware
  ↓
Authorization Middleware
  ↓
Rate Limiting Middleware
  ↓
Controller / API
  ↓
Database
  ↓
HTTP Response
  ↓
User

Each middleware performs a specific job.

How Middleware Works

Suppose your application has this API:

GET /api/users

A user sends a request to access it.

Instead of sending the request directly to the UsersController, the application may first send it through middleware.

Step 1: Logging

The first middleware records information about the request.

For example:

Method: GET
URL: /api/users
Time: 10:30:25 AM
IP: 192.168.1.10

The request then continues.

Step 2: Authentication

The authentication middleware checks who is making the request.

For example, the request may contain a JWT token:

Authorization: Bearer eyJhbGciOi...

The middleware validates the token.

If the token is valid, the request continues.

If it is invalid or expired, the middleware can return:

401 Unauthorized

The controller is never called.

Step 3: Authorization

Authentication tells us:

Who are you?

Authorization tells us:

What are you allowed to do?

For example, the user may be authenticated but may not have permission to access the Admin API.

The middleware can return:

403 Forbidden

Step 4: Controller

If all middleware checks are successful, the request finally reaches the controller.

For example:

[HttpGet]
public IActionResult GetUsers()
{
    return Ok(_userService.GetUsers());
}

The controller processes the request and creates a response.

That response then travels back through the application and is returned to the client.

Middleware in ASP.NET Core

ASP.NET Core uses middleware heavily.

A typical Program.cs may contain code like this:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddAuthentication();
builder.Services.AddAuthorization();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();

app.Run();

Here, these lines are important:

app.UseAuthentication();
app.UseAuthorization();

They add authentication and authorization middleware to the application pipeline.

The order in which middleware is added can be very important.

Middleware Pipeline

ASP.NET Core processes middleware in the order in which it is registered.

Consider:

app.UseMiddleware<MiddlewareA>();
app.UseMiddleware<MiddlewareB>();
app.UseMiddleware<MiddlewareC>();

The request travels like this:

Request
   ↓
Middleware A
   ↓
Middleware B
   ↓
Middleware C
   ↓
Controller

The response comes back in the opposite direction:

Controller
   ↓
Middleware C
   ↓
Middleware B
   ↓
Middleware A
   ↓
Response

This is why middleware is often described as a pipeline.

What Is next()?

When creating custom middleware, you will often see something called next.

For example:

public async Task InvokeAsync(HttpContext context)
{
    Console.WriteLine("Request started");

    await _next(context);

    Console.WriteLine("Request completed");
}

This line:

await _next(context);

means:

Pass the request to the next middleware in the pipeline.

Anything before _next() runs while the request is moving toward the controller.

Anything after _next() runs while the response is coming back.

For example:

public async Task InvokeAsync(HttpContext context)
{
    Console.WriteLine("Before API");

    await _next(context);

    Console.WriteLine("After API");
}

The flow becomes:

Before API
    ↓
Controller executes
    ↓
After API

This is one of the most important concepts to understand about middleware.

What Happens If Middleware Does Not Call next()?

Middleware does not always have to pass the request forward.

Suppose we create middleware that checks for an API key.

public async Task InvokeAsync(HttpContext context)
{
    if (!context.Request.Headers.ContainsKey("X-API-Key"))
    {
        context.Response.StatusCode = 401;

        await context.Response.WriteAsync("API key is required.");

        return;
    }

    await _next(context);
}

If the API key is missing, this code returns:

401 Unauthorized

and stops processing.

The next middleware and controller are never executed.

This is called short-circuiting the pipeline.

It is useful for authentication, authorization, rate limiting, maintenance mode, IP blocking and many other situations.

Creating Custom Middleware in ASP.NET Core

You can also create your own middleware.

For example, let's create middleware that logs how long each request takes.

public class RequestTimingMiddleware
{
    private readonly RequestDelegate _next;

    public RequestTimingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = Stopwatch.StartNew();

        await _next(context);

        stopwatch.Stop();

        Console.WriteLine(
            $"{context.Request.Method} " +
            $"{context.Request.Path} " +
            $"completed in {stopwatch.ElapsedMilliseconds} ms");
    }
}

Now register it:

app.UseMiddleware<RequestTimingMiddleware>();

If someone calls:

GET /api/products

you might see:

GET /api/products completed in 145 ms

This can be very useful when troubleshooting API performance.

Common Uses of Middleware

Middleware is used for many important tasks in modern applications.

Authentication

Checks whether the user is logged in and validates credentials or tokens.

app.UseAuthentication();

Authorization

Checks whether the authenticated user has permission to access a resource.

app.UseAuthorization();

Logging

Records information about incoming requests, responses, errors and execution times.

Exception Handling

Instead of writing try/catch blocks everywhere, middleware can catch unhandled exceptions from the entire application.

For example:

app.UseExceptionHandler("/error");

CORS

CORS middleware controls which websites or applications can call your API.

app.UseCors();

Rate Limiting

Rate limiting middleware prevents clients from making too many requests within a short period.

For example:

Maximum: 100 requests per minute

If the limit is exceeded, the server might return:

429 Too Many Requests

Security Headers

Middleware can add HTTP security headers to every response.

Request Modification

Middleware can add, remove or change information in an incoming request before it reaches the controller.

Response Modification

It can also modify the response before it is returned to the client.

Why Middleware Is Useful

Without middleware, you might need to repeat the same code in every controller.

Imagine writing authentication checks inside every API:

if (!IsAuthenticated())
{
    return Unauthorized();
}

You could have hundreds of endpoints.

Repeating this logic everywhere would make the application difficult to maintain.

Middleware solves this problem by moving common functionality into one central place.

Instead of:

Controller A → Authentication Code
Controller B → Authentication Code
Controller C → Authentication Code
Controller D → Authentication Code

you can have:

                Authentication
                      ↓
        ┌─────────────┼─────────────┐
        ↓             ↓             ↓
 Controller A   Controller B   Controller C

This makes the application cleaner and easier to maintain.

Middleware vs Controller

A common question is: why not put everything inside the controller?

The difference is responsibility.

Middleware usually handles functionality that applies to many or all requests, such as authentication, logging, error handling and security.

Controllers normally handle business functionality.

For example:

Middleware:
Validate JWT token

Controller:
Get customer's orders

Service:
Calculate order total

Database:
Retrieve order records

Keeping these responsibilities separate makes the application easier to understand.

Middleware vs Filters in ASP.NET Core

Middleware and filters can sometimes appear similar, but they work at different levels.

Middleware works at the HTTP request pipeline level and can affect almost every request.

Filters work mainly around MVC controllers and actions.

For example, if you want to log every HTTP request, middleware is usually a good choice.

If you want special logic to run only before or after certain controller actions, a filter may be more appropriate.

Does Middleware Connect the Frontend and Backend?

This is another common misunderstanding.

Middleware does not simply mean software connecting a frontend and backend.

For example:

React / Angular / Next.js
          ↓
        API
          ↓
      ASP.NET Core
          ↓
       Database

Middleware normally exists inside the server-side request processing pipeline.

For example:

React Application
       ↓
HTTP Request
       ↓
ASP.NET Core
       ↓
CORS Middleware
       ↓
Authentication Middleware
       ↓
Authorization Middleware
       ↓
Controller
       ↓
Service
       ↓
Database

So middleware is less about connecting two applications and more about processing communication as it passes through a system.

Middleware Exists Beyond .NET

Middleware is not an ASP.NET-only concept.

Many technologies use the same idea.

For example, Node.js with Express uses middleware:

app.use((req, res, next) => {
    console.log("Request received");
    next();
});

Next.js also has middleware that can run before certain requests are completed.

Other platforms such as Java, Python frameworks, API gateways and cloud platforms use similar concepts.

The syntax changes, but the basic idea remains the same:

Request
   ↓
Middleware
   ↓
Application Logic
   ↓
Response

Real-World Example

Imagine an e-commerce application with this endpoint:

POST /api/orders

A customer clicks Place Order.

The request might travel through:

Customer
   ↓
HTTPS Middleware
   ↓
Logging Middleware
   ↓
Rate Limiting Middleware
   ↓
Authentication Middleware
   ↓
Authorization Middleware
   ↓
Exception Handling
   ↓
Order Controller
   ↓
Order Service
   ↓
Database

The controller does not need to worry about logging every request, validating the JWT infrastructure, applying global security policies or handling every unexpected exception.

Middleware handles these common concerns.

The controller can focus on what it is supposed to do:

Create the order.

Why Middleware Order Matters

One important thing developers should remember is that middleware order can change application behavior.

For example:

app.UseAuthentication();
app.UseAuthorization();

makes sense because the application first needs to know who the user is before checking what the user is allowed to do.

Think of it like airport security.

First:

Who are you?

Then:

Are you allowed to enter this area?

Putting middleware in the wrong order can cause authentication, authorization, CORS, routing and other unexpected problems.

Final Thoughts

Middleware is one of the fundamental concepts behind modern web applications.

The easiest way to understand it is to think of middleware as a series of checkpoints that a request passes through before reaching the actual application logic.

A middleware component can:

Inspect the request
        ↓
Modify the request
        ↓
Allow it to continue
        ↓
Stop it if necessary
        ↓
Process the response

In ASP.NET Core, middleware helps keep applications organized by handling common concerns such as authentication, authorization, logging, exception handling, CORS, security and rate limiting in centralized places.

If you remember only one thing, remember this:

Middleware is code that runs in the middle of the request and response pipeline, allowing applications to inspect, modify, control and protect HTTP requests before they reach the main application logic.