Middleware in Rails: What It Is, How It Works, and Examples
In this post, We'll learn about Rails middleware: what it is, why we need it, how it works, and why it's so important. If you have a fuzzy understanding of middleware, this post will make it concrete. I'll also show you how to create and test custom middleware for your Rails app.

P.S. I suggest that you read my previous post "The Definitive Guide to Rack for Rails Developers" before reading this one. That should give you a good context for this post. If you're already familiar with Rack, keep reading.
As we learned in the previous post, the Rack protocol provides a simple interface for web applications and web servers to talk to each other. This communication happens through the middleware pipeline.
In this post, we're going to do a deep dive into middleware; especially how and where they fit into your Rails applications. We'll learn what middleware is, why we need them, how they work, and why they're so important. I'll also show you how to create custom middleware for your Rails application and how to test it.
Sounds good? Let's get started.
I've also planned a series of posts explaining each and every middleware Rails uses (the ones you see by running bin/rails middleware
command). If you'd like to receive them in your email, please subscribe to my blog. As always, a big hug and thank you if you're already a subscriber.
What You'll Learn:
- What is Middleware?
- Why Use Middleware?
- Create Custom Middleware
- Middleware API in Rails
- Running Code Before and After
- Modify Existing Middleware
- Passing Parameters to Middleware
- How to Test Middleware Classes
- Additional Resources
What is Middleware?
A middleware is nothing but a class with acall
method that recieves the incoming HTTP request (env
) from the web server, processes it before passing it to the Rails application, processes the response received from the application, and returns the response back to the web server.
When we say an application is Rack-compliant, it means the following:
- It has a
call
method that accepts a single argumentenv
, containing all the data about the request, and - It returns an array containing the status, headers, and response.
Because the Rack interface is so simple, you can use any code that implements this interface in a Rack application, allowing you to build small, focused, and reusable applications that work together to provide different functionalities. These mini-components are known as Middleware.
It's best to envision middleware as a series of "layers" HTTP requests must pass through before they hit your application. Each layer can examine the request, modify it, and even reject it entirely.
Here's a simple middleware that prints some text before and after passing the request to the application (or the next middleware in the pipeline).
class CustomMiddleware
def initialize(app)
@app = app
end
def call(env)
puts 'before'
result = @app.call(env)
puts 'after'
result
end
end
Two important things to note here:
- The middleware receives the application (or the next middleware) in the constructor.
- After receiving the response from the application, it has to return the response to the web server, or the next middleware in the pipeline.
This diagram will help you understand better.

