Kitura CouchDB

CouchDB is a NoSQL (or non-relational) database which takes a document-oriented approach to data storage.

Kitura-CouchDB is a pure Swift client which allows Kitura applications to interact with a CouchDB or Cloudant database.

In this guide we’ll demonstrate how to create a CouchDB database, define a CouchDB document, save the document to the database and finally retrieve the document from the database.


Step 1: Install CouchDB and create a database

Before we can use CouchDB within our server we first need to:

  • Download and install CouchDB.
  • Set up an admin username and password in CouchDB.
  • Create a database. (In this example we're using the name bookstore.)

It is important to remember the database name and credentials, as we will need them later on in the guide.


Step 2: Add CouchDB to your project

To use CouchDB from a server, we need to add Kitura-CouchDB to our dependencies.

If you don't have a server, follow our Create a server guide.


Step 3: Define the CouchDB routes

Next, we are going to create a file in our project for the CouchDB routes.

Open your Application.swift file:

open Sources/Application/Application.swift

Inside the postInit() function add:

initializeCouchRoutes(app: self)

Create a new file, called CouchRoutes.swift:

touch Sources/Application/Routes/CouchRoutes.swift

Open your CouchRoutes.swift file:

open Sources/Application/Routes/CouchRoutes.swift

Inside our file, we will define two routes. In the first route, we will retrieve a book from the user and save it into the bookstore database. In the second route, we will retrieve all of the documents that have been saved to the database.

import KituraContracts
import CouchDB
import LoggerAPI

func initializeCouchRoutes(app: App) {
    app.router.post("/couch", handler: app.couchSaveHandler)
    app.router.get("/couch", handler: app.couchFindAllHandler)
}
extension App {
    // Define ConnectionProperties and CouchDBClient here

    func couchSaveHandler(book: BookDocument, completion: @escaping (BookDocument?, RequestError?) -> Void) {
        // Save book here
    }

    func couchFindAllHandler(completion: @escaping ([BookDocument]?, RequestError?) -> Void) {
        // Get all books here
    }
}

Step 4: Define a Document

CouchDB is a NoSQL database for storing documents. A Document is any structure that can be represented as JSON and contains _id and _rev fields.

  • The id field is the unique identifier for the document.
  • The _rev field is the revision of the document. It is returned when you make requests and is used to prevent conflicts from multiple users updating the same document.

To define a CouchDB document, all we need to do is create a Swift object and make it conform to the Document protocol.

Create a new file, called BookDocument.swift:

touch Sources/Application/Models/BookDocument.swift

Open your BookDocument.swift file:

open Sources/Application/Models/BookDocument.swift

Inside this file, define your BookDocument:

import CouchDB

struct BookDocument: Document {
    let _id: String?
    var _rev: String?
    let title: String
    let price: Double
    let genre: String
}

Step 5: Connect to CouchDB database

Inside our App extension, we will define our connection properties for CouchDB, substituting in the credentials we defined earlier for the admin username and password:

static let properties = ConnectionProperties(
        host: "127.0.0.1",              // http address
        port: 5984,                     // http port
        secured: false,                 // https or http
        username: "<CouchDB-username>", // admin username
        password: "<CouchDB-password>"  // admin password
)

Now we can use these connection properties to create our CouchDB client:

static let couchDBClient = CouchDBClient(connectionProperties: properties)

The CouchDBClient represents a connection to a CouchDB server. It is initialized with your ConnectionProperties and handles the creation, retrieval and deletion of CouchDB databases.


Step 6: Save our document to the database

We are going to modify the couchSaveHandler we defined earlier to store a document in our database.

The first thing we need to do is connect to our bookstore database. We achieve this by using the CouchDBClient.retrieveDB() method and passing in our database name.

Inside the couchSaveHandler add the following code:

App.couchDBClient.retrieveDB("bookstore") { (database, error) in
    guard let database = database  else {
        return completion(nil, .internalServerError)
    }
    // Initialize document here
}

Since the CouchDB functions are asynchronous, we must handle responses inside the function's callback.

Now we are going to save our book document to the database. We will use the CouchDB Database class to make an HTTP request to our database. This class can make CRUD (Create, Retrieve, Update, Delete) requests for our CouchDB Document. In this case we will use create to save our book document. If the call succeeds we then return the book document with it's updated id and revision, otherwise we return an error.

database.create(bookDocument) { (response, error) in
    guard let response = response else {
        return completion(nil, RequestError(httpCode: error?.statusCode ?? 500))
    }
    var updatedBook = book
    updatedBook._id = response.id
    updatedBook._rev = response.rev
    completion(updatedBook, nil)
}

Your completed POST handler should now look as follows:

func couchSaveHandler(book: BookDocument, completion: @escaping (BookDocument?, RequestError?) -> Void) {
    App.couchDBClient.retrieveDB("bookstore") { (database, error) in
        guard let database = database  else {
            return completion(nil, .internalServerError)
        }
        database.create(book) { (response, error) in
            guard let response = response else {
                return completion(nil, RequestError(httpCode: error?.statusCode ?? 500))
            }
            var updatedBook = book
            updatedBook._id = response.id
            updatedBook._rev = response.rev
            completion(updatedBook, nil)
        }
    }
}

Step 7: Test saving a document to the database

Now we're going to test our route by passing in a book and checking that it is saved to the database.

First we need to start our Kitura server.

Once the server is running, open a terminal and run the following:

curl -X POST \
      http://localhost:8080/couch \
      -H 'content-type: application/json' \
      -d '{
      "title": "A Game of Thrones",
      "price": 14.99,
      "genre": "Fantasy"
  }'

