├── resources
├── docker-app.png
├── grafana-home.png
├── docker-context.png
├── docker-menubar.png
├── grafana-add-graph.png
├── grafana-kube-dash.png
├── prometheus-graph.png
├── grafana-datasource.png
├── grafana-import-select.png
├── prometheus-dashboard.png
└── grafana-dashboard-import.png
├── README.md
├── DeployToCloud.md
├── MonitoringKube.md
├── DatabaseWorkshop.md
├── LICENSE.txt
├── DeployingToKube.md
└── Workshop.md
/resources/docker-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IBM/ToDoBackend/HEAD/resources/docker-app.png
--------------------------------------------------------------------------------
/resources/grafana-home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IBM/ToDoBackend/HEAD/resources/grafana-home.png
--------------------------------------------------------------------------------
/resources/docker-context.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IBM/ToDoBackend/HEAD/resources/docker-context.png
--------------------------------------------------------------------------------
/resources/docker-menubar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IBM/ToDoBackend/HEAD/resources/docker-menubar.png
--------------------------------------------------------------------------------
/resources/grafana-add-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IBM/ToDoBackend/HEAD/resources/grafana-add-graph.png
--------------------------------------------------------------------------------
/resources/grafana-kube-dash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IBM/ToDoBackend/HEAD/resources/grafana-kube-dash.png
--------------------------------------------------------------------------------
/resources/prometheus-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IBM/ToDoBackend/HEAD/resources/prometheus-graph.png
--------------------------------------------------------------------------------
/resources/grafana-datasource.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IBM/ToDoBackend/HEAD/resources/grafana-datasource.png
--------------------------------------------------------------------------------
/resources/grafana-import-select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IBM/ToDoBackend/HEAD/resources/grafana-import-select.png
--------------------------------------------------------------------------------
/resources/prometheus-dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IBM/ToDoBackend/HEAD/resources/prometheus-dashboard.png
--------------------------------------------------------------------------------
/resources/grafana-dashboard-import.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IBM/ToDoBackend/HEAD/resources/grafana-dashboard-import.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Building a "ToDo" Backend with Kitura
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ## Workshop Table of Contents:
14 |
15 | 1. **[Build your Kitura app](https://github.com/IBM/ToDoBackend/blob/master/README.md)**
16 | 2. [Connect it to an SQL database](https://github.com/IBM/ToDoBackend/blob/master/DatabaseWorkshop.md)
17 | 3. [Build your app into a Docker image and deploy it on Kubernetes.](https://github.com/IBM/ToDoBackend/blob/master/DeployingToKube.md)
18 | 4. [Enable monitoring through Prometheus/Grafana](https://github.com/IBM/ToDoBackend/blob/master/MonitoringKube.md)
19 |
20 | # Building your Kitura app
21 |
22 | The application you'll create is a "ToDo list" application, as described by the [Todo-Backend](http://todobackend.com/) project.
23 |
24 | You'll learn about server-side Swift, the Kitura framework, REST APIs, OpenAPI, Docker and Kubernetes.
25 |
26 | At the end you should have a fully functioning application which passes a provided verification testsuite, running in Kubernetes.
27 |
28 | ## Prerequisites
29 |
30 | Before getting started, make sure you have the following prerequisites installed on your system.
31 |
32 | 1. macOS
33 | 2. Xcode 9.3 or later. You can install Xcode from the Mac App Store.
34 | 3. Command line tools for Xcode. You can check if these are installed (and install them if necessary) by running `xcode-select --install` in a terminal window.
35 |
36 | ## Setting up
37 |
38 | Start by cloning the testsuite to your system. You'll be using this to verify your REST API:
39 |
40 | ```
41 | cd ~
42 | git clone https://github.com/TodoBackend/todo-backend-js-spec.git
43 | ```
44 |
45 | The next step is to generate your first Kitura application. Using an application generator will get you started quickly and ensure that your Kitura application is cloud-ready.
46 |
47 | There are three ways you can generate your application:
48 |
49 | 1. Using the Kitura macOS app.
50 | 2. At the command-line, by installing the Kitura command-line interface via Homebrew.
51 | 3. Using a web browser to create a Kitura "starter kit" in IBM Cloud, then downloading the generated code in a zip file. This requires signing up for a free IBM Cloud account (no credit card is required).
52 |
53 | You can choose whichever option you prefer.
54 |
55 | ### Option 1: Use the Kitura macOS app
56 |
57 | 1. Visit [https://www.kitura.io/app.html](https://www.kitura.io/app.html) in your web browser and download the Kitura app.
58 | 2. Install the app by opening the downloaded `Kitura.dmg` and dragging the app to your `Applications` folder.
59 | 3. Ctrl-click the `Kitura` app in your `Applications` folder and choose "Open".
60 |
61 | The Kitura macOS app provides an easy point-and-click way to generate a new Kitura project. There are three templates: Skeleton, Starter, and OpenAPI.
62 |
63 | 1. Mouse over "OpenAPI" and click "Create".
64 | 2. Navigate to the "ToDoBackend" folder in your home folder.
65 | 3. Change the project name to "ToDoServer".
66 | 4. Click "Create".
67 |
68 | The Kitura app will create a new Kitura project for you, ready to deploy to the cloud.
69 |
70 | Congratulations, you have created your first Kitura application. Proceed to [the rest of the workshop](https://github.com/IBM/ToDoBackend/blob/master/Workshop.md).
71 |
72 | ### Option 2: Create your Kitura application at the command-line
73 |
74 | 1. Open a terminal window and check that you have Homebrew installed by running `brew --version`. If you need to install Homebrew, visit [brew.sh](https://brew.sh/) and follow the installation instructions.
75 | 2. Install the Kitura command-line interface:
76 | 1. Add the Kitura tap to your Homebrew: `brew tap ibm-swift/kitura`
77 | 2. Install the Kitura CLI: `brew install kitura`
78 |
79 | You can check that the Kitura CLI has been installed correctly by running `kitura --help`.
80 |
81 | Now, generate your Kitura application:
82 |
83 | ```
84 | mkdir ~/ToDoBackend/ToDoServer
85 | cd ~/ToDoBackend/ToDoServer
86 | kitura init
87 | ```
88 |
89 | This creates a fully working Kitura project that provides monitoring and metrics which can then be extended with your application logic.
90 |
91 | ## Next Steps
92 |
93 | Congratulations, you have created your first Kitura application. Proceed to [the rest of the workshop](https://github.com/IBM/ToDoBackend/blob/master/Workshop.md).
94 |
--------------------------------------------------------------------------------
/DeployToCloud.md:
--------------------------------------------------------------------------------
1 | # Deploying the Kitura ToDo Backend to IBM Cloud
2 |
3 | ### Pre-Requisites:
4 | **Create a free IBM Cloud Account**
5 | Go to the following URL, fill out the form and press `Create Account`:
6 | https://console.bluemix.net/registration?cm_sp=dw-bluemix-_-swift-_-devcenter
7 |
8 | **Install the IBM Developer Tools**
9 | Run the `init` command of the Kitura CLI:
10 | ```
11 | kitura idt
12 | ```
13 |
14 | **Obtain a GitHub ID**
15 | Go to the following URL, enter Username, Email and Password and press `Sign up for GitHub`:
16 | https://github.com/
17 |
18 | **Install the Git CLI**
19 | `brew install git`
20 |
21 |
22 | ## Building and Testing on Linux
23 | Before deploying to the cloud, it is useful to be able to build and test the Kitura application on Linux to ensure that it will compile and run when deployed. The Kitura CLI uses three features of the IBM Developer Tools: `build`, `run` and `test` to allow you to easily verify your application locally.
24 |
25 | ### 1: Build your Kitura application locally for Linux
26 | 1. Go to the root directory of your FoodTrackerServer project
27 | ```
28 | cd ~/ToDoBackend/
29 | ```
30 | 2. Build your Kitura application for Linux:
31 | ```
32 | kitura build
33 | ```
34 | This builds your Kitura application using a Linux Ubuntu 14.04 Docker container locally on your laptop, ensuring that the application will compile successful when deployed to the cloud.
35 | 3. Run you Kitura application on Linux
36 | ```
37 | kitura run
38 | ```
39 | This runs your Kitura application inside the Linux Docker container, verifying that it will run when deployed to the Cloud. Once the application is running you can connect to its URLs on localhost as before, eg:
40 | * SwiftMetrics Dashboard: http://localhost:8080/swiftmetrics-dash
41 | * Health Check endpoint: http://localhost:8080/health
42 | 4. If you have created tests for your application that are runnable using `swift test` you can also execute those using:
43 | ```
44 | idt test
45 | ```
46 |
47 | ## Deploying to IBM Cloud
48 | There are two main methods to deploying your application to IBM Cloud:
49 | 1. Using the IBM Developer Tools CLI
50 | 2. Using the IBM Cloud DevOps pipelines
51 |
52 |
53 |
54 | ### Option 1: IBM Developer Tools (IDT)
55 | 1. Go to the root directory of your FoodTrackerServer project
56 | ```
57 | cd ~/ToDoBackend/
58 | ```
59 |
60 | 2. Log in to IBM Cloud
61 | ```
62 | bluemix api https://api.ng.bluemix.net
63 | bluemix login
64 | bluemix target -o -s
65 | ```
66 | where `YOUR_ORG` is the organisation you used when signing up to IBM Cloud and `YOUR_DEV_SPACE` is the space you created.
67 |
68 | 5. Deploy your application to IBM Cloud
69 | ```
70 | idt deploy
71 | ```
72 | 6. Open your browser to the deployed provided URL to see the application running. You can now update the URL used with KituraKit in the FoodTracker application to connect from the iOS app.
73 |
74 | ### Option 2: IBM Cloud DevOps Pipelines
75 |
76 | In order to use the IBM Cloud DevOps pipelines to build, test and deploy your project, you need to host your project in a Git repository that is visible to IBM Cloud. The easiest way to do this is using GitHub.
77 |
78 | #### Create a GitHub project
79 | 1. Go to your GitHub account
80 | https://github.com
81 | 2. Go to your profile by clicking on your avatar in the top right hand corner.
82 | 3. Select the `Repositories` tab
83 | 4. Select the green `New` button
84 | 5. Give your repository a name and press `Create repository`
85 | **Note:** Keep this page for use later
86 |
87 |
88 | #### Create a Local Git Project
89 | 1. Go to the root directory of your ToDo Backend project
90 |
91 | ```
92 | cd ~/ToDoBackend/
93 | ```
94 | 2. Initialise a local git project
95 | `git init`
96 | 3. Add all your files to the project
97 | `git add -A`
98 | 4. Check those file in by as a `commit`
99 | `git commit -m "Initial commit"`
100 | 6. Push the commit to GitHub
101 | Use the two lines under `…or push an existing repository from the command line` from the page displayed when you created your GitHub page.
102 |
103 | 7. Reload the GitHub project page
104 |
105 | #### Create an IBM Cloud DevOps Toolchain for your project
106 |
107 | 1. Click the "Create Toolchain" button under the "Bluemix toolchain" section of the README.md of your GitHub project.
108 | 2. If needed, login to IBM Cloud using your credentials
109 | 3. Click the "Create" button
110 | 4. Click on the "Delivery Pipeline" tile
111 | 5. Wait for the "Deploy Stage" to complete
112 | 6. Click the link under "Last Execution Result" to check that the Kitura server is running once its status button turns green.
113 | 7. Open your browser to the deployed provided URL to see the application running. You can now update the URL used with the ToDo Backend tests to verify that your deployed application is working correctly.
114 |
--------------------------------------------------------------------------------
/MonitoringKube.md:
--------------------------------------------------------------------------------
1 | # Building a "ToDo" Backend with Kitura
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ## Workshop Table of Contents:
14 |
15 | 1. [Build your Kitura app](https://github.com/IBM/ToDoBackend/blob/master/README.md)
16 | 2. [Connect it to an SQL database](https://github.com/IBM/ToDoBackend/blob/master/DatabaseWorkshop.md)
17 | 3. [Build your app into a Docker image and deploy it on Kubernetes.](https://github.com/IBM/ToDoBackend/blob/master/DeployingToKube.md)
18 | 4. **[Enable monitoring through Prometheus/Grafana](https://github.com/IBM/ToDoBackend/blob/master/MonitoringKube.md)**
19 |
20 | # Monitoring the ToDoBackend in Kubernetes
21 |
22 | Kitura uses the SwiftMetrics module to provide monitoring data, covering metrics like CPU usage, memory usage and HTTP responsiveness.
23 |
24 | SwiftMetrics provides APIs for accessing the monitoring data, as well as providing built-in integration with the Prometheus open source monitoring tool.
25 |
26 | Whilst Prometheus can be run anywhere, it is also designed to integrate easily in a Kubernetes enviroment, with pre-build configurations to collect data from both Prometheus compatible applications and from Kubernetes itself.
27 |
28 | ## Installing Prometheus into Kubernetes
29 |
30 | Installing Prometheus into Kubernetes can be done using its provided Helm chart:
31 |
32 | ```sh
33 | helm install stable/prometheus --name prometheus --namespace prometheus
34 | ```
35 |
36 | You can then run the following two commands in order to be able to connect to Prometheus from your browser:
37 |
38 | ```sh
39 | export POD_NAME=$(kubectl get pods --namespace prometheus -l "app=prometheus,component=server" -o jsonpath="{.items[0].metadata.name}")
40 | kubectl --namespace prometheus port-forward $POD_NAME 9090
41 | ```
42 | You can now connect to Prometheus at the following address:
43 |
44 | * [http://localhost:9090](http://localhost:9090)
45 |
46 | This should show the following screen:
47 | 
48 | Prometheus will be automatically collecting data from your Kitura application, allowing you to create graphs of your data.
49 |
50 | To build your first graph, type `os_cpu_used_ratio` into the **Expression** box and click on the **Graph** tab:
51 |
52 | 
53 |
54 |
55 | Whilst Prometheus provides the ability to build simple graphs and alerts, Grafana is commonly used to build more sophisticated dashboards.
56 |
57 | ## Installing Grafana into Kubernetes
58 |
59 | Installing Grafana into Kubernetes can be done using its provided Helm chart:
60 |
61 | ```sh
62 | helm install stable/grafana --set adminPassword=PASSWORD --name grafana --namespace grafana --version 1.14.3
63 | ```
64 |
65 | You can then run the following two commands in order to be able to connect to Grafana from your browser:
66 |
67 | ```sh
68 | export POD_NAME=$(kubectl get pods --namespace grafana -l "app=grafana" -o jsonpath="{.items[0].metadata.name}")
69 | kubectl --namespace grafana port-forward $POD_NAME 3000
70 | ```
71 | You can now connect to Grafana at the following address, using `admin` and `PASSWORD` to login:
72 |
73 | * [http://localhost:3000](http://localhost:3000)
74 |
75 | This should show the following screen:
76 |
77 | 
78 |
79 | In order to connect Grafana to the Prometheus service, next click on **Add data source**.
80 |
81 | This opens a panel that should be filled out with the following entries:
82 |
83 | * Name: `Prometheus`
84 | * Type: `Prometheus`
85 | * URL: `http://prometheus-server.prometheus.svc.cluster.local`
86 |
87 | 
88 |
89 | Now click on **Save & Test** to check the connection and save the Data Source configuration.
90 |
91 | Grafana now has access to the data from Prometheus.
92 |
93 | ## Installing a Kubernetes Dashboard into Grafana
94 |
95 | The Grafana community provides a large number of pre-created dashboards which are available for download, including some which are designed to display Kubernetes data.
96 |
97 | To install one of those dashboards, click on the **+** icon and select **Import**
98 |
99 | 
100 |
101 | In the provided panel, enter `1621` into the **Grafana.com Dashboard** field in order to import dashboard number 1621, and press **Tab**.
102 |
103 | This then loads the information on dashboard `1621` from Grafana.com.
104 |
105 | Set the **Prometheus** field to `Prometheus` and click **Import**.
106 |
107 | 
108 |
109 | This will then open the dashboard, which will automatically start populating with data about your Kubernetes cluster.
110 |
111 | 
112 |
113 | ## Adding Custom Graphs
114 |
115 | In order to extend the dashboard with your own graphs, click the **Add panel** icon on the top toolbar and select **Graph**.
116 |
117 | 
118 |
119 | This creates a blank graph. Select the **Panel Title** pull down menu and select **Edit**.
120 |
121 | This opens an editor panel where you can select data that you'd like to graph.
122 |
123 | Type `os_cpu_used_ratio` into the data box, and a graph of your applications CPU data will show on the panel.
124 |
125 | You can create more complex queries and apply filters according to any kubernetes value. For example, the following will show all of the HTTP request durations for your specific application:
126 |
127 | * `http_request_duration_microseconds{kubernetes_name="todoserver-service"}`
128 |
129 | ## Next Steps
130 |
131 | You now have integrated monitoring for both your Kubernetes cluster and your deployed Kitura application.
132 |
133 | Congratulations! You have completed the workshop!
134 |
135 | ## Bonus Content
136 |
137 | * Add a Singlestat that shows how many instances of you Kitura application are currently running
138 | * Add a Singlestat that shows how many requests your Kitura app has responded to
139 |
--------------------------------------------------------------------------------
/DatabaseWorkshop.md:
--------------------------------------------------------------------------------
1 | # Building a "ToDo" Backend with Kitura
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ## Workshop Table of Contents:
14 |
15 | 1. [Build your Kitura app](https://github.com/IBM/ToDoBackend/blob/master/README.md)
16 | 2. **[Connect it to an SQL database](https://github.com/IBM/ToDoBackend/blob/master/DatabaseWorkshop.md)**
17 | 3. [Build your app into a Docker image and deploy it on Kubernetes.](https://github.com/IBM/ToDoBackend/blob/master/DeployingToKube.md)
18 | 4. [Enable monitoring through Prometheus/Grafana](https://github.com/IBM/ToDoBackend/blob/master/MonitoringKube.md)
19 |
20 | # Adding persistance to ToDoBackend with Swift-Kuery-ORM
21 |
22 | So far, our ToDoBackend has been storing, retrieving, deleting and updating "to do" items in a local in-memory `Array`.
23 |
24 | Now we will show you how to use our ORM (Object Relational Mapping) library, called Swift-Kuery-ORM, to store the "to do" items in a PostgreSQL database. This allows you to simplify the persistence of model objects with your server.
25 |
26 | ## Pre-Requisite
27 |
28 | This is a follow on tutorial to our [ToDoBackend tutorial](https://github.com/IBM/ToDoBackend). Please complete that before proceeding with this tutorial.
29 |
30 | **Homebrew** is also needed, to install Homebrew run the following Terminal command:
31 | ```
32 | /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
33 | ```
34 |
35 | ## Installing PostgreSQL
36 |
37 | In this tutorial you'll be using the [Swift Kuery PostgreSQL plugin](https://github.com/IBM-Swift/Swift-Kuery-PostgreSQL), so you will need PostgreSQL running on your local machine. You can install and start PostgreSQL as follows:
38 |
39 | ```
40 | brew install postgresql
41 | brew services start postgresql
42 | ```
43 |
44 | ## Persistance over local storage
45 | Adding a database means your "todo" items are saved even after you close your server down. Also, depending on where your database is stored, different applications on different machines can access the data (for instance, in a cloud application).
46 |
47 | ### 1. Create a Database
48 | Create a new PostgreSQL database for the project called `tododb`:
49 | ```
50 | createdb tododb
51 | ```
52 |
53 | ### 2. Updating Package.swift
54 | Your `Package.swift` needs two new Packages for the PostgreSQL and ORM to work.
55 | 1. Open the `ToDoServer` > `Package.swift` file in Xcode.
56 | 2. Add the following two lines to the end of the dependencies section of the `Package.swift` file:
57 | ```swift
58 | .package(url: "https://github.com/IBM-Swift/Swift-Kuery-ORM", from: "0.4.1"),
59 | .package(url: "https://github.com/IBM-Swift/Swift-Kuery-PostgreSQL", from: "2.1.0"),
60 | ```
61 | 3. Declare these new packages in the "Application" target (note the lack of hyphens):
62 | ```swift
63 | .target(name: "Application", dependencies: ["SwiftKueryPostgreSQL", "SwiftKueryORM", "KituraCORS", "KituraOpenAPI", "Kitura", "CloudEnvironment","SwiftMetrics","Health",]),
64 | ```
65 | In order for Xcode to pick up the new dependencies, the Xcode project now needs to be regenerated.
66 |
67 | 1. Close Xcode
68 | 2. Rengerate the Xcode project and reopen:
69 |
70 | ```bash
71 | cd ~/ToDoBackend/ToDoServer
72 | swift package generate-xcodeproj
73 | open ToDoServer.xcodeproj
74 | ```
75 |
76 | ### 3. Importing into the Application
77 | 1. In Xcode, open `Sources` > `Application` > `Application.swift`
78 | 2. Add imports for the two new libraries below the other import statements at the top of the file:
79 | ```swift
80 | import SwiftKueryORM
81 | import SwiftKueryPostgreSQL
82 | ```
83 | ### 4. Extending ToDo to conform to Model
84 | The `Model` protocol is the key to using the ORM. The `model` protocol provides the functionality for saving and retrieving items to and from the database.
85 |
86 | Earlier, we declared our `ToDo` struct, located in `Application` > `Model.swift`, to be Codable to simplify our RESTful routes for these objects on our server. The Model protocol extends what Codable does to work with the ORM, so now you also need the `ToDo` struct to conform to the `Model` protocol.
87 |
88 | At the end of your `Application.swift` file (after the final `}`) extend your object by adding the following:
89 |
90 | ```swift
91 | extension ToDo: Model {
92 | }
93 | ```
94 | This exposes new methods to ToDo for saving, finding, deleting etc. from a database.
95 |
96 | ### 5. Creating a connection with the database
97 | Add the following code, to set up your database connection pool, to the end of the `Application.swift` file, below the final curly brace:
98 |
99 | ```swift
100 | class Persistence {
101 | static func setUp() {
102 | let pool = PostgreSQLConnection.createPool(host: "localhost", port: 5432, options: [.databaseName("tododb")], poolOptions: ConnectionPoolOptions(initialCapacity: 10, maxCapacity: 50))
103 | Database.default = Database(pool)
104 | }
105 | }
106 | ```
107 | Add the following code to call the `setUp()` method and create a database table (which will be called ToDos) to the `postInit()` method within the `Application.swift` file:
108 |
109 | ```swift
110 | Persistence.setUp()
111 | do {
112 | try ToDo.createTableSync()
113 | } catch let error {
114 | print("Table already exists. Error: \(String(describing: error))")
115 | }
116 | ```
117 | To check that a database table called `ToDos` has been created, run your Kitura server and then use `psql` from the command-line as follows:
118 |
119 | ```sql
120 | psql tododb
121 | SELECT * FROM "ToDos";
122 | ```
123 | This should print the column names of the `ToDos` table with no data in (i.e. no rows).
124 |
125 | Now we will start modifying the methods which handle requests so that they no longer use the in-memory `todoStore` array and instead store and retrieve data from the database table.
126 |
127 | ### 6. Adding @escaping
128 | All of the handlers now need to have `@escaping` added to their function signature, as the ORM uses an escaping method. Add `@escaping` to each method, after `completion:`. For example:
129 | ```swift
130 | func storeHandler(todo: ToDo, completion: @escaping (ToDo?, RequestError?) -> Void ) { ...
131 | func deleteAllHandler(completion: @escaping (RequestError?) -> Void ) { ...
132 | ```
133 | ### 7. storeHandler() updates
134 | We will first update storeHandler() to use the database. Remove all of the code between the method's curly braces and paste in the following:
135 | ```swift
136 | var todo = todo
137 | if todo.completed == nil {
138 | todo.completed = false
139 | }
140 | todo.id = nextId
141 | todo.url = "http://localhost:8080/\(nextId)"
142 | nextId += 1
143 | todo.save(completion)
144 | ```
145 | This code is similar to the original but uses `todo.save(completion)` which saves the `todo` object to the database and then calls the method's completion handler.
146 | ### 8. deleteOne(), deleteAll(), getOne(), getAll()
147 | These methods are easy to update as their logic can happen with one call using the ORM!
148 | ```swift
149 | func deleteAllHandler(completion: @escaping (RequestError?) -> Void ) {
150 | ToDo.deleteAll(completion)
151 | }
152 |
153 | func deleteOneHandler(id: Int, completion: @escaping (RequestError?) -> Void ) {
154 | ToDo.delete(id: id, completion)
155 | }
156 |
157 | func getAllHandler(completion: @escaping ([ToDo]?, RequestError?) -> Void ) {
158 | ToDo.findAll(completion)
159 | }
160 |
161 | func getOneHandler(id: Int, completion: @escaping(ToDo?, RequestError?) -> Void ) {
162 | ToDo.find(id: id, completion)
163 | }
164 | ```
165 | Each one now uses one call to the database to accomplish its task. The code is now simpler and easier to understand because each method uses only one call to the database to accomplish its task, and the ORM handles all the complexity of saving and retrieving items in the database.
166 |
167 | ### 9. updateHandler()
168 |
169 | This method is a little more complex as we need to fetch the `ToDo` object that is currently stored in our database, assess the differences between this version of the `ToDo` object and the patched version being passed into the method, make the changes and then commit the modified `ToDo` object to the database
170 |
171 | ```swift
172 | func updateHandler(id: Int, new: ToDo, completion: @escaping (ToDo?, RequestError?) -> Void ) {
173 |
174 | ToDo.find(id: id) { (preExistingToDo, error) in
175 | if error != nil {
176 | return completion(nil, .notFound)
177 | }
178 |
179 | guard var oldToDo = preExistingToDo else {
180 | return completion(nil, .notFound)
181 | }
182 |
183 | guard let id = oldToDo.id else {
184 | return completion(nil, .internalServerError)
185 | }
186 |
187 | oldToDo.user = new.user ?? oldToDo.user
188 | oldToDo.order = new.order ?? oldToDo.order
189 | oldToDo.title = new.title ?? oldToDo.title
190 | oldToDo.completed = new.completed ?? oldToDo.completed
191 |
192 | oldToDo.update(id: id, completion)
193 |
194 | }
195 | }
196 | ```
197 |
198 | Again, the logic is similar to before but with some added error handling for fetching from the database in case the `id` doesn't exist.
199 |
200 | ### 10. Remove the todoStore property
201 |
202 | The final step is to remove the `todoStore` property definition from the top of the `App`class within `Application.swift` as it is no longer needed.
203 |
204 | ### 11. Running the Tests
205 |
206 | That's it! We have now modified all we need to have the ORM running inside our project. Run the Xcode project with ⌘R and use Terminal to launch the Test suite:
207 |
208 | ```bash
209 | open ~/todo-backend-js-spec/index.html
210 | ```
211 |
212 | Set the test target root to `http://localhost:8080/` and you should see them all pass, just as they did before we added a database connection.
213 |
214 | You can use `psql` to check that the todo items are being correctly stored in the database:
215 |
216 | ```sql
217 | psql tododb
218 | SELECT * FROM "ToDos";
219 | ```
220 |
221 | Congratulations! We have removed the project's dependency on a non-persistent storage option and updated it to use a persistent and accessible database, using Swift 4's Codable feature along side our ORM to maintain our ToDo type.
222 |
223 | ## Next Steps
224 |
225 | Ready to learn about Docker and Kubernetes? [This guide](https://github.com/IBM/ToDoBackend/blob/master/DeployingToKube.md) will take you, step-by-step, through setting up a Kubernetes cluster using Docker for Desktop, building a Docker image of your Kitura app, and deploying releases using both remote and local Helm charts.
226 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
--------------------------------------------------------------------------------
/DeployingToKube.md:
--------------------------------------------------------------------------------
1 | # Building a "ToDo" Backend with Kitura
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ## Workshop Table of Contents:
14 |
15 | 1. [Build your Kitura app](https://github.com/IBM/ToDoBackend/blob/master/README.md)
16 | 2. [Connect it to an SQL database](https://github.com/IBM/ToDoBackend/blob/master/DatabaseWorkshop.md)
17 | 3. **[Build your app into a Docker image and deploy it on Kubernetes.](https://github.com/IBM/ToDoBackend/blob/master/DeployingToKube.md)**
18 | 4. [Enable monitoring through Prometheus/Grafana](https://github.com/IBM/ToDoBackend/blob/master/MonitoringKube.md)
19 |
20 | # Deploying to Kubernetes using Docker and Helm
21 |
22 | Everything we have built so far has run locally on macOS. Using the built in Kubernetes support inside Docker for Desktop, we can deploy to Linux containers instead, using the Helm package manager.
23 |
24 | This mimics deploying to a cloud environment such as IBM Cloud, where we would gain support for scaling our app depending on demand, failover to keep our app running smoothly if anything goes wrong, load balancing, rolling updates and more!
25 |
26 | ## Pre-Requisites
27 |
28 | This is a follow on from the previous tutorial, [Adding persistance to ToDoBackend with Swift-Kuery-ORM](https://github.com/IBM/ToDoBackend/blob/master/DatabaseWorkshop.md).
29 |
30 | In this section you will learn about:
31 |
32 | * Installing Docker for Desktop
33 | * Installing Helm
34 |
35 | Ensure you have installed [Docker for Desktop](https://www.docker.com/products/docker-desktop) on your Mac and enabled Kubernetes within the app. To do so, select the Docker icon in the Menu Bar and click `Preferences` > `Kubernetes` Tab > `Enable Kubernetes`. It will take a few moments to install and start up, but when the indicator light in the bottom right corner is green, you're ready to go!
36 |
37 | 
38 |
39 | 
40 |
41 | You are also going to need to install Helm using brew. Helm is a package manager for Kubernetes. By installing a Helm "chart" into your Kubernetes cluster you can quickly run all kinds of different applications.
42 |
43 | Enter the following into your Terminal:
44 |
45 | ```bash
46 | brew install kubernetes-helm
47 | ```
48 | **Note:** if you get an error at this stage referencing a URL containing `kube`, then you already have Kubernetes installed on your system and are now running with multiple contexts. For the purposes of this workshop, change to the `docker-for-desktop` Kubernetes context by accessing Docker's menubar dropdown (not preferences like before), going into Kubernetes and under context, `select docker-for-desktop`.
49 |
50 | 
51 |
52 | ## Deploying a PostgreSQL database instance
53 |
54 | In this section you will learn about:
55 |
56 | * Adding repos to Helm
57 |
58 | - Creating a PostgreSQL release
59 |
60 | ### Background
61 |
62 | Our application is going to consist of two "pods" (Kubernetes Pods, not CocoaPods). In Kubernetes, a pod is typically one running Docker container, although a pod can contain multiple containers in some more complex scenarios.
63 |
64 | We are going to run two pods: one for the database and one for the Kitura server. We will start by deploying PostgreSQL to your Kubernetes cluster using a Helm chart.
65 |
66 | **Note:** A Helm chart `install` to Kubernetes is called a **release**.
67 |
68 | ### Adding a repository to Helm
69 |
70 | Helm has a repository named `stable` which contains lots of commonly used charts (blueprints to things you might want to run in a cluster, like databases!)
71 |
72 | We need to tell Helm where to look for these charts.
73 |
74 | ```bash
75 | helm init #Initialises Helm on our newly created Kubernetes cluster
76 | helm repo add stable https://kubernetes-charts.storage.googleapis.com
77 | ```
78 |
79 | ### Creating the release
80 |
81 | We can now create a PostgreSQL release called `postgresql-database`.
82 |
83 | **Note**: Kubernetes and Helm are very specific on names, and calling your database anything else will result in later parts of the tutorial requiring tweaking.
84 |
85 | ```bash
86 | helm install --name postgresql-database --set postgresDatabase=tododb stable/postgresql --version 0.17.0
87 | ```
88 |
89 | *The `--set` flag tells Helm to set some values during the `install` process. For PostgreSQL, we can specify a database we want created. We call that database `tododb` to match the name already in our Swift code from the last tutorial.*
90 |
91 | Run `kubectl get pods` to see your PostgreSQL database running inside Kubernetes.
92 |
93 | We now have a PostgreSQL database called `tododb` inside your Docker container, running within your local Kubernetes cluster. Well done!
94 |
95 | ## Edit the Swift code
96 |
97 | ### Edit the Swift code for connection to Kubernetes
98 |
99 | Your Swift code is going to need some minor changes to reflect using a database inside the local cluster, instead of the macOS database. All the changes are happening in the declaration within your `Persistence` class in `Application.swift`, inside the `let pool` assignment.
100 |
101 | Change `host:` from `localhost` to `postgresql-database`. Keep the port as `5432`.
102 |
103 | Leave `.databaseName("tododb") ` as it is. Add `.userName("postgres")`, as the username of the database we need to access is `postgres`.
104 |
105 | Finally, we are going to fetch the password for the database from a **secret**. When we created the PostgreSQL release earlier using `helm install`, a secret was created and installed to our cluster, which contained an autogenerated password. We'll learn more about Kubernetes secrets later.
106 |
107 | Add the follow to the array of Options, which will fetch an environment variable called `DBPASSWORD` from our Docker image containing our Swift app (which we will deploy later).
108 |
109 | ```swift
110 | .password(ProcessInfo.processInfo.environment["DBPASSWORD"] ?? "nil")
111 | ```
112 |
113 | These changes will allow the Kitura server to access the database and write/read data from it.
114 |
115 | After these changes, your database connection should look like this:
116 |
117 | ```swift
118 | let pool = PostgreSQLConnection.createPool(host: "postgresql-database", port: 5432, options: [.databaseName("tododb"), .password(ProcessInfo.processInfo.environment["DBPASSWORD"] ?? "nil"), .userName("postgres")], poolOptions: ConnectionPoolOptions(initialCapacity: 10, maxCapacity: 50))
119 | ```
120 |
121 | ## Docker
122 |
123 | In this section you will learn how to:
124 |
125 | - Modify the provided `Dockerfile` and `Dockerfile-tools`
126 | - Create a "build" Docker image for building your application
127 | - Build your Kitura application inside the build image
128 | - Create a run Docker image for running your application
129 | - Tag the run image so it can be installed into Kubernetes
130 |
131 | ### Modify your Dockerfile and Dockerfile-tools
132 |
133 | We are now ready to compile your code and create a Docker image, ready to deploy to Kubernetes. You already have a basic `Dockerfile`, but we need to add the PostgreSQL system library for our Swift project to use. Open up `Dockerfile` and change the line
134 |
135 | ```
136 | # RUN apt-get update && apt-get dist-upgrade -y
137 | ```
138 | to the following. Make sure to remove the # comment in front of `RUN`
139 | ```dockerfile
140 | RUN sudo apt-get update && apt-get install -y libpq-dev
141 | ```
142 |
143 | Repeat the same for `Dockerfile-tools`. When you have completed this, we can build our images.
144 |
145 | ### Building your images
146 |
147 | Now we are going to build our Kitura application into a Docker image. This will involve two Docker images: a "build" image and a "run" image. Why are there two? Because it is best practice to make Docker images as small as possible, and while we will need the Swift compiler to build our application, we won't need the compiler to run it!
148 |
149 | First we need to build a Docker image to hold our Linux build toolchain. This includes the Linux Swift compiler, along with other components and has a larger storage footprint.
150 |
151 | ```
152 | docker build -t server-build -f Dockerfile-tools .
153 | ```
154 | We now run the build image, which compiles our Kitura application in Docker. This creates the Linux binaries, and saves them to our Mac.
155 | ```
156 | docker run -v $PWD:/swift-project -w /swift-project server-build /swift-utils/tools-utils.sh build release
157 | ```
158 | Now it's time to build the "run" image containing our compiled Kitura application, but not the Swift compiler etc. This image could then be uploaded to a container registry such as Docker Hub if we wanted to do so.
159 | ```
160 | docker build -t server-run .
161 | ```
162 | ### Tagging your image for use with Helm
163 |
164 | Finally, we need to tag the image so we can reference it from our Helm chart.
165 |
166 | ```bash
167 | docker tag server-run:latest server-run:1.0.0
168 | ```
169 |
170 | Now our `server-run` Docker image contains our executable which can be deployed to Kubernetes using the Helm chart!
171 |
172 | ## Shhhh... secrets 🤫 (and how to access them)
173 |
174 | Kubernetes stored the "secret" containing the database password when we made the PostgreSQL release. We now need to expose that secret to our Kitura application in the `DBPASSWD` environment variable.
175 |
176 | In Xcode, use the project navigator to access chart > ToDoServer > `values.yaml`. At the bottom, add the following line to map our stored Kubernetes secret to a key we can use later.
177 |
178 | ```yaml
179 | secretsConfig: postgresql-database
180 | ```
181 |
182 | Now navigate into the `/templates` folder and open up `deployment.yaml`. We need to add to the `env:` section, so we can access the password as an environment variable inside our Docker container. Add the following **below** the final value declared in `env:` but **above** the line `{{- if .Values.generatedBindings.enabled }}`. Make sure the indentation all matches up.
183 |
184 | ```
185 | - name: DBPASSWORD
186 | valueFrom:
187 | secretKeyRef:
188 | name: "{{ .Values.secretsConfig }}"
189 | key: postgres-password
190 | ```
191 |
192 | We are telling Helm to set an environment variable called `DBPASSWORD` to the data in the value part of a key:value pair keyed as `postgres-password`. The `.Values.secretsConfig` looks inside the `values.yaml`, finds the key `secretsConfig`, whose value is set to the secret stored in Kubernetes.
193 |
194 | If this sounds complicated, don't worry! All it means is that the database password is supplied to the Kitura application as an environment variable. This is a good because hard-coding database passwords in your source code is never a good idea!
195 |
196 | ## Deploy with Helm
197 |
198 | In this section you will learn about:
199 |
200 | - Deploying the Docker container into Kubernetes using a local Helm chart
201 | - Accessing the OpenAPI dashboard from a browser with port forwarding
202 |
203 | ### Using Helm charts to deploy to Kubernetes
204 |
205 | First we must edit the Helm chart to point to our local, tagged `server-run` image. Using Xcode's left sidebar, navigate to `chart` > `ToDoServer` > `values.yaml`.
206 |
207 | This acts like a blueprint for Helm to use when deploying our application to Kubernetes. We need to modify the `repository`, `tag` and `pullPolicy` lines (towards the top of the file).
208 |
209 | **Caution:** make sure your indentation is consistent with the rest of the file, and YAML does not support tabs so use spaces instead!
210 |
211 | ```yaml
212 | ...
213 | image:
214 | repository: server-run
215 | tag: 1.0.0
216 | pullPolicy: IfNotPresent
217 | ...
218 | ```
219 |
220 | We are telling Helm to use a local Docker image called `server-run` tagged at `1.0.0`, and to only pull from a remote if we can't find the image locally.
221 |
222 | We are now ready to deploy our Helm chart into Kubernetes.
223 |
224 | ```bash
225 | helm install --name server chart/ToDoServer
226 | kubectl get all #Ensure the pod todoserver-deployment STATUS is Running
227 | ```
228 |
229 | Now everything is up and running! To access the database, we will use the OpenAPI UI route.
230 |
231 | ### Accessing the Application from a browser
232 |
233 | We can't navigate to `localhost:8080` as usual because our cluster isn't part of the localhost network. Port forwarding is built into the `kubectl` tools, and allows us to access our application from a browser as usual.
234 |
235 | ```bash
236 | kubectl get pods #Copy the todoserver NAME
237 | kubectl port-forward todoserver-deployment-XXXXXX-XXXXX 8080:8080
238 | ```
239 |
240 | We can now open a browser, and go to [localhost:8080/openapi/ui](localhost:8080/openapi/ui) where the OpenAPI dashboard should display. Using the drop down menus, select `POST /` and click the Example Value on the right hand side to autofill the input field. Now select `Try it out!` and a `201` response should be recieved (you may need to scroll down to see it).
241 |
242 | Now try `GET /` and the response should `200` and the Response Body should contain the ToDo we just posted to the server. Kitura is running in Docker under Kubernetes, and accessing the PostgreSQL database running in a separate Docker container!
243 |
244 | You can also reload the TodoBackend test-suite web page and check that all the tests still pass.
245 |
246 | ## Next Steps
247 |
248 | Congratulations! We have learnt about Docker, Helm and Kubernetes and deployed our own releases to the local cluster.
249 |
250 | Set up [Monitoring in Kubernetes using Prometheus and Grafana](https://github.com/IBM/ToDoBackend/blob/master/MonitoringKube.md).
251 |
252 | ## Bonus Content
253 |
254 | * Update and reinstall your Helm chart so there are three replicas of Kitura running. This provides redundancy and adds horizontal scaling.
255 | * Add a `/crash` route to your Kitura application which simply calls `fatalError()`. Then rebuild, tag and deploy your Kitura application. When you access the `/crash` route the Kitura server will crash. Use `kubectl` to see how Kubernetes automatically restarts your failed application. This provides resiliency and failover!
256 | * Push your Kitura image to Docker Hub (or another container registry) and have Helm pull your Kitura app from Docker Hub instead of using your local image. Using a container registry to store your deployment Docker images is best practice.
257 | * Deploy your Kitura app to the IBM Kubernetes Service on IBM Cloud. Many public cloud providers offer managed Kubernetes services. IBM Cloud offers a free tier so you can try out Kubernetes in the cloud for free!
258 |
259 | ### Cleaning up
260 |
261 | Finally, we will:
262 |
263 | - Delete Helm releases
264 | - Delete local Docker images
265 | - Turn off your local Kubernetes cluster
266 |
267 | **CAUTION! DO NOT DO THIS IF YOU ARE PROCEEDING TO THE NEXT SECTION, OTHERWISE YOU WILL NEED TO REDEPLOY!**
268 |
269 | We have a few things that are taking up disk space, including about 2.4GB of Docker images, as well as a running cluster which is using system resources. To clean up your system, start by deleting both the database and the Kitura server from the Kubernetes cluster.
270 |
271 | ```bash
272 | helm delete --purge server
273 | helm delete --purge postgresql-database
274 | ```
275 |
276 | Deleting the Helm release of PostgreSQL won't actually delete the underlying database storage. To do this, you can run:
277 |
278 | ```bash
279 | kubectl get pv
280 | kubectl delete pv
281 | kubectl get pvc
282 | kubectl delete pvc postgresql-database
283 | ```
284 |
285 | We can now delete our local Docker images from the system to reclaim the lost hard drive space.
286 |
287 | ```bash
288 | docker image list #We will need the IMAGE ID values
289 | docker image rm -f IMAGE-ID #Repeat on the IMAGE ID of both your run and build images
290 | ```
291 |
292 | We have now stopped the releases running in the Kubernetes cluster, and deleted their respective Docker images from the system.
293 |
294 | The final step is to open up the Docker for Desktop application preferences from the Docker Menubar icon and uncheck the `Enable Kubernetes` box under the `Kubernete`s tab. This stops your local cluster from running.
295 |
296 | ### Appendix: Tips for Kubernetes and Helm
297 |
298 | For live logs from a running pod, use
299 |
300 | ```
301 | kubectl log --timestamps=true --follow=true
302 | ```
303 |
304 | This will create a streaming output of the logs created by the pod. This works well if you are having issues connecting to the database, for example. You could have logs from the database streaming as you access the todoserver on the port-forwarded port `8080`, and get realtime feedback on what the server is reporting.
305 |
306 | To delete an individual deployment created with Helm
307 |
308 | ```bash
309 | helm list #Note the exact name (same one you chose when you ran helm install)
310 | helm delete --purge RELEASE-NAME
311 | ```
312 |
313 | Without including `--purge`, the name of the instance is not freed and if you ran `helm install` again using the same name, you could recieve an error.
314 |
--------------------------------------------------------------------------------
/Workshop.md:
--------------------------------------------------------------------------------
1 | # Building a "ToDo" Backend with Kitura
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ## Workshop Table of Contents:
14 |
15 | 1. **[Build your Kitura app](https://github.com/IBM/ToDoBackend/blob/master/README.md)**
16 | 2. [Connect it to an SQL database](https://github.com/IBM/ToDoBackend/blob/master/DatabaseWorkshop.md)
17 | 3. [Build your app into a Docker image and deploy it on Kubernetes.](https://github.com/IBM/ToDoBackend/blob/master/DeployingToKube.md)
18 | 4. [Enable monitoring through Prometheus/Grafana](https://github.com/IBM/ToDoBackend/blob/master/MonitoringKube.md)
19 |
20 | # Run the tests
21 | In order to implement a ToDo Backend, a server is required that provides support for storing, retrieving, deleting and updating "to do" items. The ToDoBackend project doesn't provide a specification 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.
22 |
23 | ### 1. Run the ToDo-Backend Tests:
24 |
25 | 1. Open the tests in a web browser: `open ~/todo-backend-js-spec/index.html`
26 | 2. Set a "test target root" of `http://localhost:8080`
27 | 3. Click "run tests".
28 |
29 | All the tests should fail. The first error reported should be as follows:
30 |
31 | :x: `the api root responds to a GET (i.e. the server is up and accessible, CORS headers are set up)`
32 |
33 | ```
34 | AssertionError: expected promise to be fulfilled but it was rejected with [Error:
35 |
36 | GET http://localhost:8080/
37 | FAILED
38 |
39 | The browser failed entirely when make an AJAX request.
40 | ```
41 |
42 | This shows that the tests made a `GET` request to `http://localhost.com:8080`, but it failed with no response. This is expected as there is no server running yet - we're going to fix that in a moment!
43 |
44 | In the instructions below, reloading the page will allow you to re-run the ToDo-Backend tests.
45 |
46 | ## Building a Kitura server
47 |
48 | Implementing a compliant ToDo Backend is an incremental task, with the aim being to pass more of the testsuite at each step. The first step is to build and run your Kitura server so it can respond to requests.
49 |
50 | ### 1. Run your Kitura server
51 |
52 | 1. Open the ToDoServer project in Xcode.
53 | ```
54 | cd ~/ToDoBackend/ToDoServer
55 | open ToDoServer.xcodeproj
56 | ```
57 |
58 | 2. Run your Kitura server in Xcode:
59 | 1) Change the selected target from "ToDoServer-Package" to the "ToDoServer > MyMac".
60 | 2) Press the `Run` button or use the `⌘+R` key shortcut.
61 | 3) Select "Allow incoming network connections" if you are prompted.
62 |
63 | 3. Check that some of the standard Kitura URLs are running:
64 | * Kitura splash screen: [http://localhost:8080/](http://localhost:8080/)
65 | * Kitura monitoring dashboard: [http://localhost:8080/swiftmetrics-dash/](http://localhost:8080/swiftmetrics-dash/)
66 | * Kitura health API: [http://localhost:8080/health](http://localhost:8080/health)
67 |
68 | ### 2. Add support for OpenAPI
69 |
70 | **Important**: If you started your project by choosing the "OpenAPI" tile in the Kitura desktop app, you can skip to Section 3.
71 |
72 | [OpenAPI](https://www.openapis.org/) is the most popular way to document RESTful web services. The OpenAPI ecosystem provides a broad range of tools and services for developers across the API lifecycle.
73 |
74 | Kitura provides a package which makes it easy to add OpenAPI support to your application. Let's add OpenAPI.
75 |
76 | 1. Open the `ToDoServer` > `Package.swift` file
77 | 2. Add the following to the end of the dependencies section of the `Package.swift` file:
78 | ```swift
79 | .package(url: "https://github.com/IBM-Swift/Kitura-OpenAPI.git", from: "1.0.0")
80 | ```
81 | 3. Update the target dependencies for the "Application" target to the following (note the lack of hyphen in KituraOpenAPI):
82 | ```swift
83 | .target(name: "Application", dependencies: ["KituraOpenAPI", "Kitura", "CloudEnvironment", "Health", "SwiftMetrics" ]),
84 | ```
85 |
86 | In order for Xcode to pick up the new dependency, the Xcode project now needs to be regenerated.
87 |
88 | 1. Close Xcode.
89 | 2. Regenerate the Xcode project and reopen:
90 | ```
91 | cd ~/ToDoBackend/ToDoServer
92 | swift package generate-xcodeproj
93 | open ToDoServer.xcodeproj
94 | ```
95 |
96 | Now we need to check if OpenAPI is enabled in our Kitura server.
97 |
98 | 1. Open the `Sources` > `Application` > `Application.swift` file
99 | 2. Check if there is an import for the KituraOpenAPI library to the start of the file:
100 | ```swift
101 | import KituraOpenAPI
102 | ```
103 | 3. Make sure if there is the following code into the end of the `postInit()` function after the call to `initializeHealthRoutes()`:
104 | ```swift
105 | KituraOpenAPI.addEndpoints(to: router)
106 | ```
107 |
108 | 4. Re-run the server project in Xcode:
109 | 1) Edit the scheme again and select a Run Executable of "ToDoServer".
110 | 2) Run the project, then "Allow incoming network connections" if prompted.
111 |
112 | ### 3. Try out OpenAPI in Kitura
113 |
114 | Now, you can open [http://localhost:8080/openapi](http://localhost:8080/openapi) and view the live OpenAPI specification of your Kitura application in JSON format.
115 |
116 | You can also open [http://localhost:8080/openapi/ui](http://localhost:8080/openapi/ui) and view SwaggerUI, a popular API development tool. You will see one route defined: the GET `/health` route you visited earlier. Click on the route to expand it, then click "Try it out!" to query the API from inside SwaggerUI.
117 |
118 | You should see a Response Body in JSON format, like:
119 |
120 | ```
121 | {
122 | "status": "UP",
123 | "details": [],
124 | "timestamp": "2018-06-04T16:03:17+0000"
125 | }
126 | ```
127 |
128 | and a Response Code of 200.
129 |
130 | Congratulations, you have added OpenAPI support to your Kitura application and used SwaggerUI to query a REST API!
131 |
132 | ### 4. Add Cross Origin Resource Sharing (CORS) Support
133 |
134 | Re-run the ToDo-Backend tests by reloading the test page in your browser.
135 |
136 | The first test should still fail with the following:
137 |
138 | :x: `the api root responds to a GET (i.e. the server is up and accessible, CORS headers are set up)`
139 |
140 | ```
141 | AssertionError: expected promise to be fulfilled but it was rejected with [Error:
142 |
143 | GET http://localhost:8080
144 | FAILED
145 |
146 | The browser failed entirely when make an AJAX request.
147 | Either there is a network issue in reaching the url, or the
148 | server isn't doing the CORS things it needs to do.
149 | ```
150 |
151 | 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.
152 |
153 | 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)](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) must be enabled.
154 |
155 | Kitura provides a package which makes it easy to enable CORS in your application. Let's add CORS to your project.
156 |
157 | 1. Open the `ToDoServer` > `Package.swift` file
158 | 2. Add the following to the end of the dependencies section of the `Package.swift` file:
159 | ```swift
160 | .package(url: "https://github.com/IBM-Swift/Kitura-CORS.git", from: "2.1.0"),
161 | ```
162 | 3. Update the target dependencies for the "Application" target to the following (note the lack of hyphen in KituraCORS):
163 | ```swift
164 | .target(name: "Application", dependencies: ["KituraCORS", "KituraOpenAPI", "Kitura", "CloudEnvironment", "Health", "SwiftMetrics" ]),
165 | ```
166 |
167 | In order for Xcode to pick up the new dependency, the Xcode project now needs to be regenerated.
168 |
169 | 1. Close Xcode.
170 | 2. Regenerate the Xcode project and reopen:
171 | ```
172 | cd ~/ToDoBackend/ToDoServer
173 | swift package generate-xcodeproj
174 | open ToDoServer.xcodeproj
175 | ```
176 |
177 | Now we need to enable CORS in our Kitura server.
178 |
179 | 1. Open the `Sources` > `Application` > `Application.swift` file
180 | 2. Add an import for the CORS library to the start of the file:
181 | ```swift
182 | import KituraCORS
183 | ```
184 | 3. Add the following code at the start of the `postInit()` function:
185 | ```swift
186 | let options = Options(allowedOrigin: .all)
187 | let cors = CORS(options: options)
188 | router.all("/*", middleware: cors)
189 | ```
190 |
191 | 4. Re-run the server project in Xcode
192 | 1) Edit the scheme again and select a Run Executable of "ToDoServer". 2) Run the project, then "Allow incoming network connections" if you are prompted.
193 |
194 | 5. Re-run the tests by reloading the test page in your web browser.
195 |
196 | The first test should now be passing! But the second test is failing:
197 |
198 | :x: `the api root responds to a POST with the todo which was posted to it`
199 |
200 | In order to fix this, we need to implement a `POST` request that saves a ToDo item.
201 |
202 | ### 5. Add Support for handling a POST request on `/`
203 |
204 | REST APIs typically consist of an 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.
205 |
206 | 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.
207 |
208 | 1. Define a data type for the ToDo items:
209 | 1. Select the Application folder in the left hand explorer in Xcode
210 | 2. Select `File` > `New` > `File...` from the pull down menu
211 | 3. Select `Swift File` and click `Next`
212 | 4. Name the file `Models.swift`, change the `Targets` from `ToDoServerPackageDescription` to `Application`, then click `Create`
213 | 5. Add the following to the created file:
214 | ```swift
215 | public struct ToDo : Codable, Equatable {
216 | public var id: Int?
217 | public var user: String?
218 | public var title: String?
219 | public var order: Int?
220 | public var completed: Bool?
221 | public var url: String?
222 |
223 | public static func ==(lhs: ToDo, rhs: ToDo) -> Bool {
224 | 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)
225 | }
226 | }
227 | ```
228 | This creates a struct for the ToDo items that uses Swift 4's `Codable` capabilities.
229 |
230 | 2. Create an in-memory data store for the ToDo items
231 | 1. Open the `Sources` > `Application` > `Application.swift` file
232 | 2. Add `todoStore`, `nextId` and `workerQueue` properties into the App class. On the line below `let cloudEnv = CloudEnv()` add:
233 | ```swift
234 | private var todoStore: [ToDo] = []
235 | private var nextId: Int = 0
236 | private let workerQueue = DispatchQueue(label: "worker")
237 | ```
238 | 3. To be able to use `DispatchQueue` on Linux, add the following `import` statement to the start of the file:
239 | ```swift
240 | import Dispatch
241 | ```
242 | 4. Add a helper method at the end of the class, before the last closing brace
243 | ```swift
244 | func execute(_ block: (() -> Void)) {
245 | workerQueue.sync {
246 | block()
247 | }
248 | }
249 | ```
250 | This will be used to make sure that access to the todoStore is serialized, so the app does not crash on concurrent requests.
251 |
252 | 3. Register a handler for a `POST` request on `/` that stores the ToDo item data.
253 | 1. Add the following into the `postInit()` function:
254 | ```swift
255 | router.post("/", handler: storeHandler)
256 | ```
257 | 2. Implement the `storeHandler()` that receives a ToDo, and returns the stored ToDo.
258 | Add the following as a function in the App class:
259 | ```swift
260 | func storeHandler(todo: ToDo, completion: (ToDo?, RequestError?) -> Void ) {
261 | var todo = todo
262 | if todo.completed == nil {
263 | todo.completed = false
264 | }
265 | todo.id = nextId
266 | todo.url = "http://localhost:8080/\(nextId)"
267 | nextId += 1
268 | execute {
269 | todoStore.append(todo)
270 | }
271 | completion(todo, nil)
272 | }
273 | ```
274 | 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.
275 | The handler then returns the updated ToDo item to the client.
276 |
277 | 4. Run the project and rerun the tests by reloading the test page in the browser.
278 |
279 | The first three tests should now pass.
280 |
281 | Open SwaggerUI again at [http://localhost:8080/openapi/ui](http://localhost:8080/openapi/ui) and expand the new POST route on `/`. Paste the following JSON into the "input" text box:
282 |
283 | ```
284 | { "title": "mow the lawn" }
285 | ```
286 |
287 | Click "Try it out!" and view the response body below. You should see a JSON object representing the new ToDo item you created in the store:
288 |
289 | ```
290 | {
291 | "id": 0,
292 | "title": "mow the lawn",
293 | "completed": false,
294 | "url": "http://localhost:8080/0"
295 | }
296 | ```
297 |
298 | Congratulations, you have successfully added a ToDo item to the store using SwaggerUI!
299 |
300 | Going back to the testsuite webpage, the next failing test says this:
301 |
302 | :x: `after a DELETE the api root responds to a GET with a JSON representation of an empty array`
303 |
304 | In order to fix this, handlers for `DELETE` and `GET` requests are needed.
305 |
306 | ### 6. Add Support for handling a DELETE request on `/`
307 |
308 | 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.
309 |
310 | In order to pass the next test, the ToDoServer needs to handle a `DELETE` on `/` resulting in removing all stored ToDo items.
311 |
312 | Register a handler for a `DELETE` request on `/` that empties the ToDo item data.
313 |
314 | 1. Add the following into the `postInit()` function:
315 | ```swift
316 | router.delete("/", handler: deleteAllHandler)
317 | ```
318 |
319 | 2. Implement the `deleteAllHandler()` that empties the todoStore
320 | Add the following as a function in the App class:
321 | ```swift
322 | func deleteAllHandler(completion: (RequestError?) -> Void ) {
323 | execute {
324 | todoStore = []
325 | }
326 | completion(nil)
327 | }
328 | ```
329 |
330 | Build and run your application again, then reload SwaggerUI to see your new DELETE route. Expand the route and click "Try it out!" to delete the contents of the store. You should see a Response Code of 204, indicating that the server successfully fulfilled the request.
331 |
332 | ### 7. Add Support for handling a GET request on `/`
333 |
334 | 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 all the data in the store.
335 |
336 | 1. Register a handler for a `GET` request on `/` that loads the data
337 | Add the following into the `postInit()` function:
338 | ```swift
339 | router.get("/", handler: getAllHandler)
340 | ```
341 | 2. Implement the `getAllHandler()` that responds with all of the stored ToDo items as an array.
342 | Add the following as a function in the App class:
343 | ```swift
344 | func getAllHandler(completion: ([ToDo]?, RequestError?) -> Void ) {
345 | completion(todoStore, nil)
346 | }
347 | ```
348 | 3. Run the project and re-run the tests by reloading the test page in the browser.
349 |
350 | The first seven tests should now pass, with the eighth test failing:
351 | :x: `each new todo has a url, which returns a todo`
352 |
353 | ```
354 | GET http://localhost:8080/0
355 | FAILED
356 |
357 | 404: Not Found (Cannot GET /0.)
358 | ```
359 |
360 | Refresh SwaggerUI again and view your new GET route. Clicking "Try it out!" will return the empty array (because you just restarted the application and the store is empty), but experiment with using the POST route to add ToDo items then viewing them by running the GET route again. REST APIs are easy!
361 |
362 | ### 8. Add Support for handling a `GET` request on `/:id`
363 |
364 | The next 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.
365 |
366 | 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.
367 |
368 | 1. Register a handler for a `GET` request on `/`:
369 | ```swift
370 | router.get("/", handler: getOneHandler)
371 | ```
372 |
373 | 2. Implement the `getOneHandler()` that receives an `id` and responds with a ToDo item:
374 | ```swift
375 | func getOneHandler(id: Int, completion: (ToDo?, RequestError?) -> Void ) {
376 | guard let todo = todoStore.first(where: { $0.id == id }) else {
377 | return completion(nil, .notFound)
378 | }
379 | completion(todo, nil)
380 | }
381 | ```
382 |
383 | 3. Run the project and re-run the tests by reloading the test page in the browser.
384 |
385 | The first nine tests now pass. The tenth fails with the following:
386 | :x: `can change the todo's title by PATCHing to the todo's url`
387 |
388 | ```
389 | PATCH http://localhost:8080/0
390 | FAILED
391 |
392 | 404: Not Found (Cannot PATCH /0.)
393 | ```
394 |
395 | Refresh SwaggerUI and experiment with using the POST route to create ToDo items, then using the GET route on `/{id}` to retrieve the stored items by ID.
396 |
397 | ### 9. Add Support for handling a `PATCH` request on `/:id`
398 |
399 | 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.
400 |
401 | 1. Register a handler for a `PATCH` request on `/`:
402 | ```swift
403 | router.patch("/", handler: updateHandler)
404 | ```
405 | 2. Implement the `updateHandler()` that receives an `id` and responds with the updated ToDo item:
406 | ```swift
407 | func updateHandler(id: Int, new: ToDo, completion: (ToDo?, RequestError?) -> Void ) {
408 | guard let index = todoStore.index(where: { $0.id == id }) else {
409 | return completion(nil, .notFound)
410 | }
411 | var current = todoStore[index]
412 | current.user = new.user ?? current.user
413 | current.order = new.order ?? current.order
414 | current.title = new.title ?? current.title
415 | current.completed = new.completed ?? current.completed
416 | execute {
417 | todoStore[index] = current
418 | }
419 | completion(current, nil)
420 | }
421 | ```
422 | 3. Run the project and rerun the tests by reloading the test page in the browser.
423 |
424 | Twelve tests should now be passing, with the thirteenth failing as follows:
425 | :x: `can delete a todo making a DELETE request to the todo's url`
426 |
427 | ```
428 | DELETE http://localhost:8080/0
429 | FAILED
430 |
431 | 404: Not Found (Cannot DELETE /0.)
432 | ```
433 |
434 | Refresh SwaggerUI and experiment with using the POST route to create ToDo items, then using the PATCH route to update an existing item. For example, if you have a ToDo item at `http://localhost:8080/0` with a title of "mow the lawn", you can change its title by issuing a PATCH with id 0 and this JSON input:
435 |
436 | ```
437 | { "title": "wash the dog" }
438 | ```
439 |
440 | You should see a response code of 200 with a response body of:
441 |
442 | ```
443 | {
444 | "id": 0,
445 | "title": "wash the dog",
446 | "completed": false,
447 | "url": "http://localhost:8080/0"
448 | }
449 | ```
450 |
451 | ### 10. Add Support for handling a DELETE request on `/:id`
452 |
453 | The failing test is trying to `DELETE` a specific ToDo item. To fix this you need an additional route handler for `DELETE` that this time accepts an ID as a parameter.
454 |
455 | 1. Register a handler for a `DELETE` request on `/`:
456 | ```swift
457 | router.delete("/", handler: deleteOneHandler)
458 | ```
459 | 2. Implement the `deleteOneHandler()` that receives an `id` and removes the specified ToDo item:
460 | ```swift
461 | func deleteOneHandler(id: Int, completion: (RequestError?) -> Void ) {
462 | guard let index = todoStore.index(where: { $0.id == id }) else {
463 | return completion(.notFound)
464 | }
465 | execute {
466 | todoStore.remove(at: index)
467 | }
468 | completion(nil)
469 | }
470 | ```
471 | 3. Run the project and rerun the tests by reloading the test page in the browser.
472 |
473 | All sixteen tests should now be passing!
474 |
475 | ### Congratulations, you've built a Kitura backend for the [Todo-Backend](https://www.todobackend.com) project!
476 |
477 | ## Next Steps
478 |
479 | ### Attach a database to your project
480 | Our [ORM tutorial](https://github.com/IBM/ToDoBackend/blob/master/DatabaseWorkshop.md) builds upon the project created in this Workshop and replaces storing ToDos in an Array with a database running locally on your machine, using an ORM (Object Relational Mapper) and PostgreSQL.
481 |
482 | ## Bonus Content
483 |
484 | ### Try out the ToDo-Backend web client
485 |
486 | Now try visiting [https://todobackend.com/client/](https://todobackend.com/client/) in your browser to view the ToDo-Backend web client. Enter an API root of `http://localhost:8080/` and use the website to interact with your REST API. You can add, remove and update ToDo items as you wish.
487 |
--------------------------------------------------------------------------------