Introduction & Architecture
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Vapor 4.122.2 (the latest 4.x release, published 2026-09-17) on Swift 6 (the local
toolchain is Swift 6.4; the latest swift.org release at the time of writing is 6.4.1), with SwiftNIO 2.104, swift-log
1.16, RoutingKit 4.9 and ConsoleKit 4.x, checked on 2026-10-17. Vapor 4’s own Package.swift declares
swift-tools-version:6.0 and the official vapor/template uses tools version 6.3, so every example here compiles in
the Swift 6 language mode. Vapor 5 exists as a beta (5.0.0-beta.4, 2026-10-13) and is not generally available:
differences that are already visible in it are called out as upcoming, never as the current behaviour.
What Vapor is
Vapor is an open-source web framework for Swift, released under the MIT licence and maintained by the Vapor organisation on GitHub. The framework’s documentation describes it as a way to build websites, APIs and cloud services with Swift’s type safety and expressiveness, and three properties shape everything else in this section:
-
Asynchronous and non-blocking. Vapor does not start a thread per request. A small number of threads (by default one per CPU core) each run an event loop that multiplexes thousands of connections. Your handlers are
asyncfunctions that suspend while waiting for a database or an HTTP call instead of holding a thread. -
Swift-native. Routes, middleware, models and configuration are ordinary Swift types: structs that conform to
Contentare request and response bodies,Codabledoes the JSON encoding, property wrappers describe database columns, protocols define extension points, and the compiler checks the result. There is no reflection-driven dependency injection container and no code generation step. -
Built on SwiftNIO. The networking layer is Apple’s SwiftNIO, the same library that powers
grpc-swift,AsyncHTTPClientand many other server-side Swift packages. Vapor adds the HTTP-framework layer on top: the router, middleware, content negotiation, sessions, authentication, validation, commands and a services mechanism for plugging in everything else.
Vapor is a framework rather than a library of parts: the Application object owns the event loop group, the logger,
the router, the middleware stack, the command-line interface and the lifecycle, and the other packages of the ecosystem
(Fluent, Leaf, Queues, JWT, Redis) register themselves on it. The section landing page
lists every topic that follows; this page gives the map you need to place each of them.
Source: Vapor docs: Welcome and vapor/vapor at 4.122.2
A short history
The dates below come from the repository and its GitHub releases:
| Version | First release | What defined it |
|---|---|---|
(repository) |
2016-01-18 |
The |
Vapor 1 |
2016-09-15 (1.0.0) |
The first stable release, built on its own HTTP and socket stack before SwiftNIO existed. |
Vapor 2 |
2017-05-17 (2.0.0) |
Second major version, still on the pre-NIO stack (applications were built around the |
Vapor 3 |
2018-05-04 (3.0.0) |
Rewritten on SwiftNIO with a new future/promise API ( |
Vapor 4 |
2020-04-09 (4.0.0) |
Redesigned API: |
Vapor 5 |
beta since 2026-09-15 |
Swift 6.4 tools, new server abstraction, |
Two conclusions matter when you read older material. First, anything written for Vapor 3 (Future<T>, Container,
Service registrations in configure(_:_:_:)) does not compile on Vapor 4. Second, much Vapor 4 material predates
Swift concurrency and builds every handler around EventLoopFuture; that style still works and is labelled legacy
in this section, but new code uses async/await.
Source: vapor/vapor releases and Vapor docs
Why server-side Swift
Swift on the server is not a replacement for every backend stack, but it has a distinct set of strengths:
-
One language end to end. If your product has an iOS or macOS client, the same
Codablemodel types can be shared between app and server through a Swift package. A change toBookis a compile error on both sides instead of a runtime decoding failure. The Codable & Serialization page covers the encoding rules both sides rely on. -
Performance and memory. Swift compiles to native code with no garbage collector; memory is managed with automatic reference counting (ARC). A typical Vapor service starts quickly, idles at tens of megabytes and has a flat latency tail, which makes it a good citizen in containers.
-
Type safety. Optionals make absent values explicit, enums with associated values model states, generics keep routes and Fluent queries typed, and since Swift 6 the compiler also rules out data races in the language mode (see Actors, Isolation & Sendable).
-
Modern concurrency.
async/await, structured concurrency and actors give straight-line asynchronous code (see Async/Await & Tasks). -
A real ecosystem. The Swift Server Workgroup (SSWG) incubates the shared packages — logging, metrics, tracing, HTTP clients, database drivers — so Vapor, Hummingbird and others interoperate.
It is not always the right choice. Weigh these before committing a team to it:
-
Hiring and community size. The pool of engineers who know Vapor is far smaller than for Spring, Django, Express or ASP.NET Core, and third-party integrations (payment providers, CMS SDKs) usually ship Java, Python or JavaScript libraries first. Expect to write more thin HTTP clients yourself.
-
Build times and tooling. Swift compiles slowly compared with Go, and a full Vapor build from clean takes minutes. Incremental builds are fine; Docker images need layer caching to stay pleasant.
-
Linux is a second-class development target for Apple tooling. You can develop on macOS and Linux, but Foundation differences and Apple-only frameworks (
SwiftUI,CoreData,Combine) are unavailable on Linux, so server code must stick to portable APIs. -
Framework churn. Vapor 3 to 4 was a rewrite and Vapor 5 will change APIs again; plan upgrade time.
-
Team already productive elsewhere. If nobody on the team writes Swift and there are no Apple clients to share models with, a mainstream stack is almost always cheaper. The Starting a New Project page gives the general decision framework.
A good fit is therefore: a team that already writes Swift for Apple platforms and wants shared models end to end, JSON APIs for iOS and macOS apps, push-notification senders, and server-rendered sites with Leaf.
Source: swift.org: Swift on Server and Vapor docs
The SwiftNIO foundation
SwiftNIO is an event-driven, non-blocking network application framework. It is the reason Vapor can be both fast and small, and a handful of its concepts appear in Vapor’s own API, so learn them before the rest of the section.
| Concept | What it is and where Vapor shows it |
|---|---|
|
The basic I/O primitive: a single thread that waits for events (readable socket, timer, scheduled task) and runs
callbacks. Work on most NIO objects must happen on their loop to be thread-safe. Vapor exposes it as |
|
A set of event loops that spreads connections among them. |
|
Owns a file descriptor (a listening socket or one accepted TCP connection), its lifetime and its inbound and
outbound events. A Vapor server holds one server |
|
An ordered chain of handlers attached to a |
|
One processing step: TLS, an HTTP codec, a compressor, a timeout. Each handler does one thing and can be reused in other servers. |
|
NIO’s copy-on-write byte container. Vapor request and response bodies are streams of |
|
The pre-concurrency result type. Still everywhere in the API, but |
|
A pool of ordinary threads for work that cannot be made non-blocking. Vapor exposes it as
|
A pipeline in miniature
The smallest useful NIO program is a TCP server whose pipeline has one handler. It upper-cases whatever it receives,
which makes the roles of ServerBootstrap, the childChannelInitializer and the handler visible:
import NIOCore
import NIOPosix
/// One pipeline stage: upper-cases every inbound ByteBuffer and writes it back.
final class ShoutHandler: ChannelInboundHandler {
typealias InboundIn = ByteBuffer
typealias OutboundOut = ByteBuffer
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
var input = unwrapInboundIn(data)
let text = input.readString(length: input.readableBytes) ?? ""
let output = context.channel.allocator.buffer(string: text.uppercased())
context.writeAndFlush(wrapOutboundOut(output), promise: nil)
}
func errorCaught(context: ChannelHandlerContext, error: any Error) {
context.close(promise: nil)
}
}
let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
let bootstrap = ServerBootstrap(group: group)
.serverChannelOption(.socketOption(.so_reuseaddr), value: 1)
.childChannelInitializer { channel in
channel.eventLoop.makeCompletedFuture {
try channel.pipeline.syncOperations.addHandler(ShoutHandler())
}
}
let channel = try await bootstrap.bind(host: "127.0.0.1", port: 9100).get()
print("listening on \(channel.localAddress!)")
try await channel.closeFuture.get()
try await group.shutdownGracefully()
Source: SwiftNIO: Conceptual overview
Notice what the example does not do: it never creates a thread per connection and never blocks. The
childChannelInitializer runs once for every accepted connection and installs that connection’s pipeline;
channelRead runs on the connection’s event loop whenever bytes arrive. Vapor’s HTTP server is exactly this, with a
longer pipeline.
How Vapor’s HTTP server sits on NIO
When you run app.execute() with the default serve command, Vapor’s HTTPServer binds a NIO ServerBootstrap on
app.eventLoopGroup. For each accepted connection its child-channel initializer builds a pipeline. The Vapor 4.122.2
source assembles this pipeline for an HTTP/1.1 connection, in order:
-
HTTPResponseEncoderand aByteToMessageHandler(HTTPRequestDecoder(...))fromNIOHTTP1— bytes to HTTP parts and back. (For TLS, NIOSSL’sNIOSSLServerHandlersits in front; for HTTP/2,NIOHTTP2frames are converted to HTTP/1 parts first.) -
Optionally an
HTTPServerPipelineHandlerwhensupportPipeliningis on. -
The response compressor and, if enabled, a request decompressor (
HTTPServer.Configuration.responseCompression,requestDecompression). -
HTTPServerResponseEncoder— turns a VaporResponseinto HTTP parts, addingServerandDateheaders. -
HTTPServerRequestDecoder— turns HTTP parts into a VaporRequest, buffering or streaming the body. -
A
HTTPServerUpgradeHandler(WebSocket upgrades) and finallyHTTPServerHandler, the only handler that knows about the framework: it callsresponder.respond(to: request)and writes the result. -
NIOCloseOnErrorHandlercloses the connection on any unhandled pipeline error.
Everything above HTTPServerHandler is NIO; everything below it — routing, middleware, your handlers — is Vapor.
That handoff point is the Responder protocol, and it is the centre of the lifecycle described below.
Why blocking an event loop is fatal
An event loop is one thread serving many connections. If a handler running on that thread blocks — Thread.sleep,
a synchronous file or network read, DispatchSemaphore.wait(), a .wait() on an EventLoopFuture, a long CPU loop — every other connection assigned to that loop stalls with it: no bytes are read, no responses are written, no timers
fire. On an 8-core machine, eight such requests freeze the whole server, however light the load looks. SwiftNIO’s
documentation and Vapor’s both say the same thing: do not block; move blocking work off the loop.
Swift concurrency changes the details but not the rule. A Vapor async handler starts on the request’s event loop and
resumes on the cooperative thread pool after its first suspension, so a blocking call inside it can freeze either an
event loop or a Swift concurrency thread (the cooperative pool has roughly one thread per core, and Swift’s concurrency
model assumes that tasks make forward progress). The three options, from best to worst, are:
import Vapor
func routes(_ app: Application) throws {
// Wrong: a synchronous closure runs on the event loop and parks it for two seconds.
app.get("blocking", "wrong") { _ -> String in
Thread.sleep(forTimeInterval: 2)
return "done (blocked a thread)"
}
// Right (1): a non-blocking suspension; the thread is free while waiting.
app.get("blocking", "async") { _ async throws -> String in
try await Task.sleep(for: .seconds(2))
return "done (suspended)"
}
// Right (2): unavoidable blocking work goes to the NIO thread pool.
app.get("blocking", "pool") { req async throws -> String in
try await req.application.threadPool.runIfActive {
Thread.sleep(forTimeInterval: 2)
return "done (on the thread pool)"
}
}
}
Source: Vapor docs: Async and SwiftNIO: Conceptual overview
Rules of thumb that follow:
-
Use the
asyncAPI of every library (Fluent, Redis,req.client,req.fileio); they are non-blocking by design. -
Wrap synchronous work (a C library, a PDF renderer, a password hash with a large cost factor) in
app.threadPool.runIfActive { ... }or a dedicated actor. -
Never call
.wait()on anEventLoopFuturefrom a request handler; Vapor crashes in debug builds with a precondition failure from NIO when you try it on an event loop..wait()is only acceptable in the main thread’s setup code, and theApplication.makeentrypoint avoids it altogether. -
Hashing with Bcrypt is deliberately slow; Vapor offers
req.password.async(...)so that it runs on the thread pool (see the Authentication and Passwords and Crypto pages).
Layers of the stack
| Layer | Responsibility |
|---|---|
Operating system |
Non-blocking sockets, readiness notification through |
SwiftNIO |
Event loops, channels and pipelines, byte buffers, futures, the thread pool. |
Vapor HTTP server |
|
Router |
|
Middleware |
Types conforming to |
Handler |
Your closure or controller method. Returns anything that is |
One subtlety separates Vapor from some other frameworks: matching the route and running middleware are not two
separate passes. DefaultResponder looks up the route first and, at start-up, has already wrapped every route’s
responder in the global middleware chain (middleware.makeResponder(chainingTo: route.responder)), so a request that
matches nothing is also sent through the same middleware, chained to a "not found" responder. That is why
ErrorMiddleware can turn an unknown URL into a JSON 404 response and why a custom logging middleware sees unmatched
requests too.
The request lifecycle
The sequence below follows one GET /books/9780441013593 request through the Bookshelf service. Every other core
page has a short "Where it runs in the request" section that places its topic on this diagram:
-
Accept. The server
Channelaccepts a connection. NIO assigns it to oneEventLoopofapp.eventLoopGroupfor its whole lifetime, and Vapor’s child-channel initializer builds the pipeline shown above. -
Decode. The pipeline parses TLS, HTTP and compression and
HTTPServerRequestDecodercreates aRequest, whose body is either collected (up toapp.routes.defaultMaxBodySize, 16 KB by default) or streamed, depending on the route’sbody:strategy. -
Hand-off.
HTTPServerHandlercalls theResponderstored inapp.responder, by defaultDefaultResponder. -
Route lookup. The
TrieRoutermatches the method and path. Matched:nameparameters are stored inreq.parameters; the matchedRouteis stored onreq.route. -
Middleware. The request goes through the default stack,
RouteLoggingMiddleware(which logs the method and path) and thenErrorMiddleware, then the middleware added withapp.middleware.use(_:), then middleware from route groups — the order in which they were registered. Any of them may short-circuit and return a response, or throw. -
Handler. Your
async throwscode decodes input (req.content,req.query,req.parameters), does its work and returns a value. The value is turned into aResponsebyencodeResponse(for:). -
Response path. The response travels back through the middleware in reverse order. If anything threw,
ErrorMiddlewareconverts the error (anAbort, aDebuggableError, or any other error) into a response with a status code and, in development, a reason. -
Encode and write.
HTTPServerResponseEncoderserialises the status, headers and body; the compressor and the HTTP codec turn them into bytes; the keep-alive connection waits for the next request.
Seeing the middleware order
The following application registers two global middleware and one group middleware, and logs a line on the way in and the way out of each. The group middleware, as the Vapor documentation states, always runs after the application-wide middleware:
import Vapor
/// Writes one line to the log on the way in and one on the way out.
struct TraceMiddleware: AsyncMiddleware {
let name: String
func respond(to request: Request, chainingTo next: any AsyncResponder) async throws -> Response {
request.logger.info("-> \(name)")
let response = try await next.respond(to: request)
request.logger.info("<- \(name)")
return response
}
}
struct Book: Content {
let isbn: String
let title: String
}
func configure(_ app: Application) async throws {
// The default stack is RouteLoggingMiddleware, then ErrorMiddleware. Ours are appended after them: A, then B.
app.middleware.use(TraceMiddleware(name: "A (app)"))
app.middleware.use(TraceMiddleware(name: "B (app)"))
try routes(app)
}
func routes(_ app: Application) throws {
app.get("health") { _ in "ok" }
// Group middleware always runs after the application-wide middleware.
let books = app.grouped("books").grouped(TraceMiddleware(name: "C (group)"))
books.get(":isbn") { req async throws -> Book in
req.logger.info("handler for \(req.url.path)")
let isbn = try req.parameters.require("isbn")
return Book(isbn: isbn, title: "Dune")
}
}
Source: Vapor docs: Middleware, order
Running the server and calling curl http://127.0.0.1:8080/books/9780441013593 logs, in this order (the default
RouteLoggingMiddleware also logs a GET /books/... line first; the request-id metadata suffix and the lines of
other requests are trimmed):
-> A (app)
-> B (app)
-> C (group)
handler for /books/9780441013593
<- C (group)
<- B (app)
<- A (app)
The response body is {"isbn":"9780441013593","title":"Dune"}. Calling an unknown URL such as /nope runs -> A
and -> B but logs no <- lines, because the not-found responder throws Abort(.notFound) and the exception
unwinds the chain until ErrorMiddleware turns it into the response {"reason":"Not Found","error":true} with status
404. The order A -> B -> C -> handler -> C -> B -> A is exactly the one the Vapor documentation draws. To run a
middleware before the default ErrorMiddleware — as the CORS middleware must, so that error responses carry CORS
headers — register it with app.middleware.use(cors, at: .beginning).
Where it runs in the request
This page is the map the other pages refer to: NIO accepts the connection and builds the pipeline, the HTTP server
creates a Request, DefaultResponder matches a route, middleware runs around it, the handler runs, and the response
travels back out the same way. The later pages each pin their topic to one of those steps; for example, content decoding
(Content and Codable) and validation
(Validation) belong to the handler step, authentication
(Authentication) to the middleware step
(Middleware), and Fluent (Fluent
Overview and Database Drivers) to work done inside the handler.
The package map
Vapor is not one repository. The Vapor product lives in vapor/vapor and depends on several small packages that
you can also use on their own. The table lists the ones this section uses, with the version tags checked on
2026-10-17 (the Vapor 4 line):
| Package | Latest tag | Role |
|---|---|---|
|
4.122.2 |
The framework: |
|
4.9.3 |
The trie-based router and |
|
4.x (5.0.0-beta.2 for Vapor 5) |
Command-line interface: |
|
4.7.1 |
|
|
2.16.2 |
WebSocket client and server on |
|
1.22.0 |
Connection pools and |
|
4.13.0 and 1.57.0 |
The ORM: models, queries, relations, migrations ( |
|
2.14.0, 4.9.0, 4.8.0, 1.4.0 |
Database drivers for Fluent (MongoDB for the Mongo driver) |
|
3.36.0 |
A database-agnostic SQL query builder and serializer used by the SQL drivers ( |
|
4.5.2 and 1.14.3 |
The Leaf templating language ( |
|
4.14.0 |
Redis client ( |
|
1.18.0 and 1.1.2 |
Background jobs and scheduled jobs (Queues & Background Jobs, Scheduled Jobs) |
|
5.1.2 and 5.7.1 |
JSON Web Token signing and verification (JSON Web Tokens (JWT)) |
|
5.1.0 (APNSwift 7.1.0) |
Apple Push Notification service client (APNs Push Notifications) |
The pages that follow add each dependency to a Package.swift with a pinned from: version. The Vapor documentation
only has a section for the packages it maintains; the Toolbox (vapor new) is a separate tool, version 20.0.2, covered
on the Installation and Toolchain page (the first project is created
on Getting Started).
The Swift Server Workgroup packages Vapor builds on
Vapor deliberately does not invent logging, metrics, tracing, HTTP-client or cryptography APIs. It depends on the shared packages promoted by the Swift Server Workgroup (SSWG) and Apple, so that a library written against them works with Vapor, Hummingbird or a plain NIO service. From the dependency list of Vapor 4.122.2:
| Package | Used by Vapor for |
|---|---|
|
The |
|
The |
|
The |
|
|
|
SHA-2, HMAC, AES and random bytes, a portable |
|
The networking base: pipelines, TLS, HTTP/2 and compression handlers. |
|
Collection algorithms used internally. |
The versions in parentheses are the latest tags of each package; Vapor’s Package.swift states only minimum versions
(swift-log from 1.8.0, swift-metrics from 2.5.0, AsyncHTTPClient from 1.24.0, swift-crypto in 1.0.0..<5.0.0),
and SwiftPM resolves the newest compatible ones. Later pages show how to bootstrap the logging
(Logging), metrics and tracing
(Tracing, Metrics & OpenTelemetry) backends.
Vapor, Hummingbird and the older frameworks
Vapor’s main contemporary is Hummingbird (version 2.28.0 at the time of writing), a lighter framework that is also
based on SwiftNIO, Swift concurrency and the SSWG packages. In one paragraph: Hummingbird is modular and minimal — the
core is a router, middleware and a server, and you add persistence, templating and authentication from separate packages — and its current version 2 is built on async/await; Vapor is batteries-included, with its own ORM, template
language, queue system, command framework and a much larger community and documentation. Choose Vapor when you want an
integrated stack and the largest amount of tutorials and third-party packages; look at Hummingbird for small services or
when you want to assemble the pieces yourself. A feature-by-feature comparison, with migration notes, is in the
Ecosystem and Framework Comparison page.
Kitura (IBM) and Perfect (PerfectlySoft) were the other early Swift server frameworks, from the Swift 2 and 3 era. Both remain on GitHub, but their repositories show no recent development (the last code commits on the default branches are from 2022 for Kitura and February 2021 for Perfect), they target old Swift versions and they do not take advantage of SwiftNIO-native async APIs in the way Vapor and Hummingbird do. Treat them as history: books from that era (Yeung’s, for instance, compares all three) explain why Vapor won, but you should not start a new project on either.
The running example: Bookshelf
Every page uses the same domain, the original Bookshelf application shared by the other backend sections of this documentation: a library of books and authors, readers who borrow them, reviews, and notifications for due dates.
The service is a single Swift package with one executable target. Requests enter through the NIO HTTP server, the
middleware and router select a controller, controllers use Fluent models and plain Swift services, and slow side
effects — emails, push notifications, overdue checks — are enqueued and processed by a second process started from the
same binary with the queues command. The next pages build it up step by step.
The complete mind-map of this section follows; use it to find where each topic is covered:
Common errors and pitfalls
-
A request hangs under light load;
topshows idle CPUs. An event loop is blocked. Look forThread.sleep, synchronous disk or network calls,semaphore.wait()or.wait()in handlers, and move them as shown above. -
A handler that mutates a captured
varand compiles with only a warning. Route closures are declared@Sendable, but Vapor 4.122’sRoutesBuildermethods are marked@preconcurrency, so even in the Swift 6 language mode the compiler reports a handler that captures and mutates a localvaras a warning ("mutation of captured var in concurrently-executing code"), not an error. The data race is still real, because handlers run concurrently on many event loops. Keep shared state in anactor, aSendabletype with a lock, or the storage of theApplication, and read it fromreq.application. -
Log line
Application.shutdown() was not called before Application deinitialized.AnApplicationmust be shut down before it is deallocated: calltry await app.asyncShutdown()on every exit path ofmain, as the template’s entrypoint does. Otherwise Vapor logs this error fromdeinitand falls back to a blocking shutdown. -
CORS headers are missing from error responses. The CORS middleware was registered after
ErrorMiddleware; useapp.middleware.use(cors, at: .beginning). -
404instead of the response you expected, for every route. Theroutes(_:)function was never called fromconfigure(_:), or the route’s path components do not match; run theroutescommand (swift run App routes) to print the registered table. -
Code from a Vapor 3 tutorial does not compile.
Future<T>,Container,ServiceandRouterwere replaced in Vapor 4; see the upgrading guide linked in the references. -
.wait()crashes in a handler. NIO refuses to block an event loop’s thread with.wait(); the fix is theasyncoverload or.get(). -
Works on macOS, fails on Linux. A Foundation or Apple-only API was used; build in Docker (
swift:6.ximage) early, and see the Installation and Toolchain page for the supported Linux distributions (images and deployment: Deployment with Docker).
Version notes
-
Vapor 3 to 4. A rewrite:
ApplicationandRequestnow carry the services;EventLoopFuturereplaces the Vapor-specificFuture; Fluent models use property wrappers (@ID,@Field);Content(aCodablespecialisation) describes request and response bodies. -
Vapor 4.50 and later. 4.50.0 (2021-10-26) added
async/awaitAPIs forClient,RoutesBuilder,ResponseEncodable,Responder,Middleware(AsyncMiddleware),AsyncPasswordHasher,Cacheand views; more followed in later releases.Application.make(anasyncfactory) andapp.asyncShutdown()are what the current template’s@main enum Entrypointuses. In 4.122.2Application,Requestand theMiddlewareprotocol areSendable. -
Vapor 5 (beta). Visible on the
mainbranch and in5.0.0-beta.4: tools version 6.4 and Apple platform minimums of 26.2, optional SwiftPM traits (WebSockets,HTTPClient,Multipart,MacroRouting),swift-configurationfor settings, macro-based routing, a server abstraction replacing the NIO pipeline above, NIO types removed from the publicResponseAPI and aRequestoverhaul, plus ConsoleKit 5 and RoutingKit 5 betas (details on the What’s New, Upgrading and Vapor 5 page). The page notes where these matter; examples target the stable 4.x line.
Source: Vapor docs: Upgrading and vapor/vapor 5.0.0-beta.4
Where the books are outdated
The three books consulted for this section (Condon et al., Yeung and Patel) were written while Vapor 3 or early Vapor 4 was current. For the concepts on this page:
-
Condon et al. cover Vapor’s team and history and the HTTP basics; if your edition predates Vapor 4.50 (October 2021), its handlers return
EventLoopFuture. The concepts (EventLoop, pipeline) are unchanged, the code needs theasyncrewrite shown here. -
Yeung’s chapter on server-side Swift compares Vapor with Kitura and Perfect as live alternatives; Kitura and Perfect are no longer actively developed, and Vapor 3’s
FutureandContainerare gone in Vapor 4. -
Patel’s early chapter builds a raw-socket HTTP server and a SwiftPM library to explain how servers work; the idea is sound, but today you would reach for NIO’s
ServerBootstrap(as above) or Vapor rather than hand-written sockets. -
None of them cover Swift 6 strict concurrency (
Sendableclosures),Application.make, or the Vapor 5 beta.