Kitura Tutorial: Getting Started With Server-Side Swift
Do you wish your iOS skills worked on the backend? This Kitura tutorial will teach you to create RESTful APIs written entirely in Swift. By David Okun.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Kitura Tutorial: Getting Started With Server-Side Swift
30 mins
- Getting Started
- Installing CouchDB
- Kitura and RESTful API Routing
- Creating the Kitura Tutorial Project
- Troubleshooting Errors
- Using Kitura With Xcode
- Setting Up Your Kitura Server
- Creating Your Model
- Connecting to CouchDB
- Persisting Your Acronyms
- Creating Your Codable Routes
- Testing Your API
- Where to Go From Here?
Are you a busy Swift developer with no time to learn Node.js, but still feel drawn to server-side development? This Kitura tutorial will teach you how to create RESTful APIs written entirely in Swift.
You’ll build a Today I Learned app to help you learn and remember common acronyms like TIL. Along the way, you’ll learn how to:
- Create a back end API from scratch.
- Link your API to a CouchDB instance running on your local machine.
- Assign
GET
,POST
andDELETE
routes for a model object.
Getting Started
To complete this Kitura tutorial, you’ll need:
- macOS 10.14 (Mojave) or higher.
- Xcode 10.1 or newer.
- Basic familiarity with Terminal, as you’ll use the command line quite a bit in this tutorial.
Installing CouchDB
You’ll use a database called CouchDB in this Kitura tutorial. It’s is a NoSQL database that strictly enforces JSON and uses revision keys for updates. So it’s safe — and fast!
docker run --rm --name couchdb -p 5984:5984 -d couchdb
When you’ve finished this tutorial, enter the following command to stop your container:
docker stop couchdb
The --rm
in the docker run
command will remove the container’s files from your system.
Homebrew, a popular package manager for macOS, is the easiest way to install CouchDB. If you don’t have Homebrew installed already, open Terminal and enter this command:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
Enter your password if prompted. You should see Installation Successful once it completes.
Next, enter this command to install CouchDB:
brew install couchdb
Once it’s installed, enter this command to start CouchDB:
brew services start couchdb
To confirm that CouchDB is installed and running, open a web browser and navigate to http://localhost:5984. You should see something like this:
{
"couchdb": "Welcome",
"uuid": "29b2fe0fb4054c61e6b4b8e01761707b",
"version": "1.7.1",
"vendor": {
"name": "Homebrew",
"version": "1.7.1"
}
}
Note: To stop CouchDB, enter brew services stop couchdb
.
Before diving into this tutorial, you’ll first need to understand a little about Kitura and REST.
Kitura and RESTful API Routing
IBM created Kitura as an open-source framework in 2015, shortly after Apple open-sourced Swift. They modeled Kitura after Express.js, the de-facto framework for creating RESTful APIs using Node.js.
REST is an acronym for Representational State Transfer. In RESTful apps, each unique URL represents an object. Non-unique URLs represent actions, which are combined with RESTful verbs like GET to fetch objects, POST to insert, DELETE to remove and PUT to update objects.
Backend development often involves many components working together. You’ll only be concerned with two back end components in this Kitura tutorial: the API and database.
For example, if you want to populate a table view with a list of acronyms and their meanings, your client app sends a GET request to the backend. In practice, your app requests the URL http://yourAPI.com/acronyms
.
The API receives your request and uses a router to decide how to handle it. The router checks all available routes, which are simply publicly accessible endpoints, to determine if there is a GET route ending in /acronyms
. If it finds one, it executes the associated route’s code.
The /acronyms
route then does the following:
- Retrieves the acronyms from the database.
- Serializes them into JSON.
- Packages them into a response.
- Sends the JSON response back to the requesting client.
This results in the following interaction between the API and database:
If an API is RESTful, then it must also be stateless. In this example, you can think of the API as the orchestrator, commanding data to and from your ecosystem. Once the request is fulfilled, the state of the API and its routes should be unchanged and able to handle the next request.
Just because the API is stateless doesn’t mean it isn’t allowed to store or modify objects. The API itself doesn’t store states, but it does query and update the database to fetch, store and modify objects’ states.
Creating the Kitura Tutorial Project
You didn’t download a starter project for this tutorial yet. Well, that’s because you’re going to create it from scratch, from the command line.
brew tap ibm-swift/kitura
brew install kitura
kitura init
Open Terminal and enter the following commands:
mkdir KituraTIL
cd KituraTIL
swift package init --type executable
This uses the Swift Package Manager to create a new executable package.
You should see output similar to the following:
Creating executable package: KituraTIL
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/KituraTIL/main.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/KituraTILTests/
Creating Tests/KituraTILTests/KituraTILTests.swift
Creating Tests/KituraTILTests/XCTestManifests.swift
Next, enter the following command to open Package.swift with Xcode:
open -a Xcode Package.swift
Replace the entire contents of Package.swift with the following:
// swift-tools-version:4.2
import PackageDescription
let package = Package(
// 1
name: "KituraTIL",
dependencies: [
// 2
.package(url: "https://github.com/IBM-Swift/Kitura.git",
.upToNextMajor(from: "2.0.0")),
// 3
.package(url: "https://github.com/IBM-Swift/HeliumLogger.git",
.upToNextMajor(from: "1.0.0")),
// 4
.package(url: "https://github.com/IBM-Swift/Kitura-CouchDB.git",
.upToNextMajor(from: "3.0.0"))
],
//5
targets: [
.target(name: "KituraTIL",
dependencies: ["Kitura" , "HeliumLogger", "CouchDB"],
path: "Sources")
]
)
Here’s what each of these commands does:
- You first set the name of your target executable. By convention, you should name this after the enclosing directory.
- Here, you declare your dependencies one-by-one; starting with Kitura itself.
- HeliumLogger is a back end logging framework, which you’ll use to log messages while your back end app is running.
- Kitura-CouchDB allows Kitura to communicate with CouchDB.
- Finally, you declare your target and its dependencies.
Save this file and go back to Terminal where you should still be in the same directory containing Package.swift. You are now going to add a document that sets the version of Swift for this project. Enter the following command:
echo "4.2.1" >| ".swift-version"
You are now ready to load your dependencies and build the project for the first time. Enter the following command in Terminal:
swift build
This will generate a lot of logging, ending with logs about compiling your project. You’ll see this output at the end:
Compile Swift Module 'KituraTIL' (1 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/KituraTIL
Troubleshooting Errors
In case you get errors from swift build, enter the following in Terminal to verify your Swift version:
swift --version
If your version is lower than Swift 4.2.1, this is likely your problem. To fix this, make sure that you have the latest version of Xcode 10 installed, and then run the following command:
sudo xcode-select -s /Applications/Xcode.app
…where Xcode.app should be replaced the path to Xcode 10.
If you’re still having trouble, it’s possible that you’re using swiftenv
or another Swift version management tool, and you may need to manually set your Swift version to 4.2.1.
Here’s the command to do this if you’re using swiftenv
:
swiftenv global 4.2.1
Using Kitura With Xcode
Still in Terminal, at the root directory for your tutorial project, enter the following command:
swift package generate-xcodeproj
You should see this output:
generated: ./KituraTIL.xcodeproj
Enter this command to open your new Xcode project:
xed .
You’ll then be greeted with this view:
From here, you need to make sure that the selected target device is My Mac. After you select that, build and run (Command + R) your Xcode project, and you’ll see this printed to the console:
Hello, world!
Program ended with exit code: 0
Awesome! You’re now ready to get your back end app up and running!
Note: If you see the warning Conversion to Swift 4.2 is available, feel free to click it. There should be no changes needed. Just click Update again, to change their build settings.
Setting Up Your Kitura Server
You need only a few lines of code to set up your server with a router and a logger.
First, create a new Swift File named Application.swift in the Sources/KituraTIL group folder — the folder that contains main.swift. Make sure to add this file to the KituraTIL executable target:
Next, replace the contents of this file with the following:
import Kitura
import LoggerAPI
public class App {
// 1
let router = Router()
public func run() {
// 2
Kitura.addHTTPServer(onPort: 8080, with: router)
// 3
Kitura.run()
}
}
Here’s what this does:
- The Router will handle incoming requests by routing them to the appropriate endpoints.
- Here, you register
router
to run on port 8080. - Kitura will run infinitely on the main run loop after you call
run()
.
With your App
class created, open main.swift and replace its contents with the following:
import Kitura
import HeliumLogger
import LoggerAPI
HeliumLogger.use()
let app = App()
app.run()
The HeliumLogger.use()
command sets up HeliumLogger
as the default logger for Kitura. It’s good practice to
Then, you create an App
instance and run it.
Build and run, and you should see log messages from Kitura appear in the console.
Next, navigate to http://localhost:8080 in your browser, and you should see this page:
Congratulations, you’re now running a basic Swift-based HTTP Server on your local machine!
Creating Your Model
In this section, you’ll create a model type that represents an acronym.
Create a new Swift File named Acronym.swift, and remember to add it to the KituraTIL target.
Replace the contents of this file with the following:
// 1
import CouchDB
// 2
struct Acronym: Document {
// 3
let _id: String?
// 4
var _rev: String?
// 5
var short: String
var long: String
}
Here’s what you’ve done:
- You need to import this module so that you can make your
struct
conform to theDocument
protocol. - By making
Acronym
conform toDocument
, you’ll be able to leverage both Codable Routing and CouchDB’sDocument
protocol. You’ll learn more about Codable Routing shortly, butDocument
allows you to take advantage of the latest database driver for CouchDB. - As with most database drivers, you to have an
_id
property for your object — theDocument
protocol requires this. - CouchDB will also make you keep track of the latest revision of a document to confirm any update or delete actions you perform on an entry, so the
Document
protocol requires this_rev
property as well. - Lastly, the object itself requires you to store both a long form and short form acronym for the sake of this tutorial.
Build your project to make sure everything looks good. Remember, again, to double check that your new files are part of the KituraTIL target!
Notice that your object conforms to the Document
protocol. This is a mechanism that forces objects readable by the CouchDB database driver to also conform to Codable
, which is going to be extremely helpful during your journey with server-side Swift.
Codable
is simply a typealias
that combines the Encodable
and Decodable
protocols. This ensures conforming objects can be converted both to and from external representations. In particular, Kitura uses this to easily convert instances to and from JSON.
Before Kitura 2.0, you had to pass a request object into every endpoint closure, parse properties manually, cast appropriately, perform necessary transformations and finally create JSON to send as a response. It was a lot of work!
You can now leverage the power of Kitura’s Codable Routing to significantly reduce the boilerplate code in your routes. Win! You simply need to make your models conform to Codable
to take advantage of this, as you did above.
With this theory out of the way, it’s now time to connect your API to CouchDB.
Connecting to CouchDB
Open Application.swift, and replace its contents with the following:
// 1
import CouchDB
import Foundation
import Kitura
import LoggerAPI
public class App {
// 2
var client: CouchDBClient?
var database: Database?
let router = Router()
private func postInit() {
// 3
}
private func createNewDatabase() {
// 4
}
private func finalizeRoutes(with database: Database) {
// 5
}
public func run() {
// 6
postInit()
Kitura.addHTTPServer(onPort: 8080, with: router)
Kitura.run()
}
}
Going over these changes:
- You first import CouchDB in order to set up your persistence layer.
- You add the properties
client
for CouchDB anddatabase
to keep track of changes. - You’ll add code here after you’ve created your instance of
App
to connect to your database. - In case your
postInit()
method doesn’t find your database, it’ll create a new one for you. - Once you’ve set up your database, you’ll list all available routes for your API to match against here.
- You call
postInit()
from withinrun()
to make this part of your API setup.
Next, complete postInit()
by replacing // 3
with the following.:
// 1
let connectionProperties = ConnectionProperties(host: "localhost",
port: 5984,
secured: false)
client = CouchDBClient(connectionProperties: connectionProperties)
// 2
client!.retrieveDB("acronyms") { database, error in
guard let database = database else {
// 3
Log.info("Could not retrieve acronym database: "
+ "\(String(describing: error?.localizedDescription)) "
+ "- attempting to create new one.")
self.createNewDatabase()
return
}
// 4
Log.info("Acronyms database located - loading...")
self.finalizeRoutes(with: database)
}
Here’s what you just did:
- You create a
ConnectionProperties
object that you use to specify configuration values and a newCouchDBClient
. - You check to see if a matching database already exists, so you don’t overwrite existing data.
- If a database does not exist, you call
createNewDatabase()
to create a new database. - If a database does exist, you call
finalizeRoutes(with:)
to configure your routes.
Next, complete createNewDatabase()
by replacing // 4
with the following:
// 1
client?.createDB("acronyms") { database, error in
// 2
guard let database = database else {
Log.error("Could not create new database: "
+ "(\(String(describing: error?.localizedDescription))) "
+ "- acronym routes not created")
return
}
self.finalizeRoutes(with: database)
}
Here’s what this does, piece by piece:
- You create your database with a given name. You can choose anything, but it’s best to keep it simple.
- You ensure the database exists, or else, you abort and log an error.
- Just like before, you call
finalizeRoutes(with:)
to configure your routes.
You won’t be able to implement finalizeRoutes(with:)
just yet. You first need to complete your persistence layer. That’s what you’ll do in the next section.
Persisting Your Acronyms
Create a Swift File named AcronymPersistence.swift, and add it to the KituraTIL target.
Replace the contents of AcronymPersistence.swift with the following:
import Foundation
import CouchDB
import LoggerAPI
extension Acronym {
// 1
class Persistence {
// 2
static func getAll(from database: Database, callback:
@escaping (_ acronyms: [Acronym]?, _ error: Error?) -> Void) {
}
// 3
static func save(_ acronym: Acronym, to database: Database, callback:
@escaping (_ acronym: Acronym?, _ error: Error?) -> Void) {
}
// 4
static func delete(_ acronymID: String, from database: Database, callback:
@escaping (_ error: Error?) -> Void) {
}
}
}
OK, take a look at what you just stubbed out:
- By adding a class to an extension of
Acronym
, you are essentially creating a namespace for your class. Because you are making use of static methods, and these technically need to be globally scoped, namespacing your persistence methods allows you to make it more difficult to call them accidentally. - Think about what you will want to do for your acronyms — you want an easy way to run your most basic operations, but you don’t want to write a whole bunch of logic into your router. This
getAll(from:callback:)
method should logically return an array ofAcronym
s! - This
save(_:to:callback:)
method should easily save an object, but you’ll do more. It is common practice for aPOST
request to return the entire created object with a 201 response code — thus, you’ll make sure you have the entire newly created object returned to you. - Lastly, when you delete an object, the HTTP route will only include a reference to the ID of the object. So you should create a method that only needs the object’s ID to delete it, even though, internally, you will also use its
_rev
value to delete it.
Now that you understand the usefulness of this class, and why you would abstract much of your database operation away from your routes, add the following code to your getAll(from:callback:)
method:
//1
database.retrieveAll(includeDocuments: true) { documents, error in
guard let documents = documents else {
Log.error("Error retrieving all documents: \(String(describing: error))")
return callback(nil, error)
}
//2
let acronyms = documents.decodeDocuments(ofType: Acronym.self)
callback(acronyms, nil)
}
SwiftyJSON
. Not bad, right?Take a look at the two main components that you’ve added to this function:
- You will notice that your
database
object has a lot of functionality attached to it. While you could write this in yourCodable
routes, it’s easier to bury this in yourPersistence
helper class. This will do most of the heavy lifting for you! - You are taking the documents that CouchDB has returned to you and encoding them into their intended native types. This is where
Codable
also shines in a big way — gone are the days of having to parse parameters from a request!
Next, beef up your save(_:to:callback:)
method by adding the following code to it:
// 1
database.create(acronym) { document, error in
guard let document = document else {
Log.error("Error creating new document: \(String(describing: error))")
return callback(nil, error)
}
// 2
database.retrieve(document.id, callback: callback)
}
Breaking this down:
- Just like before, you use your
database
object to perform your CRUD operation — but notice that you’re still getting a returned type ofDocumentResponse
. This contains an_id
and a_rev
for your newly created object, but not much else! - You take the
_id
from yourcreate
operation, and you use yourdatabase
to retrieve that specific object. You can implicitly pass thecallback
down to this operation because the twocallback
arguments have basically the same signature — this saves you some code!
Alright, time to wrap this one up! Add the following code to your delete(_:from:callback:)
method:
// 1
database.retrieve(acronymID) { (acronym: Acronym?, error: CouchDBError?) in
guard let acronym = acronym, let acronymRev = acronym._rev else {
Log.error("Error retrieving document: \(String(describing:error))")
return callback(error)
}
// 2
database.delete(acronymID, rev: acronymRev, callback: callback)
}
And, point by point:
- Just like in your
save(_:to:callback:)
method, you are retrieving an object and using a native type in the callback. You need to get the object because yourCodable
route will only provide the_id
, whereas theDatabase
method requires both an_id
and a_rev
. - Once you have your native object, you ask
database
to delete it, and just like you did insave(_:to:callback:)
, you can implicitly pass the callback to thisDatabase
operation because they have the same signature!
OK, now that you have an easy utility for working with CouchDB and your Acronym
object, it’s time to dive into setting up your Codable
routes!
Creating Your Codable Routes
Create a new file named AcronymRoutes.swift, and add it to the KituraTIL target. Replace the contents of AcronymRoutes.swift with the following:
import CouchDB
import Kitura
import KituraContracts
import LoggerAPI
// 1
private var database: Database?
func initializeAcronymRoutes(app: App) {
// 2
database = app.database
// 3
app.router.get("/acronyms", handler: getAcronyms)
app.router.post("/acronyms", handler: addAcronym)
app.router.delete("/acronyms", handler: deleteAcronym)
}
// 4
private func getAcronyms(completion: @escaping ([Acronym]?,
RequestError?) -> Void) {
}
// 5
private func addAcronym(acronym: Acronym, completion: @escaping (Acronym?,
RequestError?) -> Void) {
}
// 6
private func deleteAcronym(id: String, completion: @escaping
(RequestError?) -> Void) {
}
This is a lot of change! Walking through what you’ve just added:
- Since all of your routes will make use of your
database
object, it’s handy to keep a reference to it here. - You don’t want to constantly have to refer back to your
App
class, so this is where you store a reference to yourdatabase
object. - You are “registering” three routes here, choosing the paths that they will be registered for, and the handlers that will run when a request is made to any of them.
- Assuming you are launching your server on
localhost:8080
, your router will run this function with the data parsed from the request, if someone makes aGET
request tolocalhost:8080/acronyms
. - This function runs if someone makes a
POST
request tolocalhost:8080/acronyms
. - This function runs if someone makes a
DELETE
request tolocalhost:8080/acronyms/id
, replacing theid
part of this path with an actual ID string.
OK, it’s now time to make your server actually getAcronyms(completion:)
route handler:
guard let database = database else {
return completion(nil, .internalServerError)
}
Acronym.Persistence.getAll(from: database) { acronyms, error in
return completion(acronyms, error as? RequestError)
}
Pretend you’re a chef and kiss your fingers — this is
Next, add the following code to your addAcronym(acronym:completion:)
route handler:
guard let database = database else {
return completion(nil, .internalServerError)
}
Acronym.Persistence.save(acronym, to: database) { newAcronym, error in
return completion(newAcronym, error as? RequestError)
}
Again — just enum
case as well! Here, you send a HTTP 500 error if you can’t get a handle on your database. Command-click on .internalServerError
if you want to see what the other errors are!
The last route you need to update is your deleteAcronym(id:completion:)
route, but you can probably already figure out what’s going to go in there:
guard let database = database else {
return completion(.internalServerError)
}
Acronym.Persistence.delete(id, from: database) { error in
return completion(error as? RequestError)
}
I personally love how you can make the external parameter label in function signatures optional, as you did with Persistence.delete(_:from:callback:)
. This generally lets you focus on making your functions English-readable as well as code-readable, and this is a great example of that feature in Swift.
To complete your app, open Application.swift and complete finalizeRoutes(with:)
by replacing // 5
with the following:
self.database = database
initializeAcronymRoutes(app: self)
Log.info("Acronym routes created")
Testing Your API
Build and run your project, and navigate to http://localhost:8080 to ensure your server is still running.
Then, open Terminal, and enter the following command to GET all acronyms:
curl http://localhost:8080/acronyms
If everything is set up correctly, you should get back an empty JSON placeholder ([]
). This means you’ve correctly set up your GET route!
Now, add a new acronym to your back end — enter the following command:
curl -X POST http://localhost:8080/acronyms -H 'content-type: application/json' -d '{"short": "BRB", "long": "Be right back"}'
You should see a response like this:
{"id":"b2edde7b8032c30c7aeeff8d18000ad9","short":"BRB","long":"Be right back"}
In Xcode, Kitura’s log messages should include a Received POST type-safe request message.
To verify this actually saved to the database, enter the following GET command again, or go to http://localhost:8080/acronyms right in your browser:
curl http://localhost:8080/acronyms
If you get back the same JSON you entered, inside an array, then congratulations! You’ve successfully created a working API!
Where to Go From Here?
This tutorial has introduced you to Kitura by building a backend API. However, that is not all Kitura can do! In our Kitura Stencil Tutorial, you’ll learn how to use Kitura and Stencil to build a website that includes a front end.
You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial. You can also read through IBM’s introduction to Kitura Codable Routing.
There’s a lot of material on the internet about Kitura, and some especially great stuff available directly from IBM! If you’d like to continue learning about Codable Routing and other Kitura 2.0 features, check out this tutorial.
I encourage you to comment in the forum below if you have any questions or comments!