Persisting Data with Vapor 2 and PostgreSQL

This is Part 3 of an ongoing series on Getting Started With Vapor.

For this tutorial, we’re going to combine what we’ve learned thus far to create a few web pages using data we’ve persisted to our database. For this tutorial, we are going to be using PostgreSQL, an open source relational database. Don’t worry if you’re not totally familiar with using databases or writing SQL code. Vapor comes packed with Fluent which is a Swift ORM (Object-Relational Mapping Tool) that makes working with databases incredibly easy. While it is helpful to learn SQL and relational databases, you will notice in this tutorial it’s not necessary for completion.

For our tutorial today we will be making a simple blog application. We’re going to be keeping it simple for this tutorial so we won’t be including any authentication. Okay, let’s dive right in.

First, let’s begin by creating a new Vapor project

Step 1 — Generate a new Vapor project

Open your terminal and enter vapor new blogger

Step 2 — Generate a .xcodeproj project file

In terminal, enter cd blogger && vapor xcode This will take us into the blogger folder and generate an xcode project file

Step 3 — Install Homebrew

Homebrew is a package manager that helps developers install tools to their MacOS devices. Copy and paste the following code to install Homebrew

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

Step 4 — Install Postgresql

In terminal type brew install postgresql

I have Postgresql already installed so your terminal will look slightly different from mine.

Step 5 — Run Postgresql

To start Postgresql run the following command in a new terminal.

postgres -D /usr/local/var/postgres

Once you enter this command you should see the following:

Now that you have Postgresql up and running you need to create a database for your brand new blogger app. Don’t worry if you’re not super comfortable with SQL, we won’t be using alot of it.

Step 6 — Create a new database

In terminal, type psql in a new terminal window to open the PostgreSQLconsole. Make sure you’re not in the postgreSQL logger.

The terminal on the left is our postgresql logger. Type commands in the second window or a new one
Postgresql console

Now that you’re in the right console, we can enter some SQL (pronounced Sequel or Ess-Cue-El ) to create a new database. Enter CREATE DATABASE blogger; into your terminal to generate your database. Don’t forget the semicolon. This might be weird for Swift developers but the semicolon is common in many other languages.

Now that we have a database created let’s exit the PostgreSQL console. To do this, type \q to exit.

Step 7 — Download a PostgreSQL client

Even if you’re familiar with using SQL, it‘s super helpful to use some kind of Postgresql GUI client. These clients make working with PostgreSQL super easy since you can point and click to create new databases, tables etc. The one I like using is Postico. You can connect to your PostgreSQL database by creating a new Favorite and entering your information.

Since you are running PostgreSQL on your local machine, your host will be 127.0.0.1 and by default, PostgreSQL will run on port 5432 . Once you hit connect, you will be connected to your database.

Step 8 — Connect Vapor to PostgreSQL

To use PostgreSQL with Vapor, we will be using the VaporPostgreSQL provider. Firstly, what is a provider?

The Provider protocol creates a simple and predictable way for adding functionality and third party packages to your Vapor project. — Vapor Documentation

To add a provider to your Vapor application, you first need to add the package to Vapor. To do this, we add the following line to the bottom of our Package.swift file

.Package(url: "https://github.com/vapor/postgresql-provider", majorVersion: 1, minor: 0)

Your Package.swift file should now look like this

Package.swift file

Once you have updated your Package.swift file, you must run vapor xcode to download all the packages in the package file and regenerate the Xcode project. Open the blogger.xcodeproj and navigate to main.swift. Right below import Vapor, add import VaporPostgreSQL . After let drop = Droplet() add the following:

try drop.addProvider(VaporPostgreSQL.Provider.self)

By adding the Postgresql provider to the droplet, we have provided Vapor with functionality to utilize our database. Finally, we must add a postgresql.json file to our Config/Secrets folder. This file will contain the configuration information for our database. Since we’re adding it to the Secrets folder it will not be included in any git commits. First, right click on the Config folder, click New Folder and name this folder Secrets. Next, right click on the Secrets folder, click New File and name it postgresql.json. Inside this file add the following:

{
"host": "127.0.0.1",
"user": "johannkerr",
"password": "",
"database": "blogger",
"port": 5432
}

Change the user to your username on your computer. (Type whoami in your terminal to find out your username) The database value must match the database name you created in psql.

