If you wish to study Fluent, however you do not have a working PostgreSQL set up, you need to verify my tutorial about the best way to 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 would be able to simply substitute the beneficial PostgreSQL driver with SQLite, MySQL or MongoDB if you would like. MariaDB can be supported via 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 it’s a must to create a database, subsequent we are able to begin a brand new Vapor undertaking & write some Swift code utilizing Fluent. When you create a brand new undertaking utilizing the toolbox (vapor new myProject
) you will be requested which database driver to make use of. If you’re making a undertaking from scratch you may alter the Bundle.swift
file:
import PackageDescription
let bundle = Bundle(
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 Bundle.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’ll use a database URL string to supply the connection particulars, loaded from the native setting.
import Vapor
import Fluent
import FluentPostgresDriver
extension Utility {
static let databaseUrl = URL(string: Setting.get("DB_URL")!)!
}
public func configure(_ app: Utility) throws {
strive app.databases.use(.postgres(url: Utility.databaseUrl), as: .psql)
}
Create a brand new .env.improvement
file within the undertaking listing with the next contents:
DB_URL=postgres://myuser:[email protected]:5432/mydb
You may also configure the motive force utilizing different strategies, however I personally want this strategy, since it’s extremely simple and you may as well 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 appliance, however first guarantee that the present working listing is ready correctly, learn extra about this in my earlier tutorial about the leaf templating engine.
Effectively executed, 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 essential ideas, so it is undoubtedly value a learn. On this part, I am solely going to concentrate on a number of the “lacking elements”.
The API template pattern code comes with a Todo
mannequin which is just about a very good place to begin for us.
Discipline keys
Discipline keys can be found from the fifth main beta model of Fluent 4. Lengthy story brief, you do not have to repeat your self anymore, however you may outline a key for every database subject. As a free of charge you by no means should do the identical for id fields, since fluent has built-in help for identifiers.
extension FieldKey {
static var title: Self { "title" }
}
@ID() var id: UUID?
@Discipline(key: .title) var title: String
.id()
.subject(.title, .string, .required)
Identifiers are actually UUID varieties 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. This can be a nice change, as a result of I do not actually like serial identifiers. If you wish to go use integers as identifiers you may nonetheless do it. Additionally you may outline UUID
fields with the old-school syntax, however should you go so you may 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?
.subject("id", .uuid, .identifier(auto: false))
Find out how 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 many others.), should you achieve this you simply want an enum with a brand new subject of the given kind, plus it’s a must to conform the enum to the Codable protocol.
enum Standing: String, Codable {
case pending
case accomplished
}
@Discipline(key: "standing") var standing: Standing
.subject("standing", .string, .required)
The second choice is to make use of the brand new @Enum
subject kind and migrate all the things utilizing the enum builder. This technique requires extra setup, however I feel 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 choice in Todo.Standing.allCases {
enumBuilder = enumBuilder.case(choice.rawValue)
}
return enumBuilder.create()
.flatMap { enumType in
database.schema(Todo.schema)
.id()
.subject(.title, .string, .required)
.subject(.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 principle benefit of this strategy that Fluent can reap the benefits of the database driver’s built-in enum kind help. Additionally if you wish to retailer native enums it’s a must to migrate the fields should you introduce a brand new case. You possibly can learn extra about this within the beta launch notes. I am unable to inform you which one is the easiest way, since this can be a model new function, I’ve to run some exams. ✅
Saving choice units in Fluent
There’s a nice submit written by Bastian Inuk about managing person roles utilizing choice units in Fluent. It is best to undoubtedly have a look if you wish to use an OptionSet as a Fluent property. Anyway, I will present you the best way to 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 pink = 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]
}
@Discipline(key: .labels) var labels: Labels
.subject(.labels, .int, .required)
There’s a good Possibility protocol OptionSet
Storing dates
Fluent may retailer dates and instances 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 varieties. It is best to go along with the primary one should you do not care in regards to the hours, minutes or seconds. The second is nice should you merely need to save the day, month and yr. 💾
It is best to at all times go along with the very same TimeZone
while you save / fetch dates from the database. Once you save a date object that’s in UTC, subsequent time if you wish to filter these objects and you utilize a special time zone (e.g. PDT), you will get again a foul set of outcomes.
Right here is the ultimate instance of our Todo
mannequin together with the migration script:
ultimate 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 pink = 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?
@Discipline(key: .title) var title: String
@Discipline(key: .standing) var standing: Standing
@Discipline(key: .labels) var labels: Labels
@Discipline(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()
.subject(.title, .string, .required)
.subject(.standing, .string, .required)
.subject(.labels, .int, .required)
.subject(.due, .datetime)
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
return database.schema(Todo.schema).delete()
}
}
Yet one more factor…
Nested fields & compound fields
Typically you would possibly want to save lots of further structured information, however you do not need to introduce a relation (e.g. attributes with totally different keys, values). That is when the @NestedField
property wrapper comes extraordinarily helpful. I will not embrace right here an instance, since I had no time to do that function but, however you may learn extra about it right here with a working pattern code.
The distinction between a @CompoundField
and a @NestedField
is {that a} compound subject is saved as a flat prime degree subject within the database, however the different will likely be saved as a nested object.
Units are actually appropriate with the array database kind, you should use them like this: .subject(.mySetField, .array(of: .string), .required)
I feel we just about coated all the things that you’re going to want so as 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 must create the precise database desk that may retailer our objects in PostgreSQL. So as to create the DB schema based mostly 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, all the things that alters the database schema is a migration. It is best to know that you would be able to 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 subject.
These days I want to make use of a semantic model suffix for all my migration objects, that is actually helpful as a result of I haven’t got to suppose an excessive amount of in regards to the naming conventions, migrationv10_0 is at all times the create operation, all the things comes after this model is simply an altering the schema.
You possibly can 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 have to be cautious with relations! If you’re making an attempt to make use of a desk with a subject as a overseas key it’s a must to guarantee that the referenced object already exists, in any other case it will fail.
Throughout 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 executed subsequent time you run the migrate command.
So as to carry out a migration you may launch the Run goal with the migrate argument. When you move the --auto-migrate
flag you do not have to substantiate the migration course of. Watch out. 😳
swift run Run migrate
You possibly can revert the final batch of migrations by working the command with the –revert flag.
swift run Run migrate --revert
Here’s a fast instance the best way to run a number of schema updates through the use of flatten perform. This migration merely removes the prevailing title subject, and creates new distinctive title subject.
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 discuss with the official 4.0 Fluent docs. Please go forward learn the querying part fastidiously, and are available again to this text. The TodoController
additionally gives a primary 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 below are a couple of CRUD practices. 😅
Creating a number of data without delay
This one is straightforward, please notice that the save
technique 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 may 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 data
You possibly can question all of the required data utilizing filters and name the .delete() technique on them.
Todo.question(on: req.db)
.filter(.$standing == .accomplished)
.delete()
Find out how to replace or delete a single report?
If you already know the thing identifier it is fairly easy, the Mannequin
protocol has a discover technique for this goal. In any other case you may question the required object and request the primary one.
Fluent is asynchronous by default, because of this it’s a must to work so much with Futures and Guarantees. You possibly can learn my tutorial for newcomers 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 sort of helpful, since you do not have to unwrap optionals by hand within the different blocks. Block based mostly syntax = it’s a must to 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
Typically you need to retailer some further data in a separate database. In our case for instance we might make a dynamic tagging system for the todo objects. These tags could be saved in a separate desk and they are often related to the todos through the use of a relation. A relation is nothing greater than a overseas 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 all the things about them, however I might like so as to add a couple of notes, time to construct a one-to-many relation.
If you wish to mannequin a one-to-one relation the overseas key must be distinctive for the associated desk. Let’s add a element desk to our todo objects with a individually saved description subject.
extension FieldKey {
static var todoId: Self { "todo_id" }
static var description: Self { "description" }
}
ultimate class Element: Mannequin, Content material {
static let schema = "particulars"
@ID() var id: UUID?
@Father or mother(key: .todoId) var todo: Todo
@Discipline(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 dad or mum relation to a Todo
object via a todo_id
subject. In different phrases, we merely retailer the unique todo identifier on this desk. Afterward we’ll be capable to question the related descriptions through the use of this overseas 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.
@Kids(for: .$todo) var particulars: [Detail]
Making a relation solely takes a couple of strains 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 form of operations. Other than this limitation (that comes alongside with a one-to-one relation) you utilize each objects as traditional (discover by id, keen load the small print from the todo object, and many others.). 🤓
One-to-many relations
A one-to-many relation is rather like a one-to-one, besides that you would be able to affiliate a number of objects with the dad or mum. You possibly can even use the identical code from above, you simply should take away the distinctive constraint from the migration script. I will add some grouping function to this todo instance.
ultimate class Group: Mannequin, Content material {
static let schema = "teams"
@ID() var id: UUID?
@Discipline(key: .title) var title: String
@Kids(for: .$group) var todos: [Todo]
init() { }
init(id: UUID? = nil, title: String) {
self.id = id
self.title = title
}
}
ultimate class Todo: Mannequin, Content material {
@Father or mother(key: .groupId) var group: Group
@Kids(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(),
])
}
}
To any extent further, you will should insert the todos into a gaggle. It is alright to create a default one within the migration script, so in a while it is doable 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 gaggle", 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 dad or mum, you may merely set the brand new identifier utilizing the .$id
syntax. Do not forget to name replace or save on the thing, since it is not sufficient simply to replace the relation in reminiscence, however it’s a must to persist all the things again to the database. 💡
Many-to-many relations
You possibly can create an affiliation between two tables through the use of a 3rd one which shops overseas 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 guide with components.
Once more, Bastian Inuk has an ideal submit about the best way to use siblings in Fluent 4. I simply need to add one additional factor right here: you may retailer further data on the pivot desk. I am not going to point out you this time the best way to affiliate components with recipes & quantities, however I will put some tags on the todo objects with an essential flag choice. Thanks buddy! 😜
extension FieldKey {
static var title: Self { "title" }
static var todoId: Self { "todo_id" }
static var tagId: Self { "tag_id" }
static var essential: Self { "essential" }
}
ultimate class Tag: Mannequin, Content material {
static let schema = "tags"
@ID() var id: UUID?
@Discipline(key: .title) var title: String
@Siblings(via: TodoTags.self, from: .$tag, to: .$todo) var todos: [Todo]
init() { }
init(id: UUID? = nil, title: String) {
self.id = id
self.title = title
}
}
ultimate class TodoTags: Mannequin {
static let schema = "todo_tags"
@ID() var id: UUID?
@Father or mother(key: .todoId) var todo: Todo
@Father or mother(key: .tagId) var tag: Tag
@Discipline(key: .essential) var essential: Bool
init() {}
init(todoId: UUID, tagId: UUID, essential: Bool) {
self.$todo.id = todoId
self.$tag.id = tagId
self.essential = essential
}
}
@Siblings(via: TodoTags.self, from: .$todo, to: .$tag) var tags: [Tag]
database.schema(Tag.schema)
.id()
.subject(.title, .string, .required)
.create(),
database.schema(TodoTags.schema)
.id()
.subject(.todoId, .uuid, .required)
.subject(.tagId, .uuid, .required)
.subject(.essential, .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 approach.
The code snippet under is for academic functions solely, you need to by no means use the .wait()
technique in a real-world utility, use futures & guarantees as a substitute.
Lastly we’re in a position to tag our todo objects, plus we are able to mark a few of them as essential. 🎊
let defaultGroup = strive Group.question(on: app.db).first().wait()!
let shoplist = Group(title: "Shoplist")
let undertaking = Group(title: "Superior Fluent undertaking")
strive [shoplist, project].create(on: app.db).wait()
let household = Tag(title: "household")
let work = Tag(title: "household")
strive [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: "Challenge deadline",
labels: [.red],
due: Date(timeIntervalSinceNow: 86400 * 7),
groupId: undertaking.id!)
strive [smoothie, apples, bananas, mango, kickoff, code, deadline].create(on: app.db).wait()
let familySmoothie = TodoTags(todoId: smoothie.id!, tagId: household.id!, essential: true)
let workDeadline = TodoTags(todoId: deadline.id!, tagId: work.id!, essential: false)
strive [familySmoothie, workDeadline].create(on: app.db).wait()
That is it, now we’re prepared with our superior todo utility. 😎
Conclusion
Fluent is a loopy highly effective software. You possibly can simply make the change between the obtainable drivers. You do not even have to jot down SQL if you’re 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 neighborhood is doing such an ideal job. I hope this text will assist you to to grasp Fluent approach higher. 💧