Vapor – Deep Dive into Setup and Deployment for Heroku and Ubuntu

It can be a real challenge picking the right back end for your mobile app. For instance, Swift developers don’t have anything without needing to run on another language. Therefore, I decided to do a review of Vapor and whether it is really a right choice.

What I found was that for Swift developers, Vapor is a really great choice and fairly easy to work with. In this article I am going to cover how to get started, specifically:

Setting Up Your Mac For Vapor Development

In order to start developing a Vapor application, we’ll need to setup our Mac. According to the Vapor docs, you need Xcode 9.3 or greater. However for this tutorial, I used the latest version of macOS and Xcode at the time of writing:

  • macOS Mojave 10.14.3
  • Xcode 10.1

Prerequisites For Vapor

I also have installed Homebrew. To install Homebrew, open your terminal application and type:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`

For more details you can go to the official Homebrew home page.

Installing Vapor

Once you have Homebrew installed we can go ahead and install Vapor on your Mac. Before we proceed, we’ll need to add the Vapor repository to Homebrew.

  1. Firstly, add the Vapor repository using the tap command:
brew tap vapor/tap
  1. Next, install the Vapor toolkit:
brew install vapor/tap/vapor

As a result of installing the Vapor toolkit, we’ll have access to the Vapor CLI tool. Therefore, you can get access to the list of commands available by typing:

vapor --help

For example, with the Vapor CLI tool, you can:

  • Build the application
  • Run the application
  • setup for Heroku deployment later
  • Create the Xcode project and of course…
  • Create the new Swift package

Creating Your First Vapor Project

Therefore, we can create the Switch package which will contain the dependencies needed to run our server application with the command:

vapor new app-collection

Additionally, we can create an Xcode project from our Vapor application Swift package by typing:

vapor xcode

As a result, we can open the newly created Xcode project.

Switching the target to Run in Xcode
Switching the target to Run
Run the Vapor app from Xcode
Building and running the Run target

After that, switch the target in the top left to Run and press the play button to run the application. Consequently, you should see a log message at the bottom stating that a http server is started at port 8080.

As a result, go to the terminal and run the command:

curl http://localhost:8080/hello

Therefore, you should receive the message:

Hello, world!%

In conclusion, we’ve successfully used the provided Vapor templates with the new and xcode commands to create the Swift package and build the application in Xcode. However the application only used SQLite for our database storage. Instead, let’s switch to using a more common database: PostgreSQL.

Setting Up For PostgreSQL

PostgreSQL has been my favorite relational SQL based database server. As a result, we are going to look at converting the current Vapor project to use PostgreSQL rather than the default SQLite.

Therefore, make sure you already have PostgreSQL installed. Consequently, if you don’t you can use Homebrew to install PostgreSQL with the following command:

brew install postgresql

Further, to run the server from the Terminal:

pg_ctl -D /usr/local/var/postgres start

Similarly, you can stop the server from the Terminal with:

pg_ctl -D /usr/local/var/postgres stop

On the other hand, if you wish to run PostgreSQL as background service with launchd you can use the brew services command:

brew services start postgresql

Most importantly, once PostgreSQL is started, we’ll need to add the database and user for our application to use.

Setup Database and User

As a result of switching the PostgreSQL, we’ll need to add the user and database our Vapor application will use. Therefore, let’s run the PostgreSQL terminal-based front-end psql by going to the Terminal and typing:

psql -d postgres

After that, at the psql prompt, create the database with:

create database app_collection;

Likewise, we need to create a user. To clarify, we are creating a user with no password by typing:

create user app_collection;

Subsequently grant that user the privileges it needs with:

grant all privileges on database app_collection to app_collection;

As a result, our database should be ready for access. However, we’ll need to update the code and specifically the Swift package to use PostgreSQL.

Updating the Code For PostgreSQL

Update Package.swift with PostgreSQL

In order to use PostgreSQL in our backend application, we’ll need to update the Package.swift file to use PostgreSQL as opposed to SQLite.

Therefore, open the Package.swift and update dependencies by changing the line from:

.package(url: "https://github.com/vapor/fluent-sqlite.git", from: "3.0.0")

to the line:

.package(url: "https://github.com/vapor/fluent-postgresql.git", from: "1.0.0")

To clarify, we’ll be using Fluent, Vapor’s ORM Framework, along with PostgreSQL. In other words, we are swapping the Fluent driver for SQLite with the one for PostgreSQL. Likewise, if you can read more details on the different drivers and how their setup works in Fluent’s documentation.

Consequently, we need to also update the App targets to use the new FluentPostgreSQL dependency. Therefore change the line:

 .target(name: "App", dependencies: ["FluentSQLite", "Vapor"]),

to the line:

 .target(name: "App", dependencies: ["FluentPostgreSQL", "Vapor"]),

In addition to updating the Package.swift file, we need to actually update our Xcode project to use the new dependencies.

Update Packages

Therefore, in order to fetch and update the package with the new dependencies and update the Xcode project file, we can run the xcode command again with:

vapor xcode

As a result, vapor has fetched the Fluent PostgreSQL driver and updated the Xcode project. Therefore, we can move forward by updating the code.

Update the Code

Consequently, open the update Xcode project. Since we have removed the Fluent SQLite driver, we should no longer be able to build the project. As a result we need update our models to use PostgreSQL as well as the Provider, Database, and Migration used in our configuration. Therefore, open the configure.swift.

Firstly, let’s update the import at the top of the file from:

import FluentSQLite

to:

import FluentPostgreSQL

Secondly, update the provider by updating the line:

try services.register(FluentSQLiteProvider())

to

try services.register(FluentPostgreSQLProvider())

Further, update the database used by changing the line from:

// Configure a SQLite database
let sqlite = try SQLiteDatabase(storage: .memory)

// Register the configured SQLite database to the database config.
var databases = DatabasesConfig()
databases.add(database: sqlite, as: .sqlite)
services.register(databases)

to:

// Configure a PostgreSQL database
let postgreSQLConfig = PostgreSQLDatabaseConfig(hostname: "localhost", username: "app_collection")
let postgreSQL = PostgreSQLDatabase(config: postgreSQLConfig)

// Register the configured PostreSQL database to the database config.
var databases = DatabasesConfig()
databases.add(database: postgreSQL, as: .psql)
services.register(databases)

In other words, we are replacing the memory based SQLite database with the PostgreSQL local database configured with the user name app_collection.

To clarify, the PostgreSQLDatabaseConfig initializer can multiple parameters in this case we are using the default port number and the default database name which matches with user name, in this case app_collection.

Lastly, in the configuration.swift, update the migration for the Todo model. To clarify, migrations are used to create and update the tables and relationships used by Fluent. Therefore, in this case we just need to change the parameter in this line:

migrations.add(model: Todo.self, database: .sqlite)

to this line:

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

In the same vein, we need to change the subclasses used by our model by opening Todo.swift. After that change the import statement at the top again and then update model subclass from:

final class Todo: SQLiteModel {

to

final class Todo: PostgreSQLModel {

As a result, we can go ahead and run the Vapor application.

Run the Code

To sum up, now that have setup the PostgreSQL database on our machine, update the Swift package dependencies, and updated the code accordingly; we can build and run the Vapor application. Therefore, in Xcode, run the Run target again. After that, let’s go to the Terminal app to test the application. For example we can run again:

curl http://localhost:8080/hello

However, we can also add a Todo item by running:

curl -X POST -H "Content-Type: application/json" -d '{"title": "example"}' http://localhost:8080/todos

Consequently, we can verify the Todo item was added by calling:

curl http://localhost:8080/todos

As a result, we get the JSON response:

[{"id":1,"title":"example"}]

Subsequently, let’s deploy this to the cloud. Firstly, we’ll look into Heroku.

Deploying Vapor to Heroku

In addition to setting up our application for PostgreSQL, we will move forward with deploying it Heroku. Above all, Heroku offers the easiest way to get up and running. In addition, it offers enough plugins and services on its free tier that we can simply get started. Therefore, let’s begin by setting up our project for Heroku.

Preparing Vapor Package For Heroku

Importantly, make sure you have an account setup at Heroku. After that, go to the Terminal again, and in the project directory, we can simply run the command:

vapor heroku init

As a result, of running the command, we have created a Heroku application and added :

  • Created a Heroku application in your account then
  • Vapor buildpack for Heroku deployment
  • git remote for Heroku
  • Procfile needed for Heroku to run the application

The Procfile, which you read about in the Procfile documentation, contains the following line:

web: Run serve --env production --port $PORT --hostname 0.0.0.0

Moreover when you run the command, you do not need the supply any customizations. However we will need to add PostgreSQL to our Heroku application.

Adding PostgreSQL To Heroku Instance

Similarly, as we have setup the Vapor application in Heroku via the Terminal, we need to add PostgreSQL to our application. Therefore, we can add the Heroku repo and install the heroku command with Homebrew by running:

brew tap heroku/brew && brew install heroku

Consequently, we can add PostgreSQL by running the command:

heroku addons:create heroku-postgresql:hobby-dev

As a result of running this command, we’ve added a free hobby level instance of PostgreSQL to our application.

Consequently, Heroku will add an environment variable for the database called DATABASE_URL. Therefore, we’ll need to update our code to use this.

Update Code For Heroku PostgreSQL

Since we have added PostgreSQL to our Heroku instance, we need to update our code to use the environment the variable for the new database url, DATABASE_URL. Therefore, open the configure.swift. After that, change the lines where we setup the database from:

let postgreSQLConfig = PostgreSQLDatabaseConfig(hostname: "localhost", username: "app_collection")

to:

let postgreSQLConfig : PostgreSQLDatabaseConfig
  
if let url = Environment.get("DATABASE_URL") {
  postgreSQLConfig = PostgreSQLDatabaseConfig(url: url)!
} else {
  postgreSQLConfig = PostgreSQLDatabaseConfig(hostname: "localhost", username: "app_collection")
}

As a result of changing the code, we will be checking if there is an environment variable called DATABASE_URL. In other words, we are using the static methodEnvironment.get(_:) to fetch the string value if it exists. Secondly, if the variable doesn’t exist we assume this is being run locally and it uses the default settings. To clarify, we are using an unconditional unwrapping since we’d rather it trigger a runtime error than attempt to use the default settings. Lastly, let’s move forward with deploying our application.

Deploying Vapor Package to Heroku

Similarly, we can deploy the application to Heroku in the terminal with one of two commands. Firstly, you can push directly with git using the command:

git push heroku master

Secondly, you can push with the Vapor CLI command:

vapor heroku push

In short, either command will:

  • Firstly, push your code to the Heroku
  • Secondly, build the Application
  • Lastly, run the application (based on the command in the Procfile)

Therefore, if our application instance is called damp-spire-56788 , we should be call our application with curl:

> curl http://damp-spire-56788.herokuapp.com/hello
Hello, world!%
> curl -X POST -H "Content-Type: application/json" -d '{"title": "example"}' http://damp-spire-56788.herokuapp.com/todos
{"id":1,"title":"example"}%
> curl http://damp-spire-56788.herokuapp.com/todos 
[{"id":1,"title":"example"}]%

In short, we can setup our app for Heroku by

  • Firstly, using the Vapor CLI to setup the instance, build pack, Procfile, and git remote
  • Secondly, using setting up a PostgreSQL instance in Heroku
  • After that, updating the code to use the environment variable for the database url to access the Heroku PostgreSQL instance
  • Lastly, pushing the code to Heroku and testing the application

Similarly, now that we know how to deploy to Heroku, let’s look at what we need to do to setup our own Ubuntu server for hosting our Vapor application.

Setting Up Vapor For Ubuntu

In the same way, I showed how to setup our Vapor application for Heroku; we are going to look at what is needed to setup an Ubuntu server. In this case, we are using the latest LTS version of Ubuntu which is Bionic Beaver 18.04. However, according to the documentation, Vapor does support version 14.04 (Trusty Tahr) and up including 18.10 (Cosmic Cuttlefish).

Installing Prerequisites

With a new server setup, we need to install several package. However, first we need add Vapor’s APT repo. Therefore, on the server (as root), run the following command:

eval "$(curl -sL https://apt.vapor.sh)"

After that, we can proceed with installing the software we’ll be using to setup our server:

  • git – for fetching the code of our application
  • nginx – our http server
  • postgresql – our database server
  • supervisor – which is used to monitor and execute the application
  • swift and vapor – which is used to build and run the application

Therefore, we can install the application with:

apt install git supervisor postgresql swift vapor nginx

Setting Up the PostgreSQL Database

Similarly to how we use the DATABASE_URL on Heroku, we are going to setup environment variables in Ubuntu.

Updating the Code

Therefore, open up Xcode and in our project go back to configure.swift. After that, between the import statements but before the configure function add a struct to store the defaults for PostgreSQL:

public struct PostgresDefaults {
  public static let hostname = "localhost"
  public static let username = "app_collection"
  public static let port = 5432
}

To clarify, this struct stores the default host name, user name, and port, we use for PostgreSQL configuration

Moreover change the line in the else statement from:

postgreSQLConfig = PostgreSQLDatabaseConfig(hostname: "localhost", username: "app_collection")

to:

let hostname = Environment.get("DATABASE_HOSTNAME") ?? PostgresDefaults.hostname
let username = Environment.get("DATABASE_USERNAME") ?? PostgresDefaults.username
let database = Environment.get("DATABASE_DATABASE")
let password = Environment.get("DATABASE_PASSWORD")

let port : Int

if let portString = Environment.get("DATABASE_PORT") {
  port = Int(portString) ?? PostgresDefaults.port
} else {
  port = PostgresDefaults.port
}

postgreSQLConfig = PostgreSQLDatabaseConfig(hostname: hostname, port: port, username: username, database: database, password: password, transport: .cleartext)

To sum up, this code fetches from the environment variables for the host name, user name, database, password, and port. Consequently, if any value is invalid or does not exist it falls back to the default setup in the PostgresDefaults struct or null and let’s the PostgreSQLDatabaseConfig use its default value. Likewise, this code will still work on Heroku (with the DATABASE_URL) and on localhost with the defaults setup.

However, before we setup the environment variables for our application, we need to setup the database.

Add Database and User

Therefore, as root, sudo in as the postgres user and run psql:

sudo -u postgres psql

After that, run the following sql statements:

create database app_collection;
create user app_collection with encrypted password 'app_collection_pw';
grant all privileges on database app_collection to app_collection;

To clarify, we are creating:

  • database named app_collection
  • user named app_collection with a password app_collection_pw
  • grant the user all privileges on the database, we’ve created

As a result of setting up the database, we can begin to setup supervisor.

Setting Up Supervisor

Since we are using supervisor, we are going to be setting up a Linux user to run the actual application.

Adding a Linux User

As root user again, run the following command:

adduser app_collection

To clarify, we are setting up user called app_collection which does not need any other metadata (Full Name, Room Number, etc…). Therefore having setup the user, let’s pull and build the code.

Pulling Repo

Having setup the user in Linux, go ahead and super user in as the new user:

su -l app_collection

After that, as the new user, clone the repo:

git clone https://github.com/brightdigit/swift-vapor-app-collection-sample.git app

Subsequently, go to the directory and build the application:

cd app
vapor build

In short, we have cloned the code from our repo and built the application. As a result, we can move forward with configuring supervisor.

Configuring Supervisor

While our application will serve our Rest API and nginx will proxy that connection, supervisor will monitor and control the actual Vapor application process. Therefore, let’s create a configuration for our Vapor application. In other words, as root, create a file here:

/etc/supervisor/conf.d./app_collection.conf

After that, paste this text into the configuration file:

[program:app_collection]
environment =
        DATABASE_PASSWORD="app_collection_pw",
command=vapor run --port=3000 --env=production
directory=/home/app_collection/app/ 
autorestart=true
user=app_collection             
stdout_logfile=/var/log/supervisor/%(program_name)-stdout.log
stderr_logfile=/var/log/supervisor/%(program_name)-stderr.log

To clarify:

  • the name of this process will be program:app_collection
  • we setup an environment variable for the database password
  • the command is to run Vapor from our code’s directory
  • auto restart if the app fails
  • run as our new user
  • lastly, we have setup a path for the application output and errors

Subsequently, we can go ahead and update supervisor by with the following commands:

supervisorctl reread
supervisorctl update

After that, you can test if supervisor was successful by calling the application from port 3000:

curl http://localhost:3000/hello

In short, we’ve setup our Vapor application in supervisor to run on port 3000 with a database password setup as an environment variable. As a result, let’s setup nginx to proxy our application.

Setting Up Nginx

For our http server, we’ll be using nginx. Most importantly, we’ll be setting up a host name for our application. Therefore, we are using the nginxconfig.io to setup the config files. Consequently, you can download the specific nginx configuration we are using from here. In addition, feel free to change host name before downloading.

After that, unzip the file:

unzip nginxconfig.io-app_collection.local.zip

Firstly, we can, as root, move the files to their proper locations:

mv nginx.conf /etc/nginx/nginx.conf
mv sites-available/app_collection.local.conf /etc/nginx/sites-available
ln -s /etc/nginx/sites-available/app_collection.local.conf /etc/nginx/sites-enabled
mv nginxconfig.io /etc/nginx/

Secondly, now that we have moved the configuration files over, restart nginx:

service nginx restart

Most importantly, if you are using a .local host name, make sure to setup the host name on your network. That is to say, make sure the host name is accessible from your local development computer. For instance, on macOS or Linux, you would need to edit the /etc/hosts file. On the other hand, if this is to be a public host name, verify you have updated your dns accordingly.

After you have verified the dns or hosts file is updated, we can proceed if testing the application out:

> curl http://app_collection.local/hello
Hello, world!%
> curl -X POST -H "Content-Type: application/json" -d '{"title": "example"}' http://app_collection.local/todos
{"id":1,"title":"example"}%
> curl http://app_collection.local/todos 
[{"id":1,"title":"example"}]%

In short, we’ve successfully setup the application on an Ubuntu server.

And There’s More…

In conclusion, we’ve successfully:

  • setup our Mac for developing server-side Swift application using Vapor
  • converted the beginner template to use PostgreSQL from SQLite
  • updated the code to use environment variables for different server environments
  • setup and deployed the application to Heroku
  • setup and deployed to an Ubuntu server

To get a full video of this tutorial as well as code examples, fill out this form and let me know what other Vapor topics you are interested in learning more about such as:

  • setting up complicated database models and relationships
  • how to configure advanced Rest API routes
  • writing complex queries using Fluent ORM and Futures in Swift
  • setting up for authentication

I look forward to hearing your feedback and I’ll keep you posted on updates.