GO FURTHER, FASTER: Try the Swift Career Accelerator today! >>

Server-Side Swift: Kitura vs Vapor

Want a detailed comparison of server-side Swift frameworks? Look no further.

Paul Hudson       @twostraws

Server-Side Swift has only existed for about two years, but has already risen to be one of the most exciting areas of Swift development.

In the early days, a lot of the work was simply back-filling fundamental technologies that were either missing or incomplete on Linux, such as Foundation and GCD. But we’re well past that stage now, and are in a position to have two major frameworks to choose from: IBM’s Kitura and Qutheory’s Vapor.

Some folks have tried to compare these using artificial benchmarks – “what if I make my computer request the same page 10,000 times?” – but that’s not the goal of this article. Bluntly, no major internet site is even close to using Swift to serve its homepage, so I’m more interested in more practical things: how well each framework serves common needs of web developers.

Most of us need to handle routes, render templates, work with JSON, read HTML forms, and connect to databases. Sure, you might only do half of those in any given site, but these are common tasks all web developers are familiar with.

Not many folks have used both Kitura and Vapor extensively – I’m in the unusual position of having done so, and have actually written server-side Swift books using Kitura and Vapor documenting my experiences and helping others learn.

In this article I’ll walk through how Kitura and Vapor accomplish various tasks differently: setting up routes, reading and writing JSON, connecting to databases, and so on. I’ve taken this approach so that you can decide for yourself which framework suits you better – I’m not trying to gloss over problems or persuade you in either direction.

Hacking with Swift is sponsored by Essential Developer.

SPONSORED Transform your career with the iOS Lead Essentials. This Black Friday, unlock over 40 hours of expert training, mentorship, and community support to secure your place among the best devs. Click for early access to this limited offer and a free crash course.

Save your spot

Sponsor Hacking with Swift and reach the world's largest Swift community!

The basics

Let’s start with the simple stuff: setting up a basic server, making routes, and matching parameters inside those routes.

Kitura comes with a toolkit designed to help create, build, test, and deploy projects. It’s installed like this:

brew install ibm-swift/kitura/kitura  

However, honestly I just don’t like it very much. The “kitura init” command says it will “scaffold a bare-bones Kitura project”, but you actually end up with metrics, health monitoring, and more. This might be useful if you want to use these built-in frameworks, but it’s hardly what I’d call bare-bones.

Instead, I find it much easier to create projects by using SPM directly, like this:

swift package init --type executable
swift package generate-xcodeproj

You can then adjust the Package.swift file to include Kitura along with any other dependencies.

To be fair, Vapor isn’t a great deal better. It too has a toolkit with similar goals, installed using this:

brew install vapor/tap/vapor

However, creating projects is done by cloning a GitHub template, and at this time only one template is available for Vapor 3. Annoyingly, it’s more of a sample app than an actual template, so you’ll need to delete its example Todo List code before you can actually use it. Worse, it groups your source code into “Models” and “Controllers”, which is the kind of thing you do before you realize how terrible such a grouping is.

On the flip side, the Vapor toolkit allows you to specify other repositories to clone – a feature that the Kitura toolkit lacks. As a result, I made a simple Vapor Clean template that really is a bare-bones Vapor project, and makes a much more natural starting point:

vapor new YourApp --template=twostraws/vapor-clean

Once you have a basic server up and running, you can look at creating some simple routes. Both Kitura and Vapor create routes using closures, although after that their implementation is entirely different.

In Kitura, a basic route looks like this:

router.get("path/to/page") {
    request, response, next in
    defer { next() }

    response.send("Hello, world!")
}

That will respond to the route “path/to/page” by sending back a plain-text string, but it does two neat things.

First, the next() parameter to your closure allows you to decide whether processing should continue through other parts of your app – perhaps you might write several closures to match a given route, and you want them all to be called.

Second, all responses back are sent back using the response object that gets passed into your closure. Swift’s closure capturing means you can then write all the complex code you need without worrying about returning a value from the overall route – all you need to do is use response to send something back.

In comparison, the same route with Vapor looks like this:

router.get("path", "to", "page") { request in
    return "Hello, world!"
}

Vapor hands you the request that came in from the user, but nothing else. I can return a simple string here because Swift is able to infer the closure’s return type, but for anything even remotely more complicated you’ll need to specify the return type explicitly:

