Vapor vs. Kitura: Choosing a Server-Side Swift Framework

If you’re coming from iOS development and considering server-side Swift, one of the first questions you’ll likely ask is “Which framework should I choose, and why?” In this post we’ll compare the two most popular Server-side Swift frameworks: Vapor and Kitura. By Brian Schick.

Leave a rating/review
Save for later
Share

Server-side Swift is an exciting platform that brings the clarity, performance and compiler-driven type safety of Swift to the modern web. Drawn by Swift’s unique strengths, a rich ecosystem of frameworks has developed since Apple announced Swift’s open sourcing. If you’re coming from iOS development and considering server-side Swift, one of the first questions you’ll likely ask is: “Which framework should I choose and why?”

If that sounds like you, read on. In this post, you’ll see a comparison of the two most popular Server-side Swift frameworks: Vapor and Kitura.

When compared with traditional web frameworks for languages such as JavaScript, Ruby and PHP, Vapor and Kitura’s deep Swift-based commonalities are evident and compelling. Both frameworks contribute actively to the Swift community as a whole — especially to the Swift Server Work Group (SSWG).

When viewed side by side, each framework has a distinct personality, philosophy and approach that clearly differentiates it from the other. These differences in perspective and approach will be the main focus of this post. Both Kitura and Vapor are powerful and polished frameworks with a lot to offer. Ultimately, the answer to the question, “Which framework is right for me?” will be based in a secondary question: “Which one best matches my own approach and priorities?” Let’s dive in!

Commonalities

Both Kitura and Vapor share key lessons and organizational practices built up over years of PHP, JavaScript and Ruby web framework development. In particular, both have a recognizable top-level organization that will look familiar to virtually any developer acquainted with the Model View Controller (MVC) pattern. Here’s a side-by-side view of two simple Vapor and Kitura projects:

Both clearly favor separation of concerns by having different sections for views, models and “glue” code. There are additional commonalities like type safety and compilation.

Type Safety

Just as in iOS and macOS, server-side Swift is a type-safe language. Swift’s type safety is enforced by the compiler at a deep level, and in a web development context, this differentiates both Vapor and Kitura from modern frameworks based in JavaScript, Rails, PHP and many other languages.

Swift’s type system can initially feel constricting to developers not used to type-safety requirements. But Swift leads developers to think more precisely. In a server-side context, the end result is web code that’s more immune to many of the most pernicious errors common in other web frameworks.

A great example of how this can be seen in JSON data. In early versions of both platforms, Swift’s type safety was essentially at war with JSON’s malleable typing and its willingness to accept ad hoc properties at any time. This led to laborious and redundant manual code to check, decode and encode JSON in early versions of both frameworks.

Models were especially painful to design and maintain at this stage. For example, here’s a Vapor 2 Model:

import Vapor
import FluentProvider

final class AttachmentInfo: Model {
  
  let storage = Storage()
  private let expirationMinutes = 
    AppSettings.attachmentExpirationMinutes

  struct Properties {
    static let key = "key"
    static let path = "path"
    static let expires = "expires"
  }
  
  var key: String
  var path: String
  var expires: Date
  
  init(key: String, path: String) {
    self.key = key
    self.path = path
    self.expires = 
      Date().addingTimeInterval(expirationMinutes * 60)
  }

  init(row: Row) throws {
    key = try row.get(Properties.key)
    path = try row.get(Properties.path)
    expires = try row.get(Properties.expires)
  }
    
  func makeRow() throws -> Row {
    var row = Row()
    try row.set(Properties.key, key)
    try row.set(Properties.path, path)
    try row.set(Properties.expires, expires)
    return row
  }
}

extension AttachmentInfo: NodeRepresentable {
  func makeNode(in context: Context?) throws -> Node {
    var node = Node([ : ], in: context)
    try node.set(self.idKey, self.id)
    try node.set(Properties.key, key)
    try node.set(Properties.path, path)
    try node.set(Properties.expires, expires)
    return node
  }
}

