vapor

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Vapor Framework Guide

Vapor框架指南

Applies to: Vapor 4.x, Swift 5.9+, Fluent ORM, Leaf Templates, Server-Side Swift Language Guide: @.claude/skills/swift-guide/SKILL.md
适用版本:Vapor 4.x, Swift 5.9+, Fluent ORM, Leaf Templates, 服务端Swift 语言指南:@.claude/skills/swift-guide/SKILL.md

Overview

概述

Vapor is a server-side Swift framework for building web applications, REST APIs, and backend services. It provides type-safe routing, Fluent ORM, async/await concurrency, JWT authentication, and Leaf templating.
Use Vapor when:
  • Building Swift-native backend services
  • Sharing code between iOS/macOS clients and the server
  • You need type-safe, compile-time-checked API development
  • You want async/await patterns throughout the stack
Consider alternatives when:
  • Team lacks Swift experience
  • You need a massive middleware ecosystem (consider Express, Rails)
  • Maximum raw performance is critical (consider Rust/Actix-web)
Vapor是用于构建Web应用、REST API和后端服务的服务端Swift框架,提供类型安全路由、Fluent ORM、async/await并发能力、JWT认证、Leaf模板引擎等特性。
以下场景推荐使用Vapor:
  • 构建Swift原生后端服务
  • 实现iOS/macOS客户端与服务端的代码共享
  • 需要类型安全、编译时校验的API开发能力
  • 希望在全栈使用async/await编程模式
以下场景建议考虑替代方案:
  • 团队缺乏Swift开发经验
  • 需要庞大的中间件生态(可考虑Express、Rails)
  • 极致的原生性能是核心需求(可考虑Rust/Actix-web)

Guardrails

开发规范

Vapor-Specific Rules