router.get("path", "to", "page") { request -> String in
    return "Hello, world!"
}

You’ve probably noticed that Kitura routes are written as one single string, whereas Vapor routes pass components individually. The reason for this is down to the way they handle route parameters – parts of a route that must exist, but could be anything.

For example, we might want to match any kind of user with a route such as “/users/twostraws” – we obviously don’t want to hard-code usernames into our code, so instead we’ll use a route parameter.

Kitura places route parameters directly into the route, prefixed with a colon. You can then read each parameter out of a request.parameters dictionary, like this:

router.get("users/:name") {
    request, response, next in
    defer { next() }

    guard let name = request.parameters["name"] else { return }
    response.send("Hello, \(name)!")
}

In comparison, Vapor leans on Swift’s type system:

router.get("users", String.parameter) { request -> String in
    let name = try request.parameters.next(String.self)
    return "Hello, \(name)!"
}

You need to call request.parameters.next() once for each parameter in your route, in the order it appears.

Vapor’s approach is both simpler and safer, but the difference is more pronounced when dealing with something other than strings. If we wanted our route to match users by ID number – an integer, rather than a string – we’d need to add a regular expression to Kitura, like this:

router.get("users/:id(\\d+)") {

And then we’d have to convert the result of request.parameters["id"] to an integer inside the route.

Vapor’s type-safety makes this a non-issue:

router.get("users", Int.parameter) { request -> String in

Don’t get me wrong: regex matching is a really nice feature, but I’m not sure it’s valuable enough to warrant the rest of the problems caused by these string URLs.

Rendering content

Both Kitura and Vapor build extensively on Codable, the Swift protocol that allows us to move relatively seamlessly between custom data types and JSON.

This means that both frameworks make it trivial to return instances of your types as JSON – the whole process is effectively invisible.

With Kitura you simply need to make your structs conform directly to Codable, like this:

struct User: Codable {
    var firstName: String
    var lastName: String
}

You can then instantiate them inside a route and return them directly, like this:

router.get("json") {
    request, response, next in
    defer { next() }

    let twostraws = User(firstName: "Paul", lastName: "Hudson")
    response.send(json: twostraws)
}

Vapor is similar, but wraps Codable in its own Content protocol, like this:

struct User: Content {
    var firstName: String
    var lastName: String
}

router.get("json") { request -> User in
    let twostraws = User(firstName: "Paul", lastName: "Hudson")
    return twostraws
}

Where things get more interesting is how you render HTML using templates. Rather than building its own template engine, Kitura wraps both Stencil and Mustache so you can choose what you prefer.

If you’ve ever used Sourcery you’ll be familiar with Stencil – you get conditions, loops, blocks, filters, tags, plus the ability to add new filters and tags trivially.

Let’s start with a simple Kitura example. First you request to use the Stencil template engine:

router.setDefault(templateEngine: StencilTemplateEngine())        

Now you can call render() on your response object, passing in the name of a Stencil template and whatever context you’d like to provide:

router.get("template") {
    request, response, next in
    defer { next() }

    let haters = "hating"
    let names = ["Taylor", "Paul", "Justin", "Adele"]

    let context: [String: Any] = ["haters": haters, "names": names]
    try response.render("home", context: context)
}

In comparison, Vapor strongly prefers you to create Codable structs that define what you intend to render.

First, you set up Leaf as the default template renderer:

try services.register(LeafProvider())
config.prefer(LeafRenderer.self, for: ViewRenderer.self)

Then create some example data you want to send back

struct User: Codable {
    let username = "Unknown"
    let roles = ["Anonymous"]
}

return try request.view().render("home", User())

Although relying heavily on structs does add some extra complexity to your code, it’s easy to argue that you also benefit from extra safety – which you like depends on your preferred coding style.

Where things are more clear cut is how you customize the template environment. Stencil is highly customizable by design, so you can create an Extension instance, add your own custom filters and tags, then give that to Kitura in only a few lines of code:

let ext = Extension()

ext.registerFilter("reverse") { (value: Any?) in
    guard let unwrapped = value as? String else { return value }
    return String(unwrapped.reversed())
}

router.setDefault(templateEngine: StencilTemplateEngine(extension: ext))

In comparison, Leaf takes more work. It isn’t hard, but neither is it as trivial as with Stencil. Here’s the equivalent code for Vapor:

public final class JoinTag: TagRenderer {
    public func render(tag parsed: TagContext) throws -> Future<TemplateData> {
        try parsed.requireParameterCount(1)

        return Future.map(on: parsed.container) {
            if let str = parsed.parameters[0].string {
                return .string(String(str.reversed()))
            } else {
                return .null
            }
        }
    }
}

var tags = LeafTagConfig.default()
tags.use(JoinTag(), as: "join")
services.register(tags)

In terms of actually rendering HTML, the two have a similar range of functionality. I’m sure people have strong views on this, but ultimately which syntax you prefer really comes down to personal taste.

Here’s an example in Kitura’s Stencil:

{% if name %}
    <h1>{{ name|capitalize }}</h1>
    <p>{{ bio }}</p>
{% else %}
    <h1>Unknown staff member</h1>
    <p>We didn't recognize that person.</p>
{% endif %}

{% include "footer.stencil" %}

And in Vapor’s Leaf:

#if(name) {
    <h1>#capitalize(name)</h1>
    <p>#(bio)</p>
} else {
    <h1>Unknown staff member</h1>
    <p>We didn't recognize that person.</p>
}

#embed("footer")

Some small things do push the advantage towards Kitura:

  • Loops have an empty option, allowing you to do something in your collection is empty.
  • Stencil makes it easier to control whitespace by using {%- and -%}.
  • Stencils use in other projects means many thousands of people are already familiar with it.

Decoding data from GET and POST

If you’re making a website, chances are you’ll want to read data from GET or POST at some point. This is most commonly when users submit forms, but you might also use it to customize pages such as adjusting the sort order of results.

Let’s start with another simple example: the user requested “/form?message=hello”, and we want to read the “hello” message.

In Kitura you’d write this:

router.get("form") {
    request, response, next in
    defer { next() }

    guard let message = request.queryParameters["message"] else { return }
    response.send(message)
}

The queryParameters dictionary holds only strings, which isn’t a great surprise given that’s what URL query strings are – you’ll still need to convert to a different type as needed.

Vapor can automatically convert types for you, like this:

router.get("form") { request -> String in
    let message: String = try request.query.get(at: "message")
    return message
}

You explicitly specify the data type of message so that Vapor can check such a conversion is possible when extracting the query string. This is made possible because of Vapor’s extensive use of throwing functions – most things in Vapor can throw errors, so you’ll probably want to register a custom error handler to handle them gracefully.

There isn’t a whole lot dividing the two frameworks when it comes to query parameters, but things widen up pretty dramatically. Although Kitura attempted to simply their approach in 2.0, it’s limited in scope and poorly documented.

If you’re able to use their codable routing system then you’re in luck: you won’t need to write much work to implement basic Create, Read, Update, and Delete (CRUD) functionality.

If not, you need to do all the work yourself. To demonstrate this, we’re going to parse a form that has two fields – one for first name and one for last name – then return a User object instantiated from that data.

In Kitura, you first attach a body parser to your route, like this:

router.post("/", middleware: BodyParser())

Then you make sure there’s a request body, and make sure it was a URL-encoded form, before finally pulling out individual values:

router.post("form") {
    request, response, next in
    defer { next() }

    guard let values = request.body else {
        try response.status(.badRequest).end()
        return
    }

    guard case .urlEncoded(let body) = values else {
        try response.status(.badRequest).end()
        return
    }

    guard let firstName = body["firstName"] else { return }
    guard let lastName = body["lastName"] else { return }

    let user = User(firstName: firstName, lastName: lastName)
    response.send(json: user)
}

It certainly works, although I expect many people would hit a mental speedbump at guard case .urlEncoded(let body) = values else {.

In Vapor, the code is pretty much as simple as it can get:

router.post(User.self, at: "form") { req, user -> User in
    return user
}

If you wanted something a little less magical you could decode the User struct yourself:

router.post("form") { request -> User in
    let user = try request.content.syncDecode(User.self)
    return user
}

It’s hard to fault Vapor here – this is exactly how it should be done.

Connecting to databases

Databases go with websites like Swift goes with long compile times – we all use them nearly all the time, so anything a web framework can do to help is warmly welcomed.

Here Kitura has a rather difficult history. Earlier releases of Kitura relied on you to use SQL and NoSQL databases directly – you hand-wrote all your SQL, CouchDB views, and so on.

This got fractionally better with the introduction of Swift-Kuery, which allows you to write code like this:

let query = Select(grades.course, round(avg(grades.grade), to: 1).as("average"), from: grades)
        .group(by: grades.course)
        .having(avg(grades.grade) > 90)
        .order(by: .ASC(avg(grades.grade)))

Swift-Kuery only supports SQL databases, which is frustrating given the huge popularity of NoSQL databases.

More recently, the Kitura team have introduced Swift-Kuery-ORM, which is designed to eliminate the unwieldy syntax of Swift-Kuery altogether – “ORM” stands for Object-Relational Mapper, and ORMs are designed to convert between databases and data types in your code. So, using Swift-Kuery-ORM you just make your data model conform to the Model protocol, and you can use it more naturally.

Over in Vapor, databases are powered by Fluent – an ORM that has been around since Vapor 1. Fluent is designed to handle a range of data types easily, and is already in a proven, mature state.

Let’s take a look at some code so you can see for yourself.

In Kitura, if you had a Book model as your data type and wanted to return all the books in your database as JSON, you’d use this:

Book.findAll { books, error in
    if let books = books {
        response.send(json: books)
    }
}

If you just wanted to return the book with ID 3 you’d use this:

Book.find(id: 3) { book, error in
    if let book = book {
        response.send(json: book)
    }
}

And if you wanted to return all books in the “History” genre, you can’t.

That's right, you can’t – Swift-Kuery-ORM doesn’t support any filtering at this time, so you either get one object or all objects. If you want filtering you either need to back to Swift-Kuery, or write the SQL yourself.

This compares poorly to Fluent, which lets you move smoothly from simple commands up to all the complexity you want.

So, if you want to return all the books you’d use this:

return Book.query(on: request).all()

If you want to find just one book you’d use this:

return try Book.find(3, on: req)

And if you wanted to find all books in the “History” genre you’d use this:

try Book.query(on: req).filter(\.genre == "History").all()

Vapor relies on Swift’s keypaths to make sure your filters are type safe.

Of course, you can go as far as you like – this pulls out the first 10 history books sorted by their title ascending:

try Book.query(on: req).filter(\.genre == "History").sort(\Book.title, .ascending).all().range(..<10).all()

Fluent is, and always has been, a major selling point for Vapor, and in Vapor 3 it has gone strength to strength.

Although the Kitura team are working hard to bring Swift-Kuery-ORM to the same level as Fluent, they are quite some distance away yet – be prepared to use a blend of approaches in the meantime, and put up with some crashes too.

So… which one wins?

I have purposefully not made this article into a competition, because I don’t think that’s helpful to anyone. Sure, I think Kitura’s templates are a little better than Vapor’s, and Kitura’s response parameter makes it easier to write more complex code, but at the same Vapor makes database access and form parsing easier, so I think both frameworks have their advantages.

One thing I haven’t mentioned here is Vapor’s asynchronous architecture. You’ll hear about this a lot, often from folks who are just repeating what they heard elsewhere. This can be irritating, partly because we still don’t have any useful benchmarks of Vapor 3, but mainly because even a low-end virtual server can comfortably handle hundreds of simultaneous active users on a database-driven site – even when serving requests using something slow like Apache and PHP.

However, I can tell you that Vapor’s asynchronous architecture does add extra complexity to your code, just like any asynchronous architecture. This is unavoidable, and you will need to get comfortable with map(), flatMap(), and their friends, often using two or three of them together to the result you want. I’ve documented some ways to mitigate the problem here: what’s new in Vapor 3?

Rather than declaring a winner or loser, instead I’ve tried to present you with code examples so you can see for yourself how the two frameworks differ and make up your own mind.

Both of these projects are completely open source, and both are contributing lots back into the server-side Swift community – we should be thankful we have two such brilliant teams working to push forward the cause.

Hacking with Swift is sponsored by Essential Developer.

SPONSORED Transform your career with the iOS Lead Essentials. This Black Friday, unlock over 40 hours of expert training, mentorship, and community support to secure your place among the best devs. Click for early access to this limited offer and a free crash course.

Save your spot

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.7/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.