Image for post
Image for post

An iOS Developers journey into Server-Side Swift using Vapor

Sophie Clark
Apr 7, 2019 · 17 min read

A few months ago I attended iOSDevUK in Aberystwyth, Wales. Whilst there I attended a talk by Tim Condon which was on the topic of Vapor. I’d read a little about Server-Side Swift before the conference and was quite intrigued to learn more about it, as before then the only thing I knew I could use it for was iOS development. For me, knowing I can use Swift for something besides app development is great news as it gives more career options where I can use it (if more companies start using it Server-Side anyway) and it also makes it seem more legitimate as a “proper” programming language. After the talk and following workshop, I began playing around with Vapor a little in my spare time and also during my innovation time at work and really loved how simple it was to get it up and running. Previous to this, the last time I had written an API was during my second year of university where I created a multi player game and finished the module possibly more confused about how web APIs worked than I was when I started. However, when I came to do Vapor it seemed to make sense much more. I think this was down to a mixture of online support, more experience with Swift and even more likely, better skills when it comes to searching for answers on the internet!

What is Vapor?

Vapor is a web framework for Swift which enables you to create RESTful APIs and web applications. It works on macOS and Ubuntu and includes packages for authentication and front end development, as well as packages for all popular database management languages such as SQL, Postgres and MongoDB. Because you write it using Swift it also takes advantage of one of my favourite features, codable.

Why would you use it?

There are a few reasons I can think of as to why you would choose Vapor when making an API. The first being, you get to use Swift! If thats not a reason then I don’t know what is. The second (and slightly more serious reason) is if you were working in a startup and already had an iOS developer that knew Swift. This would enable you to get your services up and running quickly as the you could have the same developers that are working on you iOS app also working on your API, as they would be working in a language that they are (hopefully) very comfortable with, the learning curve shouldn't be that steep and they should be able to get something pretty good working pretty quickly. Also due to there being a lot of excitement at the moment around Server-Side Swift, and also due to Vapor still being fairly new, there is a lot of support online and it is also being constantly improved and updated. Another benefit of using Swift to write your backend is that because it’s a compiled language, apps written using it are likely to perform better than an interpreted language. A final reason is that as Swift is a typesafe language it removes a lot of the problems you have with Javascript frameworks. But more on that later.

Similar Frameworks

There are a few similar frameworks for creating REST services and web applications in Swift. One of these is Kitura.

Kitura was created by IBM in 2016. Due to it being created by such a large company, there is a lot of support on the GitHub page from their developers. Its not currently as popular as Vapor and Perfect, it currently has 6.9k stars on GitHub vs 16.2k and 13.4k respectively, however I’m sure that with the support of IBM this will grow. Kitura is also frequently updated, receiving new updates every few weeks (they released 22 updates in the last year!). Kituras syntax is slightly more descriptive than Vapors. However it also seems to involve more boilerplate code to do the same things as in Vapor. It is also slightly slower to adopt new features of Swift such as the SwiftNIO framework. The slower adoption does have its benefits, code written with V1 Kitura still run now, whereas the large and rapid changes in Vapor can mean you have to rewrite quite a few things when you update.

Another Swift web application framework is Perfect. Until recently this was the most popular of the three frameworks. Perfect was the first framework released for creating REST services and web applications out of the three discussed here, which is possibly why it held the lead in popularity until recently. It runs on Linux, iOS and MacOS. Perfect also has support for Mustache which enables you to also use it to develop your front end, similar to Leaf for Vapor. Perfect is also updated fairly regularly, with there being 5 releases in the last year. It also has support for a lot of database types, covering all of the most popular SQL language based ones as well as MongoDB and CouchDB amongst a few others. Perfect also has its own implementations of various authentication methods such as Turnstile and OAuth2.

Benefits over JavaScript frameworks

One of the main benefits for me for using a Swift framework over JavaScript one is that it’s a statically typed, compiled language. This means that if there are any compile errors I’ll know straight away, and it also means I won’t be able to pass numbers into Strings and vice versa. Another benefit is that because it’s a compiled language you can set breakpoints and easily step through your code to try and debug your code as opposed to setting loads of print statements in your code to figure out what is happening. This already prevents a lot of bugs that tend to happen in large backend services that are based off of JavaScript. However a plus for JavaScript frameworks such as Node.js is that it has lots and lots of different modules and support from Node Package Manager. Also the fact that Node is currently one of the most popular frameworks for backend development means that there are loads of help articles and support online.

Kotlin options

