Kitura Tutorial: Getting Started With Server-Side Swift

Do you wish your iOS skills worked on the backend? This Kitura tutorial will teach you to create RESTful APIs written entirely in Swift.

5/5 1 Rating · Leave a Rating

Version

  • Swift 4.2, macOS 10.14, Xcode 10
Update Note: This Kitura tutorial is now up to date with the latest version of Xcode, version 10.1, and uses Swift 4.2 for development. Update by David Okun. Original tutorial by David Okun.

Are you a busy Swift developer with no time to learn Node.js, but still feel drawn to server-side development? This Kitura tutorial will teach you how to create RESTful APIs written entirely in Swift.

You’ll build a Today I Learned app to help you learn and remember common acronyms like TIL. Along the way, you’ll learn how to:

  • Create a back end API from scratch.
  • Link your API to a CouchDB instance running on your local machine.
  • Assign GET, POST and DELETE routes for a model object.

Getting Started

To complete this Kitura tutorial, you’ll need:

  • macOS 10.14 (Mojave) or higher.
  • Xcode 10.1 or newer.
  • Basic familiarity with Terminal, as you’ll use the command line quite a bit in this tutorial.
Note: It’s possible to use Kitura simply with a text editor and a standalone Swift installation, which makes it possible to run Kitura even on Linux! However, this tutorial uses Xcode to take advantage of autocomplete and the nuances of a familiar development environment.

Installing CouchDB

You’ll use a database called CouchDB in this Kitura tutorial. It’s is a NoSQL database that strictly enforces JSON and uses revision keys for updates. So it’s safe — and fast!

Note: This section shows you how to use Homebrew to install and run CouchDB. If you’d prefer not to install CouchDB directly, and you have Docker installed, you may run it in Docker using the command:
docker run --rm --name couchdb -p 5984:5984 -d couchdb

When you’ve finished this tutorial, enter the following command to stop your container:

docker stop couchdb

The --rm in the docker run command will remove the container’s files from your system.

Homebrew, a popular package manager for macOS, is the easiest way to install CouchDB. If you don’t have Homebrew installed already, open Terminal and enter this command:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Enter your password if prompted. You should see Installation Successful once it completes.

Next, enter this command to install CouchDB:

brew install couchdb

Once it’s installed, enter this command to start CouchDB:

brew services start couchdb

To confirm that CouchDB is installed and running, open a web browser and navigate to http://localhost:5984. You should see something like this:

{
  "couchdb": "Welcome",
  "uuid": "29b2fe0fb4054c61e6b4b8e01761707b",
  "version": "1.7.1",
  "vendor": {
      "name": "Homebrew",
      "version": "1.7.1"
  }
}

Note: To stop CouchDB, enter brew services stop couchdb.

Before diving into this tutorial, you’ll first need to understand a little about Kitura and REST.

Kitura and RESTful API Routing

IBM created Kitura as an open-source framework in 2015, shortly after Apple open-sourced Swift. They modeled Kitura after Express.js, the de-facto framework for creating RESTful APIs using Node.js.

REST is an acronym for Representational State Transfer. In RESTful apps, each unique URL represents an object. Non-unique URLs represent actions, which are combined with RESTful verbs like GET to fetch objects, POST to insert, DELETE to remove and PUT to update objects.

Backend development often involves many components working together. You’ll only be concerned with two back end components in this Kitura tutorial: the API and database.

For example, if you want to populate a table view with a list of acronyms and their meanings, your client app sends a GET request to the backend. In practice, your app requests the URL http://yourAPI.com/acronyms.

Kitura tutorial client request made to API

The API receives your request and uses a router to decide how to handle it. The router checks all available routes, which are simply publicly accessible endpoints, to determine if there is a GET route ending in /acronyms. If it finds one, it executes the associated route’s code.

The /acronyms route then does the following:

  1. Retrieves the acronyms from the database.
  2. Serializes them into JSON.
  3. Packages them into a response.
  4. Sends the JSON response back to the requesting client.

