Name: ToDoBackend
Owner: International Business Machines
Description: This tutorial teaches how to create a Kitura backend for the Todo-Backend project, which provides tests and a web client for a "To Do List" application.
Created: 2017-10-26 05:24:06.0
Updated: 2018-04-25 00:56:31.0
Pushed: 2018-03-01 12:17:59.0
Size: 17
Language: null
GitHub Committers
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
This tutorial teaches how to create a Kitura backend for the Todo-Backend project, which provides tests and a web client for a “To Do List” application.
Note: This workshop has been developed for Swift 4, Xcode 9.x and Kitura 2.x.
Install the Kitura CLI:
Configure the Kitura homebrew tap
brew tap ibm-swift/kitura
Install the Kiura CLI from homebrew
brew install kitura
Clone this project from GitHub to your machine (don't use the Download ZIP option):
clone http://github.com/IBM/ToDoBackend
Clone the ToDo Backend tests from GitHub to your machine (don't use the Download ZIP option):
clone http://github.com/TodoBackend/todo-backend-js-spec
In order to implement a To Do Backend, a server is required that provides support for storing, retrieving, deleting and updating “to do” items. The To Do Backend project doesn't provide a specification as such for how the server must respond, rather it provides a set of tests which the server must pass. The “todo-backend-js-spec” project provides those tests.
The following steps allow you to run the tests:
/todo-backend-js-spec
index.html
This should open your browser with the test page open.test target root
of http://localhost:8080 run tests
The first error reported should be as follows:
:x: the api root responds to a GET (i.e. the server is up and accessible, CORS headers are set up)
rtionError: expected promise to be fulfilled but it was rejected with [Error:
http://localhost:8080
ED
browser failed entirely when make an AJAX request.
This shows that the tests made a GET
request to http://localhost.com:8080
, but that it failed with no response, which is expected as there is no server.
In the instructions below, reloading the page will allow you to re-run the ToDo Backend tests.
Implementing a compliant ToDo Backend is an incremental task, with the aim at each step to pass more tests. The first step is to create a Kitura server to response on requests.
Create a directory for the server project
/ToDoBackend
r ToDoServer
oDoServer
Create a Kitura starter project
ra init
The Kitura CLI will now create and build an starter Kitura application for you. This includes adding best-practice implementations of capabilities such as configuration, health checking and monitoring to the application for you.
More information about the project structure is available on kitura.io.
Open the ToDoServer project in Xcode
/ToDoBackend/ToDoServer
ToDoServer.xcodeproj
Run the server project in Xcode
Run
button or use the ?+R
key shortcut.
3. Select Allow incoming network connections
if you are prompted.Check that some of the standard Kitura URLs are running:
Rerun the tests by reloading the test page in the browser.
The first test should fail with the following:
:x: the api root responds to a GET (i.e. the server is up and accessible, CORS headers are set up)
rtionError: expected promise to be fulfilled but it was rejected with [Error:
http://localhost:8080/
ED
browser failed entirely when make an AJAX request.
er there is a network issue in reaching the url, or the
er isn't doing the CORS things it needs to do.
This test is still failing, even though the server is responding on localhost:8080
. This is because Cross Origin Resource Sharing (CORS) is not enabled.
By default, web servers only serve content to web pages that were served by that web server. In order to allow other web pages, such as the ToDo Backend test page, to connect to the server, Cross Origin Resource Sharing (CORS) must be enabled.
Add the CORS library to the ToDoServer
> Package.swift
file
Add the following to the end of the dependencies section of the Package.swift file:
ackage(url: "https://github.com/IBM-Swift/Kitura-CORS", .upToNextMinor(from: "2.1.0")),
and update the dependencies line for the Application target to the following:
arget(name: "Application", dependencies: [ "Kitura", "KituraCORS", "Configuration", "CloudEnvironment", "Health" , "SwiftMetrics",
NOTE:- In order for Xcode to pick up the new dependency, the Xcode project now needs to be regenerated.
Close Xcode, regenerate the Xcode project and reopen:
/ToDoBackend/ToDoServer
t package generate-xcodeproj
ToDoServer.xcodeproj
Open the Sources
> Application
> Application.swift
file
Add an import for the CORS library to the start of the file:
rt KituraCORS
Add the following into the start of the postInit()
function:
let options = Options(allowedOrigin: .all)
let cors = CORS(options: options)
router.all("/*", middleware: cors)
Re-run the server project in Xcode
Edit the scheme and select a Run Executable of ?ToDoServer?
2. Run the project, then Allow incoming network connections
if you are prompted.
Rerun the tests by reloading the test page in the browser.
The first test should now be passing but the second test is failing:
:x: the api root responds to a POST with the todo which was posted to it
In order to fix this, we need to implement a POST
request that saves a todo item.
REST APIs typically consist of a HTTP request using a verb such as POST
, PUT
, GET
or DELETE
along with a URL and an optional data payload. The server then handles the request and responds with an optional data payload.
A request to store data typically consists of a POST request with the data to be stored, which the server then handles and responds with a copy of the data that has just been stored. This means we need to define a ToDo type, register a handler for POST requests on /
, and implement the handler to store the data.
Define a data type for the ToDo items:
Select the Application folder in the left hand explorer in Xcode
2. Select File
> New
> File...
from the pull down menu
3. Select Swift File and click Next
4. Name the file Models.swift
, change the Targets
from ToDoServerPackageDescription
to Application
, then click Create
5. Add the following to the created file:
ic struct ToDo : Codable, Equatable {
ublic var id: Int?
ublic var user: String?
ublic var title: String?
ublic var order: Int?
ublic var completed: Bool?
ublic var url: String?
ublic static func ==(lhs: ToDo, rhs: ToDo) -> Bool {
return (lhs.title == rhs.title) && (lhs.user == rhs.user) && (lhs.order == rhs.order) && (lhs.completed == rhs.completed) && (lhs.url == rhs.url) && (lhs.id == rhs.id)
This creates a struct for the ToDo items that uses Swift 4's Codable
capabilities.
Create an in-memory data store for the ToDo items
1. Open the Sources
> Application
> Application.swift
file
2. Add a todoStore
, nextId
and a workerQueue
into the App class. On the line below let cloudEnv = CloudEnv()
add:
ate var todoStore = [ToDo]()
ate var nextId :Int = 0
ate let workerQueue = DispatchQueue(label: "worker")
To be able to use DispatchQueue
on Linux, add the following import
statement to the start of the file:
rt Dispatch
Add a helper method at the end of the class, before the last closing brace
execute(_ block: (() -> Void)) {
orkerQueue.sync {
block()
This will be used to make sure that access to shared resources is serialized so the app does not crash on concurrent requests.
Register a handler for a POST
request on /
that stores the ToDo item data
Add the following into the postInit()
function:
er.post("/", handler: storeHandler)
Implement the storeHandler that receives a ToDo, and returns the stored ToDo
Add the following as a function in the App class:
storeHandler(todo: ToDo, completion: (ToDo?, RequestError?) -> Void ) {
var todo = todo
if todo.completed == nil {
todo.completed = false
}
todo.id = nextId
todo.url = "http://localhost:8080/\(nextId)"
nextId += 1
execute {
todoStore.append(todo)
}
completion(todo, nil)
This expects to receive a ToDo struct from the request, sets completed
to false if it is nil
and adds a url
value that informs the client how to retrieve this todo item in the future.
The handler then returns the updated ToDo item to the client.
Run the project and rerun the tests by reloading the test page in the browser.
The first three tests should now pass and the fourth fails:
:X: after a DELETE the api root responds to a GET with a JSON representation of an empty array
In order to fix this, a handler for DELETE
requests and a subsequent GET
handler to return the stored ToDo items.
A request to delete data typically consists of a DELETE request. If the request is to delete a specific item, a URL encoded identifier is normally provided (eg. '/1' for the item with ID 1). If no identifier is provided, it is a request to delete all of the items.
In order to pass the next test, the ToDoServer needs to handle a DELETE
on /
resulting in removing all stored ToDo items.
DELETE
request on /
that empties the ToDo item datapostInit()
function:er.delete("/", handler: deleteAllHandler)
2. Implement the deleteAllHandler
empties the todoStore
Add the following as a function in the App class: deleteAllHandler(completion: (RequestError?) -> Void ) {
execute {
todoStore = [ToDo]()
}
completion(nil)
A request to load all of the stored data typically consists of a GET
request with no data, which the server then handles and responds with an array of the data that has just been stored.
GET
request on /
that loads the datapostInit()
function:er.get("/", handler: getAllHandler)
getAllHandler
that responds with all of the stored ToDo items as an array.
Add the following as a function in the App class: getAllHandler(completion: ([ToDo]?, RequestError?) -> Void ) {
completion(todoStore, nil)
The first seven tests should now pass, with the eighth test failing:
:x: each new todo has a url, which returns a todo
http://localhost:8080/0
ED
Not Found (Cannot GET /0.)
GET
request on '/:id'The failing test is trying to load a specific ToDo item by making a GET
request with the ID of the ToDo item that it wishes to retrieve, which is based on the ID in the url
field of the ToDo item set when the item was stored by the earlier POST
request. In the test above the reqest was for GET /0
- a request for id 0.
Kitura's Codable Routing is able to automatically convert identifiers used in the GET
request to a parameter that is passed to the registered handler. As a result, the handler is registered against the /
route, with the handler taking an extra parameter.
Register a handler for a GET
request on /
:
er.get("/", handler: getOneHandler)
Implement the getOneHandler
that receives an id
and responds with a ToDo item:
getOneHandler(id: Int, completion: (ToDo?, RequestError?) -> Void ) {
completion(todoStore.first(where: {$0.id == id }), nil)
Run the project and rerun the tests by reloading the test page in the browser.
The first nine tests now pass. The tenth fails with the following:
:x: can change the todo's title by PATCHing to the todo's url
H http://localhost:8080/0
ED
Not Found (Cannot PATCH /0.)
PATCH
request on /:id
The failing test is trying to PATCH
a specific ToDo item. A PATCH
request updates an existing item by updating any fields sent as part of the PATCH
request. This means that a field by field update needs to be done.
PATCH
request on /
:er.patch("/", handler: updateHandler)
updateHandler
that receives an id
and responds with the updated ToDo item: updateHandler(id: Int, new: ToDo, completion: (ToDo?, RequestError?) -> Void ) {
guard let idMatch = todoStore.first(where: { $0.id == id }),
let idPosition = todoStore.index(of: idMatch) else { return }
var current = todoStore[idPosition]
current.user = new.user ?? current.user
current.order = new.order ?? current.order
current.title = new.title ?? current.title
current.completed = new.completed ?? current.completed
execute {
todoStore[idPosition] = current
}
completion(todoStore[idPosition], nil)
Twelve tests should now be passing, with the thirteenth failing as follows:
:x: can delete a todo making a DELETE request to the todo's url
TE http://localhost:8080/0
ED
Not Found (Cannot DELETE /0.)
The failing test is trying to DELETE
a specific ToDo item. This means registering an additional handler for DELETE
that this time accepts an ID as a parameter.
DELETE
request on /
:er.delete("/", handler: deleteOneHandler)
deleteOneHandler
that receives an id
and removes the specified ToDo item: deleteOneHandler(id: Int, completion: (RequestError?) -> Void ) {
guard let idMatch = todoStore.first(where: { $0.id == id }),
let idPosition = todoStore.index(of: idMatch) else { return }
execute {
todoStore.remove(at: idPosition)
}
completion(nil)
All sixteen tests should now be passing!
This tutorial has helped you build a ToDo Backend for the web tests and web client from the Todo-Backend project, but one of the great values of Swift is end to end development between iOS and the server. Clone the iOSSampleKituraKit repository and open the iOSKituraKitSample.xcworkspace
to see a iOS app client for the ToDo-Backend project.
d ~
it clone https://github.com/IBM-Swift/iOSSampleKituraKit.git
d iOSSampleKituraKit/KituraiOS
pen iOSKituraKitSample.xcworkspace/
Run (?+R
) the iOS application. You should be able to use the app to add, change and delete ToDo items!