Server-side development has always been a delicate topic for iOS developers. For many years, iOS developers had to rely on other technologies to build their back-end service, which involved learning other technologies like Ruby on Rails, ASP.NET, Python, PHP, etc. Later, several new platforms evolved, providing easy integration for iOS applications without having to learn a new language. These platforms included Parse, Firebase, and CloudKit.

Parse was once among the most popular back-ends as a service platform and was introduced and maintained by Facebook. A lot of iOS developers flocked to Parse and used it as their back-end service. Unfortunately, few years later, Facebook decided to kill Parse, leaving thousands of developers stranded without a back-end.

Apple introduced the Swift language in 2014 and it instantly became one of the most loved languages by developers. In 2015, Swift went open source, allowing it to run on the cloud. Several server-side Swift frameworks emerged during this time and one of them was from IBM, called Kitura. In this article, I'm going to build a complete Kitura service that can be consumed by iOS, Web, and Android applications.

At present, the three most popular server-side Swift frameworks are Kitura, Vapor, and Perfect. Try them all!

Setting Up a Kitura Project

Kitura projects can be set up using the Swift Package Manager. Before setting up the project, make sure you have Xcode 8 or later installed. The Swift Package Manager is installed by default on Xcode 8 or later and is required to install Kitura packages.

First, create a folder called “hello-kitura” and from inside the folder, run the following command:

swift package init --type executable

This creates a default Swift package project. The project contains the following files.

  • Package.swift: This Swift file lists all the packages included in the project.
  • main.swift: This is the starting point of the application.

Open the Package.swift file and add a dependency to include the Kitura package, as shown in Listing 1. Once the dependency has been added, you need to update the main.swift file to add the code to start and run the Kitura server. Open the main.swift file and include the implementation, as shown in Listing 2. Next, build the project by executing the swift build command from the terminal. This also downloads any dependencies included in the Package.swift file.

Listing 1: Configuring the Kitura Package

import PackageDescription

let package = Package(
    name: "hello-kitura", dependencies: [
        .Package(url: "https://github.com/IBM-Swift/Kitura.git", 
        majorVersion: 1, minor: 7)
    ])

Listing 2: Hello Kitura

import Kitura

// Create a new router
let router = Router()

// Handle HTTP GET requests to /
router.get("/") {
    request, response, next in
    response.send
    ("Hello, World!")
    next()
}

// Add an HTTP server and connect it to the router
Kitura.addHTTPServer
(onPort: 8080, with: router)

// Start the Kitura runloop
(this call never returns)
Kitura.run()

Once the dependencies have been downloaded, run the following command from the terminal to run the application:

.build/debug/hello-kitura

The command won't display anything on the terminal because you don't have any network log framework added to the application. Open a browser and visit the http://localhost:8080/ URL. Port 8080 is the default port on which the Kitura server runs. When you visit the URL, you should see the result shown in Figure 1.

Figure 1: Hello Kitura
Figure 1: Hello Kitura

Congratulations! You've just created your first server-side service using the Swift programming language. Before I jump into the next section, let's discuss the implementation in the main.swift file.

The main.swift file is the entry point of the application. After importing the Kitura module, you created an instance of the Router class. The Router class is responsible for initiating networking requests in the Kitura framework.

The get function of the Router class takes a closure as an argument. The closure consists of three parameters: request, response, and next. The parameter definitions are shown below:

  • request: The user's request
  • response: The response sent from the server
  • next: Closure for the continuation of the response

From inside the get closure, you call response.send and the next() closure, which ends the response. It's very important to call either next() or response.end, which end the response.

The project isn't compatible with Xcode. In the next section, you're going to learn how to generate an Xcode project and also configure a logger, so you'll have a better idea what's going on behind the scenes.

Generating an Xcode Project and Configuring Logger

You can create an Xcode project for your Kitura app using the commands provided by the Swift Package Manager. From the terminal inside your project folder, run the following command to generate the Xcode project:

swift package generate-xcodeproj

Open the Xcode project hello-kitura.xcodeproj. Before building the project, make sure that the executable scheme is selected with Mac as the platform, as shown in Figure 2.

Figure 2: Configuring an Xcode project with Kitura
Figure 2: Configuring an Xcode project with Kitura

Once everything is set up, run the project by pressing Command + Run. This starts the Kitura server on the default port 8080 and you can visit the URL http://localhost:8080/ to view “Hello World” displayed on the screen.

One thing you'll notice is that Kitura never notifies you when the server has successfully started. This is because logging isn't enabled on the Kitura project. Logging is part of a separate package called HeliumLogger, which can be integrated using the Swift Package Manager. Update the Package.swift file to include the dependency on HeliumLogger, as shown in Listing 3.

Listing 3: Adding a HeliumLogger Package

import PackageDescription

