Database Migrations With Vapor

In this Server-Side Swift tutorial, learn how to perform database migrations with Vapor on your application database – a useful tool for tasks such as creating tables, seeding data, and adding columns. By Heidi Hermann.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Creating a Tools Migration

In Xcode, add a new folder named Migrations inside your App folder.

Next, add a new Swift file named 21-05-31_Tool+Create.swift to hold your migration defining the entity's table.

For example, you could use 20210123_Tool+AddUpdatedAt if you needed to add an updated_at field to an existing table for the Tool model.

  • Prefix the file name with the date it was created in year/month/day format. This allows you to maintain an overview of the proper order your migrations were created in and the order in which they should be executed.
  • Use a descriptive name for the migration explaining what the migration is doing to the model.
When naming your migration files, it's good practice to:

Replace the new file's contents with:

// 1
import FluentKit

// 2
extension Tool {
  struct Create: Migration {
    // 3
    func prepare(on database: Database) -> EventLoopFuture<Void> {
      return database
        .schema("tools") // 4
        .id() // 5
        .field("name", .string, .required) // 6
        .field("created_at", .datetime) // 7
        .field("updated_at", .datetime)
        .create() // 8
    }

    // 9
    func revert(on database: Database) -> EventLoopFuture<Void> {
      return database.schema("tools").delete()
    }
  }
}

Here you:

  1. Import FluentKit to expose Migration.
  2. Extend your Tool database model and create a struct called Create and make it conform to the Migration protocol. The migration is a nested type to keep migrations organized with their model.
  3. Add the required method func prepare(on:) -> EventLoopFuture.
  4. Provide the schema of the database model.
  5. Create the id for the model.
  6. Add a field for the name of the Tool.
  7. Create the two timestamps for ToolcreatedAt and updatedAt.
  8. Create the table.
  9. Add the required method, func revert(on:) -> EventLoopFuture. Inside, call the .delete() method on the Tool table.
  1. The field names are in snake_case, even though the properties in the application are in camelCase. This is a convention from PostgreSQL, since, by default, it's case insensitive in regard to keys.
  2. The .id() builder method is only available if your ID type is UUID. If you're using an int, you'll have to define the property yourself using .field("id", .int, .identifier(auto: true)).
Note:

Next, open /Configurations/configure.swift and register your migration with the application by replacing the comment on line 47 with the following:

app.migrations.add(Tool.Create())

Next time you run your application with migrate enabled, your model will be created in your database.

Running Your Application

Now, open your build scheme and add migrate under Arguments Passed On Launch.

Xcode build scheme with migrate added to the list of arguments passed on launch.

Xcode build scheme with migrate enabled.

Then, run your application.

Output dialog when running your migrations in Vapor 4.

Output dialog for the create Tool migration.

This will prompt a dialog in your output window telling you which migrations will be run and asking you if you're sure you want to go ahead with them.

Type y and press enter.

Wait for the success command to print, along with a message that the command ended. Then, open the build schema and remove the migrate argument.

You can explicitly run them from your terminal using vapor run migrate, or from inside Xcode like you just did. In both cases, you'll receive the command prompt, but you can also pass -y as an argument to skip the prompt.

You can also run the migrations automatically by either adding try app.autoMigrate().wait() inside your configure.swift, or passing the --auto-migrate flag when you run your application.

Note:
There are a number of options for you to run your migrations.

In the next section, you'll view your database.

Viewing Your Database With Postico

Now, access your database with Postico.

Postico overview of migrated tables.

View of migrated models in Postico.

You can see that Tool/code> has been added to the database, along with another table named _fluent_migrations.

First, open _fluent_migrations.

_fluent_migrations table visualized in Postico. One migration is registered in the table.

Overview of the migrations logged in the _fluent_migrations table.

Vapor autogenerates this table and holds a registry of all the migrations you've run.

Each migration has a unique identifier, the name you gave it, a batch number and timestamps for creation and update.

Since it was your first migration, the batch number is 1. Each time you run a new migration, the batch number will increase by one.

Next, open the tools table, and in the bottom-left corner, choose to view the Structure.

Structure of the tools table in the database. It has four fields with keys and types matching those you defined in your migration.

Structure of the tools table in the database.

Here, you see that the table has four columns that match the ones you defined in your migration:

  1. id: Of type UUID, and with the primary key constraint enabled.
  2. name: Of type TEXT, and with NOT NULL enabled.
  3. created_at: Of type TIMESTAMPZ (timestamp with time zone).
  4. updated_at: Of type TIMESTAMPZ.

Moving on, you'll learn about FieldKeys.

Implementing FieldKeys

As you've seen, the names of fields are defined using strings. This offers a lot of flexibility when changing your tables, but it also introduces a lot of duplicate strings in your app. And, with duplicate strings come a lot of opportunities for you to make avoidable mistakes.

Vapor 4's FieldKey type can be used to work around this. It allows you to define each key only once and reuse it throughout the project, benefitting from Swift's type safety.

Rewriting Your First Migration Using FieldKeys

First, add a new file, FieldKeys.swift, inside the /Configurations folder.

Now, paste the following:

// 1
import FluentKit

// 2
extension FieldKey {
  // 3
  static let createdAt: FieldKey = "created_at"
  static let name: FieldKey = "name"
  static let updatedAt: FieldKey = "updated_at"
}

Here, you:

  1. Import FluentKit.
  2. Extend Vapor's FieldKey.
  3. Add a new static FieldKey per unique key you'll have in your database (across all your tables).

Next, open Tool.swift, scroll to the bottom of the file and paste the following extension:

// 1
extension Tool {
  enum Create_20210531 {
    // 2
    static let schema = "tools"
    // 3
    static let name: FieldKey = .name
    static let createdAt: FieldKey = .createdAt
    static let updatedAt: FieldKey = .updatedAt
  }
}

Here, you:

  1. Extend Tool with Create_20210531. It holds all the keys you need to create your table. Note the wrapper includes the date of the migration.
  2. Add a static variable for the schema name, tools.
  3. Add a FieldKey per field, each matching the FieldKeys you added in FieldKey.swift.

Next, you'll replace your keys.