Currently in Beta - View old docs

Codable Routing

Codable routing is where the router handlers are just normal functions you might define elsewhere in your code; they take struct or class types as parameters, and respond with struct or class types via a completion handler. The only requirement is that those types conform to the Codable protocol introduced in Swift 4 (hence the name).

Prerequisites

  • Kitura Server: Learn how to create one in our Getting Started guide.
  • Kitura Style Guide: Follow our guide to see how a production ready server should be structured.
  • Book Model: Define a Book model, as described in Step 5 of the Style Guide.

Step 1: Create a file to contain the routes

Open `Sources` > `Application` > `Application.swift`.

Inside the `postInit()` function add:

initializeCodableRoutes(app: self)

Create a new file called `CodableRoutes.swift` in `Sources` > `Application` > `Routes`.

Inside this file, add the framework for our routes code:

import KituraContracts

    func initializeCodableRoutes(app: App) {
        // Register routes here
    }
    extension App {
        static var codableStore = [Book]()
        // Write handlers here
    }

We have imported the KituraContracts library as it contains the shared type definition for `RequestError` which we will use in the next step.

Step 2: Create a POST Codable Route

Inside the `initializeCodableRoutes` function add:

app.router.post("/codable", handler: app.postHandler)

What we've done is register a POST on our router that will handle any POST requests made on "/codable".

This will not compile as we haven't actually implemented the `postHandler` yet, so let's go ahead and do that.

The `postHandler` is a block of code, called a closure, that is executed when a POST request is made to "/codable".

Inside the `App` extension add:

func postHandler(book: Book, completion: (Book?, RequestError?) -> Void) {
        App.codableStore.append(book)
        completion(book, nil)
    }

We can now successfully compile the project!

Step 3: Test the POST route

With the project now compiling we can start the server.

Kitura has support for OpenAPI which makes testing Codable routes easy and provides a UI for testing.

You can add OpenAPI to your server using our OpenAPI guide.

To test the route using curl, open Terminal and enter the following:

curl -X POST \
      http://localhost:8080/codable \
      -H 'content-type: application/json' \
      -d '{
        "id": 0,
        "title": "A Game of Thrones",
        "price": 14.99,
        "genre": "Fantasy"
    }'

If the Codable route was created correctly we should see the following:

{"id": 0,"title":"A Game of Thrones","price":14.99,"genre":"Fantasy"}

We have just successfully posted a book to the server and had it returned.

Step 4: Create a GET ALL Codable Route

We register a GET route in a similar way to the POST route.

Inside the `initializeCodableRoutes` function add:

app.router.get("/codable", handler: app.getAllHandler)

Just like before we now need to define the handler.

Inside the `App` extension add:

func getAllHandler(completion: ([Book]?, RequestError?) -> Void) {
        completion(App.codableStore, nil)
    }

You may have noticed that the completion here is expecting an array of books, this is because our route does not provide an identifier, so we retrieve all of the books.

The result of all the changes should look something like this:

Now we can restart our server to test our new endpoint.

Once the server is running, post a book using the curl command in Step 3.

Open a browser at:

localhost:8080/codable

This will make a GET request to the server and we should see the book we posted:

[{
      "id": 0,
      "title": "A Game of Thrones",
      "price": 14.99,
      "genre": "Fantasy"
    }]

Step 5: Create a GET ONE Codable Route

Now we will create another GET route. This time we will register a handler for GET requests on "/codable/<id>" which will allow an identifier, <id> to be sent from the client which will identify the book to return information for.

Inside the `initializeCodableRoutes` function add:

app.router.get("/codable", handler: app.getOneHandler)

Just like before we now need to define the handler.

Inside the `App` extension add:

func getOneHandler(id: Int, completion: (Book?, RequestError?) -> Void) {
        guard id < App.codableStore.count, id >= 0 else {
            return completion(nil, .notFound)
        }
        completion(App.codableStore[id], nil)
    }

In the handler, we are provided with an identifier `id`. This is the value following our route and can be either an `Int` or a `String`, in our example we use `Int`, as our model contains an identifier of type `Int`. We then use this identifier to return a single `Book.`

Now we can restart our server to test our new endpoint.

Once the server is running, open the browser at:

localhost:8080/codable/0

This will make a GET request to the server and we should see the first book in JSON format:

{
        "id": 0,
        "title": "A Game of Thrones",
        "price": 14.99,
        "genre": "Fantasy"
    }

Now we will POST a second book to the server:

curl -X POST \
    http://localhost:8080/codable \
    -H 'content-type: application/json' \
    -d '{
      "id": 0,
      "title": "Harry Potter",
      "price": 10.00,
      "genre": "Fantasy"
    }'

Then open the browser at:

localhost:8080/codable/1

This will make a new GET request to the server and we should see the second book in JSON format:

{"id": 1,"title":"Harry Potter","price":10.00,"genre":"Fantasy"}

Step 6: Making codableStore thread safe (Optional)

Kitura route handlers are asynchronous. If threads from multiple routes access the same object at the same time they will crash. To prevent these collisions, we will serialize access to the `codableStore`.

If you have completed the Raw Routing guide, you will already have the execute function.

`Inside `Application` > `Application.swift`, Add `Dispatch` to our import statements:

import Dispatch

Inside the `App` class, add a `DispatchQueue`:

let workerQueue = DispatchQueue(label: "worker")

At the end of the `App` class, add a helper function for atomically executing code:

func execute(_ block: (() -> Void)) {
       workerQueue.sync {
           block()
       }
    }

Back in `CodableRoutes.swift`, we wrap the code in our handlers with this execute function.

Your completed `CodableRouting.swift` should now look as follows:

import KituraContracts

    func initializeCodableRoutes(app: App) {
        app.router.post("/codable", handler: app.postHandler)
        app.router.get("/codable", handler: app.getAllHandler)
        app.router.get("/codable", handler: app.getOneHandler)
    }
    extension App {
        static var codableStore = [Book]()

        func postHandler(book: Book, completion: (Book?, RequestError?) -> Void) {
            execute {
                App.codableStore.append(book)
            }
            completion(book, nil)
        }

        func getAllHandler(completion: ([Book]?, RequestError?) -> Void) {
            execute {
                completion(App.codableStore, nil)
            }
        }
        func getOneHandler(id: Int, completion: (Book?, RequestError?) -> Void) {
            execute {
                guard id < App.codableStore.count, id >= 0 else {
                    return completion(nil, .notFound)
                }
                completion(App.codableStore[id], nil)
            }
        }
    }

We can continue to make POST and GET requests from our bookstore.

However, if the server is restarted, all the data will be lost and we will have an empty array again.

In the Database Guide we will look to resolve this issue and add persistence.

Next steps

Add Kitura OpenAPI: Provides a UI for viewing information about Codable routes.

Back to top