This will make a POST request to the server and we should be returned our book document in JSON format:

{"_id":"<generated id number>","_rev":"<generated revision number>","title":"A Game of Thrones","price":14.99,"genre":"Fantasy"}

Since we did not provide an _id in our request, a UUID was generated for us.


Step 8: Retrieve the document from the database

Similar to our other handler, the first step is to connect to our bookstore database.

Inside the couchFindAllHandler add the following code:

App.couchDBClient.retrieveDB("bookstore") { (database, error) in
    guard let database = database  else {
        return completion(nil, .internalServerError)
    }
    // Retrieve documents here
}

Next we're going to retrieve our document from the database. To retrieve all the documents from a CouchDB database we need to use the aptly named retrieveAll method.

database.retrieveAll(includeDocuments: true, callback: { (allDocuments, error) in
    guard let allDocuments = allDocuments else {
        return completion(nil, RequestError(httpCode: error?.statusCode ?? 500))
    }
    // Decode and return books here
})

When we made the call to retrieveAll we set the includeDocuments parameter to true, this means that each row returned from the database will have an additional field called "doc" in it which contains the JSON document. These documents can then be decoded to a given Swift type using decodeDocuments(ofType:).

We decode all the documents that match the BookDocument type, and return them in the completion:

let books = allDocuments.decodeDocuments(ofType: BookDocument.self)
completion(books, nil)

Your completed GET handler should now look as follows:

func couchFindAllHandler(completion: @escaping ([BookDocument]?, RequestError?) -> Void) {
    App.couchDBClient.retrieveDB("bookstore") { (database, error) in
        guard let database = database  else {
            return completion(nil, .internalServerError)
        }
        database.retrieveAll(includeDocuments: true, callback: { (allDocuments, error) in
            guard let allDocuments = allDocuments else {
                return completion(nil, RequestError(httpCode: error?.statusCode ?? 500))
            }
            let books = allDocuments.decodeDocuments(ofType: BookDocument.self)
            completion(books, nil)
        })
    }
}

Step 9: Test retrieving documents from the database

If you have followed the guide so far then you will now have a book document in your database, which we can retrieve using the code we just wrote.

To do this, start the server and navigate to: localhost:8080/couch.

This will call GET on the /couch route and we will see the book we posted earlier returned in JSON format. The book data persists even if we restart the Kitura server as it is now stored in a database.