Database Migrations with Vapor

In this Server-Side Swift tutorial, see how to perform various migrations on your Vapor application database, using PostgreSQL running on Docker. By Jonathan S Wong.

Leave a rating/review
Download materials
Save for later
Share

Data is at the center of your application and at some point, how you represent that data is going to change. In this server-side Swift tutorial, you’ll learn how to migrate the data for your Vapor applications. You’ll use PostgreSQL running on Docker to store your data. You’ll learn how to view your data in a PostgreSQL client for macOS, how to add fields for your model, remove fields, how to add constraints, and how to seed your database with values.

Note: This tutorial assumes you have some experience with using Vapor to build web apps, as well as have a basic understanding of Fluent. See Getting Started with Server-side Swift with Vapor if you’re new to Vapor and Using Fluent and Persisting Models in Vapor if you’re new to Fluent. This tutorial uses PostgreSQL on Docker. If you need a refresher on Docker, see Docker on macOS: Getting Started.

Getting Started

Download the starter project at the top or bottom of this page by clicking on the Download Materials button. RoomFinder is a Vapor application to model conference rooms for your new startup. Unzip the downloaded file and then from a Terminal, cd into the folder that was unzipped and enter the following commands:

cd RoomFinder-Starter
open Package.swift

The first command takes you into the directory of the starter project for the RoomFinder app. The second command opens the project in Xcode, using Xcode 11’s new SwiftPM integrations.

Running PostgreSQL On Docker

This tutorial requires you to run PostgreSQL on Docker. Assuming you have Docker installed and running on macOS, in Terminal, enter the following command:

docker run --name postgres -e POSTGRES_DB=vapor \
-e POSTGRES_USER=vapor -e POSTGRES_PASSWORD=password \
-p 5432:5432 -d postgres

This command:

  • Runs a new container named postgres.
  • Pulls down the image if it currently does not exist on your machine.
  • Specifies the database name, username and password through environment variables.
  • Maps the host port 5432, which is the default port for Postgres, to the container port 5432.

The steps to connect to Postgres through your Vapor application are already implemented in the starter project in Sources/App/configure.swift.

Create a Room Model

You’ll start by creating a model class for the conference room data you want to migrate.

In Xcode, create a new file within the Sources/App/Models folder, and call the new file Room.swift:

Add Room.swift

Change the contents of the new file to the following:

import FluentPostgreSQL
import Vapor

final class Room: Codable {
  var id: UUID?
}

The model class contains a single optional UUID property to hold a room’s unique identifier if it has been set. Once the Room has been saved, it’s ID is set. All Fluent models must conform to Codable. The class is marked final to provide performance benefits by the compiler.

Next, you’ll add two extensions to Room.swift that make the Room class conform to convenience protocols. First,

extension Room: PostgreSQLUUIDModel {}

The Fluent packages provide Model convenience protocols for each database provider and tell Fluent which type the ID will be. There’s also PostgreSQLModel and PostgreSQLStringModel protocols for models with IDs of type Int or String.

Now add the second extension:

extension Room: Content {}

The Content protocol is added so that you can use this model in your router.

Introducing Postico

Postico is a great free PostgresQL client for macOS that you can use for this tutorial. There is a paid version as well that gives you more advanced features, but you’ll only need the free features. You can download it at https://eggerapps.at/postico/.

Once Postico is installed, you can connect to PostgreSQL on Docker just like you would if PostgreSQL was running on your local machine without Docker since you mapped your local port of 5432 to the container’s port of 5432. Enter the credentials you used earlier when creating your container, as in the screenshot below:

Postico

Remember your super secret password was password.

When you first connect Postico to your database, it should be empty:

Empty database in Postico

Create the Room Migration

In order to save your Room model data into the database, you must create a table for it. This is done through a migration. Migrations allow you to make repeatable changes to your database. By using migrations, you allow another team member to follow in your footsteps by running the same set of migrations. If you start altering the database schema outside of Fluent, other team members will be lost without those same set of manual migrations you did. It’s best practice to let the Fluent framework handle the migration for you.

In the Room model, add the following extension at the bottom:

extension Room: Migration {}

Now that your Room model conforms to Migration, you’re able to tell Fluent to create the table when the app starts. In configure.swift in the Sources/App folder, find the section labeled // Configure migrations. Add the following before services.register(migrations):

migrations.add(model: Room.self, database: .psql)

Set the active scheme to Run with My Mac as the destination. Build and run. In the console, you should see that your migration was run. You should see something similar to the console output below:

Running Migrations

Now that your first migration has been run, in Postico you can click the refresh icon in the toolbar on the right. You should now see two tables, Fluent and Room:

Postico after first run

The Fluent table is used by the Fluent framework to keep track of the migrations. Double-click on it and you should see one entry like the following:

Fluent table

Breaking down the columns, the entry has a unique id, which is of type UUID, that is used as the primary key in the Fluent table. The name column is the name of the migration. This is the name of the migration you added in configure.swift. The batch column is the group of migrations that were run at a single time. If multiple migrations are performed at the same time, they will all have the same batch number. This way, when Fluent reverts a single migration, Fluent will roll back the migrations part of the same batch. The createdAt and updatedAt columns are the timestamps the migration was created at or updated at, respectively.

When a new migration is executed, a row will be entered in the Fluent table. If you run your app again, you will not see the Preparing migration ‘Room’ in the console. You should not edit this table manually: You’re only viewing it in Postico in order to see how Fluent works on your behalf.

Click on the vapor database icon in the top toolbar to go back to the tables view. Double-click on the Room table.

Room table

You can see the Room table is empty since you haven’t added any Rooms yet. You’ve only created the table to hold the Rooms. The table has a single id column that matches the ID you created in code.

Click on the bottom segmented control Structure item to see the table structure.

Room table structure

By looking at the table structure for the Room table, the id column’s type is uuid that matches the ID type you created in code on your Room model.