This workshop will be retired on May 31, 2020.
Reading, Updating and Deleting Courses19:21 with Pasan Premaratne
In the last video we created a single route handler to create a new Course. Let's add the rest of our routes in starting with a get request to return all courses.
In the last video we created a single route handler to create a new course. 0:00 Let's run the rest of our routes in, 0:05 starting with a get request to return all courses. 0:08 Start with a comment, we'll say this a get route, and 0:11 this is going to be /api/courses, and this route is going to return all courses. 0:16 Now, like we did with the post request, the way that we create this route handler 0:23 is by calling the get handler on the router object. 0:28 And then specifying a path where each path component 0:31 is represented by a string literal, separated by commas. 0:34 Okay, so this an api, courses. 0:39 Now even though the path is the same as the previous one, 0:41 because this is a get request, as evidenced by the method we're calling. 0:44 The router will be able to figure out what handler to use, 0:48 depending on the type of request we send. 0:51 So on the client side, we can specify that this is a get or a post as well. 0:53 This method takes a closure with a request object as an argument. 0:57 The return type for this closure, since we want to send all courses back, 1:02 is a Future, which encapsulates an array of Course objects. 1:09 The body of this closure is quite straightforward. 1:14 Since Course conforms to Fluent's model protocol, we can call, so we'll return. 1:17 And we can call query on the course type directly. 1:23 And the method, as you can see here, takes an argument of databaseConnectable, and 1:26 again, our request object fulfills this role. 1:31 This method creates a QueryBuilder object, 1:34 which doesn't actually return any values, but 1:36 allows us to build a query against this type, so the course type in this case. 1:39 So here, we're saying we want to build a query on Course. 1:44 Now, we haven't specified yet what we want from the course table in the database. 1:47 And to indicate that, we're going to call all to return all instances, 1:52 and that's it. 1:56 So let's see if this works, hit save, run, to run this again, 1:58 and you're going to build. 2:02 So once the server's is up and running, you're gonna pull up Postman. 2:04 Now one thing you should know is that because we're using the default setup with 2:07 SQLite. 2:12 Every time we close a connection to the database, every time we stop the app, 2:12 whether from Xcode or from the terminal, the database is deleted. 2:16 Now this means that every time you stop the server, any courses you created and 2:20 saved in the database in any previous runs no longer exists. 2:25 To test the route we first created, we first need to execute more than one post 2:28 request to add instances to the database. 2:33 But thankfully, this isn't as annoying as it sounds. 2:36 Postman can run groups of requests at once, so 2:39 all we need to do is set it up to run through the requests every time. 2:42 So we're going to go to New here, and we're going to create a collection. 2:45 And we'll call the collection Treehouse Reviews, and hit Create. 2:50 Okay, once we're in here, let's add a request, so 2:58 we'll go ahead and copy this first one in. 3:02 So this is create course, this is that post request, we're gonna save it. 3:07 And in here, we can now modify it, this is a post from the last time, 3:13 Cmd+V to paste in the URL. 3:16 And then we'll go back to our history and just grab the JSON body to set this up. 3:19 So back in collections, we'll hit save here, 3:24 this is body, raw application JSON. 3:29 Nothing that I've done here's different, 3:33 I'm just replicating what we did in the last one. 3:35 We'll create another post route here, so we'll do, 3:37 Let me close all these out, we don't need these, and Don't Save. 3:44 All right, so new request, and it says select a collection or 3:50 a folder to save to, so we'll say Treehouse Reviews. 3:54 It's already in there, so we'll call this one create second course. 3:58 This is also a post request, and now we need a different JSON body for 4:04 this second request. 4:08 So if you go down to the code snippets along with this video, 4:10 you should be able to grab that snippet. 4:12 And then back in here, we're going to create a new post request. 4:14 So select body here, select raw, application JSON, and we'll paste that in. 4:17 Hit save, and now, once we run this collection, we should create two courses. 4:25 So to test our new route, we need to make sure that the get request works. 4:30 So again, we're going to create new request, okay, 4:33 we'll call this get all courses, and we'll add it to Treehouse Reviews. 4:37 This is a get request, and the URL here, whoops, we forgot a URL in this one. 4:43 So let's go to the first one and copy that, paste it in here. 4:47 And actually, the URL is the exact same for the get route, so 4:51 we'll paste that in here as well. 4:55 So that's all for this one, we're going to hit save, 4:56 make sure all our requests are saved. 4:59 Then you're gonna click this, what looks like a play button, this chevron icon, and 5:01 we're gonna hit Run to run the entire collection. 5:05 That doesn't actually run it, you're gonna scroll down here, and 5:09 you're gonna say Run Treehouse Reviews collection. 5:11 And then if everything worked, 5:16 you should have a series of 200 or OK status responses on every single one. 5:17 If you click on the get all courses title in the results window, 5:23 you should get a drop down. 5:27 And then when you select the response body, 5:28 you should be able to see the results from the database after we queried it. 5:31 So if you did everything correctly, 5:36 you should see a JSON object containing an array of objects matching the two 5:38 courses we created in the previous requests, including two IDs. 5:42 Cool, so this worked, okay, we have three more routes to go here. 5:46 So we're going to get rid of that, stop the app, and let's add the next ones. 5:50 So the next step is a get handler to get a single course. 5:54 Now, this is at api/courses/courseId. 5:58 So here, we're specifying the courseId, and we're going to return a single course. 6:02 Now, we'll create this like the last one by starting with the get method and 6:08 the correct path, so we can actually copy-paste this in. 6:12 The next path component typically in other frameworks would be something like this. 6:18 So it'd say, courseId, and this would 6:23 allow us to reach into that parameters dictionary and get the value we want. 6:26 But Vapor handles this far more elegantly, in my opinion. 6:31 We're requiring the client to supply a courseId as a path component, 6:35 and this is an integer. 6:40 So rather than specify a name for the parameter, 6:42 we can specify the type Int.self. 6:46 This way, if we send a request with something other than an integer 6:50 in the path, it will fail automatically. 6:53 Once we get the integer from the path, we can treat it as an ID, and query 6:56 the database for a course object whose ID property matches that integer value. 7:00 Now here's where it gets better, 7:05 Vapor actually abstracts this away from us as well. 7:07 So if we navigate to course.swift, and we'll add the following lines. 7:10 extension Course, and it conforms to the Parameter protocol. 7:15 What this protocol does is provide a static property parameter 7:21 that generates a path component for the type. 7:25 Since course is a model, 7:29 the type of the ID property is resolved as a path component. 7:30 Now since the ID on course is an integer, this is effectively the same as Int.self. 7:34 But now if we go back to the routes, we can say over here, 7:39 we can specify Course.parameter instead of Int.self. 7:42 Vapor is now aware that the integer value that we sent in a request 7:48 as the path component maps to the ID property on the course type. 7:52 Because of that, it can handle fetching the right course instance for 7:57 us automatically from the database. 8:00 Now we still need to supply a closure here, so this is the same request. 8:03 And this is going to return a Future Course, because this is a single course. 8:07 In the body of the closure, we're going to use the parameters property on the request 8:13 object, and call a next method. 8:17 So we'll say return try, now this is obviously a throwing operation, so 8:19 we're including the try call. 8:24 And here, we'll say req.parameters, and you can see, it says here, 8:26 this is a helper for accessing route parameters from this HTTP request. 8:30 And here we're gonna call next, and it asks for 8:35 a type of parameter, and we'll say Course.self. 8:38 And this is all we need to do, I get that people complain that this is magic, but 8:42 you can always write out this code explicitly. 8:47 So what's happening here is that Vapor is resolving this ID that we pass in, 8:50 it knows that it maps to a course ID. 8:55 So it resolves the ID from the Course.parameter that we've specified, and 8:57 then by doing this, it fetches the correct instance from the database and returns it, 9:01 all automatically. 9:05 So let's test this, gonna run the server again, and in Postman, 9:06 we're going to exit out of here, we're gonna duplicate this get request. 9:11 So click on the ellipsis here and hit duplicate, and 9:17 we'll call this one Get Single Course, if I can spell. 9:22 Okay, at the end of the URL we're gonna add a /2, 9:28 indicating that we want to fetch the course with an ID of 2. 9:34 All right, then make sure to save the request, save the new one, and 9:38 hit the sort of play button on the side, and run the entire collection again. 9:42 So remember, you need to go down the bottom here and hit run, and again, 9:46 you see we get all 200s back. 9:50 And if we click on Get Single Course and inspect the response body, 9:52 this correctly maps to an ID of 2. 9:56 And it is that Swift Functions JSON that we added in the second post request, so 9:58 we get the right one back. 10:03 Cool, this works too, and that is how you fetch a single course. 10:05 Let's go back here to Xcode, and we'll add the next route. 10:10 So this one is a put request to update an existing instance of course. 10:14 This one is a bit more involved than anything we've seen so far. 10:19 Because we have to both fetch an instance from the database, and 10:22 decode some JSON that we're going to send in the request body. 10:26 To keep things simple, we're going to duplicate some work here. 10:29 So we'll say put /api/courses/:courseId, so 10:32 that's what the URL is going to look like, and this route will update a course. 10:36 The request URL is the same as the previous get request. 10:43 But with this route, we're also going to send some JSON in the body. 10:47 With information about how we want to update a course. 10:50 Both the URL and the JSON body will contain an ID of a course object, but 10:53 I think that's an okay amount of duplication. 10:59 So the method on the router object is put this time. 11:01 Our path is api, courses, and again Course.parameter. 11:05 Okay, we're gonna take a request, supply a request to the closure, and 11:12 we will launch back a Future with a single Course, the updated Course. 11:17 Now the body of the closure is going to be a bit more complicated, 11:23 we'll walk through it. 11:27 So we wanna carry out three operations, 11:28 one to fetch the course whose ID matches the ID in the URL path. 11:31 Second, we want to decode the JSON into a course object. 11:35 And then third we want to update the course from the database 11:39 with the new JSON and save it. 11:42 Now ultimately, we're going to return a future. 11:44 So what we're going to do here, it's a bit unusual, 11:47 but we'll start with a flatMap call. 11:49 We're going to start with a call to flatMap, so I'll say return try. 11:52 And when you type out flatMap, you'll see multiple method signatures here. 11:56 And we're going to take the one that takes two futures, A and B, and 12:01 then a callback function. 12:05 Now, it's hard to see here because my resolution's a bit lower, so 12:07 the text is larger. 12:12 But you can see at the bottom here where the description is. 12:13 It says, calls the supplied callback when both futures have completed. 12:17 If you go to the next one, three futures, then four futures, and so on. 12:21 So we're gonna take the one that takes two futures. 12:24 Okay, so first we need to specify what our final future is going to encapsulate. 12:28 And here we're going to a flatMap(to: Course.self, 12:32 cuz that's what we want back in the end. 12:36 Next, this method signature takes two statements, and 12:38 each of these arguments returns a future. 12:42 When both of those futures resolve into a value, they're both supplied into 12:45 a callback, and we can use those values to do something eventually. 12:49 So we need to specify the first future, and here we're gonna say. 12:53 Well, we wanna query the database to get back an instance of course 12:57 that matches the ID in the URL. 13:01 This code is what we did in the previous route handler, so it's request. 13:03 We don't need a try call here, because we have a try at the front, 13:08 req.parameters.next(Course.self). 13:10 For the second argument, we're going to decode the JSON body into a course object. 13:17 Now, this is similar to what we did when we defined the body of the post request. 13:23 So here we'll say request.content.decode(Course.self). 13:26 Once these futures are resolved, the values are provided to the callback, so 13:34 let's give them argument names here. 13:38 And they're provided in the same order, so course first, and then updatedCourse. 13:41 So the first one is the one we get from the database, 13:45 the next one is the one we decode from JSON. 13:47 What we're going to do now is update the fetched course with the decoded course. 13:50 So we'll say course.name = updatedCourse.name, 13:55 and course.url = updateCourse.url. 14:02 And then we can save it, so we'll say return save, whoops, course.save(on: req). 14:07 And that is our put route, so let's test this out, 14:14 let's switch over to Postman, and in our group, we'll add a new request. 14:17 This title will be update existing course, and we'll add it to our collection. 14:22 So for the route, we're going to do localhost api/courses/2, 14:30 we're gonna update that second course. 14:35 Make sure this is a put request, and 14:39 then we also need to send the updated version of the course in the JSON body. 14:41 Now we're going to go to the create second course post route. 14:46 Copy this JSON, go to the update existing course, 14:50 go to the body, raw, application/json. 14:54 We're gonna paste this in here, and 14:58 then we'll modify the name to say something like Functions in Swift instead. 15:00 So if this is updated, it should say Functions in Swift, not Swift Functions. 15:06 Okay, so we'll save the request, and then back here, we'll build and run the server. 15:10 You need to stop and run it again, once you add the new stuff. 15:15 And then here, we'll run the collection again. 15:19 And if it worked, we should get all 200s back, there we go. 15:22 So again, you can click on this update existing course, and 15:27 inspect the response body, and see, it says Functions in Swift now. 15:32 So the JSON should be updated if your code worked, okay, awesome. 15:36 Let's wrap up this video, which is getting kind of long, 15:42 by adding in our delete handler to get rid of a single course. 15:44 We'll say delete, and this is going to be at api/courses/:courseId, 15:49 and this will delete a single course. 15:54 The URL again is the same as the put route, this time minus the JSON body, 16:00 so router.delete("api", "courses", Course.parameter). 16:05 Now the main difference here is that when we delete a course, we can't return it, 16:11 since it doesn't exist anymore. 16:15 So instead, we're going to return a status code. 16:18 This will also resolve in the future when the delete operation is done, so 16:20 we're going to wrap it in a Future, and this type is HTTPStatus. 16:25 First, we need to map the parameter in the URL to a course, we've done that. 16:31 You can inspect the body of the individual course get route, so let's start there. 16:36 We'll say a return try req.parameters.next(Course.self), 16:40 okay, so we've seen this before. 16:46 Now this returns a future course, and we want to delete it, and 16:49 map to an HTTPStatus. 16:53 If you remember, to return a future, we need flatMap, so 16:54 let's flatMap to HTTPStatus.self, we always to supply the type here. 16:59 And in this closure, we have a course that we're getting from that first operation, 17:05 and here we can go ahead and delete it. 17:10 So we'll say return course.delete(on: req). 17:12 So just like the save method, it takes a DatabaseConnectable, and 17:19 we can pass in the request object. 17:22 Now this method also resolves into a future, but because there is no return 17:25 value for a delete operation, you can see that this returns a future carrying void. 17:30 We'd like to end up with an HTTP status code, so 17:34 what we want to do here is discard this void result. 17:37 Remember, to discard a result and 17:40 return an entirely new future, we use the transform function. 17:42 So here we're going to transform it to HTTPStatus.noContent. 17:46 Okay, let's test this final round, so build and run your server, 17:52 and then go back to Postman. 17:55 We're going to add a new request here, this will be called delete single course 17:59 Now we're gonna paste in the URL from the put request, copy, 18:07 paste, make sure this is a delete route. 18:12 Hit save, and then run the entire collection like before. 18:16 So run, and we know everything else works, so let's look at the last one. 18:19 A-ha, there was an error, let's look into this, response body, unavailable. 18:27 Let me try that again, I'm gonna close this out, run. 18:34 Delete a single course. 18:40 I might have made a mistake with that, yeah, oops, I see it now. 18:41 The URL's incorrect, it is ttp, not http, there we go. 18:47 So now let's run this, thankfully, that was easy to catch, and 18:52 there we go, we see that at the bottom, let me minimize this. 18:56 Okay, so at the bottom here, you see that the delete single course route 19:01 worked out fine, it's green, and we get a 204, no content. 19:06 Cool, so that was a long video, but we've covered basically creating, 19:09 reading, updating, and deleting everything on a course object. 19:14 In the next video, let's start adding routes for review operations. 19:17
You need to sign up for Treehouse in order to download course files.Sign up