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:

Create FriendService project and launch Xcode vapor new FriendService cd FriendService/ vapor xcode 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

package file codes 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"]) ] ) 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:

Configuring server-side swift vapor 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) } 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 ) }

At the beginning of the file we import FluentMySQL and Vapor . Then we jump into the configure function.

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.

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.

You might notice that we added a Friend model to the MySQL database. At this point, this line gives us an error. That is because we haven’t yet created the Friend model so let’s do that next!

Handling the database in Server-side Swift with Vapor

As we mentioned, Fluent handles the database in Server-side Swift Vapor. Fluent is the ORM (Object-Relational-Mapper) that handles the data converting between the swift code and the database. It is an abstraction on top of the database. This also makes it easy for us to switch to a different database. This is convenient if at some point we want to start using SQLite instead of MySQL for example. The code changes are only a few lines, but keep in mind that the data transfer from one database to another might require a bit of work.

Next, let’s create a model for the friend service called Friend.swift. In the Xcode create a new folder Model and then a new file called Friend.swift inside it.

We want to store firstname , lastname and phonenumber from our friends. Let’s define a class called Friend and put those variables inside:

Friend model variable definitions 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 } } 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 } }

At the start of the file we’ll import FluentMyQL and Vapor . Then we define the class and add the user information as strings. Also we want Friend to conform to Codable . This makes sure that Fluent can convert the data under the hood as long as the model variable names match the ones in the database. This normally is the case since Fluent uses the model to create the database table(s). Also by conforming to Codable we make sure the HTTP routes can handle JSON converting to the model we defined.

The initializer is pretty straight forward so we won’t go through that. The id is something Fluent will create when the friend object is stored in the database. Lastly, by defining the class as final, we tell the compiler that no one will inherit this class and it will result in a small performance benefit. This is handy at least in heavier projects.

Friend model protocol conformations

Now the variables for our Friend model is ready, but there are some protocols we need to conform to make sure the model works with vapor and MySQL. Add the following lines below the Friend class:

Protocol Friend model need to conform to extension Friend: MySQLModel { } extension Friend: Migration { } extension Friend: Content { } extension Friend: Parameter { } 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 . And that is all we need to do for the Friend model in Server-side Swift with Vapor. Next, we are going to do some routing. So, let’s check the Routes.swift file. Defining routes for the application in Server-side Swift with Vapor In the Routes.swift you can define the routes for your app. For example, by defining: Defining a simple route router.get("hello", "jimmy") { req -> String in return "Hello Jimmy!" } 1 2 3 router . get ( "hello" , "jimmy" ) { req -> String in return "Hello Jimmy!" } Our application responses: “Hello Jimmy!” every time we send a GET request for “https://localhost:8080/hello/jimmy”. This is very quick and simple, but we don’t want to define our application routes like this. After we have added a dozen or more routes, we’ll be frustrated by the amount of code we have written to our routes file. We want to define a controller for all the grouped functionality in our app. For example, the friend related routes should be handled in the FriendController . To initialize FriendController for routing we have to add the following lines of code to routes file:

Add FriendController as a router for friend service import Vapor /// Register your application's routes here. public func routes(_ router: Router) throws { let friendController = FriendController() try router.register(collection: friendController) } 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 ) } We only need 2 lines of code. First, we create the controller and then we register it as a router. Now, this code does not compile yet because we haven’t yet created the FriendController . Let’s do that now! Handling routes inside controller with Server-side Swift with Vapor So, FriendController is the place where all friend related routes are handled. In FriendService, this is the only file handling routes. For a bigger project, you might have multiple controllers for various things. For example, if your service has a login feature you might want to define a login controller. First, we want to define a function called boot :

FriendController conforming to RouteCollection 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) } 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 ) }

FriendController need conform to RouteCollection and we need to implement the boot function to do that. This is the function that is called after the router.register() is called in Router.swift routes() function. Inside we first define the url path for the routes which in here is defined as “…/api/friends” in the router.grouped(“api”,”friends”) line. Next, we’ll add all the CRUD (create, remove, update, delete) functions that we are going use.

As you can see the HTTP-methods are the function names for the grouped router and we give the functions we implement our selves as a use parameter. For example, if you request HTTP-get to “https://localhost:8080/api/friends” the index function is called. We are going to dive into the index function in just a second, but first, let’s talk a little bit about non-blocking architecture and Future s.

Non-blocking architecture in Server-side Swift with Futures

For our web service to be efficient, it is important that we don’t block the service while we are fetching data. That’s why Vapor uses Future s to return data.

A bit simplified, Future is a wrapper around Generic type. You can define for example a Future<Friend> which means that we have Future object that will contain a Friend object. This way our service can put the request on hold, and we can continue working with it after the service completes the database request. When we work with futures we provide closure or a function where we handle what happens after the service completes the database request. This might not make that much sense at first, but let’s see a little example code to clear things up.

Synchronous vs asynchronous requests in Server-side Swift with Future

Defining synchronous and asynchronous routes // 1. Return synchronously func index(_ req: Request) throws -> [Friend] { ... } // 2. Return asynchronously func index(_ req: Request) throws -> Future<[Friend]> { … } 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 ] > { … } In the first example, when we request for an array of friends, we have to wait for our service to complete the database fetch before we can return the array of friends. This would block all other requests in that thread, which would make our service slow and irritating to use for other users. In the second example, we’ll return an array of friends wrapped in a Future , which we can return right away. This way we don’t block our service and other users won’t be affected by this single user getting his/her data. Next, let’s move on to our FriendController implementation and see the complete index function:

index function to fetch all friends func index(_ req: Request) throws -> Future<[Friend]> { return Friend .query(on: req) .all() } 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. Now that we know how to list all our friends, let’s see how to create new ones. I think it is always important to create new friendships, so let’s see how we can do it with Server-side Swift! Creating new friendships with Server-side Swift To create a new friend we use the HTTP-post method. Inside the boot function we mapped the create function to router like this:

Mapping the create function to route friendRoutes.post(use: create) 1 friendRoutes . post ( use : create )

Now let’s dive into the create function implementation:

Create function for posting a new friend 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) } } 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 Friend model 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. One important thing to notice here is the try keyword right after the return. If any of the functions (decoding, saving etc.) fails the function will throw an error. To update a friend is almost a mirror image from the friend creation, so we won’t go through it today. If we look the code in GitHub, we’ll notice that instead of the save() function call inside the closure, we use update() . Another thing to notice is that the HTTP-method used in update is put instead of post . HTTP-request with a dynamic parameter with Vapor Sometimes we come to a sad ending of a friendship. This is not something that we don’t want to aim in real life, but with Server-side Swift we need to know how to handle the situation. This time the HTTP-method we use is delete . Inside the boot function we define delete function like this: Now let’s dive into thefunction implementation:Ones again we are returning a. After a successful friend creation, the client gets a friend object with the provided information back. As we remember, ourmodel also conforms towhich enables us to encode and decode content from a HTTP-message. The content here is aobject in JSON format. There are a few steps we need to make before we can save theobject into our database.

Mapping delete route to correct function friendRoutes.delete(Int.parameter, use: delete) 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. Now,function is a bit different from the rest of the functions we defined. Notice theprovided 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 thefunction then.

Delete function implementation 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) } 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:

docker command to run MySQL server with correct configuration 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 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!