This results in the following interaction between the API and database:

Kitura tutorial API and database interaction

If an API is RESTful, then it must also be stateless. In this example, you can think of the API as the orchestrator, commanding data to and from your ecosystem. Once the request is fulfilled, the state of the API and its routes should be unchanged and able to handle the next request.

Kitura tutorial API response to client

Just because the API is stateless doesn’t mean it isn’t allowed to store or modify objects. The API itself doesn’t store states, but it does query and update the database to fetch, store and modify objects’ states.

Creating the Kitura Tutorial Project

You didn’t download a starter project for this tutorial yet. Well, that’s because you’re going to create it from scratch, from the command line.

As part of Kitura 2.0, The Swift@IBM team has created a command line interface for Kitura that streamlines generating a similar starter project, without requiring you to write any code yourself! You can try this out (after completing this tutorial, of course) by entering the following commands in Terminal:
brew tap ibm-swift/kitura
brew install kitura
kitura init

Open Terminal and enter the following commands:

mkdir KituraTIL
cd KituraTIL
swift package init --type executable

This uses the Swift Package Manager to create a new executable package.

You should see output similar to the following:

Creating executable package: KituraTIL
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/KituraTIL/main.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/KituraTILTests/
Creating Tests/KituraTILTests/KituraTILTests.swift
Creating Tests/KituraTILTests/XCTestManifests.swift

Next, enter the following command to open Package.swift with Xcode:

open -a Xcode Package.swift

Replace the entire contents of Package.swift with the following:

// swift-tools-version:4.2

import PackageDescription

let package = Package(
  // 1
  name: "KituraTIL",
  dependencies: [
    // 2
    .package(url: "https://github.com/IBM-Swift/Kitura.git",
      .upToNextMajor(from: "2.0.0")),
    // 3
    .package(url: "https://github.com/IBM-Swift/HeliumLogger.git",
      .upToNextMajor(from: "1.0.0")),
    // 4
    .package(url: "https://github.com/IBM-Swift/Kitura-CouchDB.git", 
      .upToNextMajor(from: "3.0.0"))
  ],
  //5
  targets: [
    .target(name: "KituraTIL",
      dependencies: ["Kitura" , "HeliumLogger", "CouchDB"],
      path: "Sources")
  ]
)

Here’s what each of these commands does:

  1. You first set the name of your target executable. By convention, you should name this after the enclosing directory.
  2. Here, you declare your dependencies one-by-one; starting with Kitura itself.
  3. HeliumLogger is a back end logging framework, which you’ll use to log messages while your back end app is running.
  4. Kitura-CouchDB allows Kitura to communicate with CouchDB.
  5. Finally, you declare your target and its dependencies.

Save this file and go back to Terminal where you should still be in the same directory containing Package.swift. You are now going to add a document that sets the version of Swift for this project. Enter the following command:

echo "4.2.1" >| ".swift-version"

You are now ready to load your dependencies and build the project for the first time. Enter the following command in Terminal:

swift build

This will generate a lot of logging, ending with logs about compiling your project. You’ll see this output at the end:

Compile Swift Module 'KituraTIL' (1 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/KituraTIL

Troubleshooting Errors

In case you get errors from swift build, enter the following in Terminal to verify your Swift version:

swift --version

If your version is lower than Swift 4.2.1, this is likely your problem. To fix this, make sure that you have the latest version of Xcode 10 installed, and then run the following command:

sudo xcode-select -s /Applications/Xcode.app

…where Xcode.app should be replaced the path to Xcode 10.

If you’re still having trouble, it’s possible that you’re using swiftenv or another Swift version management tool, and you may need to manually set your Swift version to 4.2.1.

Here’s the command to do this if you’re using swiftenv:

swiftenv global 4.2.1

Using Kitura With Xcode

Still in Terminal, at the root directory for your tutorial project, enter the following command:

swift package generate-xcodeproj

You should see this output:

generated: ./KituraTIL.xcodeproj

Enter this command to open your new Xcode project:

xed .

You’ll then be greeted with this view:

From here, you need to make sure that the selected target device is My Mac. After you select that, build and run (Command + R) your Xcode project, and you’ll see this printed to the console:

Hello, world!
Program ended with exit code: 0

Awesome! You’re now ready to get your back end app up and running!

Note: If you see the warning Conversion to Swift 4.2 is available, feel free to click it. There should be no changes needed. Just click Update again, to change their build settings.

Setting Up Your Kitura Server

You need only a few lines of code to set up your server with a router and a logger.

First, create a new Swift File named Application.swift in the Sources/KituraTIL group folder — the folder that contains main.swift. Make sure to add this file to the KituraTIL executable target:

Next, replace the contents of this file with the following:

import Kitura
import LoggerAPI

public class App {
  
  // 1
  let router = Router()
  
  public func run() {
    // 2
    Kitura.addHTTPServer(onPort: 8080, with: router)
    // 3
    Kitura.run()
  }
}

Here’s what this does:

  1. The Router will handle incoming requests by routing them to the appropriate endpoints.
  2. Here, you register router to run on port 8080.
  3. Kitura will run infinitely on the main run loop after you call run().

With your App class created, open main.swift and replace its contents with the following:

import Kitura
import HeliumLogger
import LoggerAPI

HeliumLogger.use()

let app = App()
app.run()

The HeliumLogger.use() command sets up HeliumLogger as the default logger for Kitura. It’s good practice to log early and log often.

Then, you create an App instance and run it.

Build and run, and you should see log messages from Kitura appear in the console.

Next, navigate to http://localhost:8080 in your browser, and you should see this page:

Congratulations, you’re now running a basic Swift-based HTTP Server on your local machine!

Creating Your Model

In this section, you’ll create a model type that represents an acronym.

Create a new Swift File named Acronym.swift, and remember to add it to the KituraTIL target.

Replace the contents of this file with the following:

// 1
import CouchDB

// 2
struct Acronym: Document {
  // 3
  let _id: String?
  // 4
  var _rev: String?
  // 5
  var short: String
  var long: String
}

Here’s what you’ve done:

  1. You need to import this module so that you can make your struct conform to the Document protocol.
  2. By making Acronym conform to Document, you’ll be able to leverage both Codable Routing and CouchDB’s Document protocol. You’ll learn more about Codable Routing shortly, but Document allows you to take advantage of the latest database driver for CouchDB.
  3. As with most database drivers, you to have an _id property for your object — the Document protocol requires this.
  4. CouchDB will also make you keep track of the latest revision of a document to confirm any update or delete actions you perform on an entry, so the Document protocol requires this _rev property as well.
  5. Lastly, the object itself requires you to store both a long form and short form acronym for the sake of this tutorial.

Build your project to make sure everything looks good. Remember, again, to double check that your new files are part of the KituraTIL target!

Notice that your object conforms to the Document protocol. This is a mechanism that forces objects readable by the CouchDB database driver to also conform to Codable, which is going to be extremely helpful during your journey with server-side Swift.

Codable is simply a typealias that combines the Encodable and Decodable protocols. This ensures conforming objects can be converted both to and from external representations. In particular, Kitura uses this to easily convert instances to and from JSON.

Before Kitura 2.0, you had to pass a request object into every endpoint closure, parse properties manually, cast appropriately, perform necessary transformations and finally create JSON to send as a response. It was a lot of work!

You can now leverage the power of Kitura’s Codable Routing to significantly reduce the boilerplate code in your routes. Win! You simply need to make your models conform to Codable to take advantage of this, as you did above.

With this theory out of the way, it’s now time to connect your API to CouchDB.

Connecting to CouchDB

Open Application.swift, and replace its contents with the following:

// 1
import CouchDB
import Foundation
import Kitura
import LoggerAPI

public class App {

  // 2
  var client: CouchDBClient?
  var database: Database?
    
  let router = Router()
    
  private func postInit() {
    // 3
  }
    
  private func createNewDatabase() {
    // 4
  }
    
  private func finalizeRoutes(with database: Database) {
    // 5
  }
    
  public func run() {
    // 6
    postInit()
    Kitura.addHTTPServer(onPort: 8080, with: router)
    Kitura.run()
  }
}

Going over these changes:

  1. You first import CouchDB in order to set up your persistence layer.
  2. You add the properties client for CouchDB and database to keep track of changes.
  3. You’ll add code here after you’ve created your instance of App to connect to your database.
  4. In case your postInit() method doesn’t find your database, it’ll create a new one for you.
  5. Once you’ve set up your database, you’ll list all available routes for your API to match against here.
  6. You call postInit() from within run() to make this part of your API setup.

Next, complete postInit() by replacing // 3 with the following.:

// 1
let connectionProperties = ConnectionProperties(host: "localhost",
                                                port: 5984,
                                                secured: false)
client = CouchDBClient(connectionProperties: connectionProperties)
// 2
client!.retrieveDB("acronyms") { database, error in
  guard let database = database else {
    // 3
    Log.info("Could not retrieve acronym database: "
      + "\(String(describing: error?.localizedDescription)) "
      + "- attempting to create new one.")
    self.createNewDatabase()
    return
  }
  // 4
  Log.info("Acronyms database located - loading...")
  self.finalizeRoutes(with: database)
}

Here’s what you just did:

  1. You create a ConnectionProperties object that you use to specify configuration values and a new CouchDBClient.
  2. You check to see if a matching database already exists, so you don’t overwrite existing data.
  3. If a database does not exist, you call createNewDatabase() to create a new database.
  4. If a database does exist, you call finalizeRoutes(with:) to configure your routes.

Next, complete createNewDatabase() by replacing // 4 with the following:

// 1
client?.createDB("acronyms") { database, error in
  // 2
  guard let database = database else {
    Log.error("Could not create new database: "
      + "(\(String(describing: error?.localizedDescription))) "
      + "- acronym routes not created")
    return
  }
  self.finalizeRoutes(with: database)
}

Here’s what this does, piece by piece:

  1. You create your database with a given name. You can choose anything, but it’s best to keep it simple.
  2. You ensure the database exists, or else, you abort and log an error.
  3. Just like before, you call finalizeRoutes(with:) to configure your routes.

You won’t be able to implement finalizeRoutes(with:) just yet. You first need to complete your persistence layer. That’s what you’ll do in the next section.

Persisting Your Acronyms

Create a Swift File named AcronymPersistence.swift, and add it to the KituraTIL target.

Replace the contents of AcronymPersistence.swift with the following:

import Foundation
import CouchDB
import LoggerAPI

extension Acronym {
  // 1
  class Persistence {
    // 2
    static func getAll(from database: Database, callback: 
      @escaping (_ acronyms: [Acronym]?, _ error: Error?) -> Void) {
    
    }
    
    // 3
    static func save(_ acronym: Acronym, to database: Database, callback: 
      @escaping (_ acronym: Acronym?, _ error: Error?) -> Void) {
    
    }
    
    // 4
    static func delete(_ acronymID: String, from database: Database, callback: 
      @escaping (_ error: Error?) -> Void) {
    
    
    }
  }
}

OK, take a look at what you just stubbed out:

  1. By adding a class to an extension of Acronym, you are essentially creating a namespace for your class. Because you are making use of static methods, and these technically need to be globally scoped, namespacing your persistence methods allows you to make it more difficult to call them accidentally.
  2. Think about what you will want to do for your acronyms — you want an easy way to run your most basic operations, but you don’t want to write a whole bunch of logic into your router. This getAll(from:callback:) method should logically return an array of Acronyms!
  3. This save(_:to:callback:) method should easily save an object, but you’ll do more. It is common practice for a POST request to return the entire created object with a 201 response code — thus, you’ll make sure you have the entire newly created object returned to you.
  4. Lastly, when you delete an object, the HTTP route will only include a reference to the ID of the object. So you should create a method that only needs the object’s ID to delete it, even though, internally, you will also use its _rev value to delete it.

Now that you understand the usefulness of this class, and why you would abstract much of your database operation away from your routes, add the following code to your getAll(from:callback:) method:

//1
database.retrieveAll(includeDocuments: true) { documents, error in
  guard let documents = documents else {
    Log.error("Error retrieving all documents: \(String(describing: error))")
    return callback(nil, error)
  }
  //2
  let acronyms = documents.decodeDocuments(ofType: Acronym.self)
  callback(acronyms, nil)
}
Note: If you’re reading this after reading the previous version of this tutorial, you might notice how much nicer this is to write than versions past with SwiftyJSON. Not bad, right?

Take a look at the two main components that you’ve added to this function:

  1. You will notice that your database object has a lot of functionality attached to it. While you could write this in your Codable routes, it’s easier to bury this in your Persistence helper class. This will do most of the heavy lifting for you!
  2. You are taking the documents that CouchDB has returned to you and encoding them into their intended native types. This is where Codable also shines in a big way — gone are the days of having to parse parameters from a request!

Next, beef up your save(_:to:callback:) method by adding the following code to it:

// 1
database.create(acronym) { document, error in
  guard let document = document else {
    Log.error("Error creating new document: \(String(describing: error))")
    return callback(nil, error)
  }
  // 2
  database.retrieve(document.id, callback: callback)
}

Breaking this down:

  1. Just like before, you use your database object to perform your CRUD operation — but notice that you’re still getting a returned type of DocumentResponse. This contains an _id and a _rev for your newly created object, but not much else!
  2. You take the _id from your create operation, and you use your database to retrieve that specific object. You can implicitly pass the callback down to this operation because the two callback arguments have basically the same signature — this saves you some code!

Alright, time to wrap this one up! Add the following code to your delete(_:from:callback:) method:

// 1
database.retrieve(acronymID) { (acronym: Acronym?, error: CouchDBError?) in
  guard let acronym = acronym, let acronymRev = acronym._rev else {
    Log.error("Error retrieving document: \(String(describing:error))")
    return callback(error)
  }
  // 2
  database.delete(acronymID, rev: acronymRev, callback: callback)
}

And, point by point:

  1. Just like in your save(_:to:callback:) method, you are retrieving an object and using a native type in the callback. You need to get the object because your Codable route will only provide the _id, whereas the Database method requires both an _id and a _rev.
  2. Once you have your native object, you ask database to delete it, and just like you did in save(_:to:callback:), you can implicitly pass the callback to this Database operation because they have the same signature!

OK, now that you have an easy utility for working with CouchDB and your Acronym object, it’s time to dive into setting up your Codable routes!

Creating Your Codable Routes

Create a new file named AcronymRoutes.swift, and add it to the KituraTIL target. Replace the contents of AcronymRoutes.swift with the following:

import CouchDB
import Kitura
import KituraContracts
import LoggerAPI

// 1
private var database: Database?

func initializeAcronymRoutes(app: App) {
  // 2
  database = app.database
  // 3
  app.router.get("/acronyms", handler: getAcronyms)
  app.router.post("/acronyms", handler: addAcronym)
  app.router.delete("/acronyms", handler: deleteAcronym)
}

// 4
private func getAcronyms(completion: @escaping ([Acronym]?, 
  RequestError?) -> Void) {
  
}

// 5
private func addAcronym(acronym: Acronym, completion: @escaping (Acronym?, 
  RequestError?) -> Void) {

}

// 6
private func deleteAcronym(id: String, completion: @escaping 
  (RequestError?) -> Void) {

}

This is a lot of change! Walking through what you’ve just added:

  1. Since all of your routes will make use of your database object, it’s handy to keep a reference to it here.
  2. You don’t want to constantly have to refer back to your App class, so this is where you store a reference to your database object.
  3. You are “registering” three routes here, choosing the paths that they will be registered for, and the handlers that will run when a request is made to any of them.
  4. Assuming you are launching your server on localhost:8080, your router will run this function with the data parsed from the request, if someone makes a GET request to localhost:8080/acronyms.
  5. This function runs if someone makes a POST request to localhost:8080/acronyms.
  6. This function runs if someone makes a DELETE request to localhost:8080/acronyms/id, replacing the id part of this path with an actual ID string.

OK, it’s now time to make your server actually do something. Add this to your getAcronyms(completion:) route handler:

guard let database = database else {
  return completion(nil, .internalServerError)
}
Acronym.Persistence.getAll(from: database) { acronyms, error in
  return completion(acronyms, error as? RequestError)
}

Pretend you’re a chef and kiss your fingers — this is beautiful. It may have taken a little bit of setup in the beginning, but now you have a drop-dead simple way to get all the acronyms in your database and respond to a request for them. No JSON, no object validation, no headache — you get an object, and you send it off, along with whatever error you might have encountered.

Next, add the following code to your addAcronym(acronym:completion:) route handler:

guard let database = database else {
  return completion(nil, .internalServerError)
}
Acronym.Persistence.save(acronym, to: database) { newAcronym, error in
  return completion(newAcronym, error as? RequestError)
}

Again — just beautiful. Notice that you have the ability to return any HTTP error code as an enum case as well! Here, you send a HTTP 500 error if you can’t get a handle on your database. Command-click on .internalServerError if you want to see what the other errors are!

The last route you need to update is your deleteAcronym(id:completion:) route, but you can probably already figure out what’s going to go in there:

guard let database = database else {
  return completion(.internalServerError)
}
Acronym.Persistence.delete(id, from: database) { error in
  return completion(error as? RequestError)
}

I personally love how you can make the external parameter label in function signatures optional, as you did with Persistence.delete(_:from:callback:). This generally lets you focus on making your functions English-readable as well as code-readable, and this is a great example of that feature in Swift.

To complete your app, open Application.swift and complete finalizeRoutes(with:) by replacing // 5 with the following:

self.database = database
initializeAcronymRoutes(app: self)
Log.info("Acronym routes created")

Testing Your API

Build and run your project, and navigate to http://localhost:8080 to ensure your server is still running.

Then, open Terminal, and enter the following command to GET all acronyms:

curl http://localhost:8080/acronyms

If everything is set up correctly, you should get back an empty JSON placeholder ([]). This means you’ve correctly set up your GET route!

Now, add a new acronym to your back end — enter the following command:

curl -X POST http://localhost:8080/acronyms -H 'content-type: application/json' -d '{"short": "BRB", "long": "Be right back"}'

You should see a response like this:

{"id":"b2edde7b8032c30c7aeeff8d18000ad9","short":"BRB","long":"Be right back"}

In Xcode, Kitura’s log messages should include a Received POST type-safe request message.

To verify this actually saved to the database, enter the following GET command again, or go to http://localhost:8080/acronyms right in your browser:

curl http://localhost:8080/acronyms

If you get back the same JSON you entered, inside an array, then congratulations! You’ve successfully created a working API!

Where to Go From Here?

This tutorial has introduced you to Kitura by building a backend API. However, that is not all Kitura can do! In our Kitura Stencil Tutorial, you’ll learn how to use Kitura and Stencil to build a website that includes a front end.

You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial. You can also read through IBM’s introduction to Kitura Codable Routing.

There’s a lot of material on the internet about Kitura, and some especially great stuff available directly from IBM! If you’d like to continue learning about Codable Routing and other Kitura 2.0 features, check out this tutorial.

I encourage you to comment in the forum below if you have any questions or comments!

Average Rating

5/5

Add a rating for this content

1 rating

Contributors

Comments