ruby 244 lines · 2 tabs

Rack middleware for request/response processing

Sarah Mitchell Feb 2026
2 tabs
# Basic middleware structure
class RequestTimerMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    start_time = Time.current

    status, headers, body = @app.call(env)

    duration = Time.current - start_time
    Rails.logger.info "Request completed in #{duration.round(3)}s"

    # Add custom header
    headers['X-Request-Duration'] = duration.to_s

    [status, headers, body]
  end
end

# API authentication middleware
class ApiAuthenticationMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    request = Rack::Request.new(env)

    # Only process API requests
    return @app.call(env) unless request.path.start_with?('/api/')

    token = request.env['HTTP_AUTHORIZATION']&.sub(/^Bearer /, '')

    unless valid_token?(token)
      return [
        401,
        { 'Content-Type' => 'application/json' },
        [{ error: 'Unauthorized' }.to_json]
      ]
    end

    # Add user to env for downstream use
    user = User.find_by(api_token: token)
    env['api_user'] = user

    @app.call(env)
  end

  private

  def valid_token?(token)
    token.present? && User.exists?(api_token: token)
  end
end

# Rate limiting middleware
class RateLimitMiddleware
  LIMIT = 100
  WINDOW = 3600  # 1 hour

  def initialize(app)
    @app = app
    @redis = Redis.new
  end

  def call(env)
    request = Rack::Request.new(env)
    client_ip = request.ip

    key = "rate_limit:#{client_ip}"
    count = @redis.get(key).to_i

    if count >= LIMIT
      return [
        429,
        {
          'Content-Type' => 'application/json',
          'X-RateLimit-Limit' => LIMIT.to_s,
          'X-RateLimit-Remaining' => '0',
          'Retry-After' => WINDOW.to_s
        },
        [{ error: 'Rate limit exceeded' }.to_json]
      ]
    end

    # Increment counter
    @redis.multi do |r|
      r.incr(key)
      r.expire(key, WINDOW)
    end

    status, headers, body = @app.call(env)

    # Add rate limit headers
    headers['X-RateLimit-Limit'] = LIMIT.to_s
    headers['X-RateLimit-Remaining'] = (LIMIT - count - 1).to_s

    [status, headers, body]
  end
end

# Registering middleware in config/application.rb
module MyApp
  class Application < Rails::Application
    # Insert at specific position
    config.middleware.use RequestTimerMiddleware

    # Insert before another middleware
    config.middleware.insert_before ActionDispatch::Session::CookieStore,
      ApiAuthenticationMiddleware

    # Insert after another middleware
    config.middleware.insert_after Rails::Rack::Logger,
      RateLimitMiddleware

    # Delete middleware
    config.middleware.delete Rack::Runtime
  end
end

# View middleware stack
# rails middleware
2 files · ruby Explain with highlit

Rack middleware processes HTTP requests/responses in Rails' stack. Middleware sits between web server and application, modifying requests before they reach controllers. I build custom middleware for logging, authentication, rate limiting, request modification. Middleware follows simple interface—call(env) returns [status, headers, body]. Each middleware can pass requests down the stack with @app.call(env). Rails includes middleware for cookies, sessions, logging, static files. Middleware order matters—authentication must run before authorization. Inserting custom middleware at the right position is crucial. Middleware enables cross-cutting concerns without polluting controllers. Understanding Rack unlocks building custom HTTP processing layers and debugging Rails internals.


Related snips

ruby
class CommentsController < ApplicationController
  before_action :set_post

  def create
    @comment = @post.comments.build(comment_params)

System test: asserting Turbo Stream responses

rails hotwire turbo
by codesnips 4 tabs
ruby
class Money
  include Comparable

  attr_reader :amount, :currency

  def initialize(amount, currency = 'USD')

Value objects for domain modeling

ruby value-objects domain-driven-design
by Sarah Mitchell 2 tabs
ruby
class Post < ApplicationRecord
  belongs_to :author, class_name: 'User'
  has_many :comments, dependent: :destroy

  scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
  scope :draft, -> { where(published_at: nil) }

ActiveRecord scopes for reusable query logic

rails activerecord patterns
by Alex Kumar 1 tab
typescript
export interface RetryOptions {
  retries: number;
  baseMs: number;
  maxMs: number;
  signal?: AbortSignal;
  onRetry?: (attempt: number, delay: number, err: unknown) => void;

Exponential backoff with jitter for retries

typescript reliability retry
by codesnips 2 tabs
ruby
module Api
  module V1
    class UsersController < BaseController
      def show
        user = User.includes(:profile).find(params[:id])

ETags for conditional requests and caching

rails caching http-caching
by Alex Kumar 1 tab
ruby
class PostsController < ApplicationController
  def index
    @posts = Post.includes(:author)
                 .order(created_at: :desc)
                 .page(params[:page])
                 .per(10)

Turbo Frames: infinite scroll with lazy-loading frame

rails turbo hotwire
by codesnips 4 tabs

Share this code

Here's the card — post it anywhere.

Rack middleware for request/response processing — share card
Link copied