let package = Package(
    name: "hello-kitura",
    dependencies: [
        .Package(url: "https://github.com/IBM-Swift/Kitura.git", 
            majorVersion: 1, minor: 7),
        .Package(url: "https://github.com/IBM-Swift/HeliumLogger.git", 
            majorVersion: 1, minor: 7)
    ])

After adding the HeliumLogger package, you still need to tell Kitura to update the dependencies. This can be done by executing the swift build command from the terminal, as shown in Figure 3.

Figure 3: Downloading Helium Logger Dependencies
Figure 3: Downloading Helium Logger Dependencies

Once the HeliumLogger dependency has been downloaded, the next step is to regenerate the Xcode project so it can see the new dependencies.

swift package generate-xcodeproj

Finally, update the main.swift file, as shown in Listing 4, to enable the Helium Logger. It's that simple.

Listing 4: Enabling the HeliumLogger

import Kitura
import HeliumLogger

// Initialize HeliumLogger
HeliumLogger.use()

// Create a new router
let router = Router()

// Handle HTTP GET requests to /
router.get("/") {
    request, response, next in
    response.send("Hello, World!")
    next()
}

Kitura.addHTTPServer(onPort: 8080, with: router)

Kitura.run()

Once the logger has been enabled, run your Kitura app again and this time, notice the output window of Xcode. Xcode now displays the log messages received from the Kitura app using Helium Logger. This will prove to be very beneficial when debugging larger apps. Figure 4 shows the output window logging Kitura events using Helium Logger.

Figure 4: The Helium Logger is enabled
Figure 4: The Helium Logger is enabled

In the next section, you'll learn how to return data to the user in JSON format.

Returning JSON Data

In the previous section, you learned how to send simple string text back to the user. This was great to get started but in real world apps, you'll be sending back either XML or, preferably, JSON. Kitura allows you to easily send back the response in JSON format. Listing 5 shows the complete implementation, which returns a task object as a JSON response.

Listing 5: Returning a Response as JSON

struct Task {

    var title :String

    func toDictionary() -> [String:Any] {
        return ["title":self.title]
    }
}

router.get("task"){
    request, response, next in

    let task = Task(title: "Mow the lawn")
    return try response.send(json:task.toDictionary()).end()
}

The Task struct defines a toDictionary function that returns the dictionary representation of the task object. This dictionary object is then passed to the send function of the Response, which returns it back to the user in JSON format.

In order to send back a collection of objects instead of a single object, you need to go through each item inside the collection and call the toDictionary function on each instance. In the next section, you will learn how to parse user requests.

Parsing a User Request

The real power of creating a back-end service using Kitura comes from the requests that users can send to the server and get a response. Kitura allows you to extract data from a user's request in several different formats. Let's take a look at each approach separately.

Parsing URL Encoded Parameters

URL-encoded parameters allow the user to pass data as part of the URL. Consider an example. Let's say you're searching for an episode of a TV show and you include the episode number in the URL. Listing 6 shows the necessary code to extract the episode number out of the request.

Listing 6: Parsing URL-Encoded Parameters

router.get("tv-shows/seinfeld/episodes/:no")
{ request, response, next in

    let name = request.parameters["no"] ?? ""
    try response.send(name).end()
}

The request instance has a parameters property that contains all the parameters that were passed by the user. After retrieving the parameter, you simply send back the response with the parameter value, as shown in Figure 5.

Figure 5: Retrieving and displaying URL-encoded parameter
Figure 5: Retrieving and displaying URL-encoded parameter

Parsing a Query Parameter

Query string parameters allow the user to pass search strings right inside the URL. Query strings can be used to dictate the search term, sort order, number of pages, and much more. The Kitura framework provides an easy way to access the query string parameters.

Listing 7 shows the implementation where you extract the query string values out of the URL. The request instance provides a queryParameters property, which contains all of the query strings associated with the URL. If a particular query string parameter isn't found, instead of throwing an error, you send back an informative message to the user.

Listing 7: Parsing Query String Parameters

router.get("search") { request, response, next in

    guard let searchText = request.queryParameters["text"],
    let sortOrder = request.queryParameters["sort"]
    else {
        return try response.send("Incorrect parameters").end()
    }

try response.send("Search Text is \(searchText) and the Sort Order is\(sortOrder)").end()
}

Parsing JSON

JSON (JavaScript Object Notation) is one of the most popular data transfer mechanisms used between the client and the server. Kitura uses the SwiftyJSON library to perform actions relating to JSON and provides an easy-to-use API to extract JSON data from the user's request.

Before you look at the JSON parsing code, you need to tell Kitura that it can expect JSON requests. This can be done by adding the middleware for the router instance. Listing 8 shows the complete implementation for setting up the middleware as well as retrieving JSON data from the request.

Listing 8: Parsing a JSON Request

// Create a new router
let router = Router()

router.all("/customer", middleware: BodyParser())

router.post("customer"){ 
    request, response, next in

    guard let body = request.body,
    let json = body.asJSON,
    let name = json["name"].string
    else {
        try response.send("Error parsing JSON").end()
        return
    }

    try response.send("The name is\(name)").end()
}

