Backend development

We want to develop a very simple backend for a Todo application.

Model

We will start by creating our model. The code is as follows:

import Vapor

final class Todo {
    var id: Int
    var name: String
    var description: String
    var notes: String
    var completed: Bool
    var synced: Bool
 
    init(id: Int, name: String, description: String, notes: String,
      completed: Bool, synced: Bool) {
        self.id = id
        self.name = name
        self.description = description
        self.notes = notes
        self.completed = completed
        self.synced = synced
    }
}

This class imports Vapor and includes some of the Todo-related properties as well as an init method.

To be able to pass this model into JSON arrays and dictionaries, we need to extend a protocol called JsonRepresentable:

extension Todo: JSONRepresentable {
    func makeJson() -> JSON {
 
        return JSON([
            "id":id,
          "name": "(name)",
   "description": "(description)",
         "notes": "(notes)",
     "completed": completed,
        "synced": synced
        ])
    }
}

Store

Then we want to store list of Todo items in memory. To be able to achieve this, we will create a new class called TodoStore. The code is as follows:

import Vapor

final class TodoStore {
 
    static let sharedInstance = TodoStore()
    private var list: [Todo] = Array<Todo>()
    private init() {
    }
}

For the sake of simplicity, we make this class a singleton that stores a list of Todo items. Also, we make the init method private to avoid non-shared instance initiation.

To allow instances of Todo to be passed into JSON arrays and dictionaries as if it were a native JSON type, we will need to extend our TodoStore by conforming to JSONRepresentable as follows:

extension TodoStore: JSONRepresentable {
    func makeJson() -> JSON {
        return JSON([
            "list": "(list)"
        ])
    }
}

Next, we add the following methods:

func addtem(item: Todo) {
    self.list.append(item)
}
 
func listItems() -> [Todo] {
    return self.list
}

As the names suggest, these methods will be used for adding and listing items. We will need a very simple find method, so let's develop it:

func find(id: Int) -> Todo? {
    return self.list.index { $0.id == id }.map { self.list[$0] }
}

Here, we use index and map higher-order functions to find the index and return the respective array element.

Then, we will need to develop update and delete methods:

func delete(id: Int) -> String {
    if self.find(id: id) != nil {
        self.list = self.list.filter { $0.id != id }
        return "Item is deleted"
    }
    return "Item not found"
}

func deleteAll() -> String {
    if self.list.count > 0 {
        self.list.removeAll()
        return "All items were deleted"
    }
    return "List was empty"

}

func update(item: Todo) -> String {
    if let index = (self.list.index { $0.id == item.id }) {
        self.list[index] = item
        return "item is up to date"
    }
    return "item not found"
}

Also, we can combine add and update as follows:

func addOrUpdateItem(item: Todo) {
    if self.find(item.id) != nil {
        update(item)
    } else {
        self.list.append(item)
    }
}

At this point, our TodoStore is capable of all CRUD operations.

Controller

The next step will be developing routing, request, and response handling. For the sake of simplicity, we will modify main.swift in the Vapor example.

We will need to make our changes after the following definition:

let app = Application()

Posting a new Todo item

The first step will be to develop a post method to create a Todo item as follows:

/// Post a todo item
app.post("postTodo") { request in
    guard let id = request.headers.headers["id"]?.values,
        name = request.headers.headers["name"]?.values,
        description = request.headers.headers["description"]?.values,
        notes = request.headers.headers["notes"]?.values,
        completed = request.headers.headers["completed"]?.values,
        synced = request.headers.headers["synced"]?.values
    else {
        return JSON(["message": "Please include mandatory parameters"])
    }
 
    let todoItem = Todo(id: Int(id[0])!,
                      name: name[0],
               description: description[0],
                     notes: notes[0],
                 completed: completed[0].toBool()!,
                    synced: synced[0].toBool()!)

    let todos = TodoStore.sharedInstance
    todos.addOrUpdateItem(item: todoItem)
 
    let json:[JSONRepresentable] = todos.listItems().map { $0 }
    return JSON(json)
}

The preceding example is going to create a Todo item. First, we check if the API user is provided with all the necessary HTTP headers with a guard expression and then we use our addItem() method in the TodoStore class to add that specific item. In the preceding code example, we needed to convert completed from Bool to String, so we extended the String function as follows and we called toBool() on completed:

extension String {
    func toBool() -> Bool? {
        switch self {
        case "True", "true", "yes", "1":
            return true
        case "False", "false", "no", "0":
            return false
        default:
            return nil
        }
    }
}

