Pragmatism in the real world

Getting Started with Kitura

Kitura is a new web framework for Swift 3 by IBM. As Swift is a compiled language, your Kitura application compiles to a binary that can either act as its own webserver (by default, on port 8090) or as a FastGCI server for use with Nginx or Apache.

By the end of this tutorial, we will have built a simple HTTP API that uses CouchDB for persistence.

Note that this tutorial doesn’t teach you Swift. I recommend The Swift Programming Language as a great place to start learning the language. Start with the tour.

Setting up Swift

To start with, we need a working Swift 3 compiler. Since September 2016, Swift 3.0 has been released, so we will use that. Note that for macOS you need Xcode installed and for Linux, you’ll need clang (sudo apt-get install clang)

To control which version of Kitura is used in any given project, we can use swiftenv which manages multiple Swift installations and selects the right one for our specific project. Follow the installation instructions to install.

For this project, we’re going to use Swift 3.0 final, so install it using:

$ swiftenv install 3.0

(Note, that on OS X, you will need Xcode 8)

Create the project directory

Let’s create our project directory now:

$ mkdir bookshelfapi
$ cd bookshelfapi
$ swiftenv local 3.0

This will create a .swift-version file in our directory and if you then run swift --version, you should see:

Swift version 3.0 (swift-3.0-RELEASE)

(on Linux – something similar on macOS)

Use version control

Everything is better with version control, so let’s get that going now:

$ git init
$ echo -e "# Kitura Bookshelf API tutorial\n" >> README.md
$ git add README.md
$ git commit -m "Add README"
$ git add .swift-version
$ git commit -m "Set swift-version to 3.0a"

Swift’s package manager creates and manages a directory called Packages and the build system uses a directory called .build, so we can add both of these to .gitignore as we don’t want git to manage them. Let’s do that now:

$ echo -e ".build/*\nPackages/*" >> .gitignore
$ git add .gitignore
$ git commit -m "Add .gitignore"

For the rest of the tutorial, I’ll assume that you will commit as and when you think that it’s wise to!

Hello world

We’re going to use the Swift Package Manager to manage our dependencies and control the building of our application. This uses a file called Package.swift in our root directory, so let’s create it:

Package.swift:

import PackageDescription

let package = Package(
    name: "BookshelfAPI"
)

Throughout this tutorial, I’ll present code that I want you to write in blocks like this with the filename to use above it. So go ahead and create the file if it doesn’t yet exist and then add the new lines of code.

All we’ve done so far is name our application as BookshelfAPI. We’ll add dependencies shortly, but first we want to check we can compile and run a simple application.

SwiftPM expects our source code to be in the Sources sub-directory and the entry point to a SwiftPM application is a file called main.swift. This can be in the Sources directory or in a sub-directory of Sources. The first level of subdirectories in Sources are special as they represent compilable units such as executables or libraries.

We’ll create Sources/BookshelfAPI to hold our source code. SwiftPM will then name our binary BookshelfAPI for us.

$ mkdir -p Sources/BookshelfAPI

We can now create our first Swift source file:

Sources/BookshelfAPI/main.swift

print("Hello world")

We can now compile our application and see if it worked:

$ swift build

This command will compile our code and put the resulting binary in the .build/debug directory. The command’s output is:

Compile Swift Module 'BookshelfAPI' (1 sources)
Linking .build/debug/BookshelfAPI

We can then run it like this:

$ .build/debug/BookshelfAPI

And you should see the output:

Hello world

We have successfully built our first Swift application.

Our first Kitura application

To write our first Kitura application, we need to bring in some dependencies. We need Kitura itself and we’ll also bring in HeliumLogger as logging makes it much easier to see what’s going on.

We do this by adding the GitHub project URLs to Package.swift along with specifying version constraints. Update Package.swift so that it looks like this:

Package.swift:

import PackageDescription

