
Server-side Swift update project to Vapor 2.0
Hello friends! In this blog post I’ll tell you all you need to do to update your server-side-swift Vapor project from version 1.0 to 2.0. At the start of 2017 I did a small server-side-swift project named FriendService using Vapor 1.0. I wanted to develop it a bit further and add some error handling, so that it would be closer to a proper service. A lot have happened in the last 8-9 months with Vapor, so the first thing to do is to update it from 1.0 to 2.0. I couldn’t find good instruction on how to do that, so I decided to write one myself. You can find all the codes on GitHub. There is a tag Vapor1.0 for the older version of the project as well as Vapor2.0 for the new version.

Update Vapor project to 2.0 version
Update project for Vapor 2.0
Now, there are a few steps you need to do to update the project. After Vapor released the 1.0, Apple has released Swift 3.1 version, which comes with an update to package manager. Vapor also made some changes to Droplet creation and configuration, and Model and Controller (ResourceRepresentable) interfaces. They also introduced some new configuration files, and made changes to Crypto files. All in all, not that much have changed, but enough that I had some trouble when moving to 2.0.
So these are the steps you need to make:
- Update Vapor using homebrew
- Update Xcode to get the latest version from swift
- Update package.swift
- Make changes to Model
- Make changes to Controller (ResourceRepresentable)
- Update Droplet creation and configuration
- Update configuration files
Update tools and Xcode
First thing you need to do is to update the tools. Make sure you have the latest version of Vapor.
1 |
brew upgrade vapor |
After you have run the command you should see this output on the terminal:
1 2 3 4 5 6 7 8 |
==> Upgrading 1 outdated package, with result: vapor/tap/vapor 2.0.4 ==> Upgrading vapor/tap/vapor ==> Downloading https://github.com/vapor/toolbox/releases/download/2.0.4/macOS-sierra ==> Downloading from https://github-production-release-asset-2e65be.s3.amazonaws.com/58954829/9fd7db0c-7f8d-11e7-97e9-511ec9d7a38 ######################################################################## 100.0% ==> mv macOS-sierra vapor 🍺 /usr/local/Cellar/vapor/2.0.4: 3 files, 8.5MB, built in 23 seconds |
Incase something went wrong, make sure you have the latest version of homebrew installed. If it is still not working, check the error printed on the terminal carefully. It should point you to the right direction.
Updating Xcode is easy, just open the App store and select update. Incase you have the latest version you can ignore this step.
Update packaging file
Next, move on to package.swift. The old version looked like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import PackageDescription let package = Package( name: "FriendService", dependencies: [ .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 1, minor: 1), .Package(url: "https://github.com/vapor/mysql-provider.git", majorVersion: 1, minor: 0) ], exclude: [ "Config", "Database", "Localization", "Public", "Resources", "Tests", ] ) |
All you need to do is to change the dependencies. The minor version is no longer needed, since small changes are non-breaking, and always work on top of the major version. Keep the paths unchanged, remove the minor version and set major versions to 2.
After the modification the file should look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import PackageDescription let package = Package( name: "FriendService", dependencies: [ .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2), .Package(url: "https://github.com/vapor/mysql-provider.git", majorVersion: 2) ], exclude: [ "Config", "Database", "Localization", "Public", "Resources", "Tests", ] ) |
Next thing to do is to update the project to see if all works as it should:
1 |
vapor update |
After answering ‘yes’ for regenerating the Xcode dependencies and for opening the Xcode, it is time to check what has changed. When you look at the project folder you should notice that there is a file called Package.pins.
Swift package manager pins project dependencies
Update Model for Vapor 2.0
1 |
let storage = Storage() |
1 2 3 4 5 6 |
init(node: Node, in context: Context) throws { id = try node.extract("id") firstname = try node.extract("firstname") lastname = try node.extract("lastname") phonenumber = try node.extract("phonenumber") } |
In the 1.0 version you can see the Node and Context parameters as well as the id variable. When you compare it to the 2.0 version:
1 2 3 4 5 |
init(row: Row) throws { firstname = try row.get(Friend.firstnameKey) lastname = try row.get(Friend.lastnameKey) phonenumber = try row.get(Friend.phonenumberKey) } |
You notice that Row now replaces Node as mentioned, and the Context is missing completely. Now check the makeNode function from the 1.0:
1 2 3 4 5 6 7 8 |
func makeNode(context: Context) throws -> Node { return try Node(node: [ "id": id, "firstname": firstname, "lastname": lastname, "phonenumber": phonenumber ]) } |
When you compare to the 2.0 version:
1 2 3 4 5 6 7 |
func makeRow() throws -> Row { var row = Row() try row.set(Friend.firstnameKey, firstname) try row.set(Friend.lastnameKey, lastname) try row.set(Friend.phonenumberKey, phonenumber) return row } |
You don’t need to give Context as a parameter anymore, and the name has changed to makeRow (quite convenient!). Also the return value is now Row instead of Node.
1 2 3 4 5 6 7 8 9 10 11 12 |
static func prepare(_ database: Database) throws { try database.create("friends") { friends in friends.id() friends.string("firstname") friends.string("lastname") friends.string("phonenumber") } } static func revert(_ database: Database) throws { try database.delete("friends") } |
As with 2.0 you notice the extension which conforms to Preparation protocol:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
extension Friend: Preparation { static func prepare(_ database: Database) throws { try database.create(self) { builder in builder.id() builder.string(Friend.firstnameKey) builder.string(Friend.lastnameKey) builder.string(Friend.phonenumberKey) } } static func revert(_ database: Database) throws { try database.delete(self) } } |
And holds the functions inside the extension.
There is also some helper protocols that Vapor introduced after the 1.0 release. Not sure if it was before the 2.0 release, but for me these were new things: JSONConvertible and Updateable. JSONConvertible is a helper protocol for JSON handling:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
extension Friend: JSONConvertible { convenience init(json: JSON) throws { try self.init( firstname: json.get(Friend.firstnameKey), lastname: json.get(Friend.lastnameKey), phonenumber: json.get(Friend.phonenumberKey) ) } func makeJSON() throws -> JSON { var json = JSON() try json.set(Friend.idKey, id) try json.set(Friend.firstnameKey, firstname) try json.set(Friend.lastnameKey, lastname) try json.set(Friend.phonenumberKey, phonenumber) return json } } |
It helps you to create an object from JSON and also convert an object back to JSON.
Updateable handles data updates inside the database:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
extension Friend: Updateable { // Updateable keys are called when `friend.update(for: req)` is called. // Add as many updateable keys as you like here. public static var updateableKeys: [UpdateableKey<Friend>] { return [ // If the request contains a String at key "firstname", "lastname" and "phonenumber" // the setter callback will be called. UpdateableKey(Friend.firstnameKey, String.self) { friend, firstname in friend.firstname = firstname }, UpdateableKey(Friend.lastnameKey, String.self) { friend, lastname in friend.lastname = lastname }, UpdateableKey(Friend.phonenumberKey, String.self) { friend, phonenumber in friend.phonenumber = phonenumber } ] } } |
The last protocol the Model needs to conform is ResponseRepresentable. By adding this extension to Friend class, Friend type can be returned directly in the route closures:
1 |
extension Friend: ResponseRepresentable { } |
Updating FriendController
What is new with controller? A little less compared to the Model protocol. Again the Node protocol is removed, so makeNode and node as a parameter is out. Other than that the “VaporMySQL” import is replaced with “MySQLProvider”. Other changes are so small that it is easiest just to look at the file and compare it to old one. Here is the updated file after all the changes have been done:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
// // FriendController.swift // FriendService // // Created by Jussi Suojanen on 20/11/16. // // import Vapor import HTTP import MySQLProvider class FriendController: ResourceRepresentable { func index(request: Request) throws -> ResponseRepresentable { return try Friend.all().makeJSON() } func create(request: Request) throws -> ResponseRepresentable { let friend = try request.friend() try friend.save() return friend } func delete(request: Request, friend: Friend) throws -> ResponseRepresentable { try friend.delete() return Response(status: .ok) } func update(request: Request, friend: Friend) throws -> ResponseRepresentable { try friend.update(for: request) try friend.save() return friend } func makeResource() -> Resource<Friend> { return Resource(index: index, store: create, update: update, destroy: delete) } } // Request extension so Friend can be created from JSON extension Request { func friend() throws -> Friend { guard let json = json else { throw Abort.badRequest } return try Friend(json: json) } } |
To summarise, the code looks cleaner and has a few less lines compared to previous version. Part of the reason is the JSONConvertable and Updataeble protocols that are now in use in the controller code.
Whats new in Droplet in Vapor 2.0?
In 1.0 version you could add all preparations and providers in the droplets constructor. Now you need a configuration variable. You add all models and providers to configuration, and Droplet takes it as parameter in its constructor. Other than that all has stayed pretty much the same. You create a controller and add it as resource with correct routes to the droplet. Then you call run for the droplet to start the service. Now the run command can throw an error so you need to add “try” in front of the call. You can check the differences from the old file:
1 2 3 4 5 6 7 8 9 10 11 12 |
import Vapor import VaporMySQL import HTTP let droplet = Droplet(preparations: [Friend.self], providers: [VaporMySQL.Provider.self]) let friendsController = FriendController() droplet.resource("listFriends", friendsController) droplet.resource("addFriend", friendsController) droplet.resource("editFriend", friendsController) droplet.run() |
compared to the updated file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import Vapor import MySQLProvider import HTTP let config = try Config() try config.addProvider(MySQLProvider.Provider.self) config.preparations.append(Friend.self) let droplet = try Droplet(config) let friendsController = FriendController() droplet.resource("listFriends", friendsController) droplet.resource("addFriend", friendsController) droplet.resource("editFriend", friendsController) try droplet.run() |
Now can I run the project?
If you hit run from the Xcode, you’ll notice that the project is still not working. First make sure that the mysql.server is running on your machine, and that target is App > MyMac. I am sad to tell you that even after these changes the app won’t work. There is still one configuration file needed. As you can see from the error provided, fluent.json is missing:
1 |
fatal error: Error raised at top level: Configuration error: Config/fluent.json required. |
So you also need to provide a configuration file for fluent. All you need to do is to create a new file under the config folder named fluent.json and add these lines to it:
1 2 3 4 5 6 7 8 9 10 11 |
{ "driver": "memory", "keyNamingConvention": "snake_case", "migrationEntityName": "fluent", "pivotNameConnector": "_", "autoForeignKeys": true, "defaultPageKey": "page", "defaultPageSize": 10, "log": false, "maxConnections":10 } |
The file simple provides all the information fluent needs to be able to present the data in the wanted format.
The last thing that you need to do, (and I promise you, this is the last thing) is to change the crypto.json file to look like this:
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "hash": { "method": "sha256", "encoding": "hex", "key": "5678123409871234" }, "cipher": { "method": "aes256", "encoding": "base64", "key": "FRIENDSERVICEISTHEGREATESTESTESTESTESTESTES=" } } |
Conclusions