Fluent 数据库与 RESTful CRUD 封面
Vapor 教程

Fluent 数据库与 RESTful CRUD

后端没有数据库,就像收银台没有账本。Fluent 是 Vapor 自带的 ORM(对象关系映射),让你用 Swift 结构体操作数据库,不用手写 SQL。这篇带你配库、建表、跑通增删改查。

一、选哪个数据库

Vapor 原生支持三种关系型驱动:

数据库特点适合场景
SQLite基于单个文件,零配置原型、本地测试(写入频率低)
MySQLLAMP 常用,云厂商普遍支持中小型 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含义
新增 CreatePOST /api/acronyms创建一个新的缩略语
列表 RetrieveGET /api/acronyms获取所有缩略语
详情 RetrieveGET /api/acronyms/<ID>获取指定 ID 的缩略语
更新 UpdatePUT /api/acronyms/<ID>更新指定 ID
删除 DeleteDELETE /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 用属性包装器表达:

定义好关系后,Fluent 能直接 acronym.$user.get(on: req.db) 把关联对象一并取出来,不用你自己 join。


本系列其他文章