Discover ways to use the Fluent ORM framework. Migrations, schemas, relations powered by PostgreSQL, written in Swift.
Vapor
If you wish to study Fluent, however you do not have a working PostgreSQL set up, you need to examine my tutorial about how you can set up and use pgSQL earlier than you begin studying this one.
Utilizing the Fluent ORM framework
The fantastic thing about an ORM framework is that it hides the complexity of the underlying database layer. Fluent 4 comes with a number of database driver implementations, this implies that you could simply exchange the beneficial PostgreSQL driver with SQLite, MySQL or MongoDB if you would like. MariaDB can be supported by the MySQL driver.
If you’re utilizing the SQLite database driver you may need to put in the corresponding bundle (brew set up sqlite) should you run into the next error: “lacking required module ‘CSQLite'”. 😊
On this tutorial we’ll use PostgreSQL, since that is the brand new default driver in Vapor 4. First you must create a database, subsequent we are able to begin a brand new Vapor undertaking & write some Swift code utilizing Fluent. Should you create a brand new undertaking utilizing the toolbox (vapor new myProject) you may be requested which database driver to make use of. If you’re making a undertaking from scratch you possibly can alter the Package deal.swift file:
import PackageDescription
let bundle = Package deal(
title: "pgtut",
platforms: [
.macOS(.v10_15)
],
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "4.3.0"),
.package(url: "https://github.com/vapor/fluent.git", from: "4.0.0-rc"),
.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0-rc")
],
targets: [
.target(name: "App", dependencies: [
.product(name: "Fluent", package: "fluent"),
.product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
.product(name: "Vapor", package: "vapor")
]),
.goal(title: "Run", dependencies: ["App"]),
.testTarget(title: "AppTests", dependencies: [
.target(name: "App"),
.product(name: "XCTVapor", package: "vapor"),
])
]
)
Open the Package deal.swift file in Xcode, wait till all of the dependencies are loaded.
Let’s configure the psql database driver within the configure.swift file. We will use a database URL string to offer the connection particulars, loaded from the native surroundings.
import Vapor
import Fluent
import FluentPostgresDriver
extension Utility {
static let databaseUrl = URL(string: Atmosphere.get("DB_URL")!)!
}
public func configure(_ app: Utility) throws {
attempt app.databases.use(.postgres(url: Utility.databaseUrl), as: .psql)
}
Create a brand new .env.growth file within the undertaking listing with the next contents:
DB_URL=postgres://myuser:[email protected]:5432/mydb
You may also configure the driving force utilizing different strategies, however I personally desire this method, since it’s totally straightforward and it’s also possible to put different particular environmental variables proper subsequent to the DB_URL.
You may also use the .env file in manufacturing mode to set your environmental variables.
Run the applying, however first ensure that the present working listing is about correctly, learn extra about this in my earlier tutorial about the leaf templating engine.
Properly carried out, you may have a working undertaking that connects to the pgSQL server utilizing Fluent. 🚀
Mannequin definition
The official documentation just about covers all of the vital ideas, so it is undoubtedly value a learn. On this part, I am solely going to concentrate on among the “lacking elements”.
The API template pattern code comes with a Todo mannequin which is just about a great place to begin for us.
Subject keys
Subject keys can be found from the fifth main beta model of Fluent 4. Lengthy story quick, you do not have to repeat your self anymore, however you possibly can outline a key for every database discipline. As a free of charge you by no means must do the identical for id fields, since fluent has built-in assist for identifiers.
extension FieldKey {
static var title: Self { "title" }
}
@ID() var id: UUID?
@Subject(key: .title) var title: String
.id()
.discipline(.title, .string, .required)
Identifiers are actually UUID sorts by default
Utilizing the brand new @ID property wrapper and the .id() migration perform will mechanically require your fashions to have a UUID worth by default. It is a nice change, as a result of I do not actually like serial identifiers. If you wish to go use integers as identifiers you possibly can nonetheless do it. Additionally you possibly can outline UUID fields with the old-school syntax, however should you go so you possibly can have some troubles with switching to the brand new MongoDB driver, so please do not do it. 🥺
@ID({custom}: "todo_id")
var id: Int?
@ID({custom}: "todo_identifier", generatedBy: .person)
var id: String?
.discipline("id", .uuid, .identifier(auto: false))
Easy methods to retailer native database enums?
If you wish to retailer enums utilizing Fluent you may have two choices now. The primary one is that you just save your enums as native values (int, string, and so forth.), should you accomplish that you simply want an enum with a brand new discipline of the given sort, plus you must conform the enum to the Codable protocol.
enum Standing: String, Codable {
case pending
case accomplished
}
@Subject(key: "standing") var standing: Standing
.discipline("standing", .string, .required)
The second possibility is to make use of the brand new @Enum discipline sort and migrate every part utilizing the enum builder. This methodology requires extra setup, however I believe it should value it on the long run.
extension FieldKey {
static var standing: Self { "standing" }
}
enum Standing: String, Codable, CaseIterable {
static var title: FieldKey { .standing }
case pending
case accomplished
}
@Enum(key: .standing) var standing: Standing
struct CreateTodo: Migration {
func put together(on database: Database) -> EventLoopFuture<Void> {
var enumBuilder = database.enum(Todo.Standing.title.description)
for possibility in Todo.Standing.allCases {
enumBuilder = enumBuilder.case(possibility.rawValue)
}
return enumBuilder.create()
.flatMap { enumType in
database.schema(Todo.schema)
.id()
.discipline(.title, .string, .required)
.discipline(.standing, enumType, .required)
.create()
}
}
func revert(on database: Database) -> EventLoopFuture<Void> {
return database.schema(Todo.schema).delete().flatMap {
database.enum(Todo.Standing.title.description).delete()
}
}
}
The primary benefit of this method that Fluent can reap the benefits of the database driver’s built-in enum sort assist. Additionally if you wish to retailer native enums you must migrate the fields should you introduce a brand new case. You may learn extra about this within the beta launch notes. I can not inform you which one is one of the best ways, since it is a model new characteristic, I’ve to run some assessments. ✅
Saving possibility units in Fluent
There’s a nice put up written by Bastian Inuk about managing person roles utilizing possibility units in Fluent. You need to undoubtedly have a look if you wish to use an OptionSet as a Fluent property. Anyway, I am going to present you how you can create this sort, so we’ll be capable to flag our todo objects. 🔴🟣🟠🟡🟢🔵⚪️
extension FieldKey {
static var labels: Self { "labels" }
}
struct Labels: OptionSet, Codable {
var rawValue: Int
static let purple = Labels(rawValue: 1 << 0)
static let purple = Labels(rawValue: 1 << 1)
static let orange = Labels(rawValue: 1 << 2)
static let yellow = Labels(rawValue: 1 << 3)
static let inexperienced = Labels(rawValue: 1 << 4)
static let blue = Labels(rawValue: 1 << 5)
static let grey = Labels(rawValue: 1 << 6)
static let all: Labels = [.red, .purple, .orange, .yellow, .green, .blue, .gray]
}
@Subject(key: .labels) var labels: Labels
.discipline(.labels, .int, .required)
There’s a good Choice protocol OptionSet
Storing dates
Fluent can even retailer dates and occasions and convert them back-and-forth utilizing the built-in Date object from Basis. You simply have to decide on between the .date or .datetime storage sorts. You need to go together with the primary one should you do not care concerning the hours, minutes or seconds. The second is sweet should you merely need to save the day, month and 12 months. 💾
You need to at all times go together with the very same TimeZone once you save / fetch dates from the database. Whenever you save a date object that’s in UTC, subsequent time if you wish to filter these objects and you utilize a unique time zone (e.g. PDT), you may get again a nasty set of outcomes.
Right here is the ultimate instance of our Todo mannequin together with the migration script:
remaining class Todo: Mannequin, Content material {
static let schema = "todos"
enum Standing: String, Codable {
case pending
case accomplished
}
struct Labels: OptionSet, Codable {
var rawValue: Int
static let purple = Labels(rawValue: 1 << 0)
static let purple = Labels(rawValue: 1 << 1)
static let orange = Labels(rawValue: 1 << 2)
static let yellow = Labels(rawValue: 1 << 3)
static let inexperienced = Labels(rawValue: 1 << 4)
static let blue = Labels(rawValue: 1 << 5)
static let grey = Labels(rawValue: 1 << 6)
static let all: Labels = [
.red,
.purple,
.orange,
.yellow,
.green,
.blue,
.gray
]
}
@ID() var id: UUID?
@Subject(key: .title) var title: String
@Subject(key: .standing) var standing: Standing
@Subject(key: .labels) var labels: Labels
@Subject(key: .due) var due: Date?
init() { }
init(id: UUID? = nil,
title: String,
standing: Standing = .pending,
labels: Labels = [],
due: Date? = nil)
{
self.id = id
self.title = title
self.standing = standing
self.labels = labels
self.due = due
}
}
struct CreateTodo: Migration {
func put together(on database: Database) -> EventLoopFuture<Void> {
return database.schema(Todo.schema)
.id()
.discipline(.title, .string, .required)
.discipline(.standing, .string, .required)
.discipline(.labels, .int, .required)
.discipline(.due, .datetime)
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
return database.schema(Todo.schema).delete()
}
}
Yet one more factor…
Nested fields & compound fields
Generally you would possibly want to save lots of further structured knowledge, however you do not need to introduce a relation (e.g. attributes with completely different keys, values). That is when the @NestedField property wrapper comes extraordinarily useful. I will not embrace right here an instance, since I had no time to do this characteristic but, however you possibly can learn extra about it right here with a working pattern code.
The distinction between a @CompoundField and a @NestedField is {that a} compound discipline is saved as a flat prime degree discipline within the database, however the different can be saved as a nested object.
Units are actually appropriate with the array database sort, you should utilize them like this: .discipline(.mySetField, .array(of: .string), .required)
I believe we just about lined every part that you’re going to want as a way to create DB entities. We’ll have a fast detour right here earlier than we get into relations. 🚧
Schemas & migrations
The Todo object is kind of prepared to make use of, however this is only one a part of the entire story. We nonetheless have to create the precise database desk that may retailer our objects in PostgreSQL. With the intention to create the DB schema primarily based on our Swift code, now we have to run the migration command.
Migration is the method of making, updating or deleting a number of database tables. In different phrases, every part that alters the database schema is a migration. You need to know that you could register a number of migration scripts and Vapor will run them at all times within the order they have been added.
The title of your database desk & the fields are declared in your mannequin. The schema is the title of the desk, and the property wrappers are containing the title of every discipline.
These days I desire to make use of a semantic model suffix for all my migration objects, that is actually useful as a result of I haven’t got to suppose an excessive amount of concerning the naming conventions, migration_v1_0_0 is at all times the create operation, every part comes after this model is simply an altering the schema.
You may implement a var title: String { "custom-migration-name" } property contained in the migration struct / class, so you do not have to place particular characters into your object’s title
You ought to be cautious with relations! If you’re attempting to make use of a desk with a discipline as a international key you must ensure that the referenced object already exists, in any other case it will fail.
Through the first migration Fluent will create an inside lookup desk named _fluent_migrations. The migration system is utilizing this desk to detect which migrations have been already carried out and what must be carried out subsequent time you run the migrate command.
With the intention to carry out a migration you possibly can launch the Run goal with the migrate argument. Should you go the --auto-migrate flag you do not have to substantiate the migration course of. Watch out. 😳
swift run Run migrate
You may revert the final batch of migrations by operating the command with the --revert flag.
swift run Run migrate --revert
Here’s a fast instance how you can run a number of schema updates by utilizing flatten perform. This migration merely removes the present title discipline, and creates new distinctive title discipline.
extension FieldKey {
static var title: Self { "title" }
}
struct UpdateTodo: Migration {
func put together(on database: Database) -> EventLoopFuture<Void> {
database.eventLoop.flatten([
database.schema(Todo.schema)
.deleteField(.title)
.update(),
database.schema(Todo.schema)
.field(.name, .string, .required)
.unique(on: .name)
.update(),
Todo(name: "Hello world").save(on: database),
])
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.eventLoop.flatten([
database.schema(Todo.schema)
.deleteField(.name)
.update(),
database.schema(Todo.schema)
.field(.title, .string, .required)
.update(),
])
}
}
Be happy to go forward, migrate the Todo scheme so we are able to write some queries.
Querying
Once more I’ve to check with the official 4.0 Fluent docs. Please go forward learn the querying part rigorously, and are available again to this text. The TodoController additionally gives a fundamental Swift pattern code. IMHO a controller is an interactor, these days I am utilizing VIPER on the backend aspect as properly (article coming quickly). Listed here are a number of CRUD practices. 😅
Creating a number of information directly
This one is easy, please word that the save methodology in Fluent behaves like an upsert command. In case your mannequin exists, it will replace in any other case it calls the create perform. Anyway you possibly can at all times name create on a bunch of fashions to carry out a batch insert.
let todos = [
Todo(title: "Publish new article tomorrow"),
Todo(title: "Finish Fluent tutorial"),
Todo(title: "Write more blog posts"),
]
todos.create(on: req.db)
Batch delete information
You may question all of the required information utilizing filters and name the .delete() methodology on them.
Todo.question(on: req.db)
.filter(.$standing == .accomplished)
.delete()
Easy methods to replace or delete a single document?
If you realize the thing identifier it is fairly easy, the Mannequin protocol has a discover methodology for this function. In any other case you possibly can question the required object and request the primary one.
Fluent is asynchronous by default, which means that you must work so much with Futures and Guarantees. You may learn my tutorial for novices about guarantees in Swift.
You should use the .map or .flatMap strategies to carry out the mandatory actions & return a correct response. The .unwrap perform is kind of useful, since you do not have to unwrap optionals by hand within the different blocks. Block primarily based syntax = you must cope with reminiscence administration. 💩
_ = Todo.discover(uuid, on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { todo -> EventLoopFuture<Void> in
todo.title = ""
return todo.save(on: req.db)
}
_ = Todo.question(on: req.db)
.filter(.$title == "Hi there world")
.first()
.unwrap(or: Abort(.notFound))
.flatMap { $0.delete(on: req.db) }
That is it about creating, requesting, updating and deleting entities.
Relations
Generally you need to retailer some further info in a separate database. In our case for instance we may make a dynamic tagging system for the todo objects. These tags could be saved in a separate desk and they are often linked to the todos by utilizing a relation. A relation is nothing greater than a international key someplace within the different desk or inside a pivot.
One-to-one relations
Fluent helps one-to-many relations out of the field. The documentation clearly explains every part about them, however I would like so as to add a number of notes, time to construct a one-to-many relation.
If you wish to mannequin a one-to-one relation the international key needs to be distinctive for the associated desk. Let’s add a element desk to our todo objects with a individually saved description discipline.
extension FieldKey {
static var todoId: Self { "todo_id" }
static var description: Self { "description" }
}
remaining class Element: Mannequin, Content material {
static let schema = "particulars"
@ID() var id: UUID?
@Guardian(key: .todoId) var todo: Todo
@Subject(key: .description) var description: String
init() { }
init(id: UUID? = nil, description: String, todoId: UUID) {
self.id = id
self.description = description
self.$todo.id = todoId
}
}
The mannequin above has a mother or father relation to a Todo object by a todo_id discipline. In different phrases, we merely retailer the unique todo identifier on this desk. Afterward we’ll be capable to question the related descriptions by utilizing this international key. Let me present you the migration:
struct CreateTodo: Migration {
func put together(on database: Database) -> EventLoopFuture<Void> {
database.eventLoop.flatten([
database.schema(Todo.schema)
.id()
.field(.title, .string, .required)
.field(.status, .string, .required)
.field(.labels, .int, .required)
.field(.due, .datetime)
.create(),
database.schema(Detail.schema)
.id()
.field(. todoId, .uuid, .required)
.foreignKey(.todoId, references: Todo.schema, .id, onDelete: .cascade, onUpdate: .noAction)
.field(.description, .string, .required)
.unique(on: .todoId)
.create(),
])
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.eventLoop.flatten([
database.schema(Detail.schema).delete(),
database.schema(Todo.schema).delete(),
])
}
}
The ultimate step right here is to increase the Todo mannequin with the kid reference.
@Youngsters(for: .$todo) var particulars: [Detail]
Making a relation solely takes a number of traces of Swift code
let todo = Todo(title: "End the Fluent article already")
todo.create(on: app.db)
.flatMap { _ in
Element(description: "write some cool issues about Fluent relations",
todoId: todo.id!).create(on: req.db)
}
Now should you attempt to add a number of particulars to the identical todo object the you will not be capable to carry out that DB question, for the reason that todo_id has a novel constraint, so that you have to be extraordinarily carful with these sort of operations. Aside from this limitation (that comes alongside with a one-to-one relation) you utilize each objects as standard (discover by id, keen load the small print from the todo object, and so forth.). 🤓
One-to-many relations
A one-to-many relation is rather like a one-to-one, besides that you could affiliate a number of objects with the mother or father. You may even use the identical code from above, you simply must take away the distinctive constraint from the migration script. I am going to add some grouping characteristic to this todo instance.
remaining class Group: Mannequin, Content material {
static let schema = "teams"
@ID() var id: UUID?
@Subject(key: .title) var title: String
@Youngsters(for: .$group) var todos: [Todo]
init() { }
init(id: UUID? = nil, title: String) {
self.id = id
self.title = title
}
}
remaining class Todo: Mannequin, Content material {
@Guardian(key: .groupId) var group: Group
@Youngsters(for: .$todo) var particulars: [Detail]
init() { }
init(id: UUID? = nil,
title: String,
standing: Standing = .pending,
labels: Labels = [],
due: Date? = nil,
groupId: UUID)
{
self.id = id
self.title = title
self.standing = standing
self.labels = labels
self.due = due
self.$group.id = groupId
}
}
struct CreateTodo: Migration {
func put together(on database: Database) -> EventLoopFuture<Void> {
database.eventLoop.flatten([
database.schema(Group.schema)
.id()
.field(.name, .string, .required)
.create(),
database.schema(Todo.schema)
.id()
.field(.title, .string, .required)
.field(.status, .string, .required)
.field(.labels, .int, .required)
.field(.due, .datetime)
.field(. groupId, .uuid, .required)
.foreignKey(.groupId, references: Group.schema, .id)
.create(),
database.schema(Detail.schema)
.id()
.field(. todoId, .uuid, .required)
.foreignKey(.todoId, references: Todo.schema, .id, onDelete: .cascade, onUpdate: .noAction)
.field(.description, .string, .required)
.unique(on: .todoId)
.create(),
Group(name: "Default").create(on: database),
])
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.eventLoop.flatten([
database.schema(Detail.schema).delete(),
database.schema(Todo.schema).delete(),
database.schema(Group.shcema).delete(),
])
}
}
Any more, you may must insert the todos into a bunch. It is alright to create a default one within the migration script, so afterward it is potential to get the id reference of the pre-existing group.
Group.question(on: req.db)
.first()
.flatMap { group in
Todo(title: "This belongs to a bunch", groupId: group!.id!).create(on: app.db)
}
Group.question(on: req.db)
.with(.$todos)
.all()
.whenSuccess { teams in
for group in teams {
print(group.title)
print(group.todos.map { "- ($0.title)" }.joined(separator: "n"))
}
}
If you wish to change a mother or father, you possibly can merely set the brand new identifier utilizing the .$ syntax. Remember to name replace or save on the thing, since it isn’t sufficient simply to replace the relation in reminiscence, however you must persist every part again to the database. 💡
Many-to-many relations
You may create an affiliation between two tables by utilizing a 3rd one which shops international keys from each of the unique tables. Sounds enjoyable? Welcome to the world of many-to-many relations. They’re helpful if you wish to construct a tagging system or a recipe e book with elements.
Once more, Bastian Inuk has a fantastic put up about how you can use siblings in Fluent 4. I simply need to add one additional factor right here: you possibly can retailer further info on the pivot desk. I am not going to indicate you this time how you can affiliate elements with recipes & quantities, however I am going to put some tags on the todo objects with an vital flag possibility. Thanks buddy! 😜
extension FieldKey {
static var title: Self { "title" }
static var todoId: Self { "todo_id" }
static var tagId: Self { "tag_id" }
static var vital: Self { "vital" }
}
remaining class Tag: Mannequin, Content material {
static let schema = "tags"
@ID() var id: UUID?
@Subject(key: .title) var title: String
@Siblings(by: TodoTags.self, from: .$tag, to: .$todo) var todos: [Todo]
init() { }
init(id: UUID? = nil, title: String) {
self.id = id
self.title = title
}
}
remaining class TodoTags: Mannequin {
static let schema = "todo_tags"
@ID() var id: UUID?
@Guardian(key: .todoId) var todo: Todo
@Guardian(key: .tagId) var tag: Tag
@Subject(key: .vital) var vital: Bool
init() {}
init(todoId: UUID, tagId: UUID, vital: Bool) {
self.$todo.id = todoId
self.$tag.id = tagId
self.vital = vital
}
}
@Siblings(by: TodoTags.self, from: .$todo, to: .$tag) var tags: [Tag]
database.schema(Tag.schema)
.id()
.discipline(.title, .string, .required)
.create(),
database.schema(TodoTags.schema)
.id()
.discipline(.todoId, .uuid, .required)
.discipline(.tagId, .uuid, .required)
.discipline(.vital, .bool, .required)
.create(),
database.schema(Tag.schema).delete(),
database.schema(TodoTags.schema).delete(),
The one new factor right here is the siblings property wrapper which defines the connection between the 2 tables. It is superior that Fluent can deal with these complicated relations in such a pleasant manner.
The code snippet under is for instructional functions solely, you need to by no means use the .wait() methodology in a real-world software, use futures & guarantees as a substitute.
Lastly we’re capable of tag our todo objects, plus we are able to mark a few of them as vital. 🎊
let defaultGroup = attempt Group.question(on: app.db).first().wait()!
let shoplist = Group(title: "Shoplist")
let undertaking = Group(title: "Superior Fluent undertaking")
attempt [shoplist, project].create(on: app.db).wait()
let household = Tag(title: "household")
let work = Tag(title: "household")
attempt [family, work].create(on: app.db).wait()
let smoothie = Todo(title: "Make a smoothie",
standing: .pending,
labels: [.purple],
due: Date(timeIntervalSinceNow: 3600),
groupId: defaultGroup.id!)
let apples = Todo(title: "Apples", groupId: shoplist.id!)
let bananas = Todo(title: "Bananas", groupId: shoplist.id!)
let mango = Todo(title: "Mango", groupId: shoplist.id!)
let kickoff = Todo(title: "Kickoff assembly",
standing: .accomplished,
groupId: undertaking.id!)
let code = Todo(title: "Code in Swift",
labels: [.green],
groupId: undertaking.id!)
let deadline = Todo(title: "Undertaking deadline",
labels: [.red],
due: Date(timeIntervalSinceNow: 86400 * 7),
groupId: undertaking.id!)
attempt [smoothie, apples, bananas, mango, kickoff, code, deadline].create(on: app.db).wait()
let familySmoothie = TodoTags(todoId: smoothie.id!, tagId: household.id!, vital: true)
let workDeadline = TodoTags(todoId: deadline.id!, tagId: work.id!, vital: false)
attempt [familySmoothie, workDeadline].create(on: app.db).wait()
That is it, now we’re prepared with our superior todo software. 😎
Conclusion
Fluent is a loopy highly effective software. You may simply make the change between the accessible drivers. You do not even have to write down SQL in case you are utilizing an ORM software, however solely Swift code, which is good.
Server aspect Swift and all of the associated instruments are evolving quick. The entire Vapor group is doing such a fantastic job. I hope this text will provide help to to grasp Fluent manner higher. 💧