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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Database Migrations With Vapor
25 mins
- Getting Started
- Configuring Your Local PostgreSQL Database Using Docker
- Connecting to Your Database
- Writing Your First Migration
- Migrations in Vapor 4
- Creating a Tools Migration
- Running Your Application
- Implementing FieldKeys
- Rewriting Your First Migration Using FieldKeys
- Replacing Stringly Typed Keys with FieldKeys in Tool and Its Migration
- Reverting Your Migration
- Rerunning Your First Migration
- Adding a Maker to Your Tools
- Adding a New Migration
- Running Your Migration
- Adding a Unique Requirement to Your Tool/Maker Combination
- Building and Running Your App
- Seeding Data Using Migrations
- Where to Go From Here?
Replacing Stringly Typed Keys with FieldKeys in Tool and Its Migration
Go through your model and replace all the keys so they refer to the new FieldKeys
constants.
Start by finding the following:
static let schema = "tools"
Replace it with this:
static let schema = Create_20210531.schema
Then, replace the properties and property wrappers for name
, createdAt
and updatedAt
with the following:
@Field(key: Create_20210531.name)
var name: String
@Timestamp(key: Create_20210531.createdAt, on: .create)
var createdAt: Date?
@Timestamp(key: Create_20210531.updatedAt, on: .update)
var updatedAt: Date?
Finally, open 21-05-31_Tool+Create.swift and replace the inside of func prepare(on:) -> EventLoopFuture
with this:
return database
.schema(Tool.schema)
.id()
.field(Tool.Create_20210531.name, .string, .required)
.field(Tool.Create_20210531.createdAt, .datetime)
.field(Tool.Create_20210531.updatedAt, .datetime)
.create()
}
This replaces the strings with the FieldKey
and schemaName
you defined earlier. Now, you have no more duplicate strings in your migration or model, giving you more type safety and making it simpler to change or update fields!
Reverting Your Migration
Next, open then scheme builder in Xcode and add a new argument, migrate --revert
. Make sure that only the checkbox for the new argument is enabled.
Buidl and run. Again, you'll see a dialog in the output window letting you know which migrations will be reverted and then prompting you to say yes, y
, or no, n
.
And, if you open Postico, you'll see that the tools table is gone and _fluent_migrations is empty.
Next, you'll rerun a migration.
Rerunning Your First Migration
Open Xcode again, and in the build scheme, replace the command argument with migrate -y
.
Then, build and run again. Like before, you'll see the dialog in the output window, but this time, the response to the prompt is inferred from the command.
If you open Postico, you'll see that the tools table is back.
Moving on, it's time to add a few things.
Adding a Maker to Your Tools
It's quite common for people to own more than one of the same tool, but from different brands. In our database, it'd be useful to keep track of the maker of a given tool, because to some people it matters when they ask to borrow (and rarely return) it.
First, go to FieldKey.swift and paste the new FieldKey
:
static let maker: FieldKey = "maker"
Next, open Tool.swift and paste the following code at the bottom of the file:
extension Tool {
enum AddMaker_20210601 {
static let maker: FieldKey = .maker
}
}
Then, inside your, model add the new field:
@OptionalField(key: AddMaker_20210601.maker)
var maker: String?
Not every tool will have a known maker, so the field is a String?
that requires the OptionalField
Property Wrapper.
Now, replace Tool with:
init(id: UUID? = nil, name: String, maker: String?) {
self.id = id
self.name = name
self.maker = maker
}
Great! You've added a maker.
Adding a New Migration
Next, create a new file for the update migration named 21-06-01_Tool+AddMaker.swift.
Inside the file, paste the following:
import FluentKit
extension Tool {
// 1
struct AddMaker: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database
.schema(Tool.schema)
// 2
.field(Tool.AddMaker_20210601.maker, .string)
// 3
.update()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database
.schema(Tool.schema)
// 4
.deleteField(Tool.AddMaker_20210601.maker)
.update()
}
}
}
Here, you:
- Create a new migration to add the maker to the database table
- Add the maker field to the database. This time the
.required
constraint is omitted, since the field can benil
. - Call the
.update()
method. - Delete the maker field on revert.
Finally, register the migration in your configure.swift by pasting the following line directly under the create migration:
app.migrations.add(Tool.AddMaker())
With that added, it's time for the next step.
Running Your Migration
Now, build and run with migrate y
enabled.
Again, the dialog is letting you know which migrations are being run. Notice that the yes / no prompt is answered automatically.
Now, open Postico. The database looks like it did before, with two tables, _fluent_migrations and tools.
This is expected, since you didn't create a new table, but rather updated the existing one.
Now, open _fluent_migrations:
Here, you can see there's a second row along with your second migration. Also note that the batch number is 2.
Adding a Unique Requirement to Your Tool/Maker Combination
As your tool catalog grows, you'll realize the need to prevent duplicate entries in the database.
So, it's time to write another migration to add a unique constraint on the combination of tool and maker. Since you aren't adding any new fields to the table, you don't have to create any new keys.
Create a new Swift file in your migrations folder named 21-06-02_Tool+MakeToolUnique.swift.
Then, paste the following:
// 1
import FluentKit
extension Tool {
struct MakeToolUnique: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database
.schema(Tool.schema)
// 2
.unique(
on: Tool.Create_20210531.name, Tool.AddMaker_20210601.maker,
name: "unique_tool_maker"
)
.update()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database
.schema(Tool.schema)
// 3
.deleteUnique(
on: Tool.Create_20210531.name,
Tool.AddMaker_20210601.maker
)
.update()
}
}
}
Here, you:
- Import
FluentKit
to expose the migration APIs - Add a unique constraint on the combination of the two fields
name
andmaker
and call it unique_tool_maker. You can add the unique constraint on any combination of one or many fields and provide a readable name for the constraint if you like. - Delete the unique constraint again when the migration reverts.
Next, register the migration in configure.swift. Paste the following line below the other migrations:
app.migrations.add(Tool.MakeToolUnique())
You're almost done!
Building and Running Your App
Now, confirm you still have the migrate argument enabled in your build scheme.
Build and run. Then, open Postico to view the tools table, selecting the Structure.
Here, you can see that the unique index is added on the combination of name
and maker
with the name you just gave it.