SuperAPI AI Directory

vapor-swift

Coding & development

View repository

Updated 10-15 20:32

About this skill

Vapor (Swift)

Treat Vapor as a typed and concurrent Swift backend. Absolute preference: Vapor 4 + async/await + async Fluent. Do not write new code with EventLoopFuture except for interoperability with legacy libs.

Stack and scope

Ask Role
Vapor HTTP, routing, middleware, content, errors
Fluent ORM, models, migrations, queries
Fluent drivers Postgres (preferred), MySQL, SQLite
JWT / sessions Auth API or web
Leaf HTML templates when the app serves pages
XCTVapor HTTP integration tests

Preserve the structure and libs already adopted by the project. In new apps, organize by domain.

Recommended organization

Sources/App/
  configure.swift      # DB, middleware global, migrations, services
  routes.swift         # apenas app.register(collection:)
  Controllers/         # RouteCollection por recurso/domínio
  Models/              # Fluent models + migrations
  DTOs/                # request/response Codable (não exponha Model cru)
  Middleware/
  Services/            # regras de negócio / integrações
  Utilities/
Tests/AppTests/
  • Controllers implement RouteCollection and have a prefix, middleware, and handlers.
  • routes.swift stays declarative: it only registers collections.
  • Global middleware (CORS, logging, correlation id, error mapping) in configure.swift.
  • Auth/authorization middleware stays in the route group.

Non-negotiable rules

  • Handlers: func ...(req: Request) async throws -> T where T: AsyncResponseEncodable.
  • Decode with req.content.decode / req.query.decode; never trust a raw body without a type.
  • Validate input at the edge; fail with Abort and correct HTTP statuses.
  • Authn ≠ Authz: authenticating does not authorize ownership/role.
  • Do not expose Model Fluent directly if there are internal fields; use DTOs.
  • Versioned migrations; never change a migration already applied in production — create another one.
  • A typed Application : configure secrets via env, do not hardcode.
  • Linux is the production target; mentally test portability (paths, Foundation, C libs).

Routing and controllers

struct TodosController: RouteCollection {
    func boot(routes: any RoutesBuilder) throws {
        let todos = routes.grouped("api", "v1", "todos")
        todos.get(use: index)
        todos.post(use: create)

        let protected = todos.grouped(UserToken.authenticator(), User.guardMiddleware())
        protected.group(":id") { todo in
            todo.get(use: show)
            todo.patch(use: update)
            todo.delete(use: delete)
        }
    }
}
  • Path params: req.parameters.get("id", as: UUID.self).
  • Query: decode into a dedicated struct Content (filters, pagination).
  • Group by version (api/v1) when the API is public or multi-client.
  • Access layers: public → authenticated → admin, each adding middleware.

Content, validation, and errors

  • Request/response: explicit structs Content / Codable .
  • Validation: Validatable + validations(_:) or clear manual validation before persisting.
  • Errors:
    • Abort(.badRequest) / .unauthorized / .forbidden / .notFound / .conflict
    • Safe messages for the client; internal details only in logs
    • Custom error middleware if the product's JSON contract requires { code, message }
  • Do not leak stack traces in production (app.environment).

Fluent

  • Models: final class / @unchecked Sendable according to the project's pattern; fields @Field, @ID, relations @Parent/@Children/@Siblings.
  • Always try await in async queries.
  • Avoid N+1: with(\.$relation) / conscious eager loading.
  • Indexes and uniques in migrations for hot paths and real constraints.
  • Transactions: req.db.transaction { db in ... } for composite writes.
  • Soft delete only with a consistent filter in all reads.
  • Multi-tenant: scope always by tenantId/userId in the query; never trust the client alone.
guard let todo = try await Todo.find(id, on: req.db) else {
    throw Abort(.notFound)
}