We will need to build and run our backend app with the vapor build and vapor run directives in the terminal application. At this point, we should get the following prompt:

Posting a new Todo item

If we point to localhost 8080 in a web browser, we should see Vapor up and running. Also, we can use the curl tool to test our post method in the terminal by copying and pasting the following code:

curl -X "POST" "http://localhost:8080/postTodo/" 
   -H "Cookie: test=123" 
   -H "id: 3" 
   -H "notes: do not forget to buy potato chips" 
   -H "Content-Type: application/json" 
   -H "description: Our first todo item" 
   -H "completed: false" 
   -H "name: todo 1" 
   -d "{}"

The result will resemble the following:

Posting a new Todo item

As we can see from the screenshot, we received a JSON response that includes our added Todo item.

Getting a list of Todo items

Our post call returns the list of items. Also, we can get items with this:

/// List todo items
app.get("todos") { request in
 
    let todos = TodoStore.sharedInstance
    let json:[JSONRepresentable] = todos.listItems().map { $0 }
    return JSON(json)
}

We will build and run our application with Vapor CLI again and we can test this get request like this:

curl -X "GET" "http://localhost:8080/todos" 
   -H "Cookie: test=123"

Getting a specific Todo item

The preceding call retrieves all the items. If we want to get a specific item, we can do that too:

/// Get a specific todo item
app.get("todo") { request in

    guard let id = request.headers.headers["id"]?.values else {
        return JSON(["message": "Please provide the id of todo item"])
    }

    let todos = TodoStore.sharedInstance.listItems()
    var json = [JSONRepresentable]()

    let item = todos.filter { $0.id == Int(id[0])! }
    if item.count > 0 {
        json.append(item[0])
    }
 
    return JSON(json)
}

Here, we check for the existence of headers and use the listItems() method in our TodoStore class to retrieve that specific item. We can test it in curl by executing the following commands in the terminal:

curl -X "GET" "http://localhost:8080/todo/" 
   -H "id: 1" 
   -H "Cookie: test=123"

Deleting an item and deleting all Todo items

The next operation that we need to implement is deleting items from our TodoStore. Let's implement the delete and deleteAll methods:

/// Delete a specific todo item
app.delete("deleteTodo") { request in
    guard let id = request.headers.headers["id"]?.values else {
        return JSON(["message": "Please provide the id of todo item"])
    }
 
    let todos = TodoStore.sharedInstance
    todos.delete(id: Int(id[0])!)

    return JSON(["message": "Item is deleted"])
}

/// Delete all items
app.delete("deleteAll") { request in
    TodoStore.sharedInstance.deleteAll()
 
    return JSON(["message": "All items are deleted"])
}

To test the delete functionality, we can execute the following commands in the terminal:

curl -X "DELETE" "http://localhost:8080/deleteTodo/" 
   -H "id: 1" 
   -H "Cookie: test=123"

To test the deleteAll functionality, we can execute the following commands in the terminal:

curl -X "DELETE" "http://localhost:8080/deleteAll" 
   -H "Cookie: test=123"

Updating a Todo item

Finally, we want to be able to update an item in our Todo list to complete it or take some notes:

/// Update a specific todo item
app.post("updateTodo") { request in
    guard let id = request.headers.headers["id"]?.values,
        name = request.headers.headers["name"]?.values,
        description = request.headers.headers["description"]?.values,
        notes = request.headers.headers["notes"]?.values,
        completed = request.headers.headers["completed"]?.values,
        synced = request.headers.headers["synced"]?.values
    else {
        return JSON(["message": "Please include mandatory parameters"])
    }
 
    let todoItem = Todo(id: Int(id[0])!,
                      name: name[0],
               description: description[0],
                     notes: notes[0],
                 completed: completed[0].toBool()!,
                    synced: synced[0].toBool()!)
 
    let todos = TodoStore.sharedInstance
    todos.update(item: todoItem)
    return JSON(["message": "Item is updated"])
}

Here, we check for the headers first and, if they are present, we use the update method in TodoStore to update a specific item in our store. We can test it like this:

curl -X "POST" "http://localhost:8080/updateTodo" 
   -H "Cookie: test=123" 
   -H "id: 3" 
   -H "notes: new note" 
   -H "name: updated name" 
   -H "description: updated description" 
   -H "completed : yes"

At this point, we should have a simple backend API to create, list, update, and delete todo items in memory. In the next section, we will develop an iOS application to leverage this API.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset