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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Kitura Tutorial: Getting Started With Server-Side Swift
30 mins
- Getting Started
- Installing CouchDB
- Kitura and RESTful API Routing
- Creating the Kitura Tutorial Project
- Troubleshooting Errors
- Using Kitura With Xcode
- Setting Up Your Kitura Server
- Creating Your Model
- Connecting to CouchDB
- Persisting Your Acronyms
- Creating Your Codable Routes
- Testing Your API
- Where to Go From Here?
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:
- 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. - 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 ofAcronym
s! - This
save(_:to:callback:)
method should easily save an object, but you’ll do more. It is common practice for aPOST
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. - 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)
}
SwiftyJSON
. Not bad, right?Take a look at the two main components that you’ve added to this function:
- You will notice that your
database
object has a lot of functionality attached to it. While you could write this in yourCodable
routes, it’s easier to bury this in yourPersistence
helper class. This will do most of the heavy lifting for you! - 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:
- Just like before, you use your
database
object to perform your CRUD operation — but notice that you’re still getting a returned type ofDocumentResponse
. This contains an_id
and a_rev
for your newly created object, but not much else! - You take the
_id
from yourcreate
operation, and you use yourdatabase
to retrieve that specific object. You can implicitly pass thecallback
down to this operation because the twocallback
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:
- 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 yourCodable
route will only provide the_id
, whereas theDatabase
method requires both an_id
and a_rev
. - Once you have your native object, you ask
database
to delete it, and just like you did insave(_:to:callback:)
, you can implicitly pass the callback to thisDatabase
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:
- Since all of your routes will make use of your
database
object, it’s handy to keep a reference to it here. - You don’t want to constantly have to refer back to your
App
class, so this is where you store a reference to yourdatabase
object. - 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.
- 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 aGET
request tolocalhost:8080/acronyms
. - This function runs if someone makes a
POST
request tolocalhost:8080/acronyms
. - This function runs if someone makes a
DELETE
request tolocalhost:8080/acronyms/id
, replacing theid
part of this path with an actual ID string.
OK, it’s now time to make your server actually 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
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 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")