Vapor 教程
Fluent 数据库与 RESTful CRUD
后端没有数据库,就像收银台没有账本。Fluent 是 Vapor 自带的 ORM(对象关系映射),让你用 Swift 结构体操作数据库,不用手写 SQL。这篇带你配库、建表、跑通增删改查。
一、选哪个数据库
Vapor 原生支持三种关系型驱动:
| 数据库 | 特点 | 适合场景 |
|---|---|---|
| SQLite | 基于单个文件,零配置 | 原型、本地测试(写入频率低) |
| MySQL | LAMP 常用,云厂商普遍支持 | 中小型 Web 应用 |
| PostgreSQL | 企业级、扩展性强、标准化好 | 生产环境、复杂查询 |
建议:开发阶段先用 SQLite——数据直接落在本地文件,省去装库的麻烦。等要上生产,再换成 MySQL 或 PostgreSQL(配合 Docker 一条命令起一个容器)。Vapor 切换驱动基本只改
configure.swift,业务代码不动。二、用 SQLite 快速起步
在 configure.swift 里注册 Fluent 与 SQLite:
import Fluent
import FluentSQLiteDriver
app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite)
app.migrations.add(CreateAcronym())如果是 Vapor 3,写法略不同(用 FluentSQLiteProvider + DatabasesConfig + MigrationConfig),但目的相同:告诉框架「用哪个库、要执行哪些迁移」。
三、定义模型与迁移
模型就是一张表的 Swift 映射,遵循 Model 协议:
import Fluent
final class Acronym: Model {
static let schema = "acronyms"
@ID(key: .id)
var id: UUID?
@Field(key: "short")
var short: String
@Field(key: "long")
var long: String
init() {}
init(id: UUID? = nil, short: String, long: String) {
self.id = id
self.short = short
self.long = long
}
}「迁移(Migration)」相当于建表语句,Fluent 用它来创建/升级表结构:
struct CreateAcronym: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema("acronyms")
.id()
.field("short", .string, .required)
.field("long", .string, .required)
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.schema("acronyms").delete()
}
}四、RESTful CRUD 路由
RESTful 的核心思想:用一个 URL 对应一个资源,用不同 HTTP 方法表达增删改查。以 /api/acronyms 为例:
| 操作 | 方法 + URL | 含义 |
|---|---|---|
| 新增 Create | POST /api/acronyms | 创建一个新的缩略语 |
| 列表 Retrieve | GET /api/acronyms | 获取所有缩略语 |
| 详情 Retrieve | GET /api/acronyms/<ID> | 获取指定 ID 的缩略语 |
| 更新 Update | PUT /api/acronyms/<ID> | 更新指定 ID |
| 删除 Delete | DELETE /api/acronyms/<ID> | 删除指定 ID |
新增——解码请求体后保存:
group.post(use: createHandler)
func createHandler(_ req: Request) throws -> EventLoopFuture<Acronym> {
let data = try req.content.decode(CreateAcronymData.self)
let acronym = Acronym(short: data.short, long: data.long)
return acronym.save(on: req.db).map { acronym }
}查询全部 / 单个:
func getAllHandler(_ req: Request) throws -> EventLoopFuture<[Acronym]> {
Acronym.query(on: req.db).all()
}
func getHandler(_ req: Request) throws -> EventLoopFuture<Acronym> {
Acronym.find(req.parameters.get("acronymID"), on: req.db)
.unwrap(or: Abort(.notFound))
}五、Fluent 查询操作
除了「全查」和「按 ID 查」,Fluent 还支持过滤、排序、分页:
// 过滤
Acronym.query(on: req.db).filter(\.$short == "OMG").all()
// 排序
Acronym.query(on: req.db).sort(\.$short, .ascending).all()
// 限制数量
Acronym.query(on: req.db).range(...4).all()\.$short 是 Swift 的 key-path 写法,编译器帮你检查字段名,拼错就会报红,比手写字符串安全得多。
六、模型关系:父子与兄弟
真实业务里数据互相有关联。Fluent 用属性包装器表达:
- 父子(Parent–Child):一个用户拥有多条缩略语 → 缩略语上加
@Parent(key: "user_id")。 - 兄弟 / 多对多(Sibling):一个缩略语可贴多个分类,一个分类也可被多个缩略语使用 → 用
@Siblings中间表。
定义好关系后,Fluent 能直接 acronym.$user.get(on: req.db) 把关联对象一并取出来,不用你自己 join。