vapor
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseVapor 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 entry point pattern with
@mainApplication.make - Group routes by resource with controllers
RouteCollection - Use Fluent property wrappers (,
@ID,@Field,@Parent) for models@Children - Use protocol for all request/response DTOs
Content - Use protocol for input validation on every endpoint
Validatable - Use for cross-cutting concerns (auth, logging, CORS)
AsyncMiddleware - Use with both
AsyncMigrationandpreparemethodsrevert - Configure databases from environment variables (never hardcode credentials)
- Use DTOs to separate API contracts from database models
- Implement pagination for all list endpoints
- Use on all route handler closures
@Sendable - Mark model classes as (Fluent requirement)
@unchecked Sendable
- 搭配使用
Application.make入口点模式@main - 使用控制器按资源对路由进行分组
RouteCollection - 模型层使用Fluent属性包装器(、
@ID、@Field、@Parent)@Children - 所有请求/响应DTO都遵循协议
Content - 每个端点的输入校验都使用协议实现
Validatable - 横切关注点(认证、日志、CORS)使用实现
AsyncMiddleware - 迁移文件使用,同时实现
AsyncMigration和prepare方法revert - 从环境变量读取数据库配置(禁止硬编码凭证)
- 使用DTO分离API契约与数据库模型
- 所有列表类型的端点都实现分页能力
- 所有路由处理闭包都使用标记
@Sendable - 模型类标记为(Fluent要求)
@unchecked Sendable
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 in production (run migrations explicitly)
autoMigrate - Do not skip in migrations (always provide rollback)
revert - Do not use or
try!in request handlersfatalError - 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.ymlLayer responsibilities:
- -- HTTP routing only: parse request, call service, write response
Controllers/ - -- Business logic, orchestration, domain rules
Services/ - -- Fluent database models with property wrappers
Models/ - -- Request/response types with validation (
DTOs/+Content)Validatable - -- Schema changes with
Migrations/andpreparerevert - -- Cross-cutting: auth, error handling, CORS, logging
Middleware/
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各层职责:
- -- 仅处理HTTP路由:解析请求、调用服务、返回响应
Controllers/ - -- 业务逻辑、流程编排、领域规则实现
Services/ - -- 带属性包装器的Fluent数据库模型
Models/ - -- 带校验能力的请求/响应类型(遵循
DTOs/+Content协议)Validatable - -- 包含
Migrations/和prepare实现的 schema 变更文件revert - -- 横切逻辑:认证、错误处理、CORS、日志
Middleware/
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 per resource. Use on all handlers. Validate before processing. Return DTOs, not Fluent models.
RouteCollection@Sendableswift
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)
}
}约定: 每个资源实现一个,所有处理函数添加标记,处理前先做校验,返回DTO而非Fluent模型。
RouteCollection@SendableFluent 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 conforming to
final class,Model,Content@unchecked Sendable - Use for UUID primary keys
@ID(key: .id) - Use for
@Timestampandcreated_atupdated_at - Use /
@Parent/@Childrenfor relationships@Siblings - Provide an empty (Fluent requirement)
init()
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 - 提供空的方法(Fluent要求)
init()
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 and
preparerevert - Create enums before referencing them in schema
- Delete enums in after deleting the table
revert - Use for foreign keys with
.references()behavioronDelete - 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 +
ContentValidatable - Response types conform to only
Content - Always validate in the controller before processing:
try CreateUserRequest.validate(content: req) - Use rules:
Validatable,.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 protocol handles JSON automatically. Configure custom encoding in :
Contentconfigure.swiftswift
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.dateEncodingStrategy = .iso8601
ContentConfiguration.global.use(encoder: encoder, for: .json)Vapor的协议会自动处理JSON序列化/反序列化,可在中配置自定义编码规则:
Contentconfigure.swiftswift
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
undefinedbash
undefinedInitialize 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
undefineddocker build -t my-vapor-app .
docker compose up -d
undefinedDependencies
依赖说明
| Package | Purpose |
|---|---|
| Core web framework |
| ORM abstraction |
| PostgreSQL support |
| SQLite (development/testing) |
| Redis caching and sessions |
| JWT authentication |
| Template engine |
| Testing utilities (included with Vapor) |
| 包名 | 用途 |
|---|---|
| 核心Web框架 |
| ORM抽象层 |
| PostgreSQL数据库支持 |
| SQLite支持(开发/测试环境) |
| Redis缓存与会话支持 |
| JWT认证能力 |
| 模板引擎 |
| 测试工具(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部署