Skip to Content
Course content

144: Sinatra Middleware and Extensions

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When you start building with Sinatra, the before filter feels like a superpower. It's the go-to for everything: authenticating a user, setting up a database connection, or initializing some request-scoped variables. But as your application grows—or worse, as you start managing three or four different Sinatra services—you'll notice a pattern. You're copy-pasting the same "setup" logic into every single app. This is where we need to stop thinking about routes and start thinking about the pipeline.

The temptation of the before filter

Let's say you're building a suite of microservices and you need a unique Request-ID for every single incoming call to make distributed tracing possible. Your first instinct might be to do this in every app:

before do
  env['request_id'] = SecureRandom.uuid
  logger.info "Processing request #{env['request_id']}"
end

It works. For a while. But there's a structural flaw here: this code only runs after the request has already been handed off to Sinatra. If the server crashes before it hits the route, or if a different piece of middleware throws an exception, your Request-ID is never generated. Furthermore, if you have five different Sinatra apps, you're maintaining this snippet in five different places. If you decide to change the header name from Request-ID to X-Correlation-ID, you're in for a tedious afternoon of find-and-replace.

Moving up the stack to Rack

Since Sinatra is built on Rack, the "correct" way to handle cross-cutting concerns like this is through middleware. Middleware sits outside your application. It's a layer in an onion; the request passes through the middleware before it ever touches your Sinatra routes, and the response passes back through it on the way out.

Here is how I'd rewrite that Request-ID logic as a Rack middleware. Notice that we aren't using Sinatra methods here—we're using the raw Rack interface:

class RequestIdMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    # We assign the ID before the app even sees the request
    request_id = SecureRandom.uuid
    env['request_id'] = request_id
    
    # Now we pass the request down the chain to the next middleware or the app
    status, headers, body = @app.call(env)
    
    # We can even modify the response on the way back out
    headers['X-Request-Id'] = request_id
    
    [status, headers, body]
  end
end

By using use RequestIdMiddleware in your Sinatra config, you've decoupled the infrastructure (tracing) from the business logic (your routes). The trade-off is a slight increase in complexity; you're now dealing with the [status, headers, body] array that Rack expects, rather than Sinatra's friendly helper methods.

Packaging logic with Extensions

Middleware is great for the pipeline, but sometimes you want to provide a set of helper methods and that middleware to multiple apps without making the developers manually configure both. This is where Sinatra Extensions come in. An extension allows you to wrap middleware and helpers into a single module that can be "registered".

Instead of telling your colleagues to "add this middleware and copy-paste these three helper methods," you give them a single module:

module Sinatra
  module RequestTracker
    def self.registered(app)
      # This tells the app to use the middleware we wrote earlier
      app.use RequestIdMiddleware
      
      # We can also add helpers directly to the app's scope
      app.helpers do
        def current_request_id
          env['request_id']
        end
      end
    end
  end
end

Now, in any of your Sinatra apps, you just add register Sinatra::RequestTracker. It's a clean, professional way to share functionality. You've essentially created your own "plugin" for Sinatra. I prefer this approach because it creates a single source of truth. If the logic for how we track requests changes, I update it in the extension, and every app gets the update upon the next deploy.




📋 Practical Task

Build a "Maintenance Mode" Extension

Your goal is to create a Sinatra Extension called Sinatra::MaintenanceMode. This extension should allow an application to be put into a read-only/maintenance state based on a simple configuration flag.

Requirements:

  • Create a Rack middleware that checks if settings.maintenance_mode is set to true.
  • If maintenance mode is active, the middleware should intercept the request and return a 503 Service Unavailable status with a plain text body saying "Site is currently down for maintenance."
  • If maintenance mode is inactive, the request should pass through to the application normally.
  • Wrap this middleware into a Sinatra Extension so that the app only needs to call register Sinatra::MaintenanceMode.
  • Verify your implementation by creating a small Sinatra app that toggles set :maintenance_mode, true and false to see the middleware in action.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.