Step 9 — Create Blogpost Model

For this application, we will be creating a simple blogging website. So each “blogpost” will need a title and a body. Let’s start by creating a new file called Blogpost.swift. Make sure the App target is checked off and you are saving in the Models folder when creating this file.

Next, add the following code to your Blogpost.swift file:

import Foundation
import Vapor
import Fluent
final class Blogpost: Model {

}

Model is a protocol provided for us by Fluent which will help us connect to our Postgresql database. Before we conform to Model and satisfy its requirements, let’s create some properties and an initializer. Since we are creating a blogging application, let’s give each blogpost a title and a body. Replace your class declaration with the following (keep the imports):

final class Blogpost: Model {
var title: String
var body: String

init(title:String, body:String) {
self.title = title
self.body = body
}

}

To create a model, we also need to include add a property to store each blogpost’s unique id. Right above title, add var id: Node? and var exists: Bool = false. Node is an intermediate data representation used in Vapor.

Node is meant to be a transitive data structure that can be used to facilitate conversions between different types. — Vapor Node Documentation

Next, let’s conform to Model by adding the necessary functions. I highly recommend also reading Vapor’s documentation on Model to see how to do this. Model is a protocol that conforms to several other protocols such as NodeInitializable and NodeRepresentable. To do this we need an initializer that can create the model from the persisted data. Add the following code right below your first initializer.

init(node: Node, in context: Context) throws {
id = try node.extract("id")
title = try node.extract("title")
body = try node.extract("body")
}

The string values you see above must correspond to the column or field names we include in our database table. You should notice the use of throws and try. Swift as a language tries to be safe as much as it can and deal with errors and Vapor follows in this style. Next, add the following function to conform to NodeRepresentable

func makeNode(context: Context) throws -> Node {
return try Node(node: [
"id": id,
"title": title,
"body": body
])
}

When a blogpost is being saved, makeNode is called therefore the string values here must also match the column or field names in the database.

Your Blogpost.swift file should now contain the following:

import Foundation
import Vapor
import Fluent
final class Blogpost: Model {
var id: Node?
var exists: Bool = false
var title: String
var body: String

init(title:String, body:String) {
self.title = title
self.body = body
}

init(node: Node, in context: Context) throws {
id = try node.extract("id")
title = try node.extract("title")
body = try node.extract("body")
}

func makeNode(context: Context) throws -> Node {
return try Node(node: [
"id": id,
"title": title,
"body": body
])
}


}

Next, we have to conform to the Preparation protocol. Some databases such as PostgreSQL and MySQL need to know about their structure before data is added. We need to provide the Blogpost class with a prepare and revert functions. Add the following code to your Blogpost.swift file below makeNode.

static func prepare(_ database: Database) throws {
try database.create("blogposts") { (blogposts) in
blogposts.id()
blogposts.string("title")
blogposts.custom("body", type: "text")
}
}

static func revert(_ database: Database) throws {
try database.delete("blogposts")
}

The prepare function creates a blogposts table and creates columns for id, title and body. title uses the default string schema constructor that gives it a default length of 255 characters. For the body of the blogpost, we will want to exceed that limit so we have use the text type in PostgreSQL . To do this we use the custom function to create the field with the name “body” and the type “text”

The revert function allows us to delete the table. If we want to delete a table we can run vapor run prepare --revert . Now our Blogpost model is complete and we can use it in our vapor project.

Step 11 — Prepare the database

Now that we have connected our models, we need to tell the Vapor droplet to prepare the database. Navigate to main.swift and add the following line below the addProvider line: drop.preparations += Blogpost.self . Hit Cmd + R to run your app. Note: Make sure to change your target from Blogger to App

Once your app is running, it will prepare the database with the Blogpost table.

Blogpost Model being prepared

Switch back to Postico and connect to your database to see your newly created table.

Now that we have created a database and a blogposts table, let’s create some routes to add data to our database.

Step 10 — Create some routes

Let’s make a quick simple route so that we can test out creating a new blogpost. Replace the existing route with the following:

drop.get { req in
var blogpost = Blogpost(title: "Hello", body: "World")
try blogpost.save()
return try blogpost.makeJSON()
}

This code creates a route at localhost that creates an instance of the Blogpost class, saves it to the database and then returns the JSON representation of it to the browser. Run the application and open your browser at http://127.0.0.1:8080.

