Codable Routing Session

HTTP is a stateless connection protocol, that is the server can't distinguish one request from another. Sessions and cookies provide HTTP with state, they allow the server to know who is making a specific request and respond accordingly.

This guide uses Kitura-Session with Codable routing leveraging type-safe sessions. If you want to use sessions with raw routing, check out the Kitura Session with raw routing guide.


Step 1: Create your session routes

In this guide we are going to create two Kitura routes:

  • A GET route, where we retrieve the books from our session.
  • A POST route, where we store a book in our session.

We are using the Book model from the routing guide in our routes, however you could use any codable object.

To use Kitura-Session from a server, we need to add Kitura-Session to our dependencies.

If you don't have a server, follow our Create a server guide.

Once that is complete, open your Application.swift file in your default text editor (or Xcode if you prefer):

Open your Application.swift file:

open Sources/Application/Application.swift

Inside the postInit() function add:

initializeTypeSafeSessionRoutes(app: self)

Create a new file, called TypeSafeSessionRoutes.swift:

touch Sources/Application/Routes/TypeSafeSessionRoutes.swift

Open your TypeSafeSessionRoutes.swift file:

open Sources/Application/Routes/TypeSafeSessionRoutes.swift

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

import KituraContracts
import KituraSession

func initializeTypeSafeSessionRoutes(app: App) {
    app.router.post("/cart", handler: app.postSessionHandler)
    app.router.get("/cart", handler: app.getSessionHandler)
}
extension App {
    // Define handlers here
}

We will add our postSessionHandler and getSessionHandler later in this guide.


Step 2: Define your Session

To use sessions with Codable routes, we need to model the structure of what we will store in the session. We do this by defining a Swift type that conforms to the TypeSafeSession protocol.

If you don't already have one, create a Middlewares folder:

mkdir Sources/Application/Middlewares

Create a new file, called CheckoutSession.swift:

touch Sources/Application/Middlewares/CheckoutSession.swift

Open your CheckoutSession.swift file:

open Sources/Application/Middlewares/CheckoutSession.swift

We can use either a class or a struct for TypeSafeMiddleware here but we will use a Class.

Inside this file, define your CheckoutSession class:

import KituraSession

final class CheckoutSession: TypeSafeSession {

    let sessionId: String                       // Requirement: every session must have an ID
    var books: [Book]                           // User-defined type, where Book conforms to Codable

    init(sessionId: String) {                   // Requirement: must be able to create a new (empty)
        self.sessionId = sessionId              // session containing just an ID. Assign a default or
        books = []                              // empty value for any non-optional properties.
    }
}
// Defines the configuration of the user's type: how the cookie is constructed and how
// the session is persisted.
extension CheckoutSession {
    static let sessionCookie: SessionCookie = SessionCookie(name: "MySession", secret: "Top Secret")
    static var store: Store?
}

When using a class you need to declare it as final.

The minimum requirements for a type-safe session are:

  • sessionID: An identifier that is unique to the session
  • Initilizer: Used to create a new session from a sessionId
  • sessionCookie: Defines the name of the cookie and the secret data used to encrypt it
  • An optional store: Defines how sessions should be persisted

With our class, we have created a session that will store an array of books, via a cookie called "MySession" and using "Top Secret" as the password to encrypt that cookie. Since we didn't specify a store a default in memory store is used.

For an example of a persistent store for sessions see Kitura-Session-Redis


Step 3: Add session POST route

Inside the app extension in TypeSafeSessionRoutes.swift, we add our postSessionHandler:

func postSessionHandler(session: CheckoutSession, book: Book, completion: (Book?, RequestError?) -> Void) {

}

We have registered our TypeSafeSession on our handler by adding it to the signature.

When the handler is called the middleware is run and our session is created.

Within postSessionHandler, we can then interact with our session:

session.books.append(book)
session.save()
completion(book, nil)

What we're doing here is storing the posted Book into the session.

We use the append method as we know books is an Array as we defined it as an Array in the CheckoutSession class.

Then we need to save the session.

Now we can add a route for retrieving the data.


Step 4: Add session GET route

Below our postSessionHandler handler add:

func getSessionHandler(session: CheckoutSession, completion: ([Book]?, RequestError?) -> Void) -> Void {
    completion(session.books, nil)
}

Add that's it! We've now enabled a Kitura Session on our server.

Your completed TypeSafeSessionRoutes.swift should now look as follows:

import KituraContracts
import KituraSession

func initializeTypeSafeSessionRoutes(app: App) {
    app.router.post("/cart", handler: app.postSessionHandler)
    app.router.get("/cart", handler: app.getSessionHandler)
}
extension App {
    func postSessionHandler(session: CheckoutSession, book: Book, completion: (Book?, RequestError?) -> Void) {
        session.books.append(book)
        session.save()
        completion(book, nil)
    }
    func getSessionHandler(session: CheckoutSession, completion: ([Book]?, RequestError?) -> Void) -> Void {
        completion(session.books, nil)
    }
}

Step 5: Test our Session

An easy way to test our our session is using Kitura OpenAPI. You can follow our Kitura OpenAPI Guide to learn how to set up OpenAPI and use the interface for testing.

Alternatively, we can test our routes by sending a request using curl, with cookies enabled.

Open Terminal and enter the following:

curl -X POST \
http://localhost:8080/cart \
-b cookies.txt -c cookies.txt \
-H 'content-type: application/json' \
-d '{
    "id": 1,
    "title": "War and Peace",
    "price": 10.99,
    "genre": "Historical drama"
}'

If our request was successful, our book will be returned to us:

{"id":1,"title":"War and Peace","price":10.99,"genre":"Historical drama"}

We will also have a cookie that curl has stored in the cookies.txt file.

To retrieve our book, we make another curl request to our server:

curl -X GET \
http://localhost:8080/cart \
-b cookies.txt -c cookies.txt

If the request is successful, it will return the book we just sent to the server.

[{"id":1,"title":"War and Peace","price":10.99,"genre":"Historical drama"}]

The cookie we sent with our request has identifed our session, so that our saved book can be returned.

Each user making requests to these routes will create their own basket of books.

We can demonstrate this by deleting our cookie:

rm cookies.txt

Followed by making a new GET request:

curl -X GET \
http://localhost:8080/cart \
-b cookies.txt -c cookies.txt

This represents a user without a session so we are returned an empty array:

[]

Next steps

Authentication: Learn about authentication and what Kitura provides.