HTTP methods & Database Operations in Server-Side Swift API

This post is originally appeared in my personal blog, available at candostdagdeviren.com

After deciding to join more conferences this year, the very first one was dotSwift. It was held in a great old theater in Paris. It was a half day conference but it was better than I guessed. There were good talks about backend development in Swift. After hearing a lot about that and great performance of Swift, I decided to give it a try. And here comes my first experiences step by step.

There are two major frameworks for backend in Swift, Kitura and Vapor. I choose Kitura first. Because Kitura’s methods naming was close to the ones that I knew from Node.js. I felt more comfortable about understanding what each method does.

First, setting up the environment was easy because I have Mac and I’m actively developing iOS applications. So, Xcode and other stuff were already set up.

Creating a Swift backend project means creating a new Swift package with single line command swift package init. This creates a structured new package. But packages are not executable and I needed an executable project to run my backend. Creating main.swift file under Sources directory gave this ability to me. At this point my current folder structure was like this:

SwiftBackend
.gitignore
Package.swift
|--Sources
main.swift
|--Tests

Now, it was time to arrange Package.swift and add Kitura. Here is the Package.swift file with Kitura added as a dependency:

import PackageDescription
let package = Package(
name: "SwiftBackend",
dependencies: [
.Package(url: "https://github.com/IBM-Swift/Kitura.git", majorVersion: 1, minor: 4)
]
)

swift build command installs the dependencies and builds the project. After adding a new dependency, it’s always logical to build the project and see if it works.

Next step is setting up an endpoint. The basic example in Kitura’s tutorial is:

import Kitura
let router = Router()
router.get("/") { request, response, next in
response.send("Hello, World!")
next()
}
Kitura.addHTTPServer(onPort: 8090, with: router)
Kitura.run()

I added this code to main.swift file. When I ran the project locally, I should have seen Hello, World! when I enter localhost:8090 via browser (or make a get request via Postman etc.). To run this project, first I needed to build it via swift build and this command created the executable for me under .build/debug/SwiftBackend . I’ve just run the executable with typing the command line .build/debug/SwiftBackend. At this point, I was able to send a request and see “Hello, World!” text in the browser. But in the console, I wasn’t seeing any logs about these requests.

HeliumLogger came into at that point. It’s a logger component which available as a separate Swift module. I added this module to Package.swift file by adding as a new dependency. At this point, my Package.swift file was like this:

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

And I ran the swift build command again to install HeliumLogger. After that, I needed to import HeliumLogger and use it in main.swift file. It was just one line. My main.swift file became like this:

import Kitura
import HeliumLogger
HeliumLogger.use()
let router = Router()
router.get("/") { request, response, next in
response.send("Hello, World!")
next()
}
Kitura.addHTTPServer(onPort: 8090, with: router)
Kitura.run()

After building and running the project again, I was able to see the request logs in the console.

As the next step, I wanted to connect database to my backend API. I used CouchDB because there is a Kitura-CouchDB package.

After adding CouchDB package to my Package.swift file, it became like this:

import PackageDescription
let package = Package(
name: "SwiftBackend",
dependencies: [
.Package(url: "https://github.com/IBM-Swift/Kitura.git", majorVersion: 1, minor: 4),
.Package(url: "https://github.com/IBM-Swift/HeliumLogger.git", majorVersion: 1, minor: 4),
.Package(url: "https://github.com/IBM-Swift/Kitura-CouchDB.git", majorVersion: 1, minor: 4)
]
)

Again, I ran the command swift build to install new package.

While implementing database operations, I wanted to create a structure to not do everything in main.swift file. First, I created a User struct as a model object. My main purpose for database operations was adding this User object to CouchDB database as a document. Easy model, easy operation. So User struct was like this:

// User.swift
import Foundation
public struct User {
let name: String
let identifier: String
}

So, as I said my main purpose was adding a document to CouchDB database. I created a DatabaseInteraction struct with one method to achieve that goal. My plan was adding all database operations to this struct. Here is the DatabaseInteraction struct:

