Menu
Coddy logo textTech

Middleware as Decorator

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 97 of 107.

Middleware is one of the most practical applications of the Decorator pattern in Go. In web development, middleware functions wrap HTTP handlers to add cross-cutting concerns like logging, authentication, or timing without modifying the original handler.

The key insight is that middleware takes a handler and returns a new handler with added behavior. This follows the Decorator pattern exactly—same interface in and out, with enhanced functionality:

type Handler func(request string) string

func LoggingMiddleware(next Handler) Handler {
    return func(request string) string {
        result := next(request)
        return "[LOG] " + result
    }
}

func AuthMiddleware(next Handler) Handler {
    return func(request string) string {
        return "[AUTH] " + next(request)
    }
}

Each middleware wraps the next handler in the chain, adding its behavior before or after calling the wrapped handler. You can stack multiple middleware by composing them:

func MainHandler(request string) string {
    return "Response to: " + request
}

handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// [LOG] [AUTH] Response to: GET /users

The order of wrapping determines the execution order—the outermost middleware runs first. This pattern is used extensively in Go web frameworks for request processing pipelines, making it easy to add or remove functionality without touching core handler logic.

challenge icon

Challenge

Easy

Let's build a request processing pipeline using the Middleware as Decorator pattern! You'll create a system where middleware functions wrap handlers to add functionality like timing, validation, and formatting—all without modifying the core handler logic.

You'll organize your code across three files:

  • handler.go: Define your handler type and the base handler that processes requests.

    Create a Handler type as a function that takes a request string and returns a response string:

    type Handler func(request string) string

    Implement a BaseHandler function that returns Processed: [request] for any request it receives.

  • middleware.go: Create your middleware functions that wrap handlers with additional behavior.

    Build three middleware functions, each taking a Handler and returning a new Handler:

    • TimingMiddleware — wraps the response with timing info, returning [TIMING] [response]
    • ValidationMiddleware — adds validation prefix, returning [VALID] [response]
    • UppercaseMiddleware — converts the entire response to uppercase

    Each middleware should call the wrapped handler and enhance its result.

  • main.go: Build a processing pipeline by composing middleware.

    Read the number of middleware layers to apply. Then for each layer, read the middleware type (timing, validation, or uppercase) and wrap your current handler with that middleware. Apply them in the order they're read—the first middleware read becomes the outermost wrapper.

    After building the pipeline, read the request string, process it through your composed handler, and print the result.

The following inputs will be provided:

  • Line 1: Number of middleware layers
  • Following lines: One middleware type per line (timing, validation, or uppercase)
  • Last line: The request to process

For example, given:

2
timing
validation
GET /users

Your output should be:

[TIMING] [VALID] Processed: GET /users

And given:

3
uppercase
timing
validation
POST /data

Your output should be:

[TIMING] [VALID] PROCESSED: POST /DATA

And given:

1
uppercase
hello world

Your output should be:

PROCESSED: HELLO WORLD

And given:

0
simple request

Your output should be:

Processed: simple request

Notice how the middleware wrapping order affects the output—the first middleware applied (outermost) runs first and sees the final result from all inner middleware. The uppercase middleware in the second example runs last (innermost), so it uppercases the base response before timing and validation add their prefixes!

Cheat sheet

Middleware in Go follows the Decorator pattern by wrapping handlers to add functionality without modifying the original handler.

Define a handler type as a function:

type Handler func(request string) string

Middleware functions take a handler and return a new handler with enhanced behavior:

func LoggingMiddleware(next Handler) Handler {
    return func(request string) string {
        result := next(request)
        return "[LOG] " + result
    }
}

func AuthMiddleware(next Handler) Handler {
    return func(request string) string {
        return "[AUTH] " + next(request)
    }
}

Stack multiple middleware by composing them:

func MainHandler(request string) string {
    return "Response to: " + request
}

handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// Output: [LOG] [AUTH] Response to: GET /users

The outermost middleware runs first, determining the execution order. Each middleware can add behavior before or after calling the wrapped handler.

Try it yourself

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Read number of middleware layers
	nLine, _ := reader.ReadString('\n')
	n, _ := strconv.Atoi(strings.TrimSpace(nLine))

	// Read middleware types
	middlewareTypes := make([]string, n)
	for i := 0; i < n; i++ {
		line, _ := reader.ReadString('\n')
		middlewareTypes[i] = strings.TrimSpace(line)
	}

	// Read the request
	request, _ := reader.ReadString('\n')
	request = strings.TrimSpace(request)

	// TODO: Start with the BaseHandler

	// TODO: Build the pipeline by wrapping the handler with middleware
	// Apply middleware in order - first middleware read becomes outermost wrapper
	// Hint: Loop through middlewareTypes in REVERSE order to achieve this
	// The last middleware applied will be the outermost (runs first)

	// TODO: Process the request through the composed handler and print the result
	fmt.Println("TODO: implement pipeline")
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming