Full Stack Swift — Deploying a Vapor app

I love Swift. I’m not sure if it’s because of the statically typed nature of the language, or the constantly growing community that are always willing to lend a helping hand. I’d probably lean more towards the latter, in hopes that I can help someone the way that the Vapor community members have helped me understand the wonderful world of web frameworks. Keep in mind that I’m a musician by trade, and this is my first tutorial, but I’m still going to try to explain things as technically proficient as possible. In other words, bear with me here, and if I’m doing something wrong please leave comments. I would always love to learn more.


Prerequisites


From the beginning

First thing is first; I’m going to start at the beginning. Where is the beginning? Installing Swift. Why? The answer is two fold. First, Vapor only works with Swift 3.0, and typically only the latest build or so. This means that we’ll need to install a development preview of the Swift toolchain to get this working. Second, we’ll all be on the same build, and that means you’ll have less problems following the tutorial. Keep in mind, Vapor AND Swift are constantly evolving. This Tutorial is written for Swift DEVELOPMENT-SNAPSHOT-2016–05–31-a, and Vapor 0.9.


Installing Swift with SwiftENV

We’re going to install a tool called SwiftENV first. Open up terminal and type the following commands:

Installing swiftenv

This should install Swift and set up the “swiftenv” command through your bash profile. You’ll probably need to restart terminal after installing this. Pop terminal back open and type the following command in:

swiftenv install DEVELOPMENT-SNAPSHOT-2016–05–31-a

This one might take a while. It’s going to download the swift development snapshot, which is quite large. After it’s finished installing, give terminal the following command to set your swiftenv settings to the snapshot that you just downloaded:

swiftenv global DEVELOPMENT-SNAPSHOT-2016-05-31-a

As long as swift is in your $PATH (if not, google it or head to swift.org’s installation instructions) you should now be able to check that everything worked with:

swift --version

Welcome, Vapor

This is where things start to get cool. The guys over at Qutheory.io have been working diligently on the Swift server side of this whole operation. You can read more documentation on their website, and their Slack channel is full of some really brilliant and kind folks.

To make developers lives easier, Vapor comes with a CLI that will help you create new vapor apps, generate Xcode projects for them, build them, run them, as well as deploy them to Heroku or Docker. How cool?! We can install it with the following commands, one at a time in terminal:

Installing Vapor CLI

Starting a new project

Now that we’ve got the CLI installed, we can start a new vapor project. Type the following into terminal:

vapor new TodoList

This will generate an example app from github. It puts it all in a folder, initializes a new git repo for us, generates an xcode project, and sets up our entire folder structure very similarly to other web server back-ends such as Ruby on Rails or Node.js/express. Neat! Let’s open the main.swift file that lives in the App directory that was just generated for us. If you’re using atom as your text editor, just type the following commands. Otherwise find and open main.swift in your favorite text editor. It lives in the TodoList/App/ directory that was just generated for you:

cd TodoList/
atom .

Main.swift

Find the main.swift file in the left tree-view. It’s in the App directory. Open it up and delete everything. We’re going to start from scratch here. While you’re at it, you’re welcome to delete the App/Controllers/UserController.swift file, as well as App/Models/User.swift — we won’t need those anymore. Next, we’ll need to do some imports:

Then we’ll need a reference to our application, as well as some configuration. (Configuration is saved in the Config/development/app.json file for future reference). Put this is main.swift as well:

Next we need to give a list keys that mustache will use, with values that route to our .mustache files. By default, it will look in the Resources/Views/ folder:

We’re only going to have two endpoints for this tutorial. One that gets a single Todo, and one that gets all of our Todos. In addition, I’ll show you how to respond differently to JSON requests versus HTML requests. A route needs an endpoint path, and a handler. You can also specify a type safe value to restrict requests to a certain type. We’ll do this as well:

And finally, we end Main.swift by starting the web app:

Here is the completed main.swift file:


TodoController.swift

Now that we have our main application running, we need something to handle the routes that we gave it. Right now our app will not compile because we’ve referenced a TodoController that doesn’t exist, so let’s create it. Switch back to terminal and type the following command:

touch App/Controllers/TodoController.swift

Now that you have an empty TodoController.swift, open that file up in a text editor and add the following imports:

Woah, woah, woah. What’s this MongoKitten thing? This is a driver for mongoDB that will help us talk to mLab.com (which will be hosting our database). It’s extremely easy to install thanks to the Vapor CLI and the Swift Package Manager. All you need to do is open the Package.swift file in your /TodoList/ directory, and add the MongoKitten package to your dependencies as follows:

Please note that you’re changing this in Package.swift, NOT in TodoController.swift.

Let’s hop back over to TodoController.swift. We need to create the class, TodoController and have it conform to Controller. We also need to give it an initializer that fires up references to our application and database, as well as a typealias for the functions that we’ve yet to build. Those are coming later:

Then we define the two functions that we referenced in Main.swift. These are the index function, and the show function. They are the two endpoints that we routed earlier!

We’re going to fill in the rest of the details later. For now, let’s deal with this Database thing that we’ve put in our controller. We have a reference to it, but it doesn’t exist yet. Let’s fix that!


Database.swift

Hop back in terminal and type:

mkdir App/Database
touch App/Database/Database.swift

Open Database.swift and add the imports:

We’ll make a class that contains the Database, with a singleton variable that will let us reference the database (since we need to reference it in TodoController.swift). We also create variables to reference our MongoDB information. We need references to our server, our database, and our collection (which holds our objects/todos/documents/whatever you want to call them). We’ll also add an initializer that will initialize our connection to our mLab.com database. Lastly, we’ll add an enum that will hold our environment variables. Here is the completed Database.swift file:

As you can see, we’re referencing app.config again, so you’ll need to set the correct values in your Config/secrets/app.json file like so:

These values come from your mLab.com account. Create a new deployment from the mLab.com account dashboard. As you can see in the config file, i named my deployment DB name “vapor_todos”:

values for Config/secrets/app.json

While you’re here, you’ll need to create a User to access the database through the Users -> Add database user page:

add a user and use this info in Config/secrets/app.json

Todo.swift

We have our main.swift file which runs our webapp and defines our routes. We have our TodoController.swift that handles our routes for us (albeit, the routes currently do nothing). Now, we need a model to handle our Todos. Simple enough, we just need a uniqueID used to search, and then some information about the Todo:

We want to be able to turn this Todo into JSON. This is simple, just conform to the JSONRepresentable protocol. Add this to the end of Todo.swift:

We also need to conform to the StringInitializable protocol to use Type-Safe routing with vapor. This is where we’ll start our string of failable convenience initializers so that we can initialize a Todo via just one param, such as its uniqueID:

The way this works is a bit confusing, but we’ve basically given ourselves a way to initialize a Todo from a document, which is the name that mongoDB gives objects. If we use type safe routing, it’ll use our StringInitializable ‘from string’ init, which will call our ‘fromUniqueId’ init, which will eventually call our ‘document’ init, which in turn calls our designated initializer in the class. Phew. Lastly, we add an extension for Mustache to conform to the MustacheBoxable protocol. This is needed to allow mustache to loop over collections on custom types. It’s very similar to the way we set up makeJson() above.

Here is the finished Todo.swift file:


Finishing TodoController.swift

We need to finish the TodoController. Right now, we have the route functions, but they don’t actually do anything yet. Let’s make the show function return JSON:

Now, for the index function we want to list all of our todos, not just one single todo. On top of that, we would like to respond differently depending on if the request wants JSON or HTML, just for fun. So we’ll add a simple if statement to check the headers. We can access the headers through request.headers[“header_key”]. Add this:

When the main.swift file requests the TodoController’s index function, it will find all of the todos in our database via database.todos.find(), and then we’ll map those to into Todo classes. After that, we map those objects into JSON objects, thanks to our Todo conforming to the JSONRepresentable protocol. We then do a small check, if the content type is JSON, we render JSON. Otherwise, we’ll try to render a view with our mustache template, and then pass variables in through its context. Since we passed the “todoList” variable through context, we’ll be able to access it in our mustache files with {{#todoList}}.

Here is the completed TodoController.swift file:


Mustache files

Generate your header and footer that we referenced in main.swift through terminal:

touch Resources/Views/Includes/header.mustache
touch Resources/Views/Includes/footer.mustache
mkdir Resources/Views/Todo/
touch Resources/Views/Todo/index.mustache

Open up header.mustache and put this in it:

Open footer.mustache and put this in it:

Open index.mustache and fill it out:

As you can see, you can pass your included files (listed in main.swift) in using {{>name_here}}. And as I mentioned before, we passed todoList through context into the mustache file, so we can loop through that context thanks to conforming to the MustacheBoxable protocol. You can loop through arrays with “#”, end with “/”, and refer to something with “.” You can do much, much more with mustache and can read more about it here.


Faking Some Data

I’m going to do another tutorial in the future on POST requests for Vapor, so for now, we’re not going to add the ability to create your own Todos. Instead, we’re going to fake some data and put it into the mLab database manually. Go to mLab.com and go to your database. You’ll want to create a new collection that coincides with the data in your Vapor app. As you can recall, we named our MongoKitten.Collection “todos”, so we’ll need to create that collection here in our database. Click Collections, then “add collection” and name it “todos”:

Then click the newly created collection and go to “Add document” and add the following code into the document:

Click “Create and Go Back”. Repeat this process for two more todos on mLab:

and


Can we build yet!?

The truth is, we could have built many times throughout this project, but I wanted to explain how everything linked together before dealing with compiling errors and such. Hop back in terminal and let’s see this thing in action! Run this:

vapor build

Cross your fingers, and on success:

vapor run

We should be up and running now, and able to test our site. Remember that we only have two routes. We have localhost:8080/todos/ and localhost:8080/todos/X, where X is a string that matches the mLab document’s “unique_id” value, because we programmed it that way through our convenience initializers! If you head to localhost:8080/todos, you’ll see our TodoController.index that is running our index.mustache view:

ignore my address bar

Click one and you’ll be taken to the json response, which equates to our TodoController.show:

ignore my address bar

Remember how we made sure our TodoController.index displayed differently for Json requests? Lets try that:

JSON will respond back. Nailed it!


Deploying to Heroku

Vapor and Heroku have really taken all the weight off the developers backs here. All you need to do is run:

git add -A
git commit -am "todo starter"
vapor init heroku

Accept all of the defaults when asked questions regarding app name, build pack, and pushing — just hit enter on all of them. After a bit of time, you’ll get hit back with an address that goes to your todo starter on Heroku. Congratulations!


Bonus: work in Xcode

Vapor has another awesome feature that I forgot to sneak in here. If at any point you want Xcode’s autocomplete, you can generate an xcode project with:

vapor xcode

I couldn’t personally get the secrets/app.json to load, but I didn’t spend much time tinkering with it. If someone else has any idea, shoot me a message.


Thanks for the help

I have to thank the guys at Vapor, the amazing Swift community, and specifically Ross Lebeau, Logan Wright, and Nathan Flurry, and @joannis via the Vapor slack for lending helping hands.


Parting Notes

If there are any errors, or if you have general comments, please shoot me a message and let me know how I can improve. Also, I’ll be heading to WWDC (unofficially) and I’m hoping to attend AltConf and a few of the parties up there next week. If anyone sees me, please reach out. I’m new to the Swift development world, and I would love to help in any way possible. I think most open source projects are a bit daunting for me to jump in, but if you have any project ideas and would like to work together, please don’t hesitate to reach out to me!