
Server-side Swift with Vapor 3 – backend set up
Today I wanted to write about Server-side Swift and how to set up a basic backend with Vapor 3 framework. We’ll go through how to create a simple CRUD backend which we can use to create, remove, update and delete information. We accomplished all this by creating a project called FriendService. FriendService is a project that allows you to store friends information and it works as a backend for Friend app. I wrote the project 2 years ago using Vapor 1, updated it to use Vapor 2 about a year ago, and finally it is time to update the project for Vapor 3. 🙂
Vapor framework has been evolving through all these years and now it is a lot simpler to get your backend up and running. Also since Vapor now also supports Codable we need to write a lot less code when storing and fetching data from the database. After comparing Vapor 2 with Vapor 3 I decided that it is easier to do a rewrite than to convert the project to Vapor 3.
We’ll also learn how to use Docker to start MySQL server in localhost. This comes handy for the database needs for our project. As always, you can get all the codes from GitHub. I encourage you to read Ray Wenderlichs and Paul Hudson’s books on Server-side Swift with Vapor. Both of the books have been a great help for me. But now it is time for us to get started.
Install Vapor and set up FriendService
We need to install Vapor for our machine before we can start our server-side Swift development. We can do that using homebrew(follow the instructions to install it, in case you don’t have it installed). All we need to do is to type brew install vapor to install Vapor.
After that we navigate to the folder we want to create the project and type the following commands:
1 2 3 4 5 |
vapor new FriendService cd FriendService/ vapor xcode |
This creates the project and after we have answered ‘yes’ to the request to open the project in Xcode, we’ll see the project opened in it. We can see there is already Todo.swift and TodoController.swift files, under the Models and Controllers folder. The first thing we’ll do is to delete these two files. Also, we want to remove all the “Todo” related stuff from the Configure.swift file to start with a fresh project.
We need two classes for the project to work the way we want. One that controls all the routes and requests for the database called FriendController. And another one that models the actual database information and table called Friend. But first, let’s look at the “Package.swift” file.
Package file with Server-side Swift with Vapor
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let package = Package( name: "FriendService", dependencies: [ .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"), .package(url: "https://github.com/vapor/fluent-mysql.git", from: "3.0.0"), ], targets: [ .target(name: "App", dependencies: ["FluentMySQL", "Vapor"]), .target(name: "Run", dependencies: ["App"]), .testTarget(name: "AppTests", dependencies: ["App"]) ] ) |
Here we define all the projects dependencies and the name for our service FriendService. Inside the Package initialization, we set the Vapor version and provide other packages we need in the project. Here we have defined that this project uses MySQL by adding fluent-MySQL package for the project. If we would like to create a web UI for the app, we should include Leaf here to include the library.
In the package file we also define the target dependencies for the App. Here we set them as [“FluentMySQL”, “Vapor”]. We also set App as the run target. Lastly, we set the testing target for AppTests.
Now, since we just created the project using the command line commands, we need to replace the SQLite related stuff with MySQL. We also need to close our Xcode and launch it using the vapor xcode command. This installs the dependencies for our app and after that, we are ready to write some code.
Configure the Server-side Swift project
Now that we have completed the project package file set up, the next logical phase is to configure the rest of our project. Let’s open our Configure.swift and see what we need in it:
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 |
import FluentMySQL import Vapor /// Called before your application initializes. public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws { /// 1. Register providers first try services.register(FluentMySQLProvider()) /// 2. Register routes to the router let router = EngineRouter.default() try routes(router) services.register(router, as: Router.self) /// 3. Register middleware var middlewares = MiddlewareConfig() // Create _empty_ middleware config middlewares.use(ErrorMiddleware.self) // Catches errors and converts to HTTP response services.register(middlewares) // 4. Configure a MySQL database var databases = DatabasesConfig() let databaseConfig = MySQLDatabaseConfig( hostname: Environment.get("DB_HOSTNAME")!, username: Environment.get("DB_USER")!, password: Environment.get("DB_PASSWORD")!, database: Environment.get("DB_DATABASE")! ) /// 5. Register the configured MySQL database to the database config. let database = MySQLDatabase(config: databaseConfig) databases.add(database: database, as: .mysql) services.register(database) /// 6. Configure migrations var migrations = MigrationConfig() migrations.add(model: Friend.self, database: .mysql) services.register(migrations) } |
-
First, we register the FluentMySQLProvider as the database for our project.
-
We register the routes for our service.
-
Register middlewares. Here we only use the basic error handling. Middleware basically intercepts the request before they reach our controller. We could, for example, add basicAuthMiddleware to handle user authentication, which would check if a user is authenticated or not.
-
Configure database. We set hostname, username, password and database(database sets the name for our database). We could set these as plain text in this file, but it would be very easy for us to slip them into the git repository. A better way is to define them as Xcode Environment variables in your scheme, and not to store that into the repository. Check the image below so you’ll know what I mean. Usually, it is not a good practice to use the !-mark to unwrap the optional values, but we can use it here since our service is not supposed to run if it cannot connect to the database. In this case, it’s better that we crash at startup than to run with incomplete configuration.
-
Register the database we just configured.
-
Configure migrations. This basically creates the database the first time we run the app. Also if we have some changes in our database model, migration takes care of the changes and migrates them to the database/table.

Server-side Swift environment arguments defined for MySQL
In the image above, we see how we can define the MySQL configurations as environment variables. Later on, we can set the variables as env parameters for our host service (Vapor Cloud, Heroku, AWS etc.) or Docker.
Handling the database in Server-side Swift with Vapor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import FluentMySQL import Vapor final class Friend: Codable { var id: Int? let firstname: String let lastname: String let phonenumber: String init(id: Int?, firstname: String, lastname: String, phonenumber: String) { self.id = id self.firstname = firstname self.lastname = lastname self.phonenumber = phonenumber } } |
Friend model protocol conformations
1 2 3 4 |
extension Friend: MySQLModel { } extension Friend: Migration { } extension Friend: Content { } extension Friend: Parameter { } |
-
Conforming MySQLModel protocol makes sure our model is compliant with fluent MySQL database. We can leave the definition empty since we have defined the id as Int. There is also MySQLUUIDModel and MySQLStringModel in case your id is defined as one of those types.
-
By conforming to Migration protocol we make sure our database changes are handled correctly. Vapor is also ready to create the Friend table in case it is not available as the project starts. Since our Friend model is pretty simple, it doesn’t need any implementation. If our model would be more complex than the one above, we might need to write some code to implement the migrations.
-
By conforming to Content we make sure that the model can be decoded to and encoded from HTTP-messages. Since our Friend model already conforms to Codable, we can leave the definition empty.
-
By conforming to Parameter we make sure that our Friend can be used as a dynamic parameter in our routes. We’ll see an example of this when we go through the FriendController.
Defining routes for the application in Server-side Swift with Vapor
1 2 3 |
router.get("hello", "jimmy") { req -> String in return "Hello Jimmy!" } |
1 2 3 4 5 6 |
import Vapor /// Register your application's routes here. public func routes(_ router: Router) throws { let friendController = FriendController() try router.register(collection: friendController) } |
Handling routes inside controller with Server-side Swift with Vapor
1 2 3 4 5 6 7 8 |
final class FriendController: RouteCollection { func boot(router: Router) throws { let friendRoutes = router.grouped("api", "friends") friendRoutes.get(use: index) friendRoutes.patch(use: update) friendRoutes.delete(Int.parameter, use: delete) friendRoutes.post(use: create) } |
Non-blocking architecture in Server-side Swift with Futures
Synchronous vs asynchronous requests in Server-side Swift with Future
1 2 3 4 5 6 7 8 |
// 1. Return synchronously func index(_ req: Request) throws -> [Friend] { ... } // 2. Return asynchronously func index(_ req: Request) throws -> Future<[Friend]> { … } |
1 2 3 4 5 |
func index(_ req: Request) throws -> Future<[Friend]> { return Friend .query(on: req) .all() } |
-
As we remember, our Friend model conforms to MySQLModel. This gives us access to Fluent’s database functions such as query and find.
-
Here we use the query function and pass the Request as a parameter. Request is a wrapper around HTTPRequest and gives us access to whatever is inside the HTTP-request passed to our router. Here the request lists all the friends inside our database.
-
all() is a function that executes the query asynchronously and collects all the found data into an array and returns them inside a Future. So in this example, all is the function that provides the waiting functionality which is run after the database request is completed.
Creating new friendships with Server-side Swift
1 |
friendRoutes.post(use: create) |
Now let’s dive into the create function implementation:
1 2 3 4 5 6 7 8 |
func create(_ req: Request) throws -> Future<Friend> { return try req .content .decode(Friend.self) .flatMap(to: Friend.self) { friend in return friend.save(on: req) } } |
Ones again we are returning a Future. After a successful friend creation, the client gets a friend object with the provided information back. As we remember, our Friendmodel also conforms to Content which enables us to encode and decode content from a HTTP-message. The content here is a Friend object in JSON format. There are a few steps we need to make before we can save the Friend object into our database.
-
We call content for the request to access the decode function.
-
Use the decode function and map the JSON object to a Friend.
-
Decode actually wraps the Friend object inside a future so we need to use flapMap to reach the actual Friend inside the future.
-
flatMap unwraps the Friend from the future. We provide a closure which our service calls after it is ready with all the unwrapping and decoding. Here we call Fluents save for our newly encoded friend which saves it to our database.
HTTP-request with a dynamic parameter with Vapor
1 |
friendRoutes.delete(Int.parameter, use: delete) |
Now, delete function is a bit different from the rest of the functions we defined. Notice the Int.parameter provided as the second parameter. We want to delete a user using the identifying id. Here we define that we want the id of the friend as a parameter and we will use it to find the user in the database. Let’s see the delete function then.
1 2 3 4 5 6 7 |
func delete(_ req: Request) throws -> Future<Friend> { let friendId = try req.parameters.next(Int.self) return Friend .find(friendId, on: req) .unwrap(or: Abort(.notFound)) .delete(on: req) } |
Since Friend conforms to Parameter we can use it as dynamic route parameter.
- We call try req.paramaters.next(Int.self) so that we get the identifier from the request the client sends.
- Then we’ll use the find function from Fluent and give the identifier as a parameter.
- We’ll unwrap the found item, and if that doesn’t work, we’ll throw a not found error.
- If we find a friend, we’ll call delete for it to remove it from the database.
Now we are all set and we should be able to run our project!
Run your Server-side Swift Vapor project
To run our Server-side Swift application in our system, we need a MySQL database. We are not going to install MySQL 0n our computer but we are going to use Docker instead. Docker is a service that you can use to run containers in your local machine or in a server that runs somewhere around the world. Basically, you can create a small instance of a “computer” with just the software you need. It is also a convenient tool if you have multiple projects that use a database. Maintaining and handling different setups are easy since you’ll have everything configured in the container.
Since Docker is a very wide topic I am not going to go through it here. I am also just scratching the surface of Docker, so I am not confident that I could write a good tutorial about it. But, I can show you how to use Docker to run our MySQL server, so if you want to test our Server-side Swift application, just follow the instructions.
Install Docker and run MySQL container it from the command line
Docker is extremely easy to set up. Go to www.docker.com/get-started , download the software, create your user account and open up a terminal.
In the terminal we write the following command:
1 |
docker run --name mysql -e MYSQL_USER=vapor -e MYSQL_PASSWORD=friend -e MYSQL_DATABASE=friends_database -p 3306:3306 -d mysql/mysql-server:5.7 |
- docker run starts our container.
- We give it a name using the – -name mysql parameter. Here we set it simply to mysql
- Then we give the environment variables we already talked about. Vapor has default variables for MySQL so we use -e MYSQL_USER=vapor, -e MYSQL_PASSWORD=friend and -e MYSQL_DATABASE=friends_database to configure our database. The Small ‘e’ stands for the environment (we are setting the environment variables..). Remember, we need to make sure the values we set here match the ones we have on our Xcode environment variables.
- The -p 3306:3306 defines the port we are using. The value is the port the host will use and the second one is the value used inside the container. The default port for MySQL is 3306. So in case you have a MySQL server running on your local machine, you might need to define something else than 3306 here.
- With -d stands for detach. This way you’ll be able to use your terminal window since it is not reserved to print outputs from the container.
- Last mysql/mysql-server:5.7 defines that we are using MySQL version 5.7.
With these parameters, the service will run in localhost and our FriendService can use it with the provided configuration. After we hit enter, the docker container should start up.
Run Server-side Swift project from Xcode
Now that the MySQL is running we can run our FriendService from the Xcode. We just click the run button and after a while, we should see Server starting on http://localhost:8080 in the output window at the down right corner of Xcode.
There is no content at our database so you need to use Postman or any other HTTP-request tool you like to post some data to our service. After you’ll successfully post a friend, you can either use GET from the HTTP-request tool or open the URL: “http://localhost/api/friend” in your browser to see what you just posted. In case you have any problems with Postman, check my older post about Vapor. In the post, I go through Postman with a bit more detail so you should be able to understand how it works.
That’s all that I want to go through in this blog post! In case you want to upload this service in the cloud, I have a post about how you can do that: Deploy project to Vapor Cloud. Thanks for reading and have a great day my friend!