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. By David Okun.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

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.