Kotlin has been used in server-side applications almost since its release with many large companies supporting and using it in their applications. Its a great choice for both Android developers who are looking to do some server-side development as well as Java backend developers due to its interoperability with Java. This fact means that its also compatible with all of the frameworks available for Java server applications. Another benefit of adopting Kotlin for Server-Side where your applications are currently written in Java is that you can introduce it slowly, starting with the classes that would benefit most from being translated. The most popular framework for Kotlin Server-Side applications is Ktor, which is written by JetBrains, which I guess makes it the official framework for Server-Side development in Kotlin.

Drawbacks

One of the main drawbacks of using Vapor, or any Swift framework for Server-Side development is that Swift is only really currently used by iOS developers on a large scale, this means that if you decide to go down the Swift route for your services it may be difficult to find people who have good experience in both API development and Swift. The learning curve can also be quite large when it comes to learning Swift if the developer comes from a JavaScript background. Vapor can also be slightly slower than some of the other frameworks however this is something that has been massively improved upon and now does much better in benchmark tests compared to a few years ago.

Performance

Vapors performance has improved rapidly over the past 3 years, most recently with the release of Vapor 3. A big reason behind this is due to the change in architecture to make it asynchronous and non-blocking, this massively increased the amount of requests per second it can receive and do. Vapor was also the first framework to start using Swifts NIO framework which is a non-blocking, event driven network application framework for servers and clients. If you want to read more about SwiftNIO I recommend checking out the repo on GitHub.

So what about benchmarks? In previous versions of Vapor it didn’t perform quite as well as other frameworks, both Swift and JavaScript based ones. However in Vapor 3 it has improved massively, part due to adopting SwifNIO. The below graphs are taken from the release blog post for Vapor 3. The details of the test can be found in the post but for the first graph it shows the number of requests handled per second in a plaintext benchmark, where the higher the number the better.

Image for post
Image for post

The second graph shows the average latency for each request during the benchmark, where the lower number the better.

Image for post
Image for post

There are a number of other articles out there for comparing benchmarks however not all of them use versions of the frameworks from the same points in time so it was difficult to find one that definitely wasn't biased, and obviously the above graphs come from the Vapor team themselves so if you are considering changing your backend to Swift it’s definitely worth running your own benchmarking tests. You can find the details of the conditions for the benchmarking tests in the Vapor 3 release article mentioned above.

Authentication

Vapor has a few different authentication methods built in. One of these is stateless authentication, which is used to protect api endpoints. The methods that come with the Vapor auth package for stateless authentication are Basic and Bearer. Basic authentication is used to authenticate each request a user makes to your server. Most web applications these days don’t use this and instead opt for an ephemeral token instead, i.e. JWT. JWT is fairly easy to implement yourself in Swift, or there are a few packages you can use if you prefer. The other method of stateless authentication in the auth package is through the use of Bearer tokens. Bearer tokens are used very commonly as they are very easy to send in the request and can contain an ephemeral token. You can then send this token with each request that requires user authentication. A basic use case for these would be: first the user would enter their login details (in this example they are using an iOS app), you would then encode them, send their credentials to the server and the server would respond with a JWT token which you would then unwrap to get out their details and access token. You would then store this token to use in the requests the app makes for the user. When the token expires and the app tries to make a request with the expired token, the server will send a 401 at which point you re-authenticate the user.

To make sending errors back to your users easier Vapor comes with a few different types of Middleware built in. To quote directly from the Vapor docs:

Middleware is a logic chain between the client and a Vapor route handler. It allows you to make operations on incoming requests before they get to the route handler, and on outgoing responses before they go to the client.

So you can use it to change the data that is send to your handlers and also use it to modify the data that is returned. When it comes to the error middleware you can use this by just adding it to your config file and then all of your controllers will use it to relay errors back to the client. This makes error handling incredibly easy and the errors are also very descriptive, for example, say you had a blog api and were wanting to create a new post. Say the Post object requires you to send a title and then the content, but you only send the title, the server would send a response back saying:

So straight away your client will know what value they aren’t submitting. The only downside is that it only tells your client the first value they are missing, so say the post object changes and now you have to send a title a subtitle and the content, if the client only sends the title it will the response will just say they are missing the subtitle and not tell them they are missing the content. This is of course easily avoided if you keep your swagger specs up to date!

Example

So now I’ve given a brief overview of Vapor and how it works I thought I would give a quick example of what an API written using it looks like. To install and use Vapor on macOS you need Xcode 9.3 or above, and Swift 4.1 or above. You will also need homebrew installed. Once you have these you can download Vapor by pasting to following commands in terminal.