Authentication and authorization

  • API: Bearer JWT or opaque token in Authorization.
  • Web: cookies/sessions with secure flags when serving a browser.
  • Chain idempotent authenticators (cookie + bearer) and end with guardMiddleware().
  • Ownership policies in the handler/service: guard todo.$user.id == user.id else { throw Abort(.forbidden) }.
  • Password hashing with Bcrypt (or Vapor's current API); never store plaintext.
  • Rate limit on login and expensive endpoints when the risk exists.

Middleware

Implement AsyncMiddleware:

struct CorrelationIDMiddleware: AsyncMiddleware {
    func respond(to request: Request, chainingTo next: any AsyncResponder) async throws -> Response {
        let id = request.headers.first(name: "X-Request-Id") ?? UUID().uuidString
        request.logger[metadataKey: "request-id"] = .string(id)
        let response = try await next.respond(to: request)
        response.headers.replaceOrAdd(name: "X-Request-Id", value: id)
        return response
    }
}
  • CORS at the start of the chain when there is a separate frontend.
  • FileMiddleware only if serving statics on purpose.
  • Do not do heavy business I/O in global middleware.

Configuration and environment

In configure.swift:

  1. Decode typed env (DB URL, JWT secret, port).
  2. Register databases and migrations.
  3. Global middleware.
  4. Lifecycle / services in app.storage or via Application extensions.
  5. autoMigrate() only in development/test — production migrates in the deploy pipeline.

Tests

  • Use XCTVapor / testing project APIs for HTTP end-to-end.
  • Isolated test DB (in-memory SQLite or CI Postgres).
  • Cover: happy path, 401/403, 400 validation, 404, ownership.
  • Avoid tests that depend on global order without resetting state.
try await app.test(.GET, "api/v1/todos") { res async in
    XCTAssertEqual(res.status, .ok)
}

Performance and concurrency

  • Do not block the event loop with heavy synchronous work; use async and non-blocking APIs.
  • Types crossing concurrency boundaries must be Sendable when required.
  • Driver connection pool: adjust only with evidence.
  • Pagination with a maximum limit; reject limit abuse.
  • Structured logs with request id; no PII/secrets.

Deploy

  • Multi-stage Docker image (macOS/CI build → Linux slim runtime).
  • Health route (GET /health) for orchestrators.
  • Graceful shutdown; migrations before releasing traffic.
  • Secrets via env/secret manager.
  • Watch build time and image size; SPM cache in CI.

WebSockets and real time

  • Use when the domain requires real push; not for simple CRUD.
  • Authenticate on connection; revalidate authorization per message when necessary.
  • Backpressure and conscious timeouts; test reconnection on the client.

Integration with the rest of the toolkit

  • HTTP contracts: align with api-design.
  • Deep authz: authentication-authorization.
  • MVVM on the SwiftUI/iOS client: mvvm-architecture + swiftui-development.
  • Go-live: production-readiness.

Anti-patterns

  • Entire business logic in routes.swift
  • EventLoopFuture in new code without necessity
  • Return Fluent Model with password hash / tokens
  • Migration edited after deploy
  • Auth only on the iOS/web client
  • try! / force unwrap in request paths
  • SQLite in multi-instance production without a plan
  • Ignore decode failures with dangerously silent defaults

Flow when implementing a feature

  1. Inspect Package.swift, configure.swift and existing controllers.
  2. Model DTO + (if needed) Fluent model/migration.
  3. Service with the business rule.
  4. Controller RouteCollection with correct authz and status.
  5. XCTVapor tests for risk cases.
  6. Update migrations/env/docs only for what changed.

Completion criteria

  • Routes registered via RouteCollection, typed and versioned when applicable
  • Handlers 100% async/await in new code
  • Input validated; correct HTTP errors
  • Authn/authz on the server with ownership covered
  • DTOs without leaking internal fields
  • Migration added (not rewritten) if the schema changed
  • Tests of critical flows passing
  • Config via env; ready for Linux/Docker if the scope includes deploy