let package = Package(
    name: "BookshelfAPI",
    dependencies: [
        .Package(url: "https://github.com/IBM-Swift/Kitura.git", majorVersion: 0, minor: 25),
        .Package(url: "https://github.com/IBM-Swift/HeliumLogger.git", majorVersion: 0, minor: 14),
    ]
)

We’ve added the dependencies array with two Package elements, one for each dependency. For the particular version of the Swift compiler, we need version 0.25.x of Kitura and 0.14.x of HeliumLogger. While Swift 3 is still in active development, we need to match the swift compiler to the Kitura version as documented in the Kitura Readme.

If we now run swift build, the Swift Package Manager will download the Kitura and HeliumLogger packages along with any dependencies and compile them for us. In this case, we would pick up the Kitura-net, Kitura-sys, LoggerAPI, BlueSocket, CCurl, CHTTPParser, SwiftyJSON, and Kitura-TemplateEngine dependencies.

Let’s write some code! Our main.swift file becomes more of a real application now. Replace the contents of main.swift with this code:

Sources/BookshelfAPI/main.swift

import HeliumLogger
import Foundation
import Kitura
import LoggerAPI
import SwiftyJSON

// Disable buffering
setbuf(stdout, nil)

// Attach a logger
Log.logger = HeliumLogger()

// setup routes
let router = Router()
router.get("/") { _, response, next in
    response.status(.OK).send(json: JSON(["hello" : "world"]))
    next()
}

// Start server
Log.info("Starting server")
Kitura.addHTTPServer(onPort: 8090, with: router)
Kitura.run()

Now, let’s look at this code stage by stage:

import HeliumLogger
import Foundation
import Kitura
import LoggerAPI
import SwiftyJSON

Firstly we import all the modules we want to use. Pretty much all Swift files have a set of imports at the top, so I won’t comment on them very often.

setbuf(stdout, nil)
Log.logger = HeliumLogger()

The LoggerAPI package implements the Log class which does nothing unless you attach an instance of a logger to it. Hence we attach a new instance of HeliumLogger so that the calls to the log within the rest of Kitura will now work. By default, HeliumLogger will output logs to stdout, so we use setbuf to disable buffering on stdout so that we can see the log output immediately.

router.get("/") { _, response, next in
    response.status(.OK).send(json: JSON(["hello" : "world"]))
    next()
}

Kitura is inspired by ExpressJS, so we set up the paths that the web application responds to in a similar way. The router instance has a set of methods for each HTTP verb that you want to respond to. In this case, we want to respond to the GET HTTP method, so we use router.get(). The first parameter is the URL path to match: “/” in this case. We’ll look at more complex paths as we build out the app. The other parameter to get() is a closure that is called when the path is matched. As the closure is the last argument to the method, we can use the trailing closure syntax.

The signature for the closure is:

func handle(request: RouterRequest, response: RouterResponse, next: () -> Void)

That is, we are passed in an instance of RouterRequest, an instance of RouterResponse and a method called next. We use request to find out information that the client sent us and perform the relevant operations. We then populate the response with the status code, headers and data that we want to send to the client and then call next(). The system is implemented as a pipeline of handlers known as middleware; calling next() means that we want the next handler in the pipeline to be called.

    response.status(.OK).send(json: JSON(["hello" : "world"]))
    next()

In this case, we set the status to .OK (i.e. an HTTP 200 response) and then use send(json:) to fill in the response’ body with some JSON. The JSON library used is SwiftyJSON which is quite a nice way to convert between JSON and Swift dictionaries. Kitura implements a final handler that will send the contents of the response back to the client, so we call next() in order to continue the pipeline execution.

Log.info("Starting server")
Kitura.addHTTPServer(onPort: 8090, with: router)
Kitura.run()

Finally, we tell Kitura that we want to run an HTTP server on port 8090 and pass it our configured router so that it can match routes. Then we run() the application.

Note, that, if you’re into Docker, then you can add this docker-compose.yml file if you want to target Linux from a Mac:

docker-compose.yml

app:
  image: ibmcom/swift-ubuntu
  ports:
    - "8090:8090"
  volumes:
    - .:/root/BookshelfAPI
  command: bash -c "make clean -C BookshelfAPI && make -C BookshelfAPI && BookshelfAPI/.build/debug/BookshelfAPI"

You can then use docker-compose up to build and run the Swift application within a Linux environment.

Using a Makefile

We also need different command line switches for swift build depending on whether we’re using Linux or OS X, so we will abstract this all away via a Makefile which the Kitura team have already written for us! The Package-Builder library has what we need, so we add it as a submodule to our project:

git submodule add git@github.com:IBM-Swift/Package-Builder.git Package-Builder

This adds the Package-Builder directory to our project and we now add our own Makefile that includes the Package-Builder one. Create a Makefile file with this in it:

Makefile

export KITURA_CI_BUILD_SCRIPTS_DIR=Package-Builder/build

-include Package-Builder/build/Makefile

Build & run our first Kitura app

We can now build the application:

make

This will invoke swift build with any required flags. The build will download the libraries specified in Package.swift and the dependencies that they have. It will then compile each library and finally compile our application. The Makefile includes informational messages in the output that are prefixed by ---.

A typical clean build looks like this (assuming the packages are already downloaded):

$ make
--- Running build on Linux
--- Build scripts directory: Package-Builder/build
--- Checking swift version
swift --version
Swift version 3.0-dev (LLVM 440a472499, Clang e10506ae1c, Swift 395e967875)
Target: x86_64-unknown-linux-gnu
--- Checking swiftc version
swiftc --version
Swift version 3.0-dev (LLVM 440a472499, Clang e10506ae1c, Swift 395e967875)
Target: x86_64-unknown-linux-gnu
--- Checking git version
git --version
git version 1.9.1
--- Checking git revision and branch
git rev-parse HEAD
87dabe99d8da9807d5c518f40ee308edfe2ba758
git rev-parse --abbrev-ref HEAD
master
--- Checking Linux release
lsb_release -d
Description:    Ubuntu 14.04.5 LTS
--- Invoking swift build
swift build -Xcc -fblocks -Xlinker -rpath -Xlinker .build/debug
Compile CHTTPParser utils.c
Compile CHTTPParser http_parser.c
Compile Swift Module 'LoggerAPI' (1 sources)
Compile Swift Module 'KituraTemplateEngine' (1 sources)
Compile Swift Module 'SwiftyJSON' (2 sources)
Compile Swift Module 'Socket' (3 sources)
Linking CHTTPParser
Compile Swift Module 'HeliumLogger' (1 sources)
Compile Swift Module 'KituraSys' (3 sources)
Compile Swift Module 'KituraNet' (29 sources)
Compile Swift Module 'Kitura' (38 sources)
Compile Swift Module 'BookshelfAPI' (1 sources)
Linking ./.build/debug/BookshelfAPI

We can then run our app:

$ .build/debug/BookshelfAPI

The app outputs its logs directly, so we can see them:

 VERBOSE: init() Router.swift line 48 - Router initialized
 INFO: BookshelfAPI main.swift line 21 - Starting server
 VERBOSE: run() Kitura.swift line 66 - Starting Kitura framework...
 VERBOSE: run() Kitura.swift line 68 - Starting an HTTP Server on port 8090...
 INFO: listen(socket:port:) HTTPServer.swift line 137 - Listening on port 8090

Test using curl

We’re writing an API, so we test with curl!

$ curl -i http://localhost:8090
HTTP/1.1 200 OK
Content-Length: 22
Content-Type: application/json
Date: Tue, 09 Aug 2016 07:36:33 GMT
Connection: Keep-Alive
Keep-Alive: timeout=60, max=19

{
  "hello": "world"
}

As you can see, using send(json:) has resulted in a valid JSON body and also the Content-Type header is set to application/json as it should be.

Done

We now have a working Kitura application which works on both Linux and OS X. It doesn’t do much, but we’ve set the stage for the next instalment!

Tutorial navigation

GitHub repository: kitura_bookshelfapi