// DatabaseInteraction.swift
import Foundation
import CouchDB
import SwiftyJSON
public struct DatabaseInteraction {
var db: Database
public init(db: Database) {
self.db = db
}
func addNewUser(_ user: User, handler: @escaping (String?, String?, JSON?, NSError?) -> ()) {
let userDict: [String: Any] = [
"name": user.name,
"identifier": user.identifier
]
let userJSON = JSON(userDict)
db.create(userJSON) { (id, revision, doc, error) in
if let error = error {
handler(nil, nil, nil, error)
return
} else {
handler(id, revision, doc, nil)
}
}
}
}

I also separated HTTP request methods to routers. First, I created UserRouter to handle HTTP requests for user and I only wrote one post method to get user data in the body.UserRouter class is like this:

// UserRouter.Swift
import Foundation
import Kitura
import CouchDB
import SwiftyJSON
public class UserRouter {
var db: DatabaseInteraction
public init(db: DatabaseInteraction) {
self.db = db
}

public func bindAll(to router: Router) {
addCreateUser(to: router)
}
private func addCreateUser(to router: Router) {
router.post("/user/", handler: { req, res, next in
guard let parsedBody = req.body else {
res.status(.badRequest)
next()
return
}
      switch(parsedBody) {
case .json(let jsonBody):
let name = jsonBody["name"].string ?? ""
let user = User(name: name, identifier: "\(name.characters.count)")
self.db.addNewUser(user) { (id, revision, doc, error) in
if let error = error {
res.status(.internalServerError)
next()
} else {
res.status(.OK)
if let doc = doc {
res.send(json: doc)
} else {
res.send("Something is wrong in the doc")
}
next()
}
}
default:
res.status(.badRequest)
next()
}
})
}
}

After that, I created the main router to manage separate routing operations from here. Here is the BackendRouter as main router object:

// BackendRouter.swift
import Foundation
import Kitura
public class BackendRouter {
  public let router = Router()
var db: DatabaseInteraction
  public init(db: DatabaseInteraction) {
self.db = db
router.get("/status") { req, res, callNextHandler in
res.status(.OK).send("Everything is working")
callNextHandler()
}

router.all("*", middleware: BodyParser())


self.routeToUser()
}

func routeToUser() {
let user = UserRouter(db: self.db)
user.bindAll(to: self.router)
}
}

Lastly, I connected all of them in the main.swift file.

// main.swift
import Kitura
import HeliumLogger
import CouchDB
HeliumLogger.use()
let connProperties = ConnectionProperties(
host: "127.0.0.1", // httpd address
port: 5984, // httpd port
secured: false, // https or http
username: "admin", // admin username
password: "password"// admin password
)
let db = Database(connProperties: connProperties, dbName: "swift_backend_test_db")
let databaseInteraction = DatabaseInteraction(db: db)
let app = MainRouter(db: databaseInteraction)
Kitura.addHTTPServer(onPort: 8090, with: app.router)
Kitura.run()

So, let’s see how the overall folder structure looks right now.

SwiftBackend
.gitignore
|--Sources
BackendRouter.swift
DatabaseInteraction.swift
main.swift
User.swift
UserRouter.swift
|--Tests
Package.swift

After all coding, it was time to build the API and send a request via Postman. I got success after some trials. (Note: Be careful about connection properties and escaping closures). Of course, there are a lot of things to improve in code. But all these codes were intended to create a working backend API developed with Swift.


In the next post, I made this project testable and Dockerized. There are some points to be careful while making it testable.

As a final step, I’ll try to upload to cloud. But my first impression was really good. I should say that I convinced to work on the backend side in Swift. It’s pretty easy. I’m working with SublimeText instead of Xcode. Thus, I can understand each line I wrote without auto-completion. If you’re working with Swift, you should definitely try to create some backend APIs with Swift.


If you liked it, please click little ❤️ below to recommend it to your friends. If you want to be updated about new blog posts, you can go to my personal website and subscribe to my mail list. You can follow me on Twitter and GitHub.