Contact

©2019 by XMLAuthor Inc.

Building a Swift Server Side API

Here's a quick tutorial on writing server side APIs with Swift and Kitura on IBM Bluemix. This tutorial starts by writing the first API for a website project called RainFonts. RainFonts will host and serve webfonts. And on the backend, RainFonts will need robust APIs.

 

The key to building solid server side APIs in Swift, is understanding Completion Handlers, JSON parsing, Routers, Cloud Database and Cloud Storage.

 

First it is important to understand Completion Handlers

 

Normally in Swift, functions have data types for their parameters, such as Int or String. But in Swift, it is possible have an entire function's block of code as a parameter. These blocks of code are called a "closure". I'm not sure why they call it a closure. I guess funblock sounds to fun. It's a good way to remember what a closure is though.

 

The iOS Developer Documentation describes Closures as

“Self-contained blocks of functionality that can be passed around and used in your code.”

 

Now if you think about APIs, they are asynchronous. When a call is made from a client App to a remote server API, the response will take awhile. The cool thing about a completion handling closure, is that it can wait to be called until the API request completes.

 

So what is the role of the completion hander? Well the funblock of code can take the results and do something, like updating a UI or sending the result back from an HTTP request.

 

The funcode closure is always wrapped in curly brackets like so:

{ (parameters) -> return type in

statements

}

So lets consume an API call and see a funblock closure in action. The HTTP API handler in this example returns two parameters, json and error. If you look carefully at the code below, the funblock returned from makeHTTPGetRequest in turn calls the onCompletion funblock passed to the getFontInfo mother function to process the JSON result. See the inline comments. This function both accepts a completion handler as a parameter and processes the makeHTTPGetRequest with a closure.

 

 

Our First API

 

Now that we have a basic understanding of how to create as well as consume closure completion handlers, lets go ahead and write our own server side API in Swift. What a great idea, the same coding language on both the client and the server. A coders dream come true.

 

Here's our game plan:

 

1. Create a protocol for your Swift API. This is important because as you add more APIs you will want them orgainized in one file. Protocols define the blueprint requirements for each API, kind of like the old header file definition of a function.

 

2. Setup your Router & Handler. The router makes it possible for anyone to consume your APIs with standard HTTP requests like GET, PUT, POST and DELETE. Your router will use your swift APIs from their handlers. IBM's Kitura framework for server side swift makes it easy for us to create routers.

 

3. Write the API. Write your API function in swift to match your protocol.

 

4. Compile and test locally.

 

5. Publish to Bluemix.

 

Lets build a simple webservice (API) that returns the count of all the fonts we have uploaded to RainFonts. That count value should be in our database. We are going to use a Cloudant database for this example and all the subsequent APIs for RainFonts. Eventually, we will write a complete set of RainFont APIs for hosting and serving webfonts on Bluemix.

 

Protocol

 

Ok, I'm going to create a swift file called RainFontsAPI.swift for the protocol. You can just follow along for now. The protocol file will look like this. Later we can can add more protocols for all the RainFonts APIs.

public protocol RainFontsAPI {

 

// Get the count of all fonts. Notice that @escaping the funblock means it can be deferred until the function completes; an async closure.

 

func countFonts(completion: @escaping (Int?, Error?) -> Void)

 

}

Routers & Handlers

I'm creating a file called RainFontsController.swift and adding the router and handler to consume our fontCount APIs and process the completion with a funblock. Here's a snippets from the RainFontsController.swift file.

 

import Foundation

import Kitura

import LoggerAPI

import SwiftyJSON

 

public final class RainFontsController {

public let fontsapi: RainFontsAPI

public let router = Router()

public let fontsPath = "api/fonts"

 

public init(backend: RainFontsAPI) {

self.fontsapi = backend

routeSetup()

}

 

public func routeSetup() {

router.all("/*", middleware: BodyParser())

// RainFonts Handling

 

// Fonts Count is a RESTful GET to fontsPath/count. The handler is getFontsCount below.

router.get("\(fontsPath)/count", handler: getFontsCount)

}

 

// Fonts count handler

private func getFontsCount(request: RouterRequest, response: RouterResponse, next: () -> Void) {

// Call the countFonts API. Notice that we pass a funblock (closure) parameter that will check for an error and proccess the count, sending int back as a JSON with status once the countFonts API completes.

fontsapi.countFonts { (count, err) in

do {

guard err == nil else {

try response.status(.badRequest).end()

Log.error(err.debugDescription)

return

}

guard let count = count else {

try response.status(.internalServerError).end()

Log.error("Failed to get count")

return

}

// looks like we got the number of fonts so return this as JSON

let json = JSON(["count": count])

try response.status(.OK).send(json: json).end()

} catch {

Log.error("Communications Error")

}

}

}

 

}

The first API

 

I've created a file called RainFonts.swift. This will contain the class and functions for our APIs.

 

Here's the beginning of the file with the countFonts class that conforms to the RainFontsAPI protocol.

 

import Foundation

import SwiftyJSON

import LoggerAPI

import CouchDB

import CloudFoundryEnv

 

#if os(Linux)

typealias Valuetype = Any

#else

typealias Valuetype = AnyObject

#endif

 

public enum APICollectionError: Error {

case ParseError

case AuthError

}

 

public class RainFonts: RainFontsAPI {

static let defaultDBHost = "localhost"

static let defaultDBPort = UInt16(5984)

static let defaultDBName = "rainfontsapi"

static let defaultUsername = "rob"

static let defaultPassword = "napali"

let dbName = "rainfontsapi"

let designName = "rainfontsdesign"

let connectionProps: ConnectionProperties

And finally, here is our first server side swift API called countFonts.

 

// Count of all the uploaded fonts by accessing the Cloudant database

// The completion handler is passed back the count and and error

// @escaping says the funblock completion handler will be called when

// the count is retreived. An async designator.

public func countFonts(completion: @escaping (Int?, Error?) -> Void) {

let couchClient = CouchDBClient(connectionProperties: connectionProps)

let database = couchClient.database(dbName)

// query the db

database.queryByView("total_fonts", ofDesign: "fontsdesign", usingParameters: []) { (doc, err) in

if let doc = doc, err == nil {

if let count = doc["rows"][0]["value"].int {

// call the passed funblock (closure) completion handler with the count

completion(count, nil)

} else {

completion(0, nil)

}

} else {

completion(nil, err)

}

}

}

 

Compile and test locally

The Cloudant NoSQL DB on Bluemix is API compatible with the Apache CouchDB, with a few subtle distinctions. This means we can use CouchDB, as we have done in our first API, test locally ad then pushing our code to the server.

 

Please reload