Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
97: Middleware in Vapor
A few years ago, I was reviewing a PR for a junior dev who was building a private API. I noticed that almost every single route handler started with the exact same five lines of code: a guard statement checking for a specific X-API-Key header, followed by a database lookup to verify that key. When he decided to change the header name to X-Auth-Token, he had to find-and-replace across forty different files. It was a tedious, error-prone mess that should have never happened.
This is exactly where Middleware comes in. Think of Middleware as a series of checkpoints or "layers" that a request must pass through before it ever hits your route logic, and that the response must pass through before it goes back to the client. Instead of polluting your controllers with repetitive logic, you pull that logic out into a standalone component that sits in the pipeline.
The Request-Response Pipeline
In Vapor, Middleware operates on a "chain." When a request hits your server, it doesn't go straight to your function; it goes into the first middleware, which can either return a response immediately (blocking the request) or pass it to the next piece of middleware in the chain. Once the final route handler generates a response, that response travels back through the middleware chain in reverse order. This allows you to modify the request on the way in and the response on the way out.
I like to visualize this as an onion. Your route handler is the core, and each piece of middleware is a layer. To get to the core, you have to peel through the layers. To leave, you have to pass back through them.
Creating a Custom API Key Guard
To build your own middleware, you implement the AsyncMiddleware protocol. The heart of this is the respond method. This is where you decide if the request is allowed to proceed. If it's not, you throw an error (like Abort(.unauthorized)) and the chain stops dead—the route handler is never even called.
import Vapor
struct APIKeyMiddleware: AsyncMiddleware {
func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
// 1. Inspect the request
guard let apiKey = request.headers.first(name: "X-API-Key"), apiKey == "secret-123" else {
throw Abort(.unauthorized, reason: "Invalid or missing API Key.")
}
// 2. Pass the request to the next middleware or the route handler
return try await next.respond(to: request)
}
}
Notice that next.respond(to: request) is the magic line. If you omit that, the request hangs or dies. By awaiting that call, you're saying, "I'm done with my checks; proceed to the next step." If you wanted to modify the response after the route handler finished, you would capture the result of that call in a variable, modify it, and then return it.
Global Application vs. Route Grouping
You have a choice in how you apply this. If you want every single request to your server—including your health check and documentation—to be filtered, you can register it globally in configure.swift using app.middleware.use(APIKeyMiddleware()). But usually, that's too aggressive.
More often, you'll want to protect a specific subset of routes. Vapor makes this easy with route groups. By wrapping your routes in a group that uses the middleware, you create a protected zone. Everything inside the curly braces is guarded; everything outside is public.
let protected = app.grouped(APIKeyMiddleware())
protected.get("profile") { req in
return "This is protected data!"
}
protected.get("settings") { req in
return "Your secret settings."
}
// This route remains public because it's not in the 'protected' group
app.get("welcome") { req in
return "Welcome to the public API!"
}
Using groups keeps your routes.swift file clean and makes it visually obvious which parts of your API are locked down and which are open to the world.
📋 Practical Task
Building a Request Execution Timer Middleware
One of the most useful things you can do with middleware is monitor performance. Your task is to create a middleware called RequestTimerMiddleware that measures how long a request takes to be processed by the server.
Requirements:
- Create a struct that conforms to
AsyncMiddleware. - Capture the current time at the very start of the
respondmethod. - Call
try await next.respond(to: request)and store the resulting response in a constant. - Calculate the time elapsed between the start and the moment the response was received.
- Print a message to the console in the format:
"Request to [METHOD] [PATH] took [X]ms"(e.g., "Request to GET /profile took 12ms"). - Register this middleware globally in your
configure.swiftso that every request to your server is timed.
There are no comments for now.