What do you want to learn?
Leverged
jhuang@tampa.cgsinc.com
Skip to main content
Pluralsight uses cookies.Learn more about your privacy
RESTful Web Services with Node.js and Express
by Jonathan Mills
Node.js is a simple and powerful tool for backend development. When combined with Express, you can create lightweight, fast, scalable APIs quickly, and simply.
Start CourseBookmarkAdd to Channel
Table of contents
Description
Transcript
Exercise files
Discussion
Learning Check
Recommended
What Is REST?
Introduction
Welcome to RESTful Web Services with Node.js and Express. I'm Jonathan Mills from Pluralsight. In this module we're going to talk about how to build a RESTful API with Node and Express. We'll start out by talking about what REST really is and then we'll setup a Node and Express environment so we can get started. We'll implement all of the REST verbs, get, put, post, patch, delete, and when you would use the different verbs. We'll wire everything up to MongoDB using Mongoose for our backend. We'll unit test with Mocha and Sinon.js, Sinon will be used for our mocking, and then we'll end with a conversation on hypermedia and why links in our APIs help our API consumers navigate our API.
What Is REST?
Before we can start building a RESTful API we need to have a good understanding of what REST really means. The term REST comes from a dissertation written by Roy Fielding back in 2000. In this dissertation he coins the term Representational State Transfer or ReST. In this chapter of the dissertation he begins to layout a series of constraints that should be in place whenever two systems talk to each other, so ultimately what REST is is just a series of rules in place for your server, so that everyone that uses your service understands what it does and how it works. Let's take a look at these constraints and see if we can't make sense of what he's talking about. The first constraint is the Client Server constraint. All that means is that you have a client and a server and the client sends a request to the server, the server sends a response back to the client. This should seem pretty straightforward to you, in fact, this is the way the web works, and chances are if you're watching this video you do some stuff in the web and this is how you understand most things to work. There's no tricks here it's just that, client sends a request, server sends a response, that's how that works. The next constraint is the constraint of the Stateless Server. As the load from the client increases on your server you start to add more servers to the mix and when that happens now you get into a situation where the server may contain some information about the client that doesn't transfer from one server to the other. In this constraint Roy Fielding suggests that you don't get into that situation. Ultimately the client sends a request to the server and it doesn't matter what server it goes to, so everything that the server's going to need to process this request should be included in that request, and then based on that request and based on all that information that's in the request it'll send a response to the server. If you find yourself on the server storing information about the request or about the client, then you're not writing a truly RESTful service. The next constraint is the constraint of caching, so ultimately as servers are sending responses back full of data, sometimes you're sending back data that doesn't change very often, so think about a list of clients or a list of book authors or something along those lines. That data's not going to change between now and 5 minutes from now the next time the client pulls that data, so this constraint says let the client know how long this data is good for, so that the client doesn't have to come back to the server for that data over and over again. We're not going to go too deep into this one in this course, but I just wanted to make sure you understood what that meant and how that works.
Uniform Interface
The last constraint we'll talk about is the constraint of the Uniform Interface. Basically what Roy Fielding's talking about here is when you're dealing with an interface for a RESTful service it will behave in a very specific way that is uniform from one service to the next, and in this he outlines four pieces to an interface that should always operate this way, the first one being the idea of resources. Whenever you're dealing with a RESTful service you'll be dealing with a resource or a series of resources and all that really means is you're dealing with nouns. Uniform Interfaces are built around things, not actions. Sometimes when you call a service it'll be authorize or login. Those are verbs. In ReST everything we're going to be dealing with is nouns, so for this project we're going to be working with books and authors. Both of these are the resources that each of our ReSt services will be dealing with. For example, dealing with a book, the URL will be HTTP://something/book. If we're dealing with authors it would be /author. That's the way a ReSt Service will be defined every time. The second piece of a uniform interface is contained within the HTTP verbs. The HTTP verbs that we use I our request will dictate the type of activity we're trying to do on the resource. For example, our HTTP Get will simply request data, so I'm just trying to get data, and it'll either return a list of objects or a specific object. The post is used to add data, so if I do post to a /book I'm going to add a new book. Delete will remove. Put is used to update, so if I would just want to update or replace a resource I use Put. Patch is coming more into favor now too for just updating a piece of our resource. Put updates the entire resource, patch will just update a piece of that resource. The last piece of the Uniform Interface is the acronym HATEOUS, which has to be the coolest acronym that we have in REST. HATEOS stands for Hypermedia as the Engine of Application State, which I know is not very descriptive at all. Basically all that means is that in each request will be a set of hyperlinks that you can use to navigate the API. It's just the API's way of letting you know what other actions you can take on this particular resource. Don't worry about that one too much right now, we will get into that later on in this course.
Setting up Our Environment
Now in order to get our environment started the first thing we have to do is download and install Node. You'll find that out on NodeJS.org. Click Install and it'll download a package or an installer for the system that you have. Go ahead and get that installed and then we'll get started. Once Node is installed we'll use Mode Package Manager to install all of the other things we're going to need for our project. Note the package manager is just a simple way to download and install packages from the internet. It's very similar to NuGet over on the .NET side. Now in order to get started with Node Package Manager the first thing we're going to do is an npm init and what npm init is going to do is gather some basic information about your project and setup a config file, so that it can save all of the packages that We're going to be using, so the first thing we'll do is give it a name, so we'll call our project BookAPI and everything else we'll just leave blank for right now. Now if you look over in our projects you'll now see we have a package.json file and that file basically just has all of that information we just entered. Now for the record I'm using a web storm for this demo. You can use any IDE you want to follow along, Sublime, Vim, Notepad, Visual Studio. Visual Studio has a Node plugin that you can download and install to get the Node syntax coloring too. The IDE doesn't really matter. I'm not going to be using anything specific to Webstorm in this demo even though there are some cool features. I'm going to intentionally not talk about those things. You can use whatever IDE you're comfortable with and get the most out of this course. Now that we have a package.json file the first package we have to install is Express. In order to install packages into our project we do an npm install, so in this case, npm install express. Now we'll do a --save and this'll tell npm to save a record of this over in the package.json. Now the reason we do this is so that when I check this into source control or I give this code to someone else I don't have to give them all of the packages. When they get my code they just do npm install and npm will look at this package.json file and see that it needs express. Then it will go and download Express on its own. Now this carrot right here basically just means when I do an npm install it'll go look for any version of Express that is compatible with 4.11.2, so if between now and the time you go install Express 4.11.3 has come out that's fine, it'll download and install that as long as it's deemed compatible. If version 5 comes out, if you have ^4.11.2 it'll only download 4.11.2, it won't go download version 5. Now that Express is installed let's go ahead and get started and set up just a basic web server using Express to do something very basic just so that you can see that your code works. We're going to create a new Javascript file called app.js. Now the first thing we have to do is create a reference to Express, so we'll do var express = require('express'). Now one quick thing to point out, notice how it doesn't know what require is. This is a webstorm specific thing, but if I right-click and go to Use Javascript Library, and then click Node.js Globals, now it starts to notice some of the node specific things. Alright, now that I have a reference to Express I can go ahead and get started. The first thing we're going to do is var app and create an instance of Express that we can use to start running our stuff, so var app = express, but then we're going to execute that. The second thing we need in order to setup our server is to setup a port, so we'll do var port, and then there's two different ways you can get the port. The first way I'm going to show you is using an environment process, so we'll do process.env.PORT and if that's not there we'll hand it a port, so we'll say port 3000. Now all this does is it looks for this in the environment and if that's not there than it gives it the port 300, so for right now, since we don't have an environment setup it'll just be 3000. Now the next thing we want to do is setup a handler for our route and there's a couple ways to do this. I'm going to show you the simplest way for right now and we'll use something a little more complicated here in a minute, but to start we'll do app.get and the first parameter is just going to do a slash, which basically means just the root of your site. The second parameter is going to be a callback function. Anytime anything hits that root slash this function will be executed and Express is going to pass two variables, req and res. Req is the request that was sent by the client, res is the response that we're going to send back. In this case we're just going to send back a simple little welcome message, something like welcome to my API. Now that we have a variable for port, we have app, we have a route, the last thing we need is to actually have Express start listening on the port, so we'll do an app.listen with the port number, and then a callback function just to let us know that the app started listening. In this case let's just do a console.log and say that we're running on PORT with our port number. Alright, now that that's done let's fire this up by typing node app.js and if we've done everything right we get a message saying Running on PORT 3000 and we should be able to go to localhost and you see we get an error, so real quick let's jump back over. Notice right here we typed req, so instead we want to do res for the response not the request, and then since I changed this code I have to stop the server and start it back up again. Now I hit refresh and you'll see welcome to my API.
Getting Gulp Set Up
Now as you can probably imagine having to stop and restart our server every time we make a change is going to be fairly tedious over the course of this course, so we're going to use Gulp to manage that process for us. Now we're not going to get too deep into Gulp in this course, a fantastic Gulp course was just released recently on Pluralsight by John Papa and I would highly recommend you go watch that course, but here we're going to do just enough to get our node server up and running, so that it'll watch for file changes and automatically restart. We'll stop this and we're going to install Gulp and we're going to have to do two installs here, so the first install is just going to be our npm install gulp --save. Now again, we do the --save, so that when you come over here to your package.json now you see Gulp as a dependency. Now I want to be able to use Gulp from the command line because I'm going to use Gulp to run my service instead of using Node, so I have to install Gulp with a -g tag, so that I have access to it from the command line. Now when Gulp runs it's going to look for a file called gulpfile.js, so let's create that file. Now the first thing you have to do for Gulp is create a reference to Gulp. Let's do var gulp = require('gulp'). Now gulp is just the task runner, so the plugin we need in order to do what we're looking to do is a plugin called nodemon, so let's do an npm install gulp-nodemon. Now let's create a reference to that too. Now for Gulp all we have to do is tell it we have a task and we'll call it the default task for right now, and then use that task to execute our nodemon plug, so we'll do gulp task and we'll call it default, and then well pass in a function that'll setup nodemon. Now nodemon's going to take a JSON object to configure itself, so the first thing it needs to know is what it's going to run, so I'll send it script and the script name is app.js. Now it also needs to know what to watch for. We want it to watch for js extensions, so we'll tell it ext: 'js'. Now remember over in our app.js we were saying sometimes the environment will contain a port. Nodemon actually will let us set that up, so we're going to setup an environment with a port equal to 8000 and I'm setting it to something different just so you can tell where it's coming from. Now every time I install a node module or something like that I don't want it to get confused, so we're going to ignore everything under node modules. Now that's it for nodemon, but we want to let ourselves know that it's restarted, so we'll do a .on('restart') just saying hey, when you restart let's execute this function and we're just going to write something out to the log that says we've restarted (Typing). Now that this Gulp file is setup we should be able to come over here and type gulp. Now notice we are now running on port 8000 instead of 3000, so we are successfully pulling our --- port out of the environment and using that instead of the hard-coded 3000, so let's just double check and make sure everything's working. We've got to change this to --- 8000 and there we are, welcome to my API. Now let's change something (Typing) and when that saves you'll see we restarted, so now as we make changes Gulp will see that we've made changes and just automatically restart our server, so we don't have to go through the process of killing the server and starting it up again.
Summary
In this module we started learning how to build a RESTful API with Node and Express. We're not done yet, in fact we've barely even gotten started, but what we have done is we've talked about what REST is, so we understand the constraints, so we understand why we do things the way that we're going to do them. We setup our Node and our Express environment, got things going a simple HTTP response just so we have the pieces in place so we can get started, and we used Gulp to run our server. In this next module we'll build off of what we have and we'll start to build out the HTTP Get verbs and hook everything up to MongoDB.
Getting Data
Introduction
This is Jon Mills and in this module we're going to talk about getting data from our API. Now we've started building out a RESTful API with Node and Express. We haven't done a whole lot yet, we've just setup the basic structure and we'll re-cover that here in just a second, but what we're going to do in this module is implement the HTTP Get verb that allows users to pull data from our API. We're going to allow them to pull a list of items or just one item. We'll wire everything up to MongoDB because it doesn't make a lot of sense to have an API that doesn't have some data behind it, and then we'll allow the user to search for items using some query string parameters.
Implementing HTTP Get
Now if you've been following along so far your code should look something like this. Basically all we've done is we've created a reference at an instance of Express, setup a port, did an app.get to our root route, so that if somebody goes to the root of our API they'll just get a message saying welcome to my API, and then we're listening on port 3000. The only other thing we did is setup Gulp to run our process and notice we have set in Gulp our port to 800 and we did that just so you could see that our application was pulling environmental variables out of nodemon running Gulp (Typing). Now in order to get started with our API we need to setup some routes that define what our API looks like and there's a couple ways to do routes in Express, the first one being how --- what we did here, this app.get, and that's a good way to do it for simple, straightforward routes like this route, but if we're going to create a bunch of routes there's a better way to do it and that's to use --- a router, so we're going to create a router called bookRouter and that's going to be equal to express.Router and that's going to give us an instance of a router and what we can do now is we can use that to define all of our routes and then come down here and do app.use and setup a base for where our API route is going to be, so let's do /api and then we'll pass in the router and that will take care of loading all of the roots up into app.use for us, so that's a cleaner way to do our API routing. Now that we have a router to use to setup all of our API roots let's go ahead and create a couple of routes for our API, so let's do bookRouter.route and we're going to setup the Books route. Now depending on who you are you might want to do Books, you might want to do Book. It really doesn't matter, it's kind of a personal preference thing, but I think Books plural tends to be more standard, so we're going to do Books, and then we're going to setup the get route and this way doing it here I can do .get, I can do .post, and I can set all of those up right there and have a nice clean look. We're just going to do get for right now, so let's just do that. This get's going to take a function that expresses going to call whenever this get route is called, so it starts with function req and res and req is going to be the request that's coming in from the client and res is going to be the response that we're going to send back. For right now let's just send some data back, so we can make sure that this route is working, so we're going to create a var responseJson that's just going to be a simple JSON object, so for right now let's just do hello with some JSON. This is my API, how about that? Let's do that and then we're going to send that back to our client, so we'll do a res.json and as opposed to the res.send that we did down here, this is just going to send a string of text. If you are sending back HTML you might do a res.render and in this case though, we're going to send back a JSON object, so we're going to do a res.json, and we're going to send back the responseJson object. Now before we go any further let's fix this to be passing back the right thing and then we have to start Gulp back up again, so let's do gulp, and that's going to kick off our API and notice how it's running on port 8000. Alright, now with our API up and running we're going to do /api/books and that will pull --- up our, This is my api. Now you might notice that my JSON is nicely formatted in my Chrome window, and that's because I have a plugin called JSONView that I downloaded and pulled from the Chrome store.
Wiring up to MongoDB and Mongoose
Now that we have our API working we want to hook it up to a real database on the backend, so that we can return some real data. Now I'm going to use MongoDB and so you can go into mongodb.org and download MongoDB and get that installed and that's what we're going to use as our backend database. Just click right here and get it started. Now if you have access to the course materials, then you'll find the instructions on how to import our book data into your MongoDB database. If you don't have access to the course materials don't worry about that too much, just follow along and in the next module we'll get to inserting data with post and then you can load your database that way and then come back and double check your get. Now in order to talk to MongoDB we're going to use an npm package called Mongoose, so we're going to kill Gulp, and then we're going to do an npm install mongoose --save, and what Mongoose is is essentially like entity framework would be in the .NET world or any ORM, depending how much your background is. It's going to take the data in Mongo and convert it into JSON objects that we can then use in our application. Now once that's done installing we need to pull that in and pull the reference to it in our code, so we're going to do mongoose = require ('mongoose'). Now that we have that we need to open a connection to the database, so we're going to create a variable called db, and that's going to be mongoose.connect and what we pass into that is our connection strings, so in this case it's going to be mongodb://localhost/bookAPI and bookAPI is just the name of the database that we're going to be connecting to. Now what that'll do is when our application starts up it's going to go ahead and open a connection to the database and hold that for us until we're ready. Now if bookAPI doesn't exist it'll actually go ahead and create it for you, which is extremely convenient for us. Now the way that Mongoose knows how to translate the data that it gets out of MongoDB it uses a thing called a model, so we're going to do var Book = require (Typing) models/bookModel, and we need to go over and create this model that lays out what the data in MongoDB is going to look like. Now that we have that let's go over and create a new folder called models and a new file in models called bookModel (Typing). Now what this is is it's basically a JSON object that lays out what a book looks like, so we start by getting a reference to mongoose (Typing) and a reference to Schema (Typing), which comes from mongoose (Typing). Now once we have a schema we're going to do var bookModel = the new Schema and we're going to layout in JSON what a book looks like, so for example in this case a book's going to have a title, and its type is going to be a string and we're going to copy that because we're going to use that one quite a bit. It's also going to have an author (Typing), which also is a type of string. Then we're going to have genre (Typing) and last, but not least we're going to have a parameter called read, and this is going to tell us whether or not we've read this book, so its type is going to be a Boolean (Typing) and we're going to give it a default value of false. Once we have that we're just going to do a module.exports = mongoose, so --- we're going to load this model into Mongoose, and we're going to call it Book, so we do a mongoose.model Book with --- bookModel, and what that does is it tells Mongoose that we have a new model or a new schema called book, and then we're going to return that in our module.exports, so that back over in our app.js we now have an instance of that book model. Now we already have a Mongoose course out on Pluralsight, so if you have more questions about Mongoose I would suggest you go watch that course when you're done here. This is pretty much all we're going to talk about in Mongoose just so we have a framework, so that we can go and start pulling data and writing data to the database. In order to use this we're going to back over to app.js and down here in our books route instead of doing this we're going to use our book, so we're going to do a Book. Remember Book is just an instance of our book schema that's hooked up to Mongoose and MongoDB and we're going to do a Book.find. Right now we're not going pass in anything, we'll start passing parameters into this find later, but for right now we're just going to do a book.find, and we're going to pass it in a callback and that callback is going to have error and books. If it errors out it'll have something in err, otherwise everything's going to be in books, and so we'll just check that. We'll say if err we'll do a console.log and we'll log the err. Otherwise, we're going to do a res.json(books) and we get rid of that one. Basically what's going to happen here is when you go to /api/books we're going to do a Book.find and just take the results and pass that back to our browser. Now in order to make sure this is working we need to go back over and do gulp, start our Gulp server back up, and then we'll check it in the browser. Our API's up and running because we have our welcome to my API, but let's go to /api/books, and what you'll see here is we are pulling back data from MongoDB and we have a list of books here, so what that means is we are now successfully connecting to MongoDB, pulling back that list of books through Mongoose, and sending that back to the browser all right here in this four or five lines of code. Now we don't really want to do console.log. Instead of that let's do a res.status if there's an error and send back a 500 error with a message that contains the error. That's a cleaner way to make sure that we're showing that there actually was an error in our API call.
Filtering with the Query String
What we have now is an API with a get method that will return back a list of books when you go to /api/books. What we want to do now is add a filtering option to our API, so if I want to read all of the science fiction books I want to be able to come up here and do ?genre=Science Fiction and have that filter our list based on the genre of the book, so what that means is when you go to api/book with a ?genre=Fiction Express is going to package up those query string parameters into a nice little JSON package in req.query, and so what you end up with is req.query = and a nice little JSON object with your query string parameters and we're going to use that to filter our list. Let's go back over here and add that req.query into our book query. If we do query = req.query remember that is a nice JSON package that includes what we're sending in. Book.find takes a JSON object as its params for its search, so if we just pass query in here and then our function that will just work as it is, so if we save that you'll see gulp will automatically restart, and then we'll pull our Chrome back up and now you'll see that we are filtering by genre, and I can also filter by author, let's say Jules Verne, and that'll filter for me as well. Now the problem with this is that whatever is typed randomly up here in this bar will filter through and go straight to the database and that's generally not the behavior we want. We'd like to sanitize user input a little bit before we start sending it over to the database, so a good way for us to do that is to come back over here and instead of sending req.query right there we'll just create an empty query and then we'll check to see if req.query.genre exists and has something in it and if it does, then query.genre is going to equal req.query.genre, and that way we are only allowing ourselves to filter on that one piece of information, so if I come back over here and I hit Enter now you see that it's not going to filter on this, but if I filter on genre it's filtering again. That's a cleaner way to make sure that we're not just taking random user input and sending that on through.
Getting a Single Item
Now the next step in this process is to figure out how to send just one ID. If you remember when we were talking about REST, ideally what we want to do is send slash and an ID number and have that return back an individual book or an individual item, so what we're going to do next is we're going to implement this /books/id and have that return just a single item from our list. The way we're going to do that is by implementing a new route in our router, so we'll do bookRouter.route and this time instead of just /books we're going to do /books/ID, let's do bookId. That makes that a little bit more readable. Okay, now we'll do the same, actually let's just copy and paste that whole thing. Get rid of the query because we're not going to do a query. Now instead of Book.find we're going to do Book.findById and we're going to pass in req.params.bookId. Now this is going to be whatever this is, so if I put just :Id it's going to be req.params.Id. In this case we did bookId to make it a little bit more readable, and so I'll get req.params.bookID. I'm going to get rid of the S's here and that's it. Now we're done implementing that piece. Once we save it our Gulp will restart automatically and let's try this again and see if that worked. There you go. Now I can get a single item by having the Id up here, so if I delete that Id I get my list, and if I want this one I'll just do / and that Id and there's my list.
Summary
In this module we're continuing our journey of building out a RESTful API with Node and Express. We've successfully implemented the HTTP Get verb to get both a list of items and a single item. We've wired everything up to MongoDB and we've implemented query string parameters to make sure we're only searching for single items. Now at this point we have a true API that's returning data from a database, but we're not even close to done yet. We still have to be able to put data into our database through the post method, so that's what we'll look at next.
Posting Data
Introduction
This is Jonathan Mills and in this module we're going to start talking about posting data into our API. Now so far we've worked on building out a RESTful API with Node and Express and we've implemented the Get verbs, which allows people to pull data from our API, but we're going to start talking about the Post verb in this module where we're going to let them add new items to our list through the HTTP Post verb. We're going to test everything with Postman, which is a Chrome extension that allows us to replicate all of the different HTTP verbs and not just the get, which is what we've been able to do so far in the browser, and by the time we're done with this our app.js file is going to be kind of unruly, so we're going to start talking about how to clean that up and segregate different pieces of our application into different files.
Using Body-parser
Now if you're following along with us, so far we've got an app.js file that has a bookRouter with a route/books and right now we're implementing the .get method on that route. In order to start taking posts we're going to implement the .post method, so we're going to come in here and we're going to do .post. Now the beauty of the router is that I can just chain these calls together, so I have bookRouter.route with the /books, and then I can do a .post and then I can chain to that the .get, and when we get down here to our bookId we'll do a .get, we'll do a .delete, we'll do a .put, and a .patch and all of those can just chain one on the other. Alright, so up here in the .post we're going to do a function(req, res), which stands for request and response, and in that we're going to create a new book, so up here we're pulling in Book, which is the model, the Mongoose model for our book. Now what we can do with that is we can just do var book (Typing) equals the new Book and what we need to pass into that book is the post data that's been sent to us. Now in order to get access to that post data we're going to have to use something called a body parser and basically what the body parser is is it's a piece of middleware that allows Express to read the body and then parse that into a JSON object that we can understand, so we're going to have to come back over to our terminal window here, and do an npm install (Typing) body-parser, and remember to do a --save because that's going to let our npm instance know that we're going to need this in the future, it --- keeps that record updated in our package.json file. Now once that's installed we're going to come back over here and we're going to add a reference to our body parser, so we're going to do bodyParser (Typing), so now that we have our reference to it we need to let our app know that we're going to use that bodyParser, and so we're going to have to do an app.use statement and typically where I do that is down here after everything's done. We're going to do an app.use (Typing) bodyParser, but we have to let it know exactly what kind of body parser we're doing, so we're going to do a bodyParser.json (Typing). We're also going to do an app.use bodyParser.urlencoded (Typing). Now now that that bodyParser has been loaded up into our app and our middleware is loaded it's going to look at the body and see if it has any JSON objects in it, and if it does it's going to take that JSON object and it's going to add it to req.body, so down here we can do req.body and use that to create our new book. Now before we go any farther let's just write that out, so let's do a console.log (Typing) book (Typing) just so that you can see what this looks like, and then once I've shown you how to use Postman and all of that, then we'll come back and we'll wire this all up and we'll save it. Now before we do anything else though we need to come over here and type gulp to get our server up and running again, and there we go.
Testing with Postman
Now in this course we're going to be using a Chrome extension called Postman in order to do our HTTP verbs back to our REST API. If you're running on Windows and you're familiar with Fiddler that's probably a good option too. If you already have something that you use that's fine, use whatever you want, but for this course I'm going to use Postman, and essentially what that's going to look like is this and up here we're just going to enter our request, so that's localhost:8000, and we get our welcome to our API. If we go /api/books we're going to get our list of books and then you'll see if we just select one we'll get back just one, and over here on the other side you'll see our history running. Now in order to do a post we're going to pick post over here and we're going to have to add a header, so this header is going to be Content Type and application/json. This is what's going to tell Express that it can parse this body. Now in order to do our post we're going to come down here and we're just going to copy this and stick it up here. We don't need the Id and let's do My New Book, which is just going to be a straight fiction book, and I wrote it (Typing). Despite the fact that I wrote it I have not read it and actually we're not going to send that one at all. If you'll remember, we added a default value to read on our MongoDB, so we're going to want to see that eventually come back as false on its own. Alright, now before I hit send remember I had selected a specific Id, so we need to get rid of that because I want to post to /api/books not with an Id after it. Now I'm going to hit Send and you'll see over here we've printed out our new title. Now I went ahead and created an Id and I went ahead and said read equals false, those are two things that we didn't have before, and we sent that back. Let's go back over and we'll wire all that up and save it back into MongoDB.
Saving Data
Alright, so coming back over to our code we have var book = new book and this is how we're creating a new Mongoose instance of that book and right now we're just passing req.body into the book and realistically, at this point, all we need to do is do a book.save, and that will create that new book in our Mongo database. Then we want to do a res.send and we're sending back our book and the reason why we're sending our book back is because we want that Id to be available to the client, whoever called our API. What we also want to send is a status, so we're going to do a res.status (Typing) and we're going to send status 201, which means created, so we're going to send back a status that says this was created, and we're going to send the actual book back as well, just to make sure everything's okay. Now that that's done we've got our book saved, we've got our status sent back, and our books sent back, let's pop back over to Postman and try this out. Alright, so back over here in Postman let's go ahead and do a send and see that we've got just a couple of books in here. We'll go back over into our post, we're going to post that My New Book, it's fiction, author Jon Mills, and we're going to click Send, and you'll see we're sending back the Id, see that we're sending back "read": false. Now we should be able to pull that Id, post that back in here, and pull that back out, and if I leave that off and just click Send you'll see at the bottom there's our new book. Our post is working, our gets are working, all of that seems to be going pretty good right now.
Code Cleanup
Now that we've got our get and our post working let's start paying a little bit of attention to our code layout. As you can see right now we've gotten a little big in our app.js and we're having to scroll a little bit, so let's pull our bookRouter out to a different spot, so right here where we're doing var bookRouter = express instead let's just do bookRouter = a require (Typing), and we're going to pull that in from a directory called Routes and a file called bookRoutes. In our project window here we are going to create a new directory called Routes and a new Javascript file called bookRoutes, and what we're going to do here is we're going to pull in a reference to express (Typing) and we're going to create a function called routes. What that routes is going to be (Typing) is that return value for this module.exports. I like to create it as a function because if I want to inject anything for testability or whatever I want to do I can just pass things in into this function and then do my module.exports (Typing). Now what we're going to put in here is essentially all of this, so all of our bookRouter code, so our creation of bookRouter, our /books route and our booksId route, and we're going to pull all that out and we're going to paste it in there (Typing). Now since we created this as a function when we created over here a bookRouter = require we're going to have to execute that in order to return back our bookRouter.
Injecting Our Model
Now just to be sure everything's still working we want to run a test real quick, so we're going to go out back to API books and we'll do a get and we'll send that, and see we get an error, so let's go back over and see what's going on. We get an error that says book is not defined, and so basically what that means is over here in bookRoutes I'm using book, but I don't have any idea of what that is, so here in that .js remember book is coming out of this book model, and instead of pulling this from everywhere I tell you what, let's just inject that in, so we're going to inject this book here, so when I create my bookRouter I'm going to go ahead and pass in that book model, that way I'm only dealing with it right there. Back over in bookRoutes.js up here on my function and I'm going to pass that through. Once I've saved everything should restart automatically, so let's try this again. If I do a get now now you see everything's working. If I pull the one I created, I click send, you see that, let's go ahead and create a new one (Typing), The Sequel, second book, and click Send, and you'll see that The Sequel website created and there it is. We'll do one more get, run that, and here down at the bottom you see I've created a new book. What we've done is moved all of our router code out into book routes just to make sure it works. One last thing we want to do is notice how we're doing a bookRouter and we're doing /books. Well if we're going to do an author router or a series router or something like that we're going to name those all what they are, so let's instead of doing /books here, let's have the app take care of that, so here for our book router we're going to be /api/books, and now what we can do is when we have an authorRouter, that would be /api/authors.
Summary
In this module we continued our path to build a RESTful API with node. We implemented the HTTP post verb that allowed a client to send a new item back to our list. We tested everything with Postman and we learned how to post and get through the Postman tool and we ended by cleaning up our project a little bit and moving all of our routes I would choose separate files. Now that we've got all that working let's start worrying about how to update some items.
Updating Data
Introduction
This is Jonathan Mills and in this module we're going to talk about updating data in our API. We've been following along building out a RESTful API using Node and Express and in this module we're going to implement the HTTP Put and Patch verbs that allow a user to update individual items. In Puts case we'll replace an existing item and in Patch's case we're going to update a specific part of an item. As part of that we'll also start to talk about Middleware. We've seen Middleware in use, but this time we're going to actually start to create our own route Middleware.
Implementing Put
What we have now is part of an API that allows us to get a list of items, post a new item back to our API, and then another set of routes that allows us to pull back individual items based upon a book Id. What we're going to do now is we're going to implement the Put verb, which basically means we're going to allow a user or a consumer of our API to replace one item with another item that they've edited. What we're going to do is come down here and we're going to do a .put and put, just like get and post, takes a function (Typing) with req and res. Now in order to update an item out of our MongoDB database I first have to get the item that I'm editing, so we need to do a Book.findById (Typing) and then the book Id. As a matter of fact, it's almost exactly this, so we're going to copy that and we're going to paste that right like that. The same thing, if I have an issue here I'm going to send back an error. If I do have a book though we're going to have to replace the contents of that book with what has come back, so we're going to do book.title = req.body.title (Typing), author with author, genre with genre (Typing), and read with read (Typing), and then we're going to do a book.save to save our changes.
Testing Put
Now back over in our Postman client we can do a quick get just to make sure that we're up and running and everything's compiled and everything's good. Now if I come down here to this book that we added, the My New Book, and I add that Id to the end and I do another get, now I see we just have our one book, and that's all fine, we've done that before, but now I want to do a .put, which basically means anything I change in here is going to change down here up to including if I do this the new result should be empty, and that is expected behavior. If the new put contains empty fields it should just return those empty fields, so we are updating and everything's working. If I want to put these items back maybe with a little change, let's give this a title (Typing). Now remember we're not doing anything with Id, so we can delete those, and hit Send. Now we see that we are actually updating as we expected, so let's go back and do a get, Send, and you'll see we are working. Now this _v is something from MongoDB that's just coming back and that's a version indicator that we're not going to worry about at all. Just ignore that and you can pretty much just pretend that doesn't exist until you get really deep into MongoDB stuff, so for right now just ignore that. They'll come back on the gets because it's something that Mongo does on its own. Now that we have put though let's go back and let's implement a patch because what if I just want to update the false to true? I don't want to have to send the entire contents of that book in order to update that one thing, I just want to be able to do a put or a patch, set that to true, delete everything else, and have that update my book to be true, so let's go back over, let's do a patch, and we'll see how that works.
Middleware
Now when we come back over to the code and we start to do our .patch (Typing) the first thing we're going to do in our function is do this book.findById and we've done it once there and we've done it once there and we're about to do it again, and so the pragmatic programmer in you should be screaming we're not supposed to repeat ourselves over and over again, and we repeat once, okay, if we repeat twice we need to stop and do something about that, and the way we're going to do that is by implementing something called Middleware that's going to inject itself in between the call and this route. Now to get a quick idea of how Middleware works let's look at this real quick. A client sends a request and that request is handled by the route in the router and then that router is going to send a response back to the client. Now when we're using Middleware what's going to happen is we create a Middleware, so that when the client sends a request this Middleware is going to drop into the middle of that request and then, after it does its piece, forward that request onto the route, and then the route will send a response back to the client, so what we're going to do right now is we're going to create this piece of Middleware that's going to intercept to the request, go and find the book by Id, add it to the request, and then forward that onto the route. Let's look at what that would look like. Now the way we are going to be doing it, we're going to do Middleware by the route, so here I'm just going to do bookRouter.use and that's the signal to say hey, I'm about to do some Middleware, and I'm going to use only for the route that has a book Id, and it takes a function that takes the normal request response and a next, and what next is going to do is tell it to pass on to the next thing that's to be done. In this case, since we only have one piece of Middleware, it's going to move onto this .get or this .put. If we had more Middleware it would then move onto the next piece of Middleware. To implement this Middleware all we're going to do is we're going to take our .get function, this book.findById, and we're going to copy that. Actually we'll just cut it altogether and then we'll --- paste it in there, so in our Middleware it's going to do a findById and it's going to find the book Id. If there's an error it's going to return the error. If the book exists we're going to add it to the request, so req.book = book, and what that's going to do is it's going to then make it available to everything down stream from here, and then we'll call next. Now if the book isn't found we're going to return the 404 (Typing). Now once that's done we have our request, it now has a book, so down here in our .get all we really have to do is do a res.json req.book because if there is no book, then it won't ever get here. We'll return a 404 up above it, and if there's an error we'll return that too. If we get to .get, then everything's been good. We've found a book from the database and we're ready to return that book back up, so in this case in our .get we'll just do a res.json and send the book back. Now the .put works the same way, so I don't need my findById, I don't need my if(err), I don't need my else (Typing). All I need to add is a req. in front of book (Typing) and now basically what it's doing is everything that's in req.book is being modified with whatever's in req.body, and then we're going to save that and then we're going to return it. That's all it takes to make that happen. Now we have a .get and we have a .put for our router. Now what we need to do is what we originally intended to start doing is add our .patch.
Implementing Patch
Now given the patch put together is just as simple as doing .patch (Typing) with the function req and res (Typing). Now remember our book is coming out of req.book and we only want to update our req.book with the items from req.body that are there, so if req.body.title (Typing) exists, then we want to update req.book (Typing). Now you could probably imagine this will become very painful very quickly, so we're not going to do it this way at all, this is silly. We instead are going to use the for in loop (Typing). What the for in loop means is that for every key in req.body it's going to give me that key name, which is extremely useful for navigating through your things like this, and so we actually can just do for var p in req.body, req.book, sub p = req.body sub p. That way we are assigning everything that's in req.body over to req.book. Now the only caveat to that is that we don't want to update the Id. That's a requirement based on what we're doing, so up here at the top we're going to check and see if req.body._id (Typing). We're going to delete req.body._id. Alright, now that that's done and we've looped through we need to do a req.save or req.book.save (Typing) and if you look up here this is not right. That's not asynchronous coding and we're going to fix that right now. If you come back down here we're going to pass in a callback and do the same if(err), so if we have an error we're going to send back an err else we are going to send back the book. Then we'll do a res.json(req.book) just like that and we'll do this same thing up here (Typing). Alright, now that we've got our save going in both places, we've got our patch, we've got our put, we've got our get, it looks like we've got most of the verbs covered. Let's go ahead and pop back over to Postman and try it all out.
Testing Patch
Now that we have all that going let's run a couple tests and make sure everything's working. I'm going to click Send on this get and you'll see I have a book that's returning authored by Jon Mills, titled My First Course. It's also read false. Now if I do a patch on this all I should have to send is read equals true and it should only update just this one item, so if I click send you'll see I did a patch over here, and I've returned back the whole book with just read equals true, and just to make sure it actually worked we can go back to the get, click Send, and there you go, it all worked. Now the other thing we want to try is making sure that I can't send the Id, so if we do a patch on this again, and I update the Id (Typing) to something different, let's just say ssss, and I send that, notice it did not update the Id, so if I go back to the get, Send, Id is not updated, so that worked out nicely for us. That one for loop was all we needed to get our patch done.
Implementing Remove
Alright, there's only one left to implement and that would be delete, so we're going to do a .delete, and this one's going to be really super simple. Get your function (Typing), req and res, and in this we're just going to do a req.book.remove, and what that's going to do is take whatever book was found up in our Middleware and remove it. Now just to make sure everything's good we're going to do our callback, so we're going to return the function with err (Typing). If in error we're going to send our status, otherwise, if it works we don't have a book to send back, that doesn't exist anymore, so we're going to do a res.status (Typing) and we're going to send back a 204, which means removed, and we're going to send the message Removed (Typing), and that's all there is to implementing the remove. Real quick let's go check that out and make sure that worked. Alright, so I have this one, My First Course, it's read, now I just want to remove it, so let me click Delete, and we are going to send that and I got a 204 status, No Content, that means it doesn't exist. What I want to do now just to make sure, is we'll do our get, we got a 404 no book found, so that was it, and that was all it took to implement that remove verb.
Summary
Alright, so in this module we finished up the verbs that we're going to use as part of our RESTful API that we're building with Node. We've implemented the put and patch verbs to replace an existing item and update part of an existing item, so that we don't have to send back and forth the entire pay load of an item, we can just send bits and pieces of it, and lastly we looked at how to remove an item using the HTTP delete verb and we figured out how to do some Middleware to help prevent us from having to replicate that findById code over and over and over again. Now at the end of this module we've successfully implemented all of the RESTful verbs, so we've done get, post, put, patch, delete, all those are done, and they're in place and implemented in our code. From here on out we're going to look at things like HATEOAS, our hyper media that we're going to implement into our API, and we're going to look at some automated testing.
Testing
Introduction
I'm Jon Mills and in this module we're going to talk about testing and I know everybody tests differently and everybody prefers different things, so we're going to take a couple of different approaches to testing, so that you can understand a couple different ways to do it, and then take a pick as to the way that works best for you and your application. As a start we've built out a RESTful API with all of the HTTP verbs necessary, we haven't yet talked about hyper media, but we will get to that in our next module. For right now we're going to talk about unit testing to start with with Mocha and as part of unit testing the first thing we need to do is separate out our code, so that we have something that we can wrap in a test, so we're going to build out controllers for our routes, and then we're going to mock out our response objects with Sinon, so that we can then unit test our controllers. After that we're going to talk about integration tests and as part of that we'll use something called Supertest that's going to allow us to execute our HTTP calls and carry that all the way through to our database, so we can do our posts and our gets and test everything end to end in a testing framework.
Controllers
Now if you've been following along your code should have a file called bookRoutes and this is where we've put all of our routing code for our API. Now what we want to do as part of our separation into controllers is if you look at this book route right here for slash, when you post we execute this function, which is an anonymous function. What we want to do as part of building out our controllers for unit testing is we want to take these anonymous functions and stick them in controllers, so that instead of that instead I just say (Typing) bookController.post and what that's going to do is execute whatever function that is, and I'll just take that function code that we just had and drop it in a controller called bookController, so the only other piece of code we need up here is we need var bookController (Typing) = require and we're going to pull this in from a folder called controllers/bookController, bookController.js. Alright, to get this going we're going to do a var bookController equals a function (Typing) and then we're going to do module.exports = bookController, now execute. Okay, now in this bookController we need to create a method called post because that's what we're going to call over on the other side and we're going to make that equal to that function that we copy and pasted out of our bookRoutes. Now notice we need this book and remember book is the Mongoose model that does all of the database work for us. Now I don't want to create a new instance of that inside my controller because I want to be able to mock that later because remember, this module is all about unit testing, so what I want to do is I want to pass that in to my bookController when it's first created, so now I added book to my function call on var bookController. Now over here in bookRoutes I want to make sure that in my bookController = require Controllers --- bookController, here I'm going to pass in Book when I do my require. Let's go ahead and do the get and so we'll pull this function out and do bookController.get. Come back over to bookController, do var get = and that function call. Here we're using something called the revealing module pattern and what that means is I have a controller and I have a series of functions and now I have to return back the functions that I'm going to expose to the outside world and, in this case, we're going to return two things. We're going to return our post function and we're going to return our get function. Now my bookRoutes will have access to two functions, the post function and the get function. Now the first big test to make sure all of this works is to come over here and type gulp to make sure everything compiles and it starts up and it's running. Here we go, we're up and running. Let's take a quick look and we can test these two controllers and make sure they're working and then we'll build some unit tests, so that you can see how this all works.
Postman and Bugs
Okay, so if we've done everything correctly we should be able to come into Postman and do localhost8000/API/books, click Send, and pull back our list of books, so that part works, let's try doing our post, and I'm going to post just a genre and an author. Let's post, make sure that works, and that worked. Now you'll notice I just posted and created a new book without a title and we probably should check for that and make sure that we don't allow books being created without a title, so in proper TDD unit testing kind of framework let's create a test that replicates this issue, and then we'll write the code to fix it, so let's break back over, and let's start looking at how to unit test our code using Mocha.
Unit Tests with Mocha
Let's start by creating a new directory called Tests and in that we're going to create a new Javascript file called bookControllerTests.js and this is where we're going to put all of our test code for our bookController. Now before we get started we need to do an NPM install of gulp-mocha, gulp is what we're going to use to run our Mocha tests, so we need the gulp plugin, gulp-mocha, we're going to do should, which is an assertion framework that we're going to use, and we're going to need Sinon, which is our mocking frame, and don't forget --save, so that it will remember that we need these. Once that's done we're going to come back over here and we're going to do a var should. We're going to pull in an instance of should or a reference to should and we're going to pull in a reference through Sinon. Now we don't need a reference to Mocha because it's actually going to run inside the Mocha framework. In our case it's going to run inside gulpMocha, but it's essentially the same thing. Now Mocha lays out very similar to a standard BDD kind of style, so you start with the describe keyword and describe what it is we're doing, so we're going to start with describe Book Controller Tests, and then it takes a function because it's Javascript (Typing) and then we can actually chain these. We're going to do another describe and here we're going to describe our Post tests, and what that means is at this point we are testing the Post method of Book Controller, and now we can layout our tests. In this case we're going to do it and then plain text, should not allow an empty title on post. Now let's take a quick look at the Book Controller and see what this Post method does. Now it's going to take Book and it's going to create an instance of Book with req.body and it's going to do save on that book and it's going to do a res.status, so in order to mock out the things that we need we need to send in a mock book, we need to send in a mock request, and a mock response, and we're going to check for a status coming back that says, hey you don't have all the stuff in here that you need, so let's go back over here, and we'll start writing this out. Let's create our function (Typing). Now one of the nice things here is that Javascript is not a type safe language, which means I don't need to have an objects of type book in order to do any of this, I can just create a book (Typing) that's just a function that takes a book and has something called save (Typing) that doesn't really do anything because if you remember over here on bookController it just calls save. It doesn't need save to do anything, so we don't need save to work in this case for this test, so I'm going to do var Book = function(book), this.save = function just like that and now I have a mock book object that I can use in my bookController test. Now unfortunately for the request and the response it's not going to be quite that easy because I actually need those to do something, so in this case we're going to do var the request actually has to contain a body that actually has some data in it and, in this case, we're just going to send it an author. Remember this test is to see whether or not it's throwing an error when there's no title, so we're going to send in an author, no title, and see what happens, so let's send just me as the author, and that's the request. The response becomes a little more difficult because if we look back over in our controller we'll see that I actually have to call some functions and I want to know what is sent as part of the status and I want to know what's sent as part of the book and so here I'm going to have to actually mock something out using our Sinon mocking framework. Now, in this case, our response needs to have two things in it, it needs to have a status and it needs to have a send. Those are the two functions that we need. Now in order to mock this out we're going to use something called a spy, so we're going to do sinon.spy and send is also going to be sinon.spy (Typing) and what this is doing is it's creating a spy using the Sinon framework that's going to keep track of what is called, what it's called with, how many times it's called, all of those types of things, so I can actually now check to see if status is called and what it's called with, and so the way we do that down here is we're going to do a res.status.calledWith. We're going to want to call this with a 400, which just means it's a bad request, and the fact that it's called with a 400 this is going to return a true or false whether or not it was called with a 400, so we want it to be, using the should assertion framework we can just say should.equal(true). Now what this also lets us do is include a helpful message, so we could just say Bad Status and then add to that what it was actually called with, so we can actually do a res.status.args, and what args is is an array of each time this function is called, so we only care about the first time, and what the arguments were for each time it was called, so in this case we only care about the first argument. This will give us an error message that says, this was called with a bad status, and here's what the status was that this was called with, and so that'll be really helpful when we're error handling our unit tests. Now we also want to check on send, so we're going to do a res.send.calledWith, and we want this to make sure we're sending back a helpful error, so let's say Title is required should.equal(true). Now we've gotten to the point where we've got our assertions going, we've got our mocks done, but we haven't called anything yet, so let's hook this all up to our bookController, and then we'll run our tests. The way we're going to do that is by creating an instance of the bookController, so we'll do var bookController (Typing) and just pull it in using require, just like we do when we use it in our actual application, so we do ../controllers/bookController (Typing), and we need to pass in that mock book that we created up above. Now in order to call this we just need to do bookController.post and make sure we send it in req and res. Now that's all we need. This is a full test. We're sending a req.body without a title, we're mocking up our book, our Mongoose model book, so that we can execute on that. We've mocked up our status and our send in our response object and we executed the Post method on our bookController. Now in order to run this we need to run it through Mocha and we're going to do that in Gulp using gulpMocha, so let's jump over to our Gulp file and we'll get that setup.
Gulp-mocha
Now in order to get Mocha running in Gulp we first have to pull in gulpMocha, so we're going to do gulpMocha (Typing) = require ('gulp-mocha'), and we installed that back when we did our npm install for should Sinon and gulpMocha. Now once we have a reference to gulpMocha we're going to create a Gulp task called test (Typing). Now we just create our function and, in this case, we're going to do the full Gulp pipeline, so we're going to do gulp.src, and in this case it's going to be all of our test files, so it's going to be tests/*.js. We're going to pull in everything in our tests directory and we're going to run that. Right now we just have one, but that doesn't always mean we'll always have one (Typing). We're going to pipe that into gulpMocha and gulpMocha takes a reporter and, in this case, we're going to use the nyan reporter just because it looks a little bit better. One last thing we're going to have change before we can run our test is to come back over to here to our bookController and see we're doing res.status and we're chaining on our .send. The way we're doing our mocking that's not going to work, so we're going to need to do a res just separate out these two calls, res.status, res.send, and we'll save that. Now in order to run these tests we're going to come over here and we're going to do a gulp test and we're doing test because that's the name of the task that we gave it and we're going to hit Enter and we should get a failed test and you'll get 1 test failed and if you come up here and look it will say AssertionError: Bad Status 201, so this is exactly what we were looking for coming out of our bookController because nothing was called with a 400 error, so let's flip back over to bookController and we'll write some code for this, and then we'll retest it and make sure we've got our test passing. Over in our bookController in our post right here let's do an if(!req.body.title), so if title does not exist in req.body we're going to do a res.status of 400 and a res.send of (Typing) Title is required. Otherwise, we'll do the rest. Now with all of that done, now that we're checking for all of this, I should be able to come out of here, rerun my test, and now you'll see I have a passing test. What we have here in our bookController test is what we call a unit test, so we are just testing the bookController all by itself. We've mocked everything around it and we know that the bookController is executing the way we want it to execute. Next we're going to look at some integration tests, which is more of an end to end test where we are actually going to replicate the HTTP post and all the way down to the database there.
Integration Tests with Supertest
Now we're going to start looking at integration tests or end to end tests for our application. In order to do that we're going to need to install two npm packages. The first one is going to be something called supertest and that's what we're going to use to execute our HTTP calls and the next one's going to be gulp-env, and what that's going to do is let us manipulate the environment variables in Gulp, so that we can setup a test environment, so that we can test everything end to end including the database. --save-dev to make sure that we're saving this in our dev environ.ent, but we're not going to install it when we get up into our production environments. It helps if we use install, so make sure we've got our install in there (Typing), and then that'll go. Alright, now over in our Gulp file we are going to make sure that we have env (Typing) and supertest (Typing). Now down here in our tests we're going to make sure that we set our environmental variables, so we're going to do env with vars (Typing) and we're going to set our environment to Test (Typing), and what that's going to do is when we get into our app.js I can do a process.env and pull in our environment, so it'll either be prod, dev or test and that'll be governed by our Gulp execution. The way we're going to do that is here on app.js instead of just connecting to our environment right here we're going to create this virtual and if(process.env.ENV is equal to test we are going to set db equal to bookAPI_test. Otherwise, (Typing) we're going to set it to bookAPI, so that way when our Gulp test runs it's going to set the env to test and our database is actually going to connect to this new MongoDB instance. Now that doesn't exist yet, but when we run our test Mongoose will see that it doesn't exist, and it'll just create it for us. Now we're going to create a new Javascript file called bookIntegrationTests (Typing) and then we've got to just write this out. We're going to start with should (Typing). Now in this case we're going to need a reference to our app, so we're going to set app equal to ../app.js, and this is what supertest is going to use to execute our HTTP calls (Typing). Here I can just pull in my model Book directly from Mongoose because it's loaded into Mongoose in our app.js, so I don't have to go through the hassle of pulling it in from the model, I can just pull it straight from Mongoose, and we're going to end with something called agent and what agent is is what we'll actually use from supertest to execute all of our HTTP calls (Typing) based on our app (Typing). The rest of this works very similar to the way that unit test works, so we do a describe, and this is going to be our Book Crud Test, so we're testing insert, update, delete, and get, and that's going to take a function (Typing). Alright, and here in the describe is where you layout all of your individual tests, so we're going to do it('Should allow a book to be posted and return a read and _id') because the way that we're going to know whether or not a book has been posted all the way to the database is by whether or not we get a read because that was a default, and an _id because that'll be created when we get to the database, and that's going to take our function (Typing) with done. Now the first thing we're going to want to do is create a book and, in this case, we're just going to do a bookPost with a title and author and a genre and that's going to be our req.body, that's going to be what we're actually posting into our function, and then we're going to use our agent to actually do the work. We're going to start with agent.post and we're going to tell it to post to api/books and when it posts to api/books we're going to send our bookPost and then we're going to expect (Typing) to get a 200 back and when it's all done we're going to have this callback called with either an err or some results and this is where we get to do our assertions, and so the first assertion we should do is results.body.read.should.equal(false) and results.body.should.have.property('_id'). Now when all of our tests are done (Typing) we're going to run a function and that function is basically going to just clear our database back again (Typing), by doing a Book.remove (Typing) and we'll run that. Then we'll call our done just to let everything know that we're all done and we're good. Now what this is going to do is it's going to send a post to api/books, that's going to go all the way through to our test database because in our app.js if our environment is test we're going to do our new API test, and then it's going to expect to have a read property that's false, and it should also now have an id. One other quick thing to remember is on here it needs to be /api/books, that's a super test thing that we just have to make sure we've got right, and also down here at the bottom we're going to want to put done. What done does, just like we have down here, is it lets supertest know, hey this test is done, now move on to the next thing, and so this done gets called, we go down to afterEach, and then that done gets called. Now the last thing we're going to have to do in order to make this work is we're going to have to have to export our app over in our app.js, so down at the bottom we need to do a module.exports = app, and that's what allows supertest to execute on the app. Alright, now that all of that's done we can come over here and we can do a Gulp test and we can run our test and see we have two passing tests, so let's change this so we can see a failing test, so result.body.read should not equal false. We'll run that and we know it will equal false because that becomes the default and you'll see now we have a failed test, expected false not to be false, so we see that this is actually running end to end all the way to the database and all the way back, so this is a good way just to wrap a set of tests around your entire application, so that you know that all of the moving pieces are working together and doing what you expect them to do.
Summary
We're still working our way through building a RESTful API with Node and Express and in this module we talked about unit testing with Mocha and we worked through how to separate our code, so that it was mockable, so that we could test just specific pieces without having to test the whole thing, and then we moved onto integration tests and we used supertest to be able to run our tests end to end, so we know the whole system works together the way we expect it to from the HTTP calls all the way back to the database.
HATEOAS
Introduction
I'm Jon Mills and in this module we're going to be talking about HATEOAS or Hypertext as the Engine of Application State. We've been working through this process of building out a RESTful API with Node and Express. We've built all the verbs, we've got a book API going, we spend last module talking about testing and integration tests versus unit tests, and how all that works. There's one more constraint that I want to talk about today and that's HATEOAS and how using a hypermedia in our application helps to build this self documenting API that becomes very easy for someone to navigate and understand what options are available to them at any time while they're using your API.
The Problem Around Navigating APIs
Now here we have the API that we just built. It's /api/books, it returns back a list of books that we have in our MongoDB database and you've seen me do this over the course of this entire course, when I want an individual item I just copy that id and I paste it up there, and that brings me to an individual book and I know that because I wrote the API, and you know that because I told you that, but all of the other consumers of this API, if we were to stick this API out on the internet, wouldn't understand intuitively that that's the right way to get to a single book because it could be /title, it could be /isbn number, it could be /something else. There's no way to intuitively just know how to get to an individual book when you're coming from a list of books, and so what HATEOAS or this hypermedia attempts to do or one of the problems it's trying to solve is to give your API away to self-document, so that a consumer of your API knows exactly what to do to get to this individual item or what other options are available, so let's pop over to our code and start to write some of these hypertext links into our API, so we can see what that looks like.
Adding Hypermedia to Our API
Now in order to get our hyperlinks in our API and our return we need to first start with our route, so if we go into bookRoutes.js you'll see that our .get just /api/books comes from our bookController, so if we pop over to bookController and our var get we need to interrupt this down here and insert links into the books that come back from MongoDB. Now unfortunately really the only way to do this is to actually physically loop through the books and append links on, so the best way to do that for us is going to be to use a forEach loop (Typing), and as we loop through this forEach loop we'll append on links to the books. Now a forEach takes a function with a couple of parameters. The first one's going to be element, index, and array. Now ultimately what we'd like to do is just change the element, so if we do element.links, and then add links on right here, but what we have to remember is that these are not just the JSON object that you get back, these are actually Mongoose models, so I can't change what those elements are without adjusting the model because every time I add something here Mongoose is going to evaluate that against this bookModel that we have and notice we don't have links in our bookModel. I'd rather not muddy the waters with our Mongoose model by adding links in here at this point. I'd rather find a different way to add links on here, so we can't do it just straight like this, we actually have to create a newBook and copy the element over, and if we do .toJSON that will actually do what we're looking for. It'll strip out all of the Mongoose stuff and just leave us with the JSON object that we can then start to edit. Now if we're going to do it this way though what we're going to need to do I create a new array, so let's do an array called returnBooks and then we'll push on our newBook, so each item in the array we'll do a returnBooks.push and we'll add the newBook. Then in the end we'll just res.json our returnBooks. Now what we're going to want to do is we're going to want to create a section of our newBook called links and this is where we're going to add all of our hyperlinks onto our objects and this is going to be the same throughout our entire API. If you want to know what else you can do with this object you look in the links section and it's just going to be a big full list. Now, in this case, we're just going to add one called self, so we're going to do newBook.links.self and we're going to set that equal to the link to the individual item for this book, and so we'll do http:// and we're going to pull the rest out of the request header, so req.headers.host, and that's going to give us that local host 3000 URL that we can just go click on. Then we're going to want to add /api/books/ + newBook._id and what that's going to do now is for every book in our return it's going to send us back the book plus a link to get to that individual book, so we'll clean this up, that should be equals not a semicolon there, we'll save that, pop over here. Now when I refresh this you'll see now I have links with a link to the individual item, so if I scroll down to, and we'll just pick one, let's go to The Dark World, I click on that, and now it takes me to the individual item, so now as a consumer of the API I just know how this works, I know how to move to an individual item, I click on that, and there we go. Now if you also remember when we're searching and we're filtering this list we didn't allow you to filter by everything, we only allowed you to filter by some items, so let's add a link on this page that helps us understand what other options we have here. Let's go back over to our code and we didn't ever move our individual items over into our bookController, so we'll make those changes here in bookRoutes. If you look at the .get all we're doing is returning the book, so there's really not a whole lot of functionality here, but instead what we're going to want to do is do var returnBook = req.book.toJSON, and then we want to do that same thing, so we want to do returnBook.links (Typing), and we have to add this step because if I just do .links.self, well links doesn't exist yet, so I can't --- add something to something that doesn't exist, I'll get an undefined error, so we've got to do this stuff first, and then we're going to do returnBook.links. we're going to call it FilterByGenre, let's do FilterByThisGenre. What that's going to do is it's going to create a link and we'll just copy this one (Typing), it's going to create a link that has a filtered list based upon that genre, so it could be other books by the genre or however we wanted to state it, and we do ?genre= + returnBook.genre. Now if we come back over to our browser and we look at this individual item, first we have to actually return the book. Now if we come back over you'll see we have a link and that's not working quite right. We'll look at that here in just a second. Let's pull up a different book and then I'll show you why. If I click on this one I now have a link that says I can filter by this genre, so I click on that, and I get all the books that are fantasy books. Now the reason why that other one didn't work is it wasn't a clean URL. Notice how I have spaces here, and so when I click here it's not going to work right, so we can replace the space with a %20. Let's hope back over and do that real quick. This is a really simple fix, so we're just going to come over here and we'll get rid of that. We'll just do var newLink = that (Typing), this equals newLink.replace, it's just a string, so we're going to do a replace, replace spaces with %20s, we'll save that. Now basically all we're doing is we're creating the string, then we're replacing %20s where we need to, and adding that to the filter by this genre, so we'll save that, we'll pop back over to our browser, refresh, and now we have a working link. There you go.
Summary
This concludes our course on building RESTful APIs with Node and Express and we've covered a lot of stuff in a pretty short period of time, but ultimately we built a RESTful API using Node and Express and we used our book as our resource to get that done. We started with a conversation about what REST is, what it means, what the constraints are, and how you work through that. We talked about all of the REST verbs, get, put, post, patch, delete, what they all do, and when you would use them, and then we talked a little bit about unit and integration testing. We covered the surface of it, let you know how to do unit testing, how to build controllers, how to do integration testing using supertest to test everything, and then this last module we started talking about hypermedia and how you can embed links in your resources, so that the user and consumer of the API knows what other options they might have as they're navigating through your API to have this self documenting API that's easy and intuitive to navigate. Well I hope you enjoyed this course. I've enjoyed making it for you and use this as a great starting point to get an API up and running using Node.
Course author
Jonathan Mills
Jonathan Mills is a JavaScript and Node.js expert working mostly in the MEAN Stack with individuals and companies to help build their technical skills to cope with the constantly changing landscape...
Course info
LevelIntermediate
Rating
(844)
My rating
Duration2h 4m
Released14 Apr 2015
Share course