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