DEV Community

Cover image for The "Middle" of Nowhere: Demystifying Middleware in Rails
Zil Norvilis
Zil Norvilis

Posted on • Originally published at norvilis.com

The "Middle" of Nowhere: Demystifying Middleware in Rails

The "Black Box" Between the Browser and Your Code

When you are learning Rails, you usually think the flow works like this:

  1. User clicks a link.
  2. Magic happens.
  3. Your PostsController runs.

Today, we are going to look at the Magic. That magic is called Middleware.

Understanding Middleware is the difference between a Junior dev who thinks "Rails just does that" and a Senior dev who knows exactly how Rails does that.

The Mental Model: The Onion

The best way to visualize Middleware is the Onion Architecture.

Imagine your application logic (your Controller) is the core of the onion. The user's request is a needle trying to reach the center.

  1. The request pierces the outer skin (Middleware 1).
  2. It goes through the next layer (Middleware 2).
  3. It reaches the center (Your Controller).
  4. Crucially: The response then has to travel back out through Middleware 2, then Middleware 1, before leaving the server.

This means Middleware can modify the Request coming in, AND the Response going out.

What actually lives there?

If you run bin/rails middleware in your terminal right now, you will see a list of about 20-30 items. Rails is essentially just a big stack of middleware.

Here are the common "layers" you use every day without knowing it:

  1. ActionDispatch::Static: "Is this request asking for logo.png? If yes, serve the file and stop here. Don't bother the controller."
  2. Rack::Cors: "Is this website allowed to talk to our API? No? Block it."
  3. ActionDispatch::Cookies: "Take this messy HTTP header and turn it into a nice cookies[:user_id] hash for the controller."
  4. Warden (if using Devise): "Is this user logged in? If not, redirect them before they even touch the application code."

How to Build Your Own (The Rack Protocol)

In Ruby, the standard for web apps is Rack. A middleware is just a Ruby class that follows a specific set of rules.

Here is a simple middleware that measures how long a request takes.

# app/middleware/timer_middleware.rb
class TimerMiddleware
  def initialize(app)
    @app = app # The "next" layer of the onion
  end

  def call(env)
    # 1. THE "ENTER" PHASE (Request coming in)
    start_time = Time.now
    puts "Request started..."

    # 2. PASS TO THE NEXT LAYER
    # This recursively calls the next middleware, all the way to the controller
    status, headers, body = @app.call(env)

    # 3. THE "EXIT" PHASE (Response going out)
    duration = Time.now - start_time
    puts "Request finished in #{duration} seconds"

    # 4. Return the response to the previous layer
    [status, headers, body]
  end
end
Enter fullscreen mode Exit fullscreen mode

To use it in Rails, you add one line to your config:

# config/application.rb
config.middleware.use TimerMiddleware
Enter fullscreen mode Exit fullscreen mode

Now, every single request to your server will be timed, and your controllers don't even know this code exists.

Why Should You Care?

You might be thinking, "This is cool, but I just write controllers."
Here is why understanding Middleware makes you a better developer:

1. Performance Debugging

If your app is slow, but your Controller code looks fast, the problem is likely Middleware. Maybe your Session store is slow, or a bad CORS configuration is hanging the request. Knowing the stack lets you debug outside the controller.

2. Separation of Concerns

Let's say you want to ban all IP addresses from a specific country.

  • Junior Dev: Adds a before_action :check_ip to ApplicationController. This is slow because the request has to go through the entire Rails boot process just to be rejected.
  • Senior Dev: Writes a Middleware. The request is rejected at the very edge of the application, saving memory and CPU cycles.

3. API Keys and Auth

If you are building an API, checking for a Bearer token in every controller is tedious. A Middleware can check the header, validate the token, and define env['current_user'] before your code ever runs.

Summary

Middleware isn't scary. It’s just a chain of passing the baton.

  • Request -> Middleware A -> Middleware B -> Controller
  • Response <- Middleware A <- Middleware B <- Controller

Next time you see a "CORS Error" or a "Session Cookie" issue, remember: Check the Onion.


Have you ever written a custom middleware to solve a weird problem? Tell me about it in the comments! πŸ‘‡

Top comments (0)