Vapor Integration
SwiftDataServer provides first-class integration with the Vapor web framework, including request-scoped contexts and convenient helper methods.
Setup
Import the VaporIntegration product and configure SwiftDataServer in your configure.swift:
import Vapor
import SwiftDataServer
import VaporIntegration
import PostgreSQLBackend
func configure(_ app: Application) async throws {
// Configure database backend
let backend = PostgreSQLBackend(
hostname: Environment.get("DB_HOST") ?? "localhost",
port: 5432,
username: Environment.get("DB_USER") ?? "postgres",
password: Environment.get("DB_PASS") ?? "password",
database: Environment.get("DB_NAME") ?? "myapp"
)
try await backend.connect()
// Create container with your models
let container = try ModelContainer(
for: User.self, Post.self,
configurations: ModelConfiguration(backend: backend)
)
// Register with Vapor
app.swiftData.use(container)
// Run migrations in development
if app.environment == .development {
try await backend.migrate(schema: container.schema)
}
}
Using in Routes
Access the model context from any request handler:
func routes(_ app: Application) throws {
// Get all users
app.get("users") { req async throws -> [User] in
try await req.fetchAll(User.self)
}
// Get user by ID
app.get("users", ":id") { req async throws -> User in
guard let id = req.parameters.get("id", as: UUID.self) else {
throw Abort(.badRequest)
}
return try await req.fetchOrAbort(User.self, id: id)
}
// Create user
app.post("users") { req async throws -> User in
let input = try req.content.decode(CreateUserInput.self)
let user = User(name: input.name, email: input.email, age: input.age)
return try await req.create(user)
}
// Update user
app.put("users", ":id") { req async throws -> User in
guard let id = req.parameters.get("id", as: UUID.self) else {
throw Abort(.badRequest)
}
let input = try req.content.decode(UpdateUserInput.self)
return try await req.update(User.self, id: id) { user in
user.name = input.name
user.email = input.email
}
}
// Delete user
app.delete("users", ":id") { req async throws -> HTTPStatus in
guard let id = req.parameters.get("id", as: UUID.self) else {
throw Abort(.badRequest)
}
try await req.delete(User.self, id: id)
return .noContent
}
}
Request Extension Methods
| Method | Description |
|---|---|
req.modelContext |
Get the request-scoped ModelContext |
req.fetchAll(_:) |
Fetch all models of a type |
req.fetch(_:id:) |
Fetch a model by ID (returns optional) |
req.fetchOrAbort(_:id:) |
Fetch a model by ID or throw 404 |
req.create(_:) |
Insert and save a new model |
req.update(_:id:_:) |
Fetch, modify, and save a model |
req.delete(_:id:) |
Delete a model by ID |
req.fetch(_:where:) |
Fetch models matching a predicate |
Custom Queries
For more complex queries, access the context directly:
app.get("users", "search") { req async throws -> [User] in
let query = try req.query.get(String.self, at: "q")
let context = try req.modelContext
let predicate = #Predicate<User> { user in
user.name.contains(query) || user.email.contains(query)
}
let descriptor = FetchDescriptor<User>(predicate: predicate)
.sorted(by: SortDescriptor(propertyName: "name"))
.limit(50)
return try await context.fetchAsync(descriptor)
}
Pagination
app.get("users") { req async throws -> PaginatedResponse<User> in
let page = try req.query.get(Int.self, at: "page") ?? 1
let pageSize = try req.query.get(Int.self, at: "pageSize") ?? 20
let context = try req.modelContext
let total = try await context.fetchCountAsync(FetchDescriptor<User>())
let users = try await context.fetchAsync(
FetchDescriptor<User>.all.paginated(page: page, pageSize: pageSize)
)
return PaginatedResponse(
items: users,
page: page,
pageSize: pageSize,
total: total,
totalPages: (total + pageSize - 1) / pageSize
)
}
Next Steps
- Defining Models - Learn the @Model macro in depth
- Predicates - Build complex queries
- PostgreSQL - Configure PostgreSQL for production