Using Fluent and Persisting Models in Vapor

The Fluent ORM lets you use any number of database engines in your Vapor app. Learn how to persist your models in your server side Swift apps using Vapor! By Tim Condon.

Leave a rating/review
Download materials
Save for later
Share
Update Note: Tim Condon updated this tutorial for Vapor 4. Tim Condon also wrote the original tutorial.

Vapor 4’s impressive advancements to Fluent make the entire database layer more cultivated. Fluent is Vapor’s ORM, or object relational mapping tool. It’s an abstraction layer between the Vapor app and the database that makes working with databases easier and includes several benefits.

With Fluent you don’t have to use the database directly! When you interact directly with a database, you write database queries as strings. These aren’t type-safe and can be painful to use from Swift.

Fluent lets you use any of several database engines, even in the same app. Even better, you don’t need to know how to write queries since you can interact with your models in a Swifty way.

Models, the Swift representation of your data, are used throughout Fluent. They’re the objects, such as user profiles, you save and access in your database. Fluent returns and uses type-safe models when interacting with the database which gives you compile-time safety.

In this tutorial, you’ll learn:

  • How to configure a database for Vapor to use.
  • How to use migrations to create tables in your database.
  • How to use Fluent to save data in Vapor apps.
Note: This tutorial follows the setup from the “Getting Started with Server-side Swift using Vapor 4” tutorial. If you haven’t followed those steps, please visit that tutorial now and return here when you’re done.

Getting Started

Use the Vapor Toolbox to create a new project. In Terminal, enter the following command:

cd ~/vapor

This command takes you into a directory called vapor inside your home directory and assumes you completed the steps in the “Getting Started with Server-side Swift using Vapor 4” tutorial.

Next, enter:

vapor new TILApp

When asked if you want to use Fluent, enter y and then press Enter. Next, enter 1 to choose PostgreSQL as the database, followed by Enter. This uses the template to create a new Vapor project called TILApp and configures PostgreSQL as the database.

New TILApp Project

The template provides example files for models, migrations and controllers. Since you’ll build your own, delete the examples. In Terminal, enter:

cd TILApp
rm -rf Sources/App/Models/*
rm -rf Sources/App/Migrations/*
rm -rf Sources/App/Controllers/*

If prompted to confirm the deletions, enter y. Now, open the project in Xcode:

open Package.swift

This creates a Xcode project from your Swift package, using Xcode’s support for Swift Package Manager. It takes a while to download all the dependencies for the first time. When it’s finished, you’ll see the dependencies in the sidebar and a TILApp scheme available:

Xcode with SwiftPM TILApp Project

First, open configure.swift in the App folder and delete the following line:

app.migrations.add(CreateTodo())

Next, open routes.swift and delete the following line:

try app.register(collection: TodoController())

You removed the remaining references to the template’s example model, migration and controller.

Next, you’ll create the model.

Creating the Model

Create a new Swift file in Sources/App/Models called Acronym.swift. Inside the new file, insert:

import Vapor
import Fluent

// 1
final class Acronym: Model {
  // 2
  static let schema = "acronyms"
  
  // 3
  @ID
  var id: UUID?
  
  // 4
  @Field(key: "short")
  var short: String
  
  @Field(key: "long")
  var long: String
  
  // 5
  init() {}
  
  // 6
  init(id: UUID? = nil, short: String, long: String) {
    self.id = id
    self.short = short
    self.long = long
  }
}

Here you:

  1. Define a class that conforms to Model.
  2. Then specify the schema as required by Model. This is the table’s name in the database.
  3. Define an optional id property that stores the ID of the model, if one has been set. This is annotated with Fluent’s @ID property wrapper which tells Fluent what to use to look up the model in the database.
  4. Then define two String properties to hold the acronym and its definition. These use the @Field property wrapper to denote a generic database field. The key parameter is the name of the column in the database.
  5. Provide an empty initializer as required by Model. Fluent uses this to initialize models returned from database queries.
  6. Finally, provide an initializer to create the model as required.

If you’re coming from Fluent 3, this model looks very different. Fluent 4 leverages property wrappers to provide strong and complex database integration. @ID marks a property as the ID for that table. Fluent uses this property wrapper to perform queries in the database when finding models.

It also uses the property wrapper for relationships. By default in Fluent, the ID must be an UUID, or Universally Unique Identifier, and called id.

@Field marks the model’s property as a generic column in the database. Fluent uses the property wrapper to perform queries with filters. Using property wrappers lets Fluent update individual fields in a model, rather than the entire model.

You can also select specified fields from the database instead of all fields for a model. However, you should only use @Field with non-optional properties. If you have an optional property in your model, you should use @OptionalField.

Now you’ll create a table for the model.

Creating a Table for the Model

Before you can save the model in the database, you must create a table for it. Fluent does this with a migration.

Migrations let you make reliable, testable, reproducible changes to your database. Developers commonly use migrations to create a database schema, or table description, for models. They’re also used to seed data into your database or make changes to your models after you’ve created them.

Fluent 3 could infer a lot of the table information for you. However, this didn’t scale to large complex projects, especially when you needed to add or remove columns or even rename them.

In Xcode, create a new Swift file in Sources/App/Migrations called CreateAcronym.swift.

Insert the following in the new file:

import Fluent

// 1
struct CreateAcronym: Migration {
  // 2
  func prepare(on database: Database) -> EventLoopFuture<Void> {
    // 3
    database.schema("acronyms")
      // 4
      .id()
      // 5
      .field("short", .string, .required)
      .field("long", .string, .required)
      // 6
      .create()
  }
  
  // 7
  func revert(on database: Database) -> EventLoopFuture<Void> {
    database.schema("acronyms").delete()
  }
}

Here the code:

  1. Defines a new type, CreateAcronym, that conforms to Migration.
  2. Then implements prepare(on:) as required by Migration. You call this method when you run your migrations.
  3. Defines the table name for this model. This must match schema from the model.
  4. Next, defines the ID column in the database.
  5. Defines columns for short and long. Sets the column type to string and marks the columns as required. This matches the non-optional String properties in the model. The field names must match the key of the property wrapper, not the name of the property itself.
  6. Then creates the table in the database.
  7. Implements revert(on:) as required by Migration. You call this function when you revert your migrations. This deletes the table referenced with schema(_:).

All references to column names and table names are strings because using properties causes issues if those property names change in the future.

Migrations only run once: Once they run in a database, they never execute again. So, Fluent won’t attempt to recreate a table if you change the migration.

Now that you have a migration for Acronym, you can tell Fluent to create the table.