Swift on Server - Persistence

In the last post we bootstrapped a Swift server. It didn't do anything and honestly the code you'll have after today won't do much either but its important to do. You can start with the code at the end of bootstrapping as a starting point for this post or you can just scroll to the bottom and get the link to the final code from today.

Today we're going to add support for the PostgreSQL database to your server. Its pretty simple to install on macOS with homebrew. You can follow the directions and learn from this other post or follow the official directions here.

After you have your database setup you'll want to create a .env file in the same folder as Package.swift. You'll create a new environment variable called DATABASE_URL and set it to your database config. I've choosen to not have a password on my local database for testing so below you'll see the template and the way I've set mine up.  

.env

DATABASE_URL=postgresql://<username>:<password>@<host>/<database>
DATABASE_URL=postgresql://sami@127.0.0.1/nodevexample

Next we'll update the Package.swift file to include two new dependencies from Vapor for connecting to and working with PostgreSQL. The first is called Fluent and its an ORM that makes working with SQL much easier in code. The second is the specific driver for PostgreSQL.

Package.swift

...
package.dependencies = [
    ...
    .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
    .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"),
]

package.targets = [
    .target(name: "Server", dependencies: [
        ...
        .product(name: "Fluent", package: "fluent"),
        .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver")
    ]),
    ...
]
...

Whenever you see the ... in code examples it just means I've omitted existing code. I haven't figured out how to just show diffs on this blog yet 😅.

Next you'll want to update your Server.swift file to connect to the database. We are using a guard statement so we can error out early. Our server won't really work without the database so why even start it if theres an error configuring our database.

Server.swift

...
import Fluent
import FluentPostgresDriver

enum ApplicationError: Error {
    case missingDatabaseURL
}

public func application() throws -> Application {
    ...
    // Configure Database
    guard let databaseURL = Environment.get("DATABASE_URL") else {
        throw ApplicationError.missingDatabaseURL
    }
    
    try application.databases.use(.postgres(url: databaseURL), as: .psql)
    ...
}

If you're running this in Xcode you might see this warning and an assertion being called. You'll just need to set a working directory for your Run scheme. Click on Run -> Edit Scheme, go to Run tab then Options tab and select a working directory. This should be the same folder your Package.swift is in. Why Xcode does not do this by default for Packages is beyond me.

[ WARNING ] No custom working directory set for this scheme

If you see the same "Server starting ..." message then yay you've configured your database and your server is connecting to it just fine. Thats great and all but you at least want to get something out of it to see that its working correctly 🤣.

Next we're going to make a basic route that fetches some info from the database to make sure the connection is good. In a future post we'll go into more details on routing so just take this with a hint of magic for now (but its really not magic).

Create a new folder and file in same folder as Server.swift to match the following.

- Routes 
    - DatabaseRoutes.swift
- Server.swift

DatabaseRoutes.swift

import Vapor
import FluentPostgresDriver

struct DatabaseRoutes: RouteCollection {
    func boot(routes: RoutesBuilder) throws {
        routes.get("database", "version", use: databaseVersion)
    }
    
    func databaseVersion(request: Request) throws -> EventLoopFuture<String> {
        guard let postgres = request.db as? PostgresDatabase else {
            throw Abort(.imATeapot)
        }
        
        return postgres
            .simpleQuery("SELECT version();")
            .map { $0.debugDescription }
    }
}

What we're doing is creating a route on the server so when you go to <host>/database/version it'll query the database and get the version from it and return is as a string. Don't worry too much about the EventLoopFuture or RouteCollection stuff just get. I'll explain that in a future post. We just want to make sure our database is working and querable.

You'll also need to update Server.swift to register your route collection.

public func application() throws -> Application {
    ...
    try application.register(collection: DatabaseRoutes())
    return application
}

Now you can run your server and test it by hitting that endpoint with curl. You should get a string back with the database version and some other info.

~ curl 127.0.0.1:8080/database/version
[["version": "PostgreSQL 13.3 on x86_64-apple-darwin20.4.0, compiled by Apple clang version 12.0.5 (clang-1205.0.22.9), 64-bit"]]                         

So yay its all working! If you ran into some other message or an error I would check the above links on PostgreSQL to see what the error could be. Vapor and Fluent are pretty good about giving good error messages or bubbling up errors form the database directly.

You can see the final code for this post here. If you have any questions feel free to create an issue on that repo. I'll eventually set up some comment system on this site but this is enough for today 😘.

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