When making a web request of a web server, either from a browser of from a library making a REST request, the request consists of a HTTP request method, eg. GET, PUT or POST, a URI or path on which the request is made, eg. ‘/’ for the root, and optionally a body of data associated with the request.

When a request is made by a web browser to load a website, this is actually consists of a GET request to the root URI, eg: ‘GET /’, with no body data.

Routing refers to how a web application handles that request. In Kitura, you typically register handlers (function or closures) against HTTP methods and URIs/paths that should be run when it matches the route request from the client. The route definition takes the following structure:

	router.METHOD(PATH, handler: HANDLER)

Where:

  • router is an instance of the Kitura Router
  • METHOD is a HTTP request method in lowercase
  • PATH is the path on the server
  • HANDLER is the function or closure to execute

Kitura 1.x supported “Raw Routing” only, where the route handlers were called with RouterRequest and RouterResponse objects with which to handle the client request and build the response, along with a next completion handler. This approach provides great flexibility and control, but requires you to understand the structure of requests, how to interpret HTTP request headers correctly, how to verify data, and to manually carry out things like JSON parsing.

Kitura 2.0 introduces “Codable Routing”. Here the the router handlers are much like normal functions you might define elsewhere in your code: they take stuct or class types as parameters, and respond with stuct or class types via a completion handler, with just an additional requirement that those types implement Codable from Swift 4 (hence the name).

Codable Routing isn’t suitable for every use case and scenario, so Raw Routing is still available where you need the power and flexibility, but it is perfect for building REST APIs such as you might want to do to build a Backend For Frontend (BFF) for an iOS app.

Building REST APIs

Representational state transfer (REST) or RESTful APIs are a way of building web services to allow clients and servers to communicate using standard HTTP protocols. Essentially its an architectural and design pattern for building an API using HTTP request methods, URI paths, and request and response body data.

Below is a typical REST API for creating an maintaining a set of “To Do” items in a todo list. These provide Create, Read, Update and Delete (CRUD) APIs allowing a client to interact with these “To Do” items:

Action HTTP method Path Request Body Response Body
Create or store a new item POST /todos To Do item to store Stored To Do item
Read all items GET /todos All stored To Do items
Read item #<id> GET /todos/<id> Stored To Do item
Replace item #<id> PUT /todos/<id> Replacement To Do item Updated To Do item
Partially update item #<id> PATCH /todos/<id> Replacement To Do item Updated To Do item
Delete item #<id> DELETE /todos/<id>
Delete all items DELETE /todos

Some of the APIs also have additional requirements. For example, Create should also return an HTTP response code of 201 (Created) and optionally store the location of the created item in the HTTP “Location” header on a successful create.

In addition to the laying out the APIs correctly, you also need to deal with the encoding and decoding of the request and response bodies, checking and setting the ‘Content-Type’ HTTP header for the protocol (typically ‘application/json’) and validating the data is correct.

Codable Routing removes the complexity of building REST APIs and dealing with request and response body data by allowing you to register handlers that work directly with concrete Swift types.

Building a Create API with a Codable Route for POST requests

Building a Codable Route for a POST API to implement storing a To Do item requires only 4 lines of code. The first step is to register a handler for POST requests on “/todos”:

router.post("/todos", handler: storeHandler)

and then provide an implementation of the storeHandler:

func storeHandler(todo: ToDo, completion: (ToDo?, RequestError?) -> Void ) -> Void {
    todoStore.append(todo)
    completion(todo, nil)
}

Here the store handler accepts a todo item of type ToDo and responds using an asynchronous completion handler. The handler then stores the received todo item in the todoStore and returns with the created todo item in the case of success, and an Error, in the the case of failure.

The input parameter and the response parameter in completion handler can be any Swift type you want, as long as it conforms to Codable. For example, we could use the following as our ToDo type:

public struct ToDo : Codable {
    public var title: String		// title of the todo item
    public var order: Int		    // order to display the todo item
    public var completed: Bool	    // has the item been completed
}

At this point the server has stored the item, but the client has no way of referring to that specific item on the server if it wants to get that item, update it, or delete it. In order to do that, some kind of unique identifier is required.

