Swift on Server - Models

Today is the day! You get to store some information in the database! Its a great day! Okay enough enthusium. This is an important part of making a server application. Like a third of everything. Storing information.

We're going to start off from where we last ended so you can use the Persistence example (or your own project) as the starting point.

User Model

First we need to create a new model. In SQL land this will correspond to a table in the database. Fluent has a protocol called Model that our model will inherit from. There are also helpers for IDs, fields, and relations.

First create a new folder in your Server folder and call it Models. In this new folder create a file called User.swift and fill in the following.

User.swift

import Foundation
import Fluent

final class User: Model {
    static var schema: String = "users"

    @ID(key: .id) var id: UUID?
    @Field(key: "username") var username: String
    @Field(key: "password_hash") var passwordHash: String
    @Timestamp(key: "created_at", on: .create) var createdAt: Date?
    @Timestamp(key: "updated_at", on: .update) var updatedAt: Date?
    @Timestamp(key: "deleted_at", on: .delete) var deletedAt: Date?
    
    init() {}
    
    init(username: String, passwordHash: String) {
        self.username = username
        self.passwordHash = passwordHash
    }
}

Theres a few things happening here. First its a final class. This means that the class can not be subclassed. This is fine for our model class since it corresponds to a table in the database and every table should have its own class.

There is a static property called schema, this is just defining which table in the database this model will be queried from. Tradition says that models should be singular and the table name should be plural but you do you do.

The syntax for @something is called a property wrapper and is a new(er) feature of Swift. It lets you create reusable functionality that can be applied to the property it wraps. Currently these can not be nested/composed so only one property wrapper per property.

Technically all the properties are columns in the table but ID and Timestamp have special properties on top of the generic Field.

We are adding a universally unique id (UUID) to the model since we want some way to refer to it in the future. Things like emails or usernames are bad ids since they can (or should be able to) changed by the user in the future. So don't use them as an id!

Instead of password we are storing a hash of the password. Don't be bad and store the password in plaintext! In a future post when we are creating new users I'll show you how to hash the password and store it safely.

The rest of the fields are pretty self explanatory.

Why do we have two different init functions? Because the first one is for Fluent to use when querying and creating the returned models and the seconds is for us to use when creating a new user. The second init is the one you should update/use as you add more fields to the model.

User Migration

Now we can't use the model just yet because our database has no tables and no idea about the user model/table. So we need to create a migration to update the database.

So in the Models folder create a new folder called Migrations and make a file called UserMigrations.swift.

UserMigrations.swift

import Fluent

extension User {
    struct Migrations {
        struct Create: Migration {
            func prepare(on database: Database) -> EventLoopFuture<Void> {
                database.schema("users")
                    .id()
                    .field("username", .string, .required)
                    .field("password_hash", .string, .required)
                    .field("created_at", .string)
                    .field("updated_at", .string)
                    .field("deleted_at", .string)
                    .create()
            }
            
            func revert(on database: Database) -> EventLoopFuture<Void> {
                database.schema("users")
                    .delete()
            }
        }
    }
}

The extension / struct / struct pattern might look a bit odd at first but its because we want to namespace our migrations to the model they are relevant to. This lets us access this migration by calling User.Migrations.Create().

In Fluent, a migration has two required functions: prepare and revert. I think they should be named migrate and revert but I didn't write the library 🤷. Prepare usually creates/modifies the table/columns and revert undoes the changes.

In prepare we are telling the database to select the table/schema called "users" and then listing a bunch of columns we want it to create. As with the model property wrapper, id has a special helper.

Revert just does the opposite. It just deletes the table/schema. If prepare was only adding columns then revert would just remove those columns instead of deleting the whole table.

Both of these functions return EventLoopFuture<Void> which means they return some future which will eventually be resolved. In other languages this function would usually be marked as "async" but Swift doesn't have that yet, its coming in Swift 5.5 which is close to release. If its released before I get further into these posts I'll update the code examples otherwise I'll write a post explaining how to use the EventLoop and Futures in another post. Don't worry too much about it for now.

Update Server

So you have your model and your migration written out. Now all you need to do is update the server so it knows about the migration and then run it so the database is migrated.

Server.swift

public func application() throws -> Application {
    ...
    application.migrations.add([
        User.Migrations.Create(),
    ])
    
    return application
}

Its actually really easy to add migrations to the Server. The order matters since they will be applied (or reverted) in the order listed.

Migrate Database

Next go to your console and run the migration. You should be prompted to complete the migration.

swift run Run migrate

Migrate Command: Prepare
The following migration(s) will be prepared:
+ Server.User.Migrations.Create on default
Would you like to continue?
y/n> y
Migration successful

Now if you open up your database in the GUI of your choice, mine is TablePlus, you should see two tables. One is called "_fluent_migrations" which will list all the migrations Fluent has run on your database. The other is "users" which is the table you just defined!

Congrats to being able to store data in the database! The completed code can be seen here. Next time we'll go into actually storing something now that the table is there.

Subscribe to nodev

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe