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 3 of 4 of this article. Click here to view the first page.

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")