What it is
Vapor is the leading server-side Swift web framework, built on SwiftNIO with routing, middleware, an ORM and authentication.
Routes are closures registered on the Application. Content conformance handles decoding and encoding, and Fluent provides the ORM layer.
Installation
.package(url: "https://github.com/vapor/vapor.git", from: "4.106.0")Getting started
The smallest useful thing you can do with it, and what each part means.
swift
struct CreateBook: Content, Validatable {
let title: String
let year: Int
static func validations(_ v: inout Validations) {
v.add("title", as: String.self, is: !.empty && .count(...200))
v.add("year", as: Int.self, is: .range(1400...2100))
}
}
func routes(_ app: Application) throws {
app.get("books", ":id") { req async throws -> Book in
guard let id = req.parameters.get("id", as: UUID.self) else {
throw Abort(.badRequest, reason: "invalid id")
}
guard let book = try await Book.find(id, on: req.db) else {
throw Abort(.notFound)
}
return book
}
app.post("books") { req async throws -> Response in
try CreateBook.validate(content: req)
let input = try req.content.decode(CreateBook.self)
let book = Book(title: input.title, year: input.year)
try await book.save(on: req.db)
return try await book.encodeResponse(status: .created, for: req)
}
}Advanced usage
Where the library earns its place over a simpler alternative.
swift
struct APIKeyMiddleware: AsyncMiddleware {
func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
guard request.headers.first(name: "X-API-Key") == expectedKey else {
throw Abort(.unauthorized)
}
return try await next.respond(to: request)
}
}
let protected = app.grouped(APIKeyMiddleware())
protected.get("admin", "stats") { req in try await Stats.current(on: req.db) }
struct CreateBookTable: AsyncMigration {
func prepare(on database: Database) async throws {
try await database.schema("books")
.id()
.field("title", .string, .required)
.field("year", .int, .required)
.unique(on: "title", "year")
.create()
}
func revert(on database: Database) async throws {
try await database.schema("books").delete()
}
}Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- Value of type Request has no member db
- Fluent is not configured. Add the driver package and call app.databases.use in configure.
- The server blocks under load
- Synchronous work on the event loop. Move blocking calls to a thread pool or make them async.
Best practices
- Use route groups for authentication so protection is the default for a section.
- Write both prepare and revert in migrations; one you cannot roll back is a liability.
- Use async/await routes rather than the older EventLoopFuture API.
- Be realistic about ecosystem size — choose Vapor when the team is already Swift.
Background
Why it exists, and what it was reacting to.
Vapor makes it practical to write a back end in Swift, which appeals to iOS teams wanting one language across client and server. It is mature, though its ecosystem is far smaller than Node's or Java's.
