Fluent¶
Fluent is an ORM framework for Swift. It takes advantage of Swift's strong type system to provide an easy-to-use interface for your database. Using Fluent centers around the creation of model types which represent data structures in your database. These models are then used to perform create, read, update, and delete operations instead of writing raw queries.
Configuration¶
When creating a project using vapor new
, answer "yes" to including Fluent and choose which database driver you want to use. This will automatically add the dependencies to your new project as well as example configuration code.
Existing Project¶
If you have an existing project that you want to add Fluent to, you will need to add two dependencies to your package:
- vapor/fluent@4.0.0
- One (or more) Fluent driver(s) of your choice
.package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
.package(url: "https://github.com/vapor/fluent-<db>-driver.git", from: <version>),
.target(name: "App", dependencies: [
.product(name: "Fluent", package: "fluent"),
.product(name: "Fluent<db>Driver", package: "fluent-<db>-driver"),
.product(name: "Vapor", package: "vapor"),
]),
Once the packages are added as dependencies, you can configure your databases using app.databases
in configure.swift
.
import Fluent
import Fluent<db>Driver
app.databases.use(<db config>, as: <identifier>)
Each of the Fluent drivers below has more specific instructions for configuration.
Drivers¶
Fluent currently has four officially supported drivers. You can search GitHub for the tag fluent-driver
for a full list of official and third-party Fluent database drivers.
PostgreSQL¶
PostgreSQL is an open source, standards compliant SQL database. It is easily configurable on most cloud hosting providers. This is Fluent's recommended database driver.
To use PostgreSQL, add the following dependencies to your package.
.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0")
.product(name: "FluentPostgresDriver", package: "fluent-postgres-driver")
Once the dependencies are added, configure the database's credentials with Fluent using app.databases.use
in configure.swift
.
import Fluent
import FluentPostgresDriver
app.databases.use(
.postgres(
configuration: .init(
hostname: "localhost",
username: "vapor",
password: "vapor",
database: "vapor",
tls: .disable
)
),
as: .psql
)
You can also parse the credentials from a database connection string.
try app.databases.use(.postgres(url: "<connection string>"), as: .psql)
SQLite¶
SQLite is an open source, embedded SQL database. Its simplistic nature makes it a great candidate for prototyping and testing.
To use SQLite, add the following dependencies to your package.
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0")
.product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver")
Once the dependencies are added, configure the database with Fluent using app.databases.use
in configure.swift
.
import Fluent
import FluentSQLiteDriver
app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite)
You can also configure SQLite to store the database ephemerally in memory.
app.databases.use(.sqlite(.memory), as: .sqlite)
If you use an in-memory database, make sure to set Fluent to migrate automatically using --auto-migrate
or run app.autoMigrate()
after adding migrations.
app.migrations.add(CreateTodo())
try app.autoMigrate().wait()
// or
try await app.autoMigrate()
Tip
The SQLite configuration automatically enables foreign key constraints on all created connections, but does not alter foreign key configurations in the database itself. Deleting records in a database directly, might violate foreign key constraints and triggers.
MySQL¶
MySQL is a popular open source SQL database. It is available on many cloud hosting providers. This driver also supports MariaDB.
To use MySQL, add the following dependencies to your package.
.package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.0.0")
.product(name: "FluentMySQLDriver", package: "fluent-mysql-driver")
Once the dependencies are added, configure the database's credentials with Fluent using app.databases.use
in configure.swift
.
import Fluent
import FluentMySQLDriver
app.databases.use(.mysql(hostname: "localhost", username: "vapor", password: "vapor", database: "vapor"), as: .mysql)
You can also parse the credentials from a database connection string.
try app.databases.use(.mysql(url: "<connection string>"), as: .mysql)
To configure a local connection without SSL certificate involved, you should disable certificate verification. You might need to do this for example if connecting to a MySQL 8 database in Docker.
var tls = TLSConfiguration.makeClientConfiguration()
tls.certificateVerification = .none
app.databases.use(.mysql(
hostname: "localhost",
username: "vapor",
password: "vapor",
database: "vapor",
tlsConfiguration: tls
), as: .mysql)
Warning
Do not disable certificate verification in production. You should provide a certificate to the TLSConfiguration
to verify against.
MongoDB¶
MongoDB is a popular schemaless NoSQL database designed for programmers. The driver supports all cloud hosting providers and self-hosted installations from version 3.4 and up.
Note
This driver is powered by a community created and maintained MongoDB client called MongoKitten. MongoDB maintains an official client, mongo-swift-driver, along with a Vapor integration, mongodb-vapor.
To use MongoDB, add the following dependencies to your package.
.package(url: "https://github.com/vapor/fluent-mongo-driver.git", from: "1.0.0"),
.product(name: "FluentMongoDriver", package: "fluent-mongo-driver")
Once the dependencies are added, configure the database's credentials with Fluent using app.databases.use
in configure.swift
.
To connect, pass a connection string in the standard MongoDB connection URI format.
import Fluent
import FluentMongoDriver
try app.databases.use(.mongo(connectionString: "<connection string>"), as: .mongo)
Models¶
Models represent fixed data structures in your database, like tables or collections. Models have one or more fields that store codable values. All models also have a unique identifier. Property wrappers are used to denote identifiers and fields as well as more complex mappings mentioned later. Take a look at the following model which represents a galaxy.
final class Galaxy: Model {
// Name of the table or collection.
static let schema = "galaxies"
// Unique identifier for this Galaxy.
@ID(key: .id)
var id: UUID?
// The Galaxy's name.
@Field(key: "name")
var name: String
// Creates a new, empty Galaxy.
init() { }
// Creates a new Galaxy with all properties set.
init(id: UUID? = nil, name: String) {
self.id = id
self.name = name
}
}
To create a new model, create a new class conforming to Model
.
Tip
It's recommended to mark model classes final
to improve performance and simplify conformance requirements.
The Model
protocol's first requirement is the static string schema
.
static let schema = "galaxies"
This property tells Fluent which table or collection the model corresponds to. This can be a table that already exists in the database or one that you will create with a migration. The schema is usually snake_case
and plural.
Identifier¶
The next requirement is an identifier field named id
.
@ID(key: .id)
var id: UUID?
This field must use the @ID
property wrapper. Fluent recommends using UUID
and the special .id
field key since this is compatible with all of Fluent's drivers.
If you want to use a custom ID key or type, use the @ID(custom:)
overload.
Fields¶
After the identifier is added, you can add however many fields you'd like to store additional information. In this example, the only additional field is the galaxy's name.
@Field(key: "name")
var name: String
For simple fields, the @Field
property wrapper is used. Like @ID
, the key
parameter specifies the field's name in the database. This is especially useful for cases where database field naming convention may be different than in Swift, e.g., using snake_case
instead of camelCase
.
Next, all models require an empty init. This allows Fluent to create new instances of the model.
init() { }
Finally, you can add a convenience init for your model that sets all of its properties.
init(id: UUID? = nil, name: String) {
self.id = id
self.name = name
}
Using convenience inits is especially helpful if you add new properties to your model as you can get compile-time errors if the init method changes.
Migrations¶
If your database uses pre-defined schemas, like SQL databases, you will need a migration to prepare the database for your model. Migrations are also useful for seeding databases with data. To create a migration, define a new type conforming to the Migration
or AsyncMigration
protocol. Take a look at the following migration for the previously defined Galaxy
model.
struct CreateGalaxy: AsyncMigration {
// Prepares the database for storing Galaxy models.
func prepare(on database: Database) async throws {
try await database.schema("galaxies")
.id()
.field("name", .string)
.create()
}
// Optionally reverts the changes made in the prepare method.
func revert(on database: Database) async throws {
try await database.schema("galaxies").delete()
}
}
The prepare
method is used for preparing the database to store Galaxy
models.
Schema¶
In this method, database.schema(_:)
is used to create a new SchemaBuilder
. One or more field
s are then added to the builder before calling create()
to create the schema.
Each field added to the builder has a name, type, and optional constraints.
field(<name>, <type>, <optional constraints>)
There is a convenience id()
method for adding @ID
properties using Fluent's recommended defaults.
Reverting the migration undoes any changes made in the prepare method. In this case, that means deleting the Galaxy's schema.
Once the migration is defined, you must tell Fluent about it by adding it to app.migrations
in configure.swift
.
app.migrations.add(CreateGalaxy())
Migrate¶
To run migrations, call swift run App migrate
from the command line or add migrate
as an argument to Xcode's App scheme.
$ swift run App migrate
Migrate Command: Prepare
The following migration(s) will be prepared:
+ CreateGalaxy on default
Would you like to continue?
y/n> y
Migration successful
Querying¶
Now that you've successfully created a model and migrated your database, you're ready to make your first query.
All¶
Take a look at the following route which will return an array of all the galaxies in the database.
app.get("galaxies") { req async throws in
try await Galaxy.query(on: req.db).all()
}
In order to return a Galaxy directly in a route closure, add conformance to Content
.
final class Galaxy: Model, Content {
...
}
Galaxy.query
is used to create a new query builder for the model. req.db
is a reference to the default database for your application. Finally, all()
returns all of the models stored in the database.
If you compile and run the project and request GET /galaxies
, you should see an empty array returned. Let's add a route for creating a new galaxy.
Create¶
Following RESTful convention, use the POST /galaxies
endpoint for creating a new galaxy. Since models are codable, you can decode a galaxy directly from the request body.
app.post("galaxies") { req -> EventLoopFuture<Galaxy> in
let galaxy = try req.content.decode(Galaxy.self)
return galaxy.create(on: req.db)
.map { galaxy }
}
Seealso
See Content → Overview for more information about decoding request bodies.
Once you have an instance of the model, calling create(on:)
saves the model to the database. This returns an EventLoopFuture<Void>
which signals that the save has completed. Once the save completes, return the newly created model using map
.
If you're using async
/await
you can write your code as so:
app.post("galaxies") { req async throws -> Galaxy in
let galaxy = try req.content.decode(Galaxy.self)
try await galaxy.create(on: req.db)
return galaxy
}
In this case, the async version doesn't return anything, but will return once the save has completed.
Build and run the project and send the following request.
POST /galaxies HTTP/1.1
content-length: 21
content-type: application/json
{
"name": "Milky Way"
}
You should get the created model back with an identifier as the response.
{
"id": ...,
"name": "Milky Way"
}
Now, if you query GET /galaxies
again, you should see the newly created galaxy returned in the array.
Relations¶
What are galaxies without stars! Let's take a quick look at Fluent's powerful relational features by adding a one-to-many relation between Galaxy
and a new Star
model.
final class Star: Model, Content {
// Name of the table or collection.
static let schema = "stars"
// Unique identifier for this Star.
@ID(key: .id)
var id: UUID?
// The Star's name.
@Field(key: "name")
var name: String
// Reference to the Galaxy this Star is in.
@Parent(key: "galaxy_id")
var galaxy: Galaxy
// Creates a new, empty Star.
init() { }
// Creates a new Star with all properties set.
init(id: UUID? = nil, name: String, galaxyID: UUID) {
self.id = id
self.name = name
self.$galaxy.id = galaxyID
}
}
Parent¶
The new Star
model is very similar to Galaxy
except for a new field type: @Parent
.
@Parent(key: "galaxy_id")
var galaxy: Galaxy
The parent property is a field that stores another model's identifier. The model holding the reference is called the "child" and the referenced model is called the "parent". This type of relation is also known as "one-to-many". The key
parameter to the property specifies the field name that should be used to store the parent's key in the database.
In the init method, the parent identifier is set using $galaxy
.
self.$galaxy.id = galaxyID
By prefixing the parent property's name with $
, you access the underlying property wrapper. This is required for getting access to the internal @Field
that stores the actual identifier value.
Seealso
Check out the Swift Evolution proposal for property wrappers for more information: [SE-0258] Property Wrappers
Next, create a migration to prepare the database for handling Star
.
struct CreateStar: AsyncMigration {
// Prepares the database for storing Star models.
func prepare(on database: Database) async throws {
try await database.schema("stars")
.id()
.field("name", .string)
.field("galaxy_id", .uuid, .references("galaxies", "id"))
.create()
}
// Optionally reverts the changes made in the prepare method.
func revert(on database: Database) async throws {
try await database.schema("stars").delete()
}
}
This is mostly the same as galaxy's migration except for the additional field to store the parent galaxy's identifier.
field("galaxy_id", .uuid, .references("galaxies", "id"))
This field specifies an optional constraint telling the database that the field's value references the field "id" in the "galaxies" schema. This is also known as a foreign key and helps ensure data integrity.
Once the migration is created, add it to app.migrations
after the CreateGalaxy
migration.
app.migrations.add(CreateGalaxy())
app.migrations.add(CreateStar())
Since migrations run in order, and CreateStar
references the galaxies schema, ordering is important. Finally, run the migrations to prepare the database.
Add a route for creating new stars.
app.post("stars") { req async throws -> Star in
let star = try req.content.decode(Star.self)
try await star.create(on: req.db)
return star
}
Create a new star referencing the previously created galaxy using the following HTTP request.
POST /stars HTTP/1.1
content-length: 36
content-type: application/json
{
"name": "Sun",
"galaxy": {
"id": ...
}
}
You should see the newly created star returned with a unique identifier.
{
"id": ...,
"name": "Sun",
"galaxy": {
"id": ...
}
}
Children¶
Now let's take a look at how you can utilize Fluent's eager-loading feature to automatically return a galaxy's stars in the GET /galaxies
route. Add the following property to the Galaxy
model.
// All the Stars in this Galaxy.
@Children(for: \.$galaxy)
var stars: [Star]
The @Children
property wrapper is the inverse of @Parent
. It takes a key-path to the child's @Parent
field as the for
argument. Its value is an array of children since zero or more child models may exist. No changes to the galaxy's migration are needed since all the information needed for this relation is stored on Star
.
Eager Load¶
Now that the relation is complete, you can use the with
method on the query builder to automatically fetch and serialize the galaxy-star relation.
app.get("galaxies") { req in
try await Galaxy.query(on: req.db).with(\.$stars).all()
}
A key-path to the @Children
relation is passed to with
to tell Fluent to automatically load this relation in all of the resulting models. Build and run and send another request to GET /galaxies
. You should now see the stars automatically included in the response.
[
{
"id": ...,
"name": "Milky Way",
"stars": [
{
"id": ...,
"name": "Sun",
"galaxy": {
"id": ...
}
}
]
}
]
Query Logging¶
The Fluent drivers log the generated SQL at the debug log level. Some drivers, like FluentPostgreSQL, allow this to be configured when you configure the database.
To set the log level, in configure.swift (or where you set up your application) add:
app.logger.logLevel = .debug
This sets the log level to debug. When you next build and run your app, the SQL statements generated by Fluent will be logged to the console.
Next steps¶
Congratulations on creating your first models and migrations and performing basic create and read operations. For more in-depth information on all of these features, check out their respective sections in the Fluent guide.