Open Postico to ensure that your blog post has been created. You may need to hit the refresh button if you had Postico running already.

Now that we’ve created a really simple route let’s add two more routes to our droplet. The first route will simply generate a form while the second one will receive a post action that will save the submitted blog post.

Remove our previous route and replace it with the following:

drop.get { req in
return try drop.view.make("new")
}

As you will remember from the last tutorial, this route will return a page created from the new.leaf file. We haven’t created that file yet so let’s go ahead and create it. Right click on your Views folder, click New File and name the file new.leaf. Add the following code to new.leaf

//1
#extend("base")
#export("head") {
<title>My Blog App</title>
//2
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
}
#export("body") {
// 3
<div class="col-lg-4">
<form action="/submit" method="POST">
<div class="form-group">
<label for="title">Title</label>
<input type="text" class="form-control" name="title" placeholder="Title">
</div>
<div class="form-group">
<label for="body">Body</label>
<textarea class="form-control" rows="3" name="body"></textarea>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
}

1 . First, we extend the base.leaf file by using extend("base"). This allows us to use base.leaf as a template for other files

2. Then, we export the head section and include Bootstrap. Bootstrap is a widely popular HTML, CSS and JS framework that will provide us with some out of the box styling for our form.

3. Next, we export the body section and add our submit form. Our submit form has a field for the post title and a text area for the post body. The form sends a POST action to /submit which we will eventually build.

Now, that we have our form built, let’s run our project just to make sure its working. If you open your app in our browser you should see the following. Go ahead and try to submit a blogpost.

Since we haven’t created a /submit route, if you click submit you will get a 404 error. Right below our last route let’s add a route to handle submit.

drop.post("submit") { req in

guard let title = req.formURLEncoded?["title"]?.string, let body = req.formURLEncoded?["body"]?.string else {
return "Missing Fields"
}

var blogpost = Blogpost(title: title, body: body)
try blogpost.save()

return try blogpost.makeJSON()
}

The code above creates a Post request route. From the request, we get the values submitted through our url encoded form and then use them to create an instance of Blogpost, save it and then return the JSON representation of it. Go ahead and run your application and submit a short blogpost. Once you hit the submit you should see something looking like this.

Our database table has also been updated to reflect our submission.

Step 11 — Querying the database

After adding items to the database, you may want to be able to retrieve the items you have persisted. Vapor and Fluent provide very simple ways for us to get back information. Let’s create a route to get all of the blog posts from the database.

drop.get("blogposts") { req in

let blogposts = try Blogpost.query().all()
return try blogposts.makeJSON()

}

We’ve created a GET route at /blogposts that when hit, calls the query function on Blogpost which creates a Blogpost query and then the all() function which returns all Blogposts from the query in an array. Once we have our blogposts array, we can call makeJSON() to render it to the browser. Let’s run our app and navigate to /blogposts.

As expected, our route displays both blog posts we saved. What if we wanted to get specifically the blog with the id 2 or any id how would we do that?. Let’s create a new route to handle this scenario. Right below the last route add the following code.

drop.get("blogposts", Int.self) { req, blogId in

guard let blog = try Blogpost.query().filter("id", blogId).first() else { return "No Blog found" }

return try blog.makeJSON()

}

In the above code, we create a GET route to handle requests at blogposts/{blogId}. The blogId here is a number that can be typed into the browser, for example, blogposts/1 should return the blog titled “Hello”. Instead of using query().all(), we use the filter() function that takes two arguments: one for the column name eg id, title, body and the second for what we want to search for. In this case, we would like to filter based on the number typed into the browser with the url so we use the blogId . We use the first() function to get the first item in the array. If no item is found at first this means that the filter has found no results, therefore, we can return to indicate to the user that nothing has been found. If an item is found we then use makeJSON() to output it to the browser. Run your code and try navigating to localhost:8080/blogposts/1 which should return the blog titled “Hello” and also try navigating to localhost:8080/blogposts/3 which we know has no blog.

Voila! We’ve now persisted data to our database and also retrieved that data back from the database. Now it’s your turn to expand this example further. Take what you learned in this tutorial and Part 2 to create a web page for each individual blog and all the blogs listed. HINT Use the Leaf’s loop tag to display all blogs. Good luck!

If you’re looking for the completed code, you can find it on my Github