brew tap vapor/tap
brew install vapor/tap/vapor

Once its finished installing you can start your first Vapor project! To do that, first, navigate to the folder where you want to save your project in Terminal. Once you’re inside that folder you can create your Vapor project. To do this paste the following command into terminal, replacing “ProjectName” with whatever you want to name it.

vapor new ProjectName

To build and run your new project you first need to generate an Xcode project. To do this just navigate to your new project folder and enter the following command in terminal:

cd ProjectName
vapor build
vapor run

If you then go to http://localhost:8080/hello in a web browser you should see the text “Hello, world!”

You can also build and run your application by generating an Xocde project and running it the same way you would an iOS or Mac app. To do this enter the following in terminal inside your project folder:

vapor xcode

This will generate an Xcode project and then ask if you want to open it. To open it straight away without it asking just do the following instead:

vapor xcode -y

Then once Xcode has opened you can press the run button and it should start running on port 8080, and you can check that its working the same way as above. If nothing happens make sure the run scheme is selected and you’re deploying to your mac:

Image for post
Image for post

Looking into the project that has been created you will see a few different files and classes. The first of these is the package file. Vapor uses Swift Package Manager to install dependencies. At the moment yours should look like this:

If you look inside the dependencies array you will see two packages. One is Vapor, which should be self explanatory the other is Fluent SQLite. Fluent is a type-safe ORM framework built by the Vapor team. It’s what you use to create, save and update your databases. In the default project it adds the package for SQLite databases. There are also packages for PostgreSQL and mySQL, along with DatabaseKit which can be used as a core framework to make your own integration.

Below the dependencies array are the targets. This is where you specify what frameworks you want available for each of your targets. As you can see in the App target the FluentSQLite and Vapor frameworks have been added as the dependencies. In the Run and AppTests targets you will see that these both depend on the App target as a dependency.

If you then move to the routes.swift file you will see the current routes your api has. Your routes file should look something like below:

As you can seem there are a couple of routes registered already. The “It works” example makes a route which returns “It works” when you go to the base url i.e http://localhost:8080/ and do a get request. The hello world example is the route you used above when you went to http://localhost:8080/hello/. In the example for configuring a controller it is basically registering new paths and telling Vapor which handlers to use for each request.

If we then move to the TodoController.swift file you should see the following:

The first function you will see if the index handler. This handles all get requests to the endpoint http://localhost:8080/todos by doing a query on the Todo database and returning all of them. You will also notice its return type is Future<[Todo]>. A future is what vapor uses to handle asyncronous calls. This is necessary as requests wont always be completed immediately, so making the calls asynchronous stops your service from being blocked every time a client makes a request until it is completed. The second function is the create handler. As you can probably guess, this is the handler that handles the creation of new to-do list items. This function takes advantage of codable by trying to decode a Todo object from the request body. Once this is complete it saves the new to-do item and returns it. The final function is the delete handler. This works by passing the id of the todo item you want to delete as a parameter in the url. As we don’t want to return the object that’s being deleted we just pass back a future http status code. Thats that the .transform(to:) function does. The .ok turns it into a 200 status code. If command click into .ok you can see all of the response types available.

The first thing we should do here is tidy up a bit. The routes for the to-do model should be moved to the to-do controller. So we need to add the following method to it right at the beginning:

The ToDoController class also needs to be changed to a struct and needs to inherit from RouteCollection. Your ToDoController should now look like this:

The boot function can be refactored a bit so that we don’t have to set the path as “todos” each time we add a new route. We can do this by creating a new router for the todos path using the grouped function. This is shown below:

Here a new route has been created based of the one that is passed into the boot function, with the path of “todo”. The the get, post, and delete handler creation has been updated to remove “todo” from each, as we are now basing it off the todoRoute which used “todo” as its path. Now the only thing you need to do is go back to routes.swift and where you were registering the todo paths for the todo controller before you can now just initialise the todo controller and then register it to the router, like below:

If you build and run again, all the requests should still work the same way.

The next thing we should look at is linking those todo items to users, as we don’t want all of the todo items to show for everyone. To do this go back to terminal and enter :

touch Sources/App/Models/User.swift

This makes a new swift file that we will use for the User model. The reason we are creating the file outside of Xcode is because Xcode isn’t technically a requirement of Vapor as you could write all of your Vapor services using a Linux machine and any editor you chose which can format Swift code. Making the file outside of Xcode means its not linked to the xcodeproj file and also ensures that the file is linked to the correct targets.