Why Use Middleware?
Middleware is very useful for writing logic that is not specific to your web application, such as authenticating the request, logging, or error handling. It focuses on doing one thing and doing it well.
Middleware sits between the user and the application code. When an HTTP request comes in, the middleware can intercept, examine, and modify it. Similarly, it can examine and modify the HTTP response before forwarding it to the user.
Middleware provides a convenient mechanism for inspecting and filtering HTTP requests entering your application. For example, Rails includes the Rack::MethodOverride
middleware that inspects and overrides the HTTP verb of the incoming request.
Using middleware also simplifies your application code, and it can only focus on the logic related to the application.
Authentication is another good use case for middleware. If the user is not authenticated, the middleware will redirect the user to your application's login screen. However, if the user is authenticated, the middleware will allow the request to proceed further into the application.
Additional middleware can be written to perform a variety of tasks besides authentication. For example, a logging middleware might log all incoming requests to your application.
There are several middleware (is 'middlewares' a word?) included in the Rails framework. You can see a list of all middleware by running the bin/rails middleware
command. The Rails guides also provide a brief description of what each middleware does.
In the next section, we'll learn how you can create your own middleware.
Create Custom Middleware
Let's say you want to add a new middleware for your application that does a simple thing: verify a token sent in the header by the client code. If this token matches our secret token, allow the request to proceed. Otherwise, immediately return an error without passing the request to the application.
How do you do that?
Before writing any new code, let's add some unit tests first.
- In the first test, we'll pass the secret token in the headers, and expect that the application will allow access and return a normal response.
- In the second test, we won't pass any token, on which the application should return an error.
require "test_helper"
class TokenAccessTest < ActionDispatch::IntegrationTest
test 'allows access for valid tokens' do
get middleware_path, headers: { 'token' => 'my-secret-token' }
assert_equal 'success', response.body
end
test 'returns error for invalid or missing tokens' do
get middleware_path
assert_equal 'invalid or missing token', response.body
end
end
Let's take care of the plumbing first, by adding a route and a controller#action
to handle the incoming request. I'll add a /middleware
route, which is accessible as middleware_path
that will be dispatched to the MiddlewareController#show
method.
# config/routes.rb
get 'middleware', to: 'middleware#show', as: 'middleware'
# app/controllers/middleware_controller.rb
class MiddlewareController < ApplicationController
def show
render plain: 'success'
end
end
If I run the tests now, the second test fails as we're not verifying the token at all. Let's fix that by introducing a custom middleware, which is just a Ruby class with the interface we saw in the introduction. As mentioned above, it will have a call
method that takes the incoming request and returns an array containing the HTTP status, headers, and the response body.
Where do you put middleware in your Rails app?
Middleware can't be inapp
because they can't be reloaded. They should be inlib
. - Rafael Franca
Okay, let's create a new folder called middleware
in the lib
folder and add VerifyToken
middleware in it. Its constructor receives the app
object which is your application, also a middleware.
# lib/middleware/verify_token.rb
module Middleware
class VerifyToken
def initialize(app)
@app = app
end
def call(env)
request = ActionDispatch::Request.new(env)
if request.headers['token'] != 'my-secret-token'
return [200, {}, ['invalid or missing token']]
end
@app.call(env)
end
end
end
As you can see, in the call
method, we'll first verify the token and return an error if it doesn't match our secret token. If it matches, we send the normal response.
Makes sense?
If anything doesn't make sense at this point, please leave a comment below and I'll try to clarify it the best I can.
We're not done yet, we need to tell Rails to insert this middleware in the middleware stack. Let's do this using the config.middleware
object.
Middleware API in Rails
Rails provides a simple configuration interface config.middleware
for adding, removing, and modifying the middleware stack.
The object returned by config.middleware
method is an instance of Rails::Configuration::MiddlewareStackProxy
class. As the name suggests, it's a proxy for the Rails middleware stack that lets you configure the middleware for your application.
You can either config.middleware
in the application.rb
file or one of the environment-specific configuration files under the environments/<env>.rb
file.
To add a new middleware, use the config.middleware.use
method, passing the name of the Ruby class that acts as middleware. Rails will insert this middleware at the end of the existing middleware stack, meaning it will get executed in the end, just before your application is called.
# config/application.rb
require_relative "../lib/middleware/verify_token"'
module Blog
class Application < Rails::Application
config.middleware.use Middleware::VerifyToken
end
end
Run the tests again, and they should pass this time! We've successfully used middleware to verify an incoming request.
Instead of putting your middleware in the end, if you wish to insert it before or after another middleware, you can do so using the insert_before
or insert_after
method.
config.middleware.insert_before ActionDispatch::Callbacks, VerifyToken
This will insert the VerifyToken
callback before the ActionDispatch::Callbacks
middleware runs.
Before vs. After
Middleware can perform tasks before or after passing the request deeper into the application. For example, the following middleware performs some task both before and after the request is handled by the application:
module Middleware
class DoSomething
def initialize(app)
@app = app
end
def call(env)
# perform something before passing the request to the application
process_request(env)
response = @app.call(env)
# perform something after receiving the response from the application.
# It should also return the response.
process_response(response)
end
end
end
No matter where a middleware performs its action, it still has to return a valid Rack-compatible response to the next middleware in the pipeline, or the web server.
Modify Existing Middleware
In addition to inserting your own middleware, Rails also lets you modify its existing middleware stack, by allowing you to replace or even delete a middleware.
Replace Middleware
You can replace existing middleware in the middleware stack using config.middleware.swap
method. For example,
config.middleware.swap ActionDispatch::RequestId, Custom::RequestId
will replace the ActionDispatch::RequestId
module with the Custom::RequestId
module.
Delete Middleware
You can delete existing middleware using the config.middleware.delete
method.
config.middleware.delete ActionDispatch::Flash
Now the flash middleware won't run when Rails executes the middleware pipeline. You can also verify that it's removed by running the bin/rails middleware
command.
Passing Parameters to Middleware
So far, we've run our middleware code without providing any additional parameters. Rails also makes it easy to pass extra parameters. For example, we can pass a default token for the VerifyToken
middleware as follows:
# config/application.rb
# Set custom middleware
config.middleware.use Middleware::VerifyToken, default_token: 'another-token'
This parameter will be available in the constructor of the middleware. You can set it to an instance variable and access it anywhere else in the class.
module Middleware
class VerifyToken
def initialize(app, default_token: nil)
@app = app
@default_token = default_token
end
def call(env)
# remaining code
end
end
end
You can pass more than one parameters, if needed.
How to Test Middleware Classes
If you're adding a custom middleware with significant code or logic, it's a good idea to add tests for it. That said, you can simply test the side-effect of the middleware without directly testing the middleware class, as we've seen earlier, and that should be totally fine for most cases.
However, what if you want to actually test the VerifyToken
class? How to initialize its dependency app
, which is the whole Rails application itself?
Don't worry, you can pass the world's simplest Rack app instead ;)
require "test_helper"
require "middleware/verify_token.rb"
class VerifyTokenTest < ActiveSupport::TestCase
setup do
@app = ->(env) { [200, { 'Content-Type' => 'text/plain' }, [ 'success' ]] }
@middleware = Middleware::VerifyToken.new(@app)
@env = Rack::MockRequest.env_for("/middleware")
end
test 'deleting the associated quotes' do
response = @middleware.call(@env)
assert_equal [200, {}, ["invalid or missing token"]], response
end
end
Note that we're using Rack::MockRequest
to build the env
hash.
Now that you have all the necessary dependencies, you can test your custom middleware to your heart's content.
That’s a wrap. I should probably stop here. If you're still reading this (and I know you are), you should have a much better understanding of the concept of middleware, instead of it just being a fuzzy concept at the back of your mind.
Here're some great resources for you to learn more about middleware and solidify your understanding.
Additional Resources
- Rails on Rack: Official Rails Guides explaining the middleware API
- What is Rack Middleware?: Lots of good answers on this old question on Stack Overflow
- Pipeline Design Pattern: Formal theory behind the architectural concepts behind Rack and the concept of middleware
- Rack Middleware (Railscasts): Old is gold. I miss Railscasts.
I hope you liked this article and you learned something new.
As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I look forward to hearing from you.
If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you.