Returning an identifier to the client for the stored data is typically done in one of two ways:

  1. Adding an additional Optional field to the type
    Here the ToDo struct would be updated to add an extra field:

    public struct ToDo : Codable {
        public var title: String		
        public var order: Int		
        public var completed: Bool	
        public var id: Int?			// Additional field for identifier
    }
    

    And the storeHandler function would then set it:

        func storeHandler(todo: ToDo, completion: (ToDo?, RequestError?) -> Void ) -> Void {
            var todo = todo
            let id = todoStore.count
            todo.id = id
            todoStore.append(todo)
            completion(todo, nil)
        } 
    
  2. Responding with an additional Identifier value

    Alternatively you can choose to respond with an additional value in the completion handler that conforms to the Identifier protocol, which String and Int have been extended to conform to already. The additional identifier value is then stored in the “Location” header of the response.

    Here the storeHandler would become the following:

        func storeHandler(todo: ToDo, completion: (Int?, ToDo?, RequestError?) -> Void ) -> Void {
            var todo = todo
            let id = todoStore.count
            todoStore.append(todo)
            completion(id, todo, nil)
        } 
    

Building a Read API with a Codable Route for GET requests

Now that the Kitura application can store a ToDo item and inform the client where it is, there needs to be a REST API to allow the client to read it. This is done by registering a handler for GET requests on ‘/todos/‘.

router.get("/todos", handler: getOneHandler)

The registration of the handler is only against the “/todos” path, but we also need to accept an sent from the client. This is done in the handler itself:

func getOneHandler(id: Int, completion: (ToDo?, RequestError?) -> Void ) -> Void {
    completion(todoStore[id.id], nil)
}

Here the Kitura router itself takes the parses the URI path, and converts the id into an Int before calling the handler. Similar to the way that you can specify incoming data parameters of as type that implements Codable, it is possible to specify identifier parameters as any type that implements Identifier – which is a protocol provided by Kitura.

Building Identifiers with the Identifier Protocol

Defining an Identifier for use with Codable Routing has two requirements. The first is that it must implement the Identifier protocol, creating an instance that can be used as an identifier from a string constructor.

The following is an example of a custom Identifier called Item:

public struct Item: Identifier {
    public var value: String
    public let id: Int

    public init(value: String) throws {
        if let id = Int(value) {
            self.id = id
            self.value = value
        } else {
            throw IdentifierError.invalidValue
        }
    }
}

The second requirement is that the original String used in the constructor must be stored in the value field. The reason for this is to allow the Identifier to be used when sharing code: data types and APIs, with the KituraKit client.

Sharing code with the client using KituraKit

Because the Codable Handlers that are registered with the Router do not use complex RouterRequest and RouterResponse objects but instead work with concrete Swift Types, it becomes must easier to share code between the client and the server.

The ease of which you can share code, including the Codable Types, with the client depends on the client connection library used. If the client library does not support passing Codable types directly, then the types need to be encoded to JSON manually using JSONEncoder.encode(), and the responses decoded using JSONDecoder.decode(). Additionally the Identifiers need to be encoded into the URL.

In order to simplify this as much as possible, Kitura also provides KituraKit: a pure-Swift client library that can be used on both iOS and Linux that mirrors the Kitura Router API as closely as possible in order to maximize code sharing.

With KituraKit it becomes possible to share and import your Codable and Identifier types and use KituraKit to make client calls to the Kitura server with matching APIs. The following code makes a Read (GET) call from the client to the Kitura server using KituraKit:

        let client = KituraKit(baseURL: "http://localhost:8080")

        let id = todo.id
        client.get("/todos", id: id, completion: completion)
        
        func completion(todo: ToDo?, error: RequestError?) {
            guard error == nil else {
                print("Error reading ToDo item from Kitura: \(error!)")
                return
            }
            guard let todo = todo else {
                print("Error reading todo from Kitura, no error and no response")
                return
            }
            print("Read \(todo.title) from Kitura server")
        }

Building a real REST API with Codable Routing and KituraKit

The above examples are just fragments of what’s required to use Codable Routing to implement a real RESTful API, and KituraKit to share code and exploit it from a Swift client. The following provide two step-by-step tutorials for building a real API:

  • FoodTracker Backend
    This builds a Kitura backend for the FoodTracker iOS application that is provided as part of the Apple tutorials for building your first iOS app. This uses Codable Routing to build the server, and updates the FoodTracker iOS app with KituraKit to be able to store and retrieve data from the Kitura server.
  • ToDo Backend
    This builds a Kitura backend that passes the specification and tests for the ToDo web client using Codable Routing. Additionally we provide an example iOS app implementation of the ToDo web client that uses KituraKit to communicate with the Kitura server.

4 comments on"Introducing Codable Routing in Kitura 2.0"

  1. Wow.. 🙂 Fantastic.. Let me try out!!

  2. ChrisBailey October 31, 2017

    If you have any questions, you can talk to the Kitura community directly on Slack: http://swift-at-ibm-slack.mybluemix.net/

  3. Søren Sønderby Nielsen November 03, 2017

    Nice work! Just FIY. In the sentence “The additional identifier value is then stores in the “Location” header of the response.”, stores should probably be stored 🙂

Leave a Reply