Then enter:

vapor xcode -y

To regenerate the Xcode project. Open the User.swift file and enter the following:

We define variables for the users ID, their name and their username, and then declare a standard initialiser. Below that you will see a few extensions. The first is the migration extension. This is where you would put anything that you wanted to happen when a migration takes place. Right now it’s empty as we’ve only just created the model. The next is the Content extension. Content is a protocol which inherits from Codable which is why we get to have the same super simple models that we can have in iOS apps, it’s what makes mapping the data received in requests so easy. The final one is parameter, this is what lets you find a user object by passing the ID in the request path.

Next we need to add the User model to the configuration in the migrations. Go to configure.swift and enter the following under the current code in configure migrations:

Next we need to make a controller for the user database. Go to terminal and enter the following:

touch Sources/App/Controllers/UserController.swift

Then rebuild the project:

vapor xcode -y

For now we’re just going to copy the to-do controller and change the names. So your UserController.swift should look like the following:

Here what we’ve done is created a new route in the boot function called user, then we’ve specified the handlers for getting, creating and deleting a user.

Next we need to add these new routes to the main router. Open routes.swift and below where we register the todo controller, register the User controller:

If you build and run now you should be able to create, get and delete your users.

But what about if we only want to get one to-do item, or one User? This can be done easily by creating a new get handler which instead of returning all of the to-do objects or user objects, it will return the one which we want, and we will get it by sending the id in the request url.

To start, go to TodoController.swift and copy the following into your code into the boot function and below the other handlers:

This registers a new get route to return a single todo item by passing in a todo parameter. Then the get function searches the todo database for a todo item with the matching ID to the one entered in the url as a parameter. This works because the SQLiteModel which the Todo model and User model inherit from requires you to implement a variable called id. This id variable is then used in an extension of SQLiteModel where we set the id variable to be the IDKey. You can see this by opening the SQLiteModel protocol.

We will then do the same in the UserController:

If you now re-run your project, make a todo item or user and then do a get request to http://localhost:8080/todo/1 or http://localhost:8080/user/1 you should get the todo item or user you just created.

What about linking a todo item to a user? We can do this by setting a todo item as a child of a user. First open Todo.swift and add the prepare function to the migration extension and the extension which defines a user as a parent:

We also add a vairable called userID which is of type User.ID. In the last extension we create a variable called user which is of type Parent. Inside the chevrons we define that relationship, so <Todo, User> means that user is the parent of a todo item. This is a computed property which returns the userID variable we have just defined in the main class code which can be queried on to return the full User object.

Next we need to do something similar in the User model. Go to User.swift and update your code to the following:

Here we’ve added the extension to user which defines a variable called todoItems which can be queriedto find the todoItems for the specified user.

Next we need to add some new routes to the controllers. First open UserController.swift and add the UserWithTodos struct and the getTodosForUser function and handler:

So what does the getTodosForUser handler do? First it finds the requested User, then does a query on the todoItems child object and returns all of them. Then it maps those into a new struct UserWithTodos which contains all the user details and the list of todoItems and returns it. If you run the project now and make a user followed by at least one todo item (you will now need to add a user id to the json object when you submit a todo item) and then do a get request to http://localhost:8080/user/{userID}/todoItems you should get your user and their todo items back.

Next we need to do something similar for Todo items, where we return the user that owns a specified Todo item. Go to TodoController.swift and add a new handler called getUserForTodo and a new struct called UserForTodoItem:

The getUserForTodo handler finds the specified Todo item and then queries for its user parent object and links the two together. We then map those into the UserForTodoItem object and return it. If you build and run again and add a user and some todo items and then do a request to http://localhost:8080/todos/{todoItemID}/getUser you should get back the details of the todo item and its associated user.

That was a pretty quick demo of how to get started implementing your own web APIs using Swift. If you want to go a bit further see the recommendations below.

Where to go from here?

If you’re interested in Server-Side Swift and want to learn more I recommend checking out the book from the Ray Wenderlich site, along with the tutorials they have on the site. Paul Hudson from Hacking with Swift has also written a book on Vapor development which I haven’t read yet but if his previous books and tutorials are anything to go by then it will be great. You can also join the Vapor discord group where the creators and contributors of Vapor are really active and usually answer any questions really quickly.

Compare the Market

The people behind comparethemarket.com and the Meerkat App

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store