You can use any networking tool to invoke the post request. Figure 6 shows the result when the action was invoked using POSTMAN.

Figure 6: Parsing JSON Data
Figure 6: Parsing JSON Data

Integrating with SQLite Database

The real benefit of back-end systems is their ability to integrate with the databases. Kitura allows open-source Swift providers to integrate with SQLite, PostgreSQL and even CouchDB databases. In this section, you're going to see how Kitura can be integrated with a SQLite relational database.

The first step in integrating with the SQLite database is to add a SQLite provider in a Package.swift file. Update the Package.swift file to add a dependency on the Vapor SQLite provider, as shown in Listing 9.

Listing 9: Adding a SQLite Package Dependency

import PackageDescription

let package = Package(
    name: "hello-kitura",
    dependencies:[
        .Package(url:"https://github.com/IBM-Swift/Kitura.git",majorVersion:1),
        .Package(url:"https://github.com/IBM-Swift/HeliumLogger.git",majorVersion:1),
       .Package(url:https://github.com/vapor/sqlite,majorVersion:1)
    ]
)

Next, run the swift build command, which downloads all the dependencies mentioned in the Package.swift file. Finally, run the generate-xcodeproj command to regenerate the Xcode project with the new dependencies, like this:

swift package generate-xcodeproj

At this point, your Kitura project has successfully added a dependency on the SQLite database. In the next section, you'll create your database with some dummy records.

Creating a SQLite Database

There are tons of different ways to create a SQLite database. I'm going to use the Base Mac App, which provides a GUI for creating databases. Using it, create a database called todolistDB.sqlite that consists of a single table called tasks, as shown in Figure 7.

Figure 7: TODOList Database Schema
Figure 7: TODOList Database Schema

The database consists of a few records, which you fetch using the Kitura SQLite provider. Please also note that the database file todolist.sqlite is placed inside the Documents directory of your Mac.

Integrating SQLite with the Kitura App

After the package has downloaded, the next step is to integrate SQLite with your Kitura App. Remember that you stored the database, todolist.sqlite, in the Documents directory. You need to get a reference to the Documents directory so you can refer to the database. Using the path to the database, you can initialize a SQLite instance that can be used for communicating with the database. Listing 10 shows the code used to initialize the SQLite object.

Listing 10: Integrating a SQLite Instance


import SQLite

guard let documentsDirectory = NSSearchPathForDirectoriesInDomains (
    .documentDirectory,
    .userDomainMask, true)
    .first else {
        fatalError
        ("Documents directory not accessible")
    }

let filePath = documentsDirectory.appending("/todolist.sqlite")

let sqlite = try SQLite(path: filePath)

Once the SQLite object has been initialized, you can use the functions provided by the class to perform the SQL queries against the SQLite database.

Fetching Records from the Database

Let's start with retrieving all of the records from the database. The SQLite object consists of an execute method that can execute a SQL query. Listing 11 shows the implementation of the tasks/all action used to fetch all of the tasks from the database.

Listing 11: Fetching all the records from the database


router.get("tasks/all") {
    request, response, next in

    let sql = "select * from tasks;"
    let result = try sqlite.execute(sql)
    let tasks = result.map(Task.init)

    let dictionaries = tasks.map {$0.serialize()}

    response.send(json :dictionaries)
    next()
}

For each retrieve task, you create the Task object and then add it to the Tasks collection. Finally, you convert all of the items in the collection to the Dictionary and send them back as a JSON response. Figure 8 shows the JSON response displayed in the browser.

Figure 8: Displaying JSON Response in the Browser
Figure 8: Displaying JSON Response in the Browser

Persisting a Record in the Database

In the last section, you learned how to fetch all of the records from the Tasks table. Now you're going to insert a new record in the Tasks table. Once the new task has been inserted, you're going to return the newly generated database ID in the response. The user is going to pass a new task in the form of JSON using a POST request.

Listing 12 shows the complete implementation of the ask action that extracts the new task title from the user request and then generates an Insert SQL query. Once the query is generated, it's executed using the SQLite execute method. Finally, you used the lastId property of the SQLite object to return the newly created primary key to the user.

Listing 12: Inserting New Task in the SQLite database

router.post("task") { 
    request, response, next in

    guard let body = request.body,
    let json = body.asJSON,
    let title = json["title"].string
    else {
        try response.status
        (.badRequest).end()
        return
    }

    let sql = String(format: "insert into tasks(title) values('%@')", title)

    _ = try sqlite.execute(sql)

    let result = ["lastId":sqlite.lastId]

    response.send(json :["result":result])
    next()
}

Conclusion

Server-side Swift is the next frontier for the Swift language. Now iOS developers can take their existing skills and use their favorite language, Swift, to implement back-end services on the Cloud. This is a major step forward in the advancement and the future of Swift language. The Kitura framework provides an easy-to-use API to meet your Web service needs.