This workshop will be retired on May 31, 2020.
Creating a Course10:09 with Pasan Premaratne
In this video let's define all our routes and implement one to create and save a new Course
Before we start implementing any routes, let's define our API and try and 0:00 follow some best practices. 0:04 Now, I say follow here because for the sake of ease, we're going to, potentially, 0:06 have some duplicated logic, but it's going to be minor and that's okay. 0:10 Let's start by specifying all the endpoints or routes we're going to define. 0:13 So we'll start with a create or a POST route and define it as /api/courses. 0:17 To create an actual course, we're going to send a post request with a JSON body. 0:24 The app will then decode the body and create an instance of course to save. 0:29 The next operation will be reading the courses. 0:33 And for this, we're going to define two routes at two URLs. 0:36 So the first is a GET request to /api/courses/. 0:39 And this route is going to return all the courses saved in the database. 0:43 If this is your first time working with server side apps and 0:47 you're confused to the fact that both the URLs I just showed you are the same, 0:51 but do different things, note that the app knows which route to invoke based 0:56 on the type of request we send. 1:00 A POST request is handled differently to a GET one. 1:02 But a second GET request is to fetch a single course and looks like this. 1:06 The last bit here is just a place holder for 1:11 an actual course ID that we'll specify when entering the URL. 1:13 If you have experience with server side apps in Swift or in another language, you 1:17 may be used to actually writing out the course ID parameters like you see here. 1:23 So that you can access it later as a key in a parameters dictionary. 1:28 Vapor, however, goes all in on the type safety and generics 1:32 of Swift to make definining this much more straightforward and less error prone. 1:35 I'm all about avoiding stringly typed code. 1:41 Okay, that leaves two more operations. 1:43 A PUT request to update a course which would look exactly like the route we 1:45 defined a second ago. 1:49 Except this time, we're going to have a JSON body with the updated information. 1:51 Now the last shot also looks the same, 1:55 except that it's going to delete the course whose ID we've specified. 1:57 We're going to implement this route in the routes function defined in routes.swift. 2:01 For now, we'll define all of them in here, and 2:06 later we'll refactor these a bit to use controllers. 2:08 So if you aren't here already, navigate to routes.swift. 2:11 Let's add comments in here to explain each route handler. 2:14 We're going to put all the code inside the route's function. 2:18 Now for each route, we're going to call a method that defines the request type. 2:22 So first, let's start with the post request to create a course. 2:26 Now I'll mark the section off as Course Routes, so 2:29 it shows up in the jump bar over here. 2:34 And the first, now side note, if you're not using 2:37 XCode you don't need to type this out, this is just an XCode feature. 2:40 Okay, so the first is post /api/courses, and 2:45 this route is going to create a new course. 2:50 I'm gonna leave a note here that C\course info is in the JSON body. 2:53 We'll start by calling the post method on the router object, so router.post. 2:59 And here we're going to pass in the URL path that this route will handle. 3:05 Now in most frameworks, 3:08 you would write out the entire path here as a string including the slashes. 3:10 So you would say api/courses, something like this. 3:16 In Vapor however, that's a detail that's abstracted out. 3:20 Instead, we would provide each path component as a comma separated 3:24 string literal. 3:27 So over here, you'll notice that I haven't put a forward slash here. 3:29 Do I need to? 3:31 Do I need to put one at the end over here? 3:33 All these things are abstracted out and we don't have to worry about it. 3:35 So each of these path components is going to be a separate string literal separated 3:37 by commas. 3:42 This ensures that we don't forget or 3:42 mistakenly omit parts of the path that are structural. 3:45 And the method also takes a closure provided as an argument to 3:48 the closure is the request object sent to the server. 3:53 Now, the return type here for this closure is defined as a generic value so 3:57 we can define whatever we want. 4:01 We're going to send a JSON response back containing the object that was saved. 4:03 So here, we're going to return a future that encapsulates a course. 4:08 Vapor takes this eventual course that the future resolves to, 4:13 puts it in a response object and returns it to the client. 4:16 Let's implement a body for this closure. 4:21 We're assuming here that the request is going to contain a JSON body 4:24 with data to create a course instance. 4:28 Because course conforms to codable, 4:31 we can ask Vapor to decode the JSON body directly to an instance of course. 4:34 But we have to add one line of code before we can do that. 4:39 So back in course.swift, add conformance. 4:42 So in an extension, we'll add conformance to the content protocol. 4:47 This is another helper protocol which allows us to decode the JSON 4:53 body without the need to explicitly define a decoder. 4:57 We don't need to check the body and do any of that. 5:01 So back inside routes.swift in the closure body, 5:04 we can use this and we'll say, 5:09 return try req.content.decode(Course.self). 5:12 So the request object contains a content container that is a helper object for 5:18 encoding and decoding content from an http message. 5:24 So on this, we're calling decode and providing the final type that we desire. 5:29 So make sure you say course.self here to indicate that we want a course instance 5:34 and Vapor handles the rest. 5:39 So this is some really nice syntactic sugar, if you ask me. 5:41 Decoding the JSON body into an instance is a throwing operation, so we've called try. 5:45 The closure itself, this one here, is throwing, so 5:50 we don't actually need to handle any errors. 5:54 At any point, this is done by the response object, so 5:55 if this fails you'll get the appropriate errors back at the client. 5:58 Decoding the JSON into an instance of course, 6:02 is not the only thing we want to do. 6:05 This is a post route, so we want to persist this to disk. 6:07 Persisting the instance to the database is not a synchronous operation, so 6:12 what we want to return here is a future course. 6:16 When the course is saved and we get it back, we can return it in the response. 6:19 Remember that to return a future, we call flatMap on an operation, so 6:24 here we're going to call flatMap on this decoded object, so we'll say flatMap. 6:28 We need to specify what the underlying value encapsulated 6:33 in this future that we're now creating with the flatMap call will be. 6:37 So here you can see the method signature, flatMap to a type. 6:40 So we want to return a Course here, so the argument is Course.self. 6:44 Now the instance of Course that results from this decoding 6:49 operation is passed into the closure that we defined on flatMap. 6:54 So think of it this way, this is a chain of operations. 6:59 This one is handled first, it returns a future. 7:00 The underlined course from that future is taken, extracted out, and passed 7:03 to this flatMap call, and we can use it inside this closure by defining it here. 7:08 So we'll say, course in. 7:14 So now on this course, and 7:15 remember this is the course object that we've decoded from the request body. 7:17 On this course, we're going to call save. 7:21 The save method takes, as an argument, an instance of database connectible. 7:24 Types that conform to this protocol should be capable of creating a connection to 7:29 a database and cleaning up connections once done. 7:33 A request object fulfills these requirements, so 7:36 we can pass that in and return the save object. 7:40 So we'll say return course.save(on: req). 7:42 And with that, we have our first route. 7:47 So let's test it. 7:50 Now, make sure you have the run scheme here and 7:52 we're gonna hit play to build and run. 7:53 And now if you're in the terminal, if you're not using xCode, at the terminal, 7:56 in the directory, at the root, run Vapor build followed by Vapor run. 8:00 And here, we have our URL so I'm gonna copy it. 8:05 Since this app builds an API, we can't simply point our browser at the URL and 8:10 check it out. 8:14 Instead we're going to use what's call an API development environment to experiment. 8:14 I'm going to use the app Postman, but there are other clients available. 8:19 Check the notes section for a download link to get started. 8:22 So we're gonna head over to Postman and in here, 8:26 we're going to create a POST request with this URL. 8:30 And remember that our route is at /API/courses like we defined. 8:34 And then next we need to provide JSON in the body of the request. 8:40 This JSON will be decoded to an instance of course on the server. 8:45 So you're going to navigate to body, select raw, and 8:48 then from the drop down here instead of text, select JSON. 8:52 This sets the content type on the requeest header to application/json 8:56 automatically so that our API knows what's incoming. 9:01 Now in the note section, you can find the snippet of JSON to post into the editor. 9:05 Once you grab that snippet, 9:10 paste it in here, hit Send and if everything worked correctly, remember that 9:12 you should have the API running at this point from Xcode or from the terminal. 9:16 If everything worked correctly, we should get a 200 or okay status in the response. 9:21 So I'm gonna hit Send and it doesn't look like anything happened. 9:27 But remember that our route returns the instance of the course created on saving. 9:30 So now down here, you'll see stuff about the response, so let's grab that up here. 9:35 Here you'll see it says status code 200 OK, which means that it worked. 9:39 And in the body of the response, you'll see some JSON here. 9:44 And you can set it to pretty print the JSON so it's formatted nicely. 9:47 Note that while this looks like the same JSON as we sent in the request body, 9:51 it's different because this actually includes an ID for 9:56 the object created in the database, so it worked. 9:59 Now you know how to create a post request in Vapor. 10:03 In the next video, let's add the remaining course routes. 10:06
You need to sign up for Treehouse in order to download course files.Sign up