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 1 $ 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 1 2 3 $ 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) 1 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

" >> README.md $ git add README.md $ git commit -m "Add README" $ git add .swift-version $ git commit -m "Set swift-version to 3.0a" 1 2 3 4 5 6 $ git init $ echo -e "# Kitura Bookshelf API tutorial

" > > 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/*

Packages/*" >> .gitignore $ git add .gitignore $ git commit -m "Add .gitignore" 1 2 3 $ echo -e ".build/*

Packages/*" > > .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" ) 1 2 3 4 5 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 1 $ mkdir -p Sources/BookshelfAPI

We can now create our first Swift source file:

Sources/BookshelfAPI/main.swift

print("Hello world") 1 print ( "Hello world" )

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

$ swift build 1 $ 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 1 2 Compile Swift Module 'BookshelfAPI' (1 sources) Linking .build/debug/BookshelfAPI

We can then run it like this:

$ .build/debug/BookshelfAPI 1 $ .build/debug/BookshelfAPI

And you should see the output:

Hello world 1 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), ] ) 1 2 3 4 5 6 7 8 9 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() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 1 2 3 4 5 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() 1 2 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() } 1 2 3 4 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) 1 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() 1 2 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() 1 2 3 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" 1 2 3 4 5 6 7 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 1 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 1 2 3 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 1 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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 $ 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 1 $ .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 1 2 3 4 5 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" } 1 2 3 4 5 6 7 8 9 10 11 $ 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