ruby 42 lines · 8 steps

Building a middleware pipeline in Ruby

Compose a chain of block-based middlewares into a single callable that wraps each layer around the next.

Explained by highlit
1class Pipeline
2 def initialize
3 @middlewares = []
4 end
5 
6 def use(&middleware)
7 @middlewares << middleware
8 self
9 end
10 
11 def call(request)
12 stack = @middlewares.reverse.reduce(->(req) { req }) do |next_step, middleware|
13 ->(req) { middleware.call(req, next_step) }
14 end
15 stack.call(request)
16 end
17end
18 
19pipeline = Pipeline.new
20 
21pipeline.use do |request, nxt|
22 started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
23 response = nxt.call(request)
24 elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
25 response.merge(duration_ms: (elapsed * 1000).round(2))
26end
27 
28pipeline.use do |request, nxt|
29 token = request.dig(:headers, "Authorization")
30 next { status: 401, body: "Unauthorized" } unless token == "Bearer secret"
31 
32 nxt.call(request.merge(current_user: "alice"))
33end
34 
35pipeline.use do |request, nxt|
36 Rails.logger.info("#{request[:method]} #{request[:path]} user=#{request[:current_user]}")
37 nxt.call(request)
38end
39 
40pipeline.use do |request, _nxt|
41 { status: 200, body: "Hello, #{request[:current_user]}" }
42end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Folding functions with reduce turns a list of layers into one nested callable, each holding a reference to the next.
  2. 2Passing a next_step callback lets each middleware decide whether to continue, short-circuit, or transform the request.
  3. 3Returning self from configuration methods enables fluent chaining and a clean setup API.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a middleware pipeline in Ruby — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code