extension AttachmentInfo: Preparation {
  static func prepare(_ database: Database) throws {
    try database.create(self) { builder in
      builder.id()
      builder.string(Properties.key)
      builder.string(Properties.path)
      builder.date(Properties.expires)
    }
  }
  
  static func revert(_ database: Database) throws {
    try database.delete(self)
  }
}

It’s 78 lines of code — and several not-so-DRY methods — for a Model with just three simple properties. The equivalent early Kitura code suffered from the same linguistic limitations.

This core problem was addressed in Swift 4 with the Codable protocol, which effectively transformed JSON handling from a liability to an asset throughout the Swift platform. To their credit, both Kitura and Vapor aggressively incorporated Codable, removing the need for swaths of verbose code and striking a near-perfect balance between Swift’s inherent type safety and JSON’s flexibility and adaptability. As a result, in Vapor 3, the 78-line model above becomes:

import Vapor

struct AttachmentInfo: Content, Migration {
  let key: String
  let path: String
  let expires: Date

  init(key: String, path: String) {
    self.key = key
    self.path = path
    self.expires = 
      Date().addingTimeInterval(AppSettings.attachmentExpirationMinutes * 60)
  }
}

Kitura 2’s streamlined model is slightly different, but shows nearly identical benefits derived from Codable:

import Foundation

struct AttachmentInfo: Codable {
  var key: String
  var path: String
  var expires: Date

  init(key: String, path: String) {
    self.key = key
    self.path = path
    self.expires = 
      Date().addingTimeInterval(AppSettings.attachmentExpirationMinutes * 60)
  }
}

This is a huge win, and the benefits go well beyond the surface. Both platforms use Codable up and down their stacks, allowing developers to work directly with Swift structs and types, while still providing full flexibility to ingest and output rich JSON data. That’s about as good a definition of “best of both worlds” as I know! :]

The Advantages of Compilation

Server-side Swift web apps are statically compiled. This provides multiple benefits:

Last, it’s important to note that, although Swift on the client side is most typically limited to Apple hardware and OS’s, this limitation doesn’t carry across to Swift on the Server. Thanks to the efforts of Apple and the Swift developer community, server-side Swift can be deployed also on Ubuntu Linux, multiple cloud platforms, and almost anywhere using Docker. This isn’t a competitive advantage, but it’s worth noting that server-side Swift is on par with Rails, JavaScript and PHP web apps in this respect.

  • Compiled web apps are safer by design. There’s significantly less exposure and danger if the only assets exposed are compiled bit code, rather than human-readable source code. This doesn’t give server-side Swift a pass from other security risks, but it’s significant.
  • Compiled apps tend to be more performant relative to their transpiled or just-in-time-compiled counterparts. A series of benchmarks early in server-side Swift’s evolution typically showed order of magnitude-level performance benefits relative to common alternatives. For example, the Vapor team presented this platform comparison shortly after the release of Vapor 1, demonstrating impressive performance even at that early stage:
  • But what if you don’t need screaming speed? Micro-services and other lightweight components written in a server-side Swift framework are typically expected to be exceptionally efficient in their energy and resource use.
    Note: It’s unfortunate that there’s a notable lack of objective benchmarks — especially few since the earliest proof-of-concept days of the platform. In my own experience, Vapor web apps and services my team has delivered to enterprise clients have typically been shown to reduce system load 80% – 90% relative to Node.js-based services they’ve replaced. Obviously, this is no substitute for objective benchmarking, which we hope to see emerge soon.
Note: It’s unfortunate that there’s a notable lack of objective benchmarks — especially few since the earliest proof-of-concept days of the platform. In my own experience, Vapor web apps and services my team has delivered to enterprise clients have typically been shown to reduce system load 80% – 90% relative to Node.js-based services they’ve replaced. Obviously, this is no substitute for objective benchmarking, which we hope to see emerge soon.