Vapor专属规则

  • Use the
    @main
    entry point pattern with
    Application.make
  • Group routes by resource with
    RouteCollection
    controllers
  • Use Fluent property wrappers (
    @ID
    ,
    @Field
    ,
    @Parent
    ,
    @Children
    ) for models
  • Use
    Content
    protocol for all request/response DTOs
  • Use
    Validatable
    protocol for input validation on every endpoint
  • Use
    AsyncMiddleware
    for cross-cutting concerns (auth, logging, CORS)
  • Use
    AsyncMigration
    with both
    prepare
    and
    revert
    methods
  • Configure databases from environment variables (never hardcode credentials)
  • Use DTOs to separate API contracts from database models
  • Implement pagination for all list endpoints
  • Use
    @Sendable
    on all route handler closures
  • Mark model classes as
    @unchecked Sendable
    (Fluent requirement)
  • 搭配
    Application.make
    使用
    @main
    入口点模式
  • 使用
    RouteCollection
    控制器按资源对路由进行分组
  • 模型层使用Fluent属性包装器(
    @ID
    @Field
    @Parent
    @Children
  • 所有请求/响应DTO都遵循
    Content
    协议
  • 每个端点的输入校验都使用
    Validatable
    协议实现
  • 横切关注点(认证、日志、CORS)使用
    AsyncMiddleware
    实现
  • 迁移文件使用
    AsyncMigration
    ,同时实现
    prepare
    revert
    方法
  • 从环境变量读取数据库配置(禁止硬编码凭证)
  • 使用DTO分离API契约与数据库模型
  • 所有列表类型的端点都实现分页能力
  • 所有路由处理闭包都使用
    @Sendable
    标记
  • 模型类标记为
    @unchecked Sendable
    (Fluent要求)

Anti-Patterns

反模式

  • Do not expose Fluent models directly as API responses (use DTOs)
  • Do not put business logic in controllers (use a service layer)
  • Do not use
    autoMigrate
    in production (run migrations explicitly)
  • Do not skip
    revert
    in migrations (always provide rollback)
  • Do not use
    try!
    or
    fatalError
    in request handlers
  • Do not store request-scoped state in global variables
  • 禁止直接将Fluent模型作为API响应返回(应使用DTO)
  • 禁止在控制器中编写业务逻辑(应使用服务层)
  • 生产环境禁止使用
    autoMigrate
    (需显式执行迁移)
  • 迁移文件禁止省略
    revert
    实现(必须提供回滚能力)
  • 请求处理函数中禁止使用
    try!
    fatalError
  • 禁止在全局变量中存储请求作用域的状态

Project Structure

项目结构

MyVaporApp/
├── Package.swift
├── Sources/
│   └── App/
│       ├── Controllers/        # RouteCollection implementations
│       │   ├── UserController.swift
│       │   └── AuthController.swift
│       ├── Models/             # Fluent models
│       │   ├── User.swift
│       │   └── Post.swift
│       ├── DTOs/               # Request/response types (Content + Validatable)
│       │   ├── UserDTO.swift
│       │   └── CreateUserRequest.swift
│       ├── Migrations/         # AsyncMigration implementations
│       │   ├── CreateUser.swift
│       │   └── CreatePost.swift
│       ├── Middleware/         # AsyncMiddleware implementations
│       │   ├── JWTAuthMiddleware.swift
│       │   └── AppErrorMiddleware.swift
│       ├── Services/          # Business logic (protocol + implementation)
│       │   ├── UserService.swift
│       │   └── EmailService.swift
│       ├── Extensions/
│       │   └── Request+Extensions.swift
│       ├── configure.swift    # Database, middleware, JWT, Leaf setup
│       ├── routes.swift       # Top-level route registration
│       └── entrypoint.swift   # @main entry point
├── Tests/
│   └── AppTests/
│       ├── UserControllerTests.swift
│       └── AuthControllerTests.swift
├── Resources/
│   └── Views/                 # Leaf templates
│       └── index.leaf
├── Public/                    # Static files
│   ├── css/
│   └── js/
└── docker-compose.yml
Layer responsibilities:
  • Controllers/
    -- HTTP routing only: parse request, call service, write response
  • Services/
    -- Business logic, orchestration, domain rules
  • Models/
    -- Fluent database models with property wrappers
  • DTOs/
    -- Request/response types with validation (
    Content
    +
    Validatable
    )
  • Migrations/
    -- Schema changes with
    prepare
    and
    revert
  • Middleware/
    -- Cross-cutting: auth, error handling, CORS, logging
MyVaporApp/
├── Package.swift
├── Sources/
│   └── App/
│       ├── Controllers/        # RouteCollection实现
│       │   ├── UserController.swift
│       │   └── AuthController.swift
│       ├── Models/             # Fluent模型
│       │   ├── User.swift
│       │   └── Post.swift
│       ├── DTOs/               # 请求/响应类型(遵循Content + Validatable协议)
│       │   ├── UserDTO.swift
│       │   └── CreateUserRequest.swift
│       ├── Migrations/         # AsyncMigration实现
│       │   ├── CreateUser.swift
│       │   └── CreatePost.swift
│       ├── Middleware/         # AsyncMiddleware实现
│       │   ├── JWTAuthMiddleware.swift
│       │   └── AppErrorMiddleware.swift
│       ├── Services/          # 业务逻辑(协议+实现)
│       │   ├── UserService.swift
│       │   └── EmailService.swift
│       ├── Extensions/
│       │   └── Request+Extensions.swift
│       ├── configure.swift    # 数据库、中间件、JWT、Leaf配置
│       ├── routes.swift       # 顶层路由注册
│       └── entrypoint.swift   # @main入口点
├── Tests/
│   └── AppTests/
│       ├── UserControllerTests.swift
│       └── AuthControllerTests.swift
├── Resources/
│   └── Views/                 # Leaf模板
│       └── index.leaf
├── Public/                    # 静态资源文件
│   ├── css/
│   └── js/
└── docker-compose.yml
各层职责:
  • Controllers/
    -- 仅处理HTTP路由:解析请求、调用服务、返回响应
  • Services/
    -- 业务逻辑、流程编排、领域规则实现
  • Models/
    -- 带属性包装器的Fluent数据库模型
  • DTOs/
    -- 带校验能力的请求/响应类型(遵循
    Content
    +
    Validatable
    协议)
  • Migrations/
    -- 包含
    prepare
    revert
    实现的 schema 变更文件
  • Middleware/
    -- 横切逻辑:认证、错误处理、CORS、日志

Application Setup

应用初始化

Entry Point and Configuration

入口点与配置

swift
// entrypoint.swift
import Vapor
import Logging

@main
enum Entrypoint {
    static func main() async throws {
        var env = try Environment.detect()
        try LoggingSystem.bootstrap(from: &env)
        let app = try await Application.make(env)
        do {
            try await configure(app)
            try await app.execute()
        } catch {
            app.logger.report(error: error)
            try? await app.asyncShutdown()
            throw error
        }
    }
}

// configure.swift -- database, middleware, migrations, routes
func configure(_ app: Application) async throws {
    app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
    app.middleware.use(AppErrorMiddleware())
    app.middleware.use(CORSMiddleware())

    // Database (always from environment variables)
    if let databaseURL = Environment.get("DATABASE_URL") {
        try app.databases.use(.postgres(url: databaseURL), as: .psql)
    } else {
        app.databases.use(.postgres(
            hostname: Environment.get("DB_HOST") ?? "localhost",
            port: Environment.get("DB_PORT").flatMap(Int.init) ?? 5432,
            username: Environment.get("DB_USER") ?? "vapor",
            password: Environment.get("DB_PASSWORD") ?? "vapor",
            database: Environment.get("DB_NAME") ?? "vapor_dev"
        ), as: .psql)
    }

    app.migrations.add(CreateUser())
    if app.environment == .development { try await app.autoMigrate() }
    try routes(app)
}

// routes.swift -- group public vs protected
func routes(_ app: Application) throws {
    app.get("health") { _ -> HTTPStatus in .ok }
    let api = app.grouped("api", "v1")
    try api.register(collection: AuthController())

    let protected = api.grouped(JWTAuthMiddleware())
    try protected.register(collection: UserController())
}
swift
// entrypoint.swift
import Vapor
import Logging

@main
enum Entrypoint {
    static func main() async throws {
        var env = try Environment.detect()
        try LoggingSystem.bootstrap(from: &env)
        let app = try await Application.make(env)
        do {
            try await configure(app)
            try await app.execute()
        } catch {
            app.logger.report(error: error)
            try? await app.asyncShutdown()
            throw error
        }
    }
}

// configure.swift -- 数据库、中间件、迁移、路由配置
func configure(_ app: Application) async throws {
    app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
    app.middleware.use(AppErrorMiddleware())
    app.middleware.use(CORSMiddleware())

    // 数据库配置(始终从环境变量读取)
    if let databaseURL = Environment.get("DATABASE_URL") {
        try app.databases.use(.postgres(url: databaseURL), as: .psql)
    } else {
        app.databases.use(.postgres(
            hostname: Environment.get("DB_HOST") ?? "localhost",
            port: Environment.get("DB_PORT").flatMap(Int.init) ?? 5432,
            username: Environment.get("DB_USER") ?? "vapor",
            password: Environment.get("DB_PASSWORD") ?? "vapor",
            database: Environment.get("DB_NAME") ?? "vapor_dev"
        ), as: .psql)
    }

    app.migrations.add(CreateUser())
    if app.environment == .development { try await app.autoMigrate() }
    try routes(app)
}

// routes.swift -- 公开与受保护路由分组
func routes(_ app: Application) throws {
    app.get("health") { _ -> HTTPStatus in .ok }
    let api = app.grouped("api", "v1")
    try api.register(collection: AuthController())

    let protected = api.grouped(JWTAuthMiddleware())
    try protected.register(collection: UserController())
}

Routing with Controllers

控制器路由实现

swift
struct UserController: RouteCollection {
    func boot(routes: RoutesBuilder) throws {
        let users = routes.grouped("users")
        users.get(use: index)
        users.get(":userID", use: show)
        users.put(":userID", use: update)
        users.delete(":userID", use: delete)
    }

    @Sendable
    func index(req: Request) async throws -> PaginatedResponse<UserResponse> {
        let page = try req.query.decode(PageRequest.self)
        let result = try await User.query(on: req.db)
            .filter(\.$isActive == true)
            .sort(\.$createdAt, .descending)
            .paginate(PageRequest(page: page.page, per: page.per))
        let items = try result.items.map { try UserResponse(user: $0) }
        return PaginatedResponse(items: items, metadata: PageMetadata(
            page: page.page, perPage: page.per,
            total: result.metadata.total, totalPages: result.metadata.pageCount
        ))
    }

    @Sendable
    func show(req: Request) async throws -> UserResponse {
        guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else {
            throw Abort(.notFound, reason: "User not found")
        }
        return try UserResponse(user: user)
    }
}
Conventions: Implement
RouteCollection
per resource. Use
@Sendable
on all handlers. Validate before processing. Return DTOs, not Fluent models.
swift
struct UserController: RouteCollection {
    func boot(routes: RoutesBuilder) throws {
        let users = routes.grouped("users")
        users.get(use: index)
        users.get(":userID", use: show)
        users.put(":userID", use: update)
        users.delete(":userID", use: delete)
    }

    @Sendable
    func index(req: Request) async throws -> PaginatedResponse<UserResponse> {
        let page = try req.query.decode(PageRequest.self)
        let result = try await User.query(on: req.db)
            .filter(\.$isActive == true)
            .sort(\.$createdAt, .descending)
            .paginate(PageRequest(page: page.page, per: page.per))
        let items = try result.items.map { try UserResponse(user: $0) }
        return PaginatedResponse(items: items, metadata: PageMetadata(
            page: page.page, perPage: page.per,
            total: result.metadata.total, totalPages: result.metadata.pageCount
        ))
    }

    @Sendable
    func show(req: Request) async throws -> UserResponse {
        guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else {
            throw Abort(.notFound, reason: "User not found")
        }
        return try UserResponse(user: user)
    }
}
约定: 每个资源实现一个
RouteCollection
,所有处理函数添加
@Sendable
标记,处理前先做校验,返回DTO而非Fluent模型。

Fluent Models

Fluent模型

Model with Property Wrappers

带属性包装器的模型实现

swift
import Fluent
import Vapor

final class User: Model, Content, @unchecked Sendable {
    static let schema = "users"

    @ID(key: .id)
    var id: UUID?

    @Field(key: "email")
    var email: String

    @Field(key: "password_hash")
    var passwordHash: String

    @Enum(key: "role")
    var role: Role

    @Timestamp(key: "created_at", on: .create)
    var createdAt: Date?

    @Timestamp(key: "updated_at", on: .update)
    var updatedAt: Date?

    @Children(for: \.$user)
    var posts: [Post]

    init() {}

    init(id: UUID? = nil, email: String, passwordHash: String, role: Role = .user) {
        self.id = id
        self.email = email
        self.passwordHash = passwordHash
        self.role = role
    }

    enum Role: String, Codable, CaseIterable {
        case admin, user, guest
    }
}
Model conventions:
  • Always mark as
    final class
    conforming to
    Model
    ,
    Content
    ,
    @unchecked Sendable
  • Use
    @ID(key: .id)
    for UUID primary keys
  • Use
    @Timestamp
    for
    created_at
    and
    updated_at
  • Use
    @Parent
    /
    @Children
    /
    @Siblings
    for relationships
  • Provide an empty
    init()
    (Fluent requirement)
swift
import Fluent
import Vapor

final class User: Model, Content, @unchecked Sendable {
    static let schema = "users"

    @ID(key: .id)
    var id: UUID?

    @Field(key: "email")
    var email: String

    @Field(key: "password_hash")
    var passwordHash: String

    @Enum(key: "role")
    var role: Role

    @Timestamp(key: "created_at", on: .create)
    var createdAt: Date?

    @Timestamp(key: "updated_at", on: .update)
    var updatedAt: Date?

    @Children(for: \.$user)
    var posts: [Post]

    init() {}

    init(id: UUID? = nil, email: String, passwordHash: String, role: Role = .user) {
        self.id = id
        self.email = email
        self.passwordHash = passwordHash
        self.role = role
    }

    enum Role: String, Codable, CaseIterable {
        case admin, user, guest
    }
}
模型约定:
  • 始终声明为
    final class
    ,遵循
    Model
    Content
    @unchecked Sendable
    协议
  • UUID主键使用
    @ID(key: .id)
    声明
  • created_at
    updated_at
    字段使用
    @Timestamp
    声明
  • 关联关系使用
    @Parent
    /
    @Children
    /
    @Siblings
    声明
  • 提供空的
    init()
    方法(Fluent要求)

Migrations

迁移文件

swift
import Fluent

struct CreateUser: AsyncMigration {
    func prepare(on database: Database) async throws {
        let role = try await database.enum("user_role")
            .case("admin").case("user").case("guest")
            .create()

        try await database.schema("users")
            .id()
            .field("email", .string, .required)
            .field("password_hash", .string, .required)
            .field("role", role, .required)
            .field("created_at", .datetime)
            .field("updated_at", .datetime)
            .unique(on: "email")
            .create()
    }

    func revert(on database: Database) async throws {
        try await database.schema("users").delete()
        try await database.enum("user_role").delete()
    }
}
Migration rules:
  • Always implement both
    prepare
    and
    revert
  • Create enums before referencing them in schema
  • Delete enums in
    revert
    after deleting the table
  • Use
    .references()
    for foreign keys with
    onDelete
    behavior
  • Add indexes for frequently queried columns
swift
import Fluent

struct CreateUser: AsyncMigration {
    func prepare(on database: Database) async throws {
        let role = try await database.enum("user_role")
            .case("admin").case("user").case("guest")
            .create()

        try await database.schema("users")
            .id()
            .field("email", .string, .required)
            .field("password_hash", .string, .required)
            .field("role", role, .required)
            .field("created_at", .datetime)
            .field("updated_at", .datetime)
            .unique(on: "email")
            .create()
    }

    func revert(on database: Database) async throws {
        try await database.schema("users").delete()
        try await database.enum("user_role").delete()
    }
}
迁移规则:
  • 始终同时实现
    prepare
    revert
    方法
  • 在schema中引用枚举前先创建枚举
  • revert
    中先删除表再删除枚举
  • 外键使用
    .references()
    声明,并配置
    onDelete
    行为
  • 高频查询字段添加索引

DTOs and Validation

DTO与校验

Request/Response DTOs

请求/响应DTO实现

swift
import Vapor

struct CreateUserRequest: Content, Validatable {
    let email: String
    let password: String
    let name: String

    static func validations(_ validations: inout Validations) {
        validations.add("email", as: String.self, is: .email)
        validations.add("password", as: String.self, is: .count(8...))
        validations.add("name", as: String.self, is: !.empty)
    }
}

struct UserResponse: Content {
    let id: UUID
    let email: String
    let name: String
    let role: User.Role
    let createdAt: Date?

    init(user: User) throws {
        self.id = try user.requireID()
        self.email = user.email
        self.name = user.name
        self.role = user.role
        self.createdAt = user.createdAt
    }
}
DTO conventions:
  • Request types conform to
    Content
    +
    Validatable
  • Response types conform to
    Content
    only
  • Always validate in the controller before processing:
    try CreateUserRequest.validate(content: req)
  • Use
    Validatable
    rules:
    .email
    ,
    .count(range)
    ,
    !.empty
    ,
    .url
    ,
    .alphanumeric
swift
import Vapor

struct CreateUserRequest: Content, Validatable {
    let email: String
    let password: String
    let name: String

    static func validations(_ validations: inout Validations) {
        validations.add("email", as: String.self, is: .email)
        validations.add("password", as: String.self, is: .count(8...))
        validations.add("name", as: String.self, is: !.empty)
    }
}

struct UserResponse: Content {
    let id: UUID
    let email: String
    let name: String
    let role: User.Role
    let createdAt: Date?

    init(user: User) throws {
        self.id = try user.requireID()
        self.email = user.email
        self.name = user.name
        self.role = user.role
        self.createdAt = user.createdAt
    }
}
DTO约定:
  • 请求类型遵循
    Content
    +
    Validatable
    协议
  • 响应类型仅需遵循
    Content
    协议
  • 控制器处理前始终先做校验:
    try CreateUserRequest.validate(content: req)
  • 使用
    Validatable
    内置规则:
    .email
    .count(range)
    !.empty
    .url
    .alphanumeric

Middleware

中间件

Custom AsyncMiddleware

自定义AsyncMiddleware

swift
import Vapor
import JWT

struct JWTAuthMiddleware: AsyncMiddleware {
    func respond(
        to request: Request,
        chainingTo next: any AsyncResponder
    ) async throws -> Response {
        guard let token = request.headers.bearerAuthorization?.token else {
            throw Abort(.unauthorized, reason: "Missing authorization token")
        }

        let payload = try await request.jwt.verify(token, as: UserPayload.self)

        guard let userID = UUID(payload.subject.value),
              let user = try await User.find(userID, on: request.db),
              user.isActive else {
            throw Abort(.unauthorized, reason: "User not found or inactive")
        }

        request.auth.login(user)
        return try await next.respond(to: request)
    }
}
swift
import Vapor
import JWT

struct JWTAuthMiddleware: AsyncMiddleware {
    func respond(
        to request: Request,
        chainingTo next: any AsyncResponder
    ) async throws -> Response {
        guard let token = request.headers.bearerAuthorization?.token else {
            throw Abort(.unauthorized, reason: "Missing authorization token")
        }

        let payload = try await request.jwt.verify(token, as: UserPayload.self)

        guard let userID = UUID(payload.subject.value),
              let user = try await User.find(userID, on: request.db),
              user.isActive else {
            throw Abort(.unauthorized, reason: "User not found or inactive")
        }

        request.auth.login(user)
        return try await next.respond(to: request)
    }
}

Error Middleware

错误处理中间件

swift
import Vapor

struct AppErrorMiddleware: AsyncMiddleware {
    func respond(
        to request: Request,
        chainingTo next: any AsyncResponder
    ) async throws -> Response {
        do {
            return try await next.respond(to: request)
        } catch let abort as AbortError {
            let body = ErrorResponse(error: true, reason: abort.reason, code: abort.status.code)
            return try await body.encodeResponse(status: abort.status, for: request)
        } catch {
            request.logger.error("Unexpected error: \(error)")
            let reason = request.application.environment.isRelease
                ? "An internal error occurred" : error.localizedDescription
            let body = ErrorResponse(error: true, reason: reason, code: 500)
            return try await body.encodeResponse(status: .internalServerError, for: request)
        }
    }
}

struct ErrorResponse: Content {
    let error: Bool
    let reason: String
    let code: UInt
}
swift
import Vapor

struct AppErrorMiddleware: AsyncMiddleware {
    func respond(
        to request: Request,
        chainingTo next: any AsyncResponder
    ) async throws -> Response {
        do {
            return try await next.respond(to: request)
        } catch let abort as AbortError {
            let body = ErrorResponse(error: true, reason: abort.reason, code: abort.status.code)
            return try await body.encodeResponse(status: abort.status, for: request)
        } catch {
            request.logger.error("Unexpected error: \(error)")
            let reason = request.application.environment.isRelease
                ? "An internal error occurred" : error.localizedDescription
            let body = ErrorResponse(error: true, reason: reason, code: 500)
            return try await body.encodeResponse(status: .internalServerError, for: request)
        }
    }
}

struct ErrorResponse: Content {
    let error: Bool
    let reason: String
    let code: UInt
}

Content Negotiation

内容协商

Vapor's
Content
protocol handles JSON automatically. Configure custom encoding in
configure.swift
:
swift
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.dateEncodingStrategy = .iso8601
ContentConfiguration.global.use(encoder: encoder, for: .json)
Vapor的
Content
协议会自动处理JSON序列化/反序列化,可在
configure.swift
中配置自定义编码规则:
swift
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.dateEncodingStrategy = .iso8601
ContentConfiguration.global.use(encoder: encoder, for: .json)

Authentication (JWT)

认证(JWT)

swift
// configure.swift -- register signing key
guard let jwtSecret = Environment.get("JWT_SECRET") else {
    fatalError("JWT_SECRET environment variable not set")
}
await app.jwt.keys.add(hmac: HMACKey(from: jwtSecret), digestAlgorithm: .sha256)

// Payload definition
struct UserPayload: JWTPayload {
    var subject: SubjectClaim
    var expiration: ExpirationClaim
    var isAdmin: Bool?

    func verify(using algorithm: some JWTAlgorithm) throws {
        try expiration.verifyNotExpired()
    }
}

// Token generation (in login handler)
let payload = UserPayload(
    subject: .init(value: try user.requireID().uuidString),
    expiration: .init(value: Date().addingTimeInterval(3600))
)
let token = try await req.jwt.sign(payload)
swift
// configure.swift -- 注册签名密钥
guard let jwtSecret = Environment.get("JWT_SECRET") else {
    fatalError("JWT_SECRET environment variable not set")
}
await app.jwt.keys.add(hmac: HMACKey(from: jwtSecret), digestAlgorithm: .sha256)

// Payload定义
struct UserPayload: JWTPayload {
    var subject: SubjectClaim
    var expiration: ExpirationClaim
    var isAdmin: Bool?

    func verify(using algorithm: some JWTAlgorithm) throws {
        try expiration.verifyNotExpired()
    }
}

// 登录处理函数中生成Token
let payload = UserPayload(
    subject: .init(value: try user.requireID().uuidString),
    expiration: .init(value: Date().addingTimeInterval(3600))
)
let token = try await req.jwt.sign(payload)

Commands Reference

常用命令参考

bash
undefined
bash
undefined

Initialize project

初始化项目

swift package init --type executable --name MyVaporApp
swift package init --type executable --name MyVaporApp

Resolve dependencies

安装依赖

swift package resolve
swift package resolve

Build and run

构建并运行

swift build swift run App serve --hostname 0.0.0.0 --port 8080
swift build swift run App serve --hostname 0.0.0.0 --port 8080

Run tests

运行测试

swift test swift test --filter AppTests
swift test swift test --filter AppTests

Run database migrations manually

手动执行数据库迁移

swift run App migrate swift run App migrate --revert
swift run App migrate swift run App migrate --revert

Docker build

Docker构建

docker build -t my-vapor-app . docker compose up -d
undefined
docker build -t my-vapor-app . docker compose up -d
undefined

Dependencies

依赖说明

PackagePurpose
vapor/vapor
Core web framework
vapor/fluent
ORM abstraction
vapor/fluent-postgres-driver
PostgreSQL support
vapor/fluent-sqlite-driver
SQLite (development/testing)
vapor/redis
Redis caching and sessions
vapor/jwt
JWT authentication
vapor/leaf
Template engine
XCTVapor
Testing utilities (included with Vapor)
包名用途
vapor/vapor
核心Web框架
vapor/fluent
ORM抽象层
vapor/fluent-postgres-driver
PostgreSQL数据库支持
vapor/fluent-sqlite-driver
SQLite支持(开发/测试环境)
vapor/redis
Redis缓存与会话支持
vapor/jwt
JWT认证能力
vapor/leaf
模板引擎
XCTVapor
测试工具(Vapor内置)

Advanced Topics

进阶主题

For detailed patterns, WebSocket integration, Leaf templates, queues, testing, and deployment, see:
  • references/patterns.md -- Fluent query patterns, relationships, eager loading, WebSocket, Leaf templates, background queues, comprehensive testing, Docker deployment
如需了解详细设计模式、WebSocket集成、Leaf模板、队列、测试、部署相关内容,请查看:
  • references/patterns.md -- Fluent查询模式、关联关系、预加载、WebSocket、Leaf模板、后台队列、全量测试、Docker部署

External References

外部参考