Spring Ahead Sale — Save on Everything.All videos. All books. Now 50% off.

Build your mobile development skills and save! Stay ahead of the rest with an Ultimate book & video subscription. Starting at just $149/year as part of the Spring Ahead sale.

Home Server-Side Swift Tutorials

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!

4.7/5 3 Ratings

Version

  • Swift 5, macOS 10.15, Xcode 11
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.

Running Migrations

Open configure.swift and, after app.databases.use(_:as:), add:

// 1
app.migrations.add(CreateAcronym())
  
// 2
app.logger.logLevel = .debug

// 3
try app.autoMigrate().wait()

Here your new code:

  1. Adds CreateAcronym to the list of migrations to run.
  2. Sets the log level for the app to debug. This provides more information and lets you see your migrations.
  3. Automatically runs migrations and waits for the result. Fluent lets you choose when to run your migrations. This is helpful when you need to schedule them. You can use wait() here since you’re not running on an EventLoop.

To test with PostgreSQL, you’ll run the Postgres server in a Docker container. Open Terminal and enter:

docker run --name postgres -e POSTGRES_DB=vapor_database \
  -e POSTGRES_USER=vapor_username \
  -e POSTGRES_PASSWORD=vapor_password \
  -p 5432:5432 -d postgres

Here’s what this does:

  1. Runs a new container named postgres.
  2. Then specifies the database name, username and password through environment variables.
  3. Lets apps connect to the Postgres server on its default port: 5432.
  4. Then runs the server in the background as a daemon.
  5. Uses the Docker image named postgres for this container. If the image isn’t present on your machine, Docker automatically downloads it.

To check if your database is running, enter the following in Terminal to list all active containers:

docker ps

Starting Docker Container

If you have any questions about Docker, check out this post. It breaks down everything you need to know.

Now you’re ready to run the app! Set the active scheme to TILApp with My Mac as the destination. Build and run. Check the console and see the migrations ran.

You’ll see something similar to this:

Acronym migration running

Now that you’ve run the migration, you need to save the model.

Saving Models

When your app’s user enters a new acronym, you need a way to save it.

In Vapor 4, Codable makes this trivial. Vapor provides Content, a wrapper around Codable, which lets you convert models and other data between various formats. It’s used extensively in Vapor.

Open Acronym.swift and add the following to the end of the file to make Acronym conform to Content:

extension Acronym: Content {}

Since Acronym already conforms to Codable via Model, you don’t have to add anything else. To create an acronym, the client sends a POST request containing a JSON payload that looks similar to this:

{
  "short": "OMG",
  "long": "Oh My God"
}

You need a route to handle this POST request and save the new acronym. Open routes.swift and at the end of routes(_:) add:

// 1
app.post("api", "acronyms") { req -> EventLoopFuture<Acronym> in
  // 2
  let acronym = try req.content.decode(Acronym.self)
  // 3
  return acronym.save(on: req.db).map { 
    // 4
    acronym 
  }
}

Here you:

  1. Register a new route at /api/acronyms that accepts a POST request and returns EventLoopFuture. It returns the acronym once it’s saved.
  2. Decode the request’s JSON into an Acronym model using Codable.
  3. Save the model using Fluent and the database from Request.
  4. Because save(on:) returns EventLoopFuture use map to return the acronym when the save completes.

Fluent and Vapor’s integrated use of Codable makes this simple. Since Acronym conforms to Content, it’s easily converted between JSON and Model. This lets Vapor return the model as JSON in the response without any effort on your part.

Build and run the app to try it out. A good tool to test this is RESTed, available as a free download from the Mac App Store. Other tools such as Paw and Postman are suitable as well.

In RESTed, configure the request as follows:

  • URL: http://localhost:8080/api/acronyms
  • method: POST
  • Parameter encoding: JSON-encoded

Add two parameters with names and values:

  • short: OMG
  • long: Oh My God

Setting the parameter encoding to JSON-encoded ensures RESTed sends the data as JSON. It’s important to note this also sets the Content-Type header to application/json, which tells Vapor the request contains JSON. If you’re using a different client to send the request, you may need to set this manually.

Click Send Request and you’ll see the acronym provided in the response. The id field has a value since you now saved it in the database:

RESTed response to POST

Troubleshooting

Some folks get the following error when they run the app:

“[ ERROR ] role "vapor_username" does not exist (InitializeSessionUserId)

You might have this issue if you have PostgreSQL installed on the machine hosting the docker container. To handle this error, stop the PostgreSQL server. For example, if you installed PostgreSQL using Homebrew, type the following into Terminal:

brew services stop postgresql

Then, you can start the docker container and run the app according to the instructions. That should solve the "vapor_username" does not exist’ issue. If it persists, join the chat and discuss the problem there.

Where to Go From Here?

You can download the final project by clicking the Download Materials button at the top and bottom of this page.

In this tutorial you learned how to create models in Vapor and save them to a database. If you enjoyed this tutorial, check out our full-length book on Vapor development: Server Side Swift with Vapor.

If you’re new to web development but have experience with Swift, you’ll find it’s easy to create robust, fully-featured web apps and web APIs with Vapor 4.

Whether you’re looking to create a back end for your iOS app or want to create fully-featured web apps, Vapor is the perfect platform for you.

If you have any questions or comments please join the discussion below.

Average Rating

4.7/5

Add a rating for this content

3 ratings

More like this

Contributors

Comments