├── 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 | Kitura Bird 5 |

6 | 7 |

8 | 9 | Slack 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 | Kitura Bird 5 |

6 | 7 |

8 | 9 | Slack 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 | ![prometheus-dashboard](./resources/prometheus-dashboard.png) 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 | ![prometheus-graph](./resources/prometheus-graph.png) 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 | ![grafana-home](./resources/grafana-home.png) 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 | ![grafana-datasource](./resources/grafana-datasource.png) 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 | ![grafana-import-select](./resources/grafana-import-select.png) 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 | ![grafana-dashboard-import](./resources/grafana-dashboard-import.png) 108 | 109 | This will then open the dashboard, which will automatically start populating with data about your Kubernetes cluster. 110 | 111 | ![grafana-kube-dash](./resources/grafana-kube-dash.png) 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 | ![grafana-add-graph](./resources/grafana-add-graph.png) 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 | Kitura Bird 5 |

6 | 7 |

8 | 9 | Slack 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 | Kitura Bird 5 |

6 | 7 |

8 | 9 | Slack 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 | ![docker-menubar](./resources/docker-menubar.png) 38 | 39 | ![docker-app](./resources/docker-app.png) 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 | ![docker-context](./resources/docker-context.png) 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 | Kitura Bird 5 |

6 | 7 |

8 | 9 | Slack 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 | --------------------------------------------------------------------------------