How to Create and Test a Node.js Blog API

Ben Merchant
26 min readJun 11, 2018

--

Update: June 18, 2019:

Turns out that, yes, this will teach you how to spin up a Node API. However, since publishing, I’ve learned how to better separate my concerns or ensure that each component has a ‘single responsibility’. (The ‘S’ in SOLID) As such, I hope to publish a ‘correction’ story’ in one week’s time.

I look forward to learning with you.

[Unedited original text after the fold.]

I’m in the process of designing my own portfolio website from scratch with no templates or frameworks. Ostensibly, every website needs a blog section to let visitors know the state of things. I thought it’d be nice to blog about my blog (meta) and hopefully help someone else learn a bit about Node.js.

Things you must know beforehand:

  • What JSON is
  • How to enter commands into the shell (command line)
  • Basic JavaScript syntax and terminology (ES6 a plus, if not you’ll pick it up)

Things you must do first:

What we won’t do in this tutorial: write any HTML (we’ll cover that in a future post). We will be designing back end API. It will handle HTTP requests from the browser (or Postman, or our tests) and respond with data from our database and whatever else we want to say. This is a web-server or a web-service. You could give someone the documentation to your API and they could create their own website or mobile app using your service.

I’m using Node.js version 8.11.1. I’m sure it will work in future versions. But might not in previous versions.

If you want to see the finished code, go to the repo here.

I always start with a brand new GitHub repo. Go to yours and create one. Call it whatever you want. Do NOT initialize the repository with a README. For this tutorial we’ll call it “portapi” as it is an api for my portfolio. I struggled over whether or not to include Git in this tutorial and decided to do so as a way to make this a little closer to an intermediate tutorial as opposed to the plethora of beginner code-alongs.

Open your command prompt and navigate to wherever you like to develop apps. We need to create a brand new directory.

$ mkdir portapi

Don’t type any dollar signs you see in code snippets. They represent the Git bash command prompt.

Don’t type the ‘$’ sign.

Now move into your new directory.

$ cd portapi

Next, enter these commands to create a README file. Initialize your new local directory as an empty Git repository. Stage the README to me committed. Record the changes with the a commit message. Add a new remote

$ echo "# Hello, portapi!" >> README.md
$ git init
$ git add README.md
$ git commit -m "first commit"
$ git remote add origin https://github.com/<YOUR_GITHUB_NAME>/portapi.git
$ git push -u origin master

Go back to your repo on Github. It should have the README in it and say “Hello, portapi!”. I have gotten in the habit of doing this every time I start anything project. Note: you will have to enter your GitHub credentials to proceed. I stored mine to Git Bash.

Back in the command line type.

$ npm init

This begins the process of creating a package.json file for our project. Just hit enter for every prompt except for entry point. Type in “server.js”. Why? It doesn’t matter too much, but I like to think that this is a web-server, not a fully fledged app. Even though I’ll call it an app sometimes.

Go to your text editor (I like Atom) and open the newly created package.json. We are going to add some dependencies. Some people prefer to install dependencies on the command line. Sometimes, I do too. But for a project like this, I prefer to add them to the package.json and install them all at once. After the last field (mine says homepage, yours will be different if you didn’t git init), add the following fields.

"dependencies": {},
"devDependencies": {}

Dependencies are other Node packages that are necessary for your app or package to work. DevDependencies are packages that help you develop the app and are not necessary for your app’s functionality. Next, we’ll fill these two objects with everything we’ll need starting with the DevDependencies.

// package.json
"devDependencies": {
"chai": "4.1.2",
"chai-http": "4.0.0",
"mocha": "5.1.1",
"nodemon": "1.17.3"
}

What are these packages that we need to develop our app? The first three are for testing purposes. I’ve come to believe that automating tests from day one/line one makes everything simpler later. And you won’t need to download Postman (even though you should).

Nodemon is a module that will automatically restart your server every time you hit CTRL + S (which should be frequently).

Let’s get our core modules installed now.

// package.json
"dependencies": {
"body-parser": "1.18.2",
"express": "4.16.3",
"mongoose": "5.0.16"
}

Express.js is a Node.js web framework. It will do all of the heavy lifting of handling HTTP request for us [POST, GET, PUT, DELETE]aka [CREATE, READ, UPDATE, DELETE]. Some people like to start with read, but how can you read what has not yet been created?

We need “body-parser” to get the body of incoming HTTP POST requests. Is there a way to do this without body-parser? Yes. We could also write this without Express at all, but I am not well-versed enough to be writing about it. Remember, these modules are just collections of functions someone wrote. You could write your own functions the same way you could write your own web environment in C++. But, what would Sweet Brown say about that?

Mongoose is an Object Document Mapper for MongoDB. It handles the type casting and validation we will want for our blog. You could check out express-validator if you like. We could use it later, but that will be a chance to write one simple function instead of requiring another package.

You may see some package.jsons and notice that the version numbers are prepended with carats (^) or tildes (~). We are going to omit these in order to force our app to always use the same version. That way if it works today, it will work tomorrow, period

Let’s install our packages. Go back to your command line and type

$ npm install

This will install 334 packages. That sounds like alot, it is. But this is the most basic plan I could come up with. It’s a little less than 15 MB.

It’s Coding Time

In the command line type:

$ touch server.js

You could also just create a new file in your editor, but this is simpler. Go to your editor and open this new server.js and type:

// server.js
console.log('Hello Node');

I like to make sure thing’s are working from time to time. The beginning seems like a good time. We are going to run our new app. Save server.js and go back to the command line and type

$ nodemon
Well, hello to you too.

Remember when we changed the entry point when we ran npm init? Since we named it server.js and the file containing the ‘Hello Node’ console.log() is called server.js, Nodemon knows to “enter” our app in this file. Nodemon is still running, still listening for changes to our app. Go back to server.js and add another console.log() and save the file. Check your command window.

Nodemon recognized that we changed a file and automatically restarted our server. You should see “Hello Node” and whatever your new console.log() said. If it didn’t work. Try again. Don’t move on until it does. Remember to remove both console logs and have a blank server.js before we move on.

Just in case there is any confusion on what your code should look like at this point.

Some modules required

We “installed” the modules we’ll need. But that simply means that we downloaded some files from npm to our local machine. Our app has no idea what packages we want to use. It doesn’t know anything at this point. Let’s include them. (Include, require, import: tell one file to use other files)

// server.js
const bodyParser = require('body-parser');
const express = require('express');
const mongoose = require('mongoose');

Now all of the functions in those three modules are available to server.js. But how do we get to the API itself? We need to set it up so that we can navigate to it on a port. What are ports?

Underneath the require statements type

// server.js
const app = express();
app.listen(8080, err => {
if(err) console.error(err);
console.log('Server listening on port 8080...');
});

The first line actually creates (initializes) our application by calling the express function we required above. The variable name ‘app’ is a convention, and I strongly recommend you follow it. Every tutorial and stack overflow answer will use it as well as the Express docs.

The next bit is a function that listens for connections on the port we specify. In this case 8080. After the port number we have a callback function. I’m using ES6 arrow functions for this tutorial. Learn them. Learn ES6 in general. I’ll expound on that in a future post.

In the arrow function we are handling any errors that may occur by logging them to the console. The console.log() is just for our peace of mind. It’s nice to know that everything is working. Save your server.js. Since I never told you how to stop nodemon, it should still be running. It should have detected the changes we just made and look like this.

Take a break to call your mother and let her know you just coded your first server.

Handle your routes

Now we are going to stop are server to create some more files. In your command prompt window press CTRL + C. No, it doesn’t copy anything to clipboard, but I guarantee you’ll try to use it as such in the future and have to restart your server. I still do. (To copy in Git Bash you press CTRL + INS).

Our server is no longer listening and we are once again able to enter commands into the prompt. We are going to go ahead and create all of the files and directories we’ll need for our blog. Enter all of these commands and we’ll walk through them.

$ mkdir app
$ cd app
$ touch config.js
$ touch router.js
$ mkdir controllers
$ cd controllers
$ touch blogpost.controller.js
$ cd ..
$ mkdir models
$ cd models
$ touch blogpost.model.js
$ cd ../..
$ mkdir test
$ cd test
$ touch blogpost.test.js
$ cd ..

What did we just do? We created an app directory to hold the nuts and bolts of our app. In that directory we created two files: config.js and router.js. We also nested two directories in the app directory: controllers and models. In the controllers directory we created blogpost.controller.js. In the models directory we created blogpost.model.js.

Finally, we created a test directory in the root directory and created the blogpost.test.js file in it. You could argue that the test directory and file are optional, and I would argue you are wrong.

We test first. We test often.

Make sure you are back in the root directory and start your server again. Remember how? If not, the answer is type “nodemon”.

config.js

Look at the app.listen() method in server.js. Do you notice something ugly? Something ‘wrong’?

We hard-coded the port numbers. This is taboo in development. It’s not technically wrong. I mean, hey, it works right. But if we are just going to hard code data, then why are we creating an API in the first place? Why not hardcode our posts in HTML files?

Because it’s inefficient and sloppy.

I don’t want to maintain an indefinite number of HTML files. I’d rather download over 3600 files from a npmjs.com and write a hundred lines of code so that in the future I can sit back on the beach, open a form, and publish a blog post in minutes.

The config.js file is where we will store our port number. We could just make it a variable in server.js, but in the future we might want to add a number of other configuration options. If/when that day comes, we have a nice place to do it.

Let’s open config.js, create an object, and export it. Trigger warning: excessive code for our purposes, but I’m assuming you plan on doing more than write one simple API.

// config.js
const config = {
port: process.env.PORT || 8080,
db: 'mongodb://localhost/myblog',
test_port: 4242,
test_db: 'mongodb://localhost/myblog_test'
}
module.exports = config;

‘port’ and ‘test_port’ are ports we want our server to listen on. You may recognize the 8080 we used earlier. This is the port that will be utilized on our local machines. The 4242 is for testing purposes and will be used after we create our first route.

What about the ‘process.env.PORT’ variable. This is for when we host our API later on AWS (or Google, Heroku, etc). Somewhere in the dizzying myriad of settings you choose when you launch your app, you can set environment variables. One of these variables is the port you want to use. If you don’t set that port, our app will ‘default’ to 8080. The ‘||’ means ‘OR’ in JavaScript, but you already knew that.

‘db’ and ‘test_db’ are the locations of our local MongoDB instance. You did install MongoDB right? In a future tutorial we will connect with MongoDB’s cloud-based Atlas service (the url is literally …/cloud/atlas, we dev’s are not a glum lot). For now, we are going to do everything locally. We need to use a separate database for our tests because our tests will be completely emptying the database every time we run a test.

Lastly, we export the object as a module to be used in server.js.

Speaking of server.js, let’s go back to it and give the app.listen() method a makeover as well as import the config module we just created.

// server.js
const config = require('./app/config');
app.set('port', config.port);
app.listen(app.get('port'), err=>{
if(err) console.error(err);
console.log(`Server listening on port ${app.get('port')}...`);
});

First we import our config module. You may notice that we didn’t include the ‘.js’ in the config file name. You don’t have to. Node.js knows what you mean.

We set the port of the app to that from config.js. We didn’t have to call it ‘port’. We could have called it ‘thePortIWantToUseForMyApp’. Express doesn’t care. Node doesn’t care. But I care. I really do. So let’s just call our port ‘port’.

You’ll see in app.listen() we are now ‘getting’ the port we just ‘set’. We could have just used config.port here, but then you wouldn’t have learned about app.set(). Look at the console.log(). Notice that we are no longer using single quotes. We switched to backticks in order to implement ES6 template literals. We used a dollar sign followed by our app.get() in curly brackets. This will work for any variable. Note: This won’t work in every browser as ES6 adoption isn’t universal, but we aren’t in the browser are we?

Your server should still be running. Assuming you’ve saved config.js and server.js, it should have restarted and logged the exact same message it did before. A common error here would be to not switch from single quotes to backticks.

blogpost.model.js

As with almost any file or variable in this tutorial, you are free to use whatever names you want. The ‘.model’ is completely superfluous. It’s redundant even. The file is in the models directory for Pete’s sake. But using this naming convention removes all ambiguity about what type of file your editing in the future. Consider a future where you have dozen of directories and hundreds of files. You’ll thank me then. Open it up and type this

// blogpost.model.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// BlogPost Schema
const BlogPostSchema = mongoose.Schema({
url: {
type: String,
required: true,
unique: true
},
title: {
type: String,
required: true
},
body: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
},
tags: [String],
updated: {
type: Date
}
});
module.exports = mongoose.model('BlogPost', BlogPostSchema);

First we import mongoose then initialize the mongoose.Schema constructor as ‘Schema’. This constructor is how we will ‘model’ or define the format of our MongoDB documents. These first two lines will be the same in any model file.

The next declaration is the instantiation of a new model. I kept it as simple as possible, so we are only going to see two different data types, String and Date. These are JavaScript native types. Notice that some are required and some are not, this will help with our validation later on.

We also made ‘url’ unique. This is because we don’t want any confusion when someone permalinks or manually enters the url to a blog post they like and want to check later. We defaulted the ‘date ’(in this case it is the date the post was published) to Date.now. This means whenever we add a blog post to the database the current UNIX timestamp will be saved in our database. ‘Tags’ is an array of strings. We will be able to add as many as we like, as long as its not in the millions, in which case that would put a document over the 16 MB limit.

The last line does two things. The right half of the equals sign is making a copy of ‘BlogPostSchema’ and we are telling Mongoose that the MongoDB collection where we want to store blog posts is called BlogPosts. The collection name will be plural, but we give it as singular name that we will use to instantiate new blog posts. It handles the interpretation automatically. The left side of the equals sign is exporting our model to the rest of the app. This is a Node.js object.

blogpost.controller.js

Now we have a model for all blog posts that will be added to our MongoDB database. We know that every post will have the same format so that when we write the front end, we can be confident that no matter which post we pull to display on our site, it will be set up in the same way. Every post will definitely have a url, title, body, and date. They may or may not have tags. Posts may never be updated, but if they are, we have a place to store the time it happened.

We now need to define the instructions for what our API needs to do when we send it data from the browser (or a test or Postman). Here is some code. We’ll add more later for different CRUD operations, but for now, we just want to POST (HTTP verb) posts (blog posts).

// blogpost.controller.js
"use strict"
const BlogPost = require('../models/blogpost.model');
// create a new Blog Post
exports.publishPost = (req, res) => {
const NewBlogPost = new BlogPost(req.body);
NewBlogPost.save((err, blogPost) => {
if(err) {
return res.status(422).json({
msg: 'Server encountered an error publishing blog post.',
error: err
});
}
else {
return res.status(200).json({
msg: 'Successfully published blog post.',
blogPost: blogPost
});
}
});
};

We start with a “use strict” expression. This tells our app to run in strict mode. At it’s most basic, this prevents us from doing certain things like using undeclared variables, duplicating parameter names in function definitions, and duplicating a property name in an object literal. Not that I would let you make any of these mistakes, but there are times you want to use this mode. This is one of those times. The controller is arguably the most important part of our app. It is defines all behaviors for our CRUD operations.

We then import our model.

Next we define a function that will be immediately exported. ‘req’ and ‘res’ are conventions in Node.js to represent the request and response objects. You can rename them, but why confuse your future self for the sake of contrarianism.

Request is data coming from the user (GET, POST, etc…)

Response is what the web-service will send back

All of the data about a new post will be in the body of the request object. Express tidies this up for us into a neat object named req.body. We assign that to a new object called NewBlogPost. We construct this new blog post using the model ‘BlogPost’ we just imported at the top of the script.

Everything else is one function. It is the Model.save() method. We are only going to pass one argument, a callback function to let us know what happened when the .save() method was called. The first branch of the if statement lets us know what happens if there was an error saving the new BlogPost to the database. The second is the success branch. We have to pass two arguments to this callback. The first is ‘err’, an error object in the case that something goes wrong. The second is ‘blogPost’ which is an object that will hold the saved blogPost.

If .save() fails, the callback function is going to return an HTTP status code of ‘422’. This is code lets us know that we have an “ Unprocessable Entity”. This code let’s us know that “The request was well-formed but was unable to be followed due to semantic errors”. We attach that to the response object with res.status(422). Then we chain on some JSON for a more detailed, human-friendly response from the server. We are going to send an object with two properties: the first is a message (‘msg’) to let ourselves (or another future developer, it’s not all about you) know what when wrong in plain English. The second property (‘error’) has the value ‘err’ which is the error object sent back from our app. When we test our app, we will see some error objects from Mongoose.

If .save() is successful we respond with a success status of ‘200’ and some JSON. Similar setup except instead of sending an error (because there was none), we return the blogPost object that was just stored in the database.

router.js

Our app now knows what to do when it receives a new blogPost (controller), and it knows what it should look like (model). But it doesn’t yet know when to do it. For this we need to define some routes. Here’s the code for router.js.

// router.js
const express = require('express');
const BlogController = require('./controllers/blogpost.controller');module.exports = app => {
// route groups
const apiRoutes = express.Router();
const blogPostRoutes = express.Router();
// middleware for apiRoutes
apiRoutes.use('/blogPosts', blogPostRoutes);
//// Blog Post Routes //////////////
// POST a new blog post
blogPostRoutes.post('/', BlogController.publishPost);
// url for all API routes
app.use('/api', apiRoutes);
};

First we need to import express. We need to do this so that we can call the .Router() method to create two new router objects in just a little bit. Next we import the controller module we just created.

The rest of the code is one function. We pass app as an argument so that we can attach our routes at the end. We also export this function to be used in server.js.

We set up two route groups. One will be our all encompassing apiRoutes. For this tutorial we will only have blog posts, so this isn’t really a necessary step. But consider a future where you want to save details of people that visit your site, or their comments. Those would fall under your API, but wouldn’t be blog posts. Which is our next group, blogPostRoutes.

Now we need to set up the middeware for our apiRoutes. We simply need to tell it what do do when a route is encountered. If you peek at the last line of code you’ll see we’re telling the app itself what do to when a request comes on ‘/api’. It should use apiRoutes middleware. So whenever a request comes on ‘/api/blogPosts’, our app will use whatever functions we attach to blogPostRoutes.

We’re only going to define behavior for one HTTP verb for blogPostRoutes, POST at this time. So whenever a HTTP POST request comes on /api/blogposts/, our app is going to use the function BlogController.publishPost(). We just wrote that function in the controller in the previous section.

Back to server.js

I’m going to go ahead a share the entire server.js instead of just the lines you need to add. I recommend you go line by line and add or modify each line as you encounter it as opposed to just copy and pasting. If you want to copy and paste, just clone the repo. Note: Node is not going to like it when you save this file because MongoDB is not running. We’ll start MongoDB in the next step.

// server.js
const bodyParser = require('body-parser');
const express = require('express');
const mongoose = require('mongoose');
const config = require('./app/config');
const router = require('./app/router');
const app = express();app.use(bodyParser.json());app.set('port', config.port);
app.listen(app.get('port'), err=>{
if(err) console.error(err);
console.log(`Server listening on port ${app.get('port')}...`);
const db = mongoose.connect(config.db);
mongoose.connection.on('connected', () => {
console.log(`Mongoose connected to ${config.db}`);
});
});
router(app);

The first additions are the imports for config.js and router.js. Now our app is fully aware of the functions we just wrote and we can use them at will in server.js.

Next we tell the app to use bodyParser. We need this in order for the app to be able to parse the req.body in the controller function .publishPost(). Why this doesn’t just come with express, I don’t know. I think it used to. We use the .json() method for parsing JSON requests. Our front end isn’t going to be sending data straight from a form (I read somewhere that that’s no longer fashionable). We are going to send it as JSON.

Now look inside our app.listen() method after the console.log where we announce the server is listening. This is where we actually connect to the database via mongoose.connect(). It handles all the nitty gritty in the background. Then, we call the .on() method to let us know when the database is connected.

If for some reason your server isn’t running, type ‘nodemon’ in your shell, and you should see this.

Server running and connected to our database

This would be a good time to push some code. Press CTRL+C to stop the server and get your prompt back.

We don’t need to push all of those node modules to our repo. Add a new file to your root directory as such:

touch .gitignore

Open this newly created file and add this single line.

node_modules/

This will tell git to ignore this directory and all of its contents. Now, type these commands.

$ git add .
$ git commit -m "POST route for blogPosts written but not tested"
$ git push -u origin master

Time for a Test

We need to get MongoDB up and running. Open up a separate shell window and navigate to where your mongod.exe is located. Depending on the version of MongoDB you have installed, it may be in a differently location. In my case it was:

$ cd "c:\program files\mongodb\server\3.6\bin"

Then type:

$ mongod

You now have MongoDB up and running. You can minimize this new shell window.

Now that we have one fully formed route, we need to test it to make sure it works as expected. First we need to modify our server.js so that we use our test_port and test_db.

// server.js
if(process.env.NODE_ENV === "test") {
app.set('port', config.test_port);
app.listen(app.get('port'), err => {
if(err) console.error(err);
console.log(`Server listening on port$ {app.get('port')}...`);
const db = mongoose.connect(config.test_db);
});
} else {
app.set('port', config.port);
app.listen(app.get('port'), err => {
if(err) console.error(err);
console.log(`Server listening on port ${app.get('port')}...`);
const db = mongoose.connect(config.db);
mongoose.connection.on('connected', () => {
console.log(`Mongoose connected to ${config.db}`);
});
});
}

We set up a simple if statement to check if we are running tests or not. If we are running tests, we want to use our test variables. If we are in ‘live’ (or ‘production’) mode, we want to use the regular port and db variables. We will set that environment variable to test in the next step.

blogpost.test.js

Let’s break this into two parts for digestibility: the set up and the test itself.

// blogpost.test.js
process.env.NODE_ENV = 'test';
const BlogPost = require('../app/models/blogpost.model');
const server = require('../server');
const chai = require('chai');
const expect = chai.expect;
const chaiHttp = require('chai-http');
chai.use(chaiHttp);
const blogURL = '/api/blogposts';

As I just promised, we set the Node environment variable to ‘test’.

Next we required a few things. First we import the BlogPost model so that we can use the methods from mongoose.model. Namely .remove(). Then we import our old friend server.js. Remind me to go back there and add a small line of code so that this line works.

The next four lines are our for our testing framework. And the last line is the URL for our API. The one we set up in router.js. Here is the test itself.

// blogpost.test.js
describe('Blog Posts', () => {
beforeEach(done => {
BlogPost.remove({}, err => {
if(err) console.error(err);
done();
});
});
describe('/POST/ - publish a BlogPost', () => {
it('it should POST a new BlogPost', done => {
const NewBlogPost = {
url: 'my-first-blog-post',
title: 'My First Blog Post',
body: 'This is some text. Lorem ipsum... etc...',
tags: ['blog', 'nodejs', 'api']
};
chai.request(server)
.post(blogURL)
.send(NewBlogPost)
.end((err, res) => {
expect(res).to.have.status(200);
expect(res.body).to.be.an('object');
expect(res.body).to.have.property('msg')
.eql('Successfully published blog post.');
expect(res.body).to.have.property('blogPost');
expect(res.body.blogPost).to.have.property('url').and.be.an('string').eql(NewBlogPost.url);
expect(res.body.blogPost).to.have.property('title').and.be.an('string').eql(NewBlogPost.title);
expect(res.body.blogPost).to.have.property('body').and.be.an('string').eql(NewBlogPost.body);
expect(res.body.blogPost).to.have.property('tags').and.be.an('array').and.have.length(NewBlogPost.tags.length).eql(NewBlogPost.tags);
expect(res.body.blogPost).to.have.property('date');
expect(res.body.blogPost).to.have.property('_id'); done();
});
});
});
});

First we describe what this block of testing does. It’s a way of documenting the tests for your future self and others unlucky enough to maintain your code. All tests related to Blog Posts will fall inside that first arrow function’s brackets.

beforeEach() is executed before each test in this block of code. Not to be confused with before() which only executes once before the entire block. So before each test we want to remove all blog posts from the database. Now you see why we had to establish a test port and db. It’d be a shame if we had published a few dozen posts then decided to test some new functionality and wiped our production database. The BlogPost.remove() is a mongoose method.

Next is a nested describe block. This is the test itself. The it let’s us know we are actually starting a test.

We need some data to send to our API, so we create a new object called ‘NewBlogPost’ with some sample data that represents a typical blog post.

Now we actually perform an HTTP request. We have to use our server.js module because that is our app. Next we tell Mocha to perform a POST request. Then we tell it what to send, in this case the ‘NewBlogPost’ object we just created.

Now we .end() our test by testing the response body from our API. So, what do we expect the response to be. I’ll log the response body so you can see for yourself what it looks like.

This response body is what we will run our expect() tests on. First we test the response itself (not the body) to make sure we got the desired HTTP response code of 200. Then we make sure the res.body is an object, that that object has the property ‘msg’, and that that message is what we specified in blogpost.controller.js.

Next we check for the ‘blogPost’ property. The next four expects() are easy to predict. We are testing if our res.body has the four properties we sent in the request. We are testing their types and if they are equal to what we set above. We are also testing the length of the ‘tags’ array.

The next two properties for which we are checking their existence were automatically generated by our app. The ‘date’ property we set as a default in our model. ‘_id’ is automatically set by MongoDB itself.

Finally, we are done() with our test.

One more thing we need to do to actually run our test. We need our app to know what to do when we run tests. Go to package.json and locate the scripts property. The value should be an object with one property, “test”. Remove the value of the “test” property and replace with the following value

// package.json// replace this
“test”: “echo \”Error: no test specified\” && exit 1"
// with this
"test": “mocha --timeout 9999 --exit”

Mocha is our testing framework. We set the test to timeout at 9999 milliseconds (almost 10 seconds). And lastly we tell it to exit our app, or stop listening on any ports, once it has completed all tests.

We should be ready. Let’s try it out. Stop your server if it’s running and type.

$ npm test
Our test failed.

What went wrong? You see that TypeError: app.address is not a function? Remember I told you to remind me to add a line of code to server.js? Well, you forgot. Let’s go back to server.js and add this line to the very bottom of the file.

// needed for testing porpoises only
module.exports = app;

We were unable to import server.js to our test because we never exported it. In production we won’t need to do this. Now go back and run the test again. Here’s a screenshot of what you should be seeing:

Hooray! You’re an app-tester.

We need to test our failure branch

Remember in our model we required some of our fields. Let’s test our app to ensure those validations are working. Let’s see what happens if we try to publish a blog post without a body.

Go back to blogpost.test.js and immediately after the it block and right before the closing curly bracket of the ‘/POST/’ describe blog enter this code.

it('it should not POST a new BlogPost without a body', done => {
const NewBlogPost = {
url: 'my-first-blog-post',
title: 'My First Blog Post',
tags: ['blog', 'nodejs', 'api']
};
chai.request(server)
.post(blogURL)
.send(NewBlogPost)
.end((err, res) => {
expect(res).to.have.status(422);
expect(res.body).to.be.an('object');
expect(res.body).to.have.property('msg')
.eql('Server encountered an error publishing blog post.');
expect(res.body).to.have.property('error').and.be.an('object');
expect(res.body.error).to.have.property('errors')
.and.be.an('object');
expect(res.body.error.errors).to.have.property('body')
.and.be.an('object');
expect(res.body.error.errors.body).to.have.property('kind')
.eql('required');
done();
});
});

You’ll notice this is very similar to the previous ‘success’ test. The first difference is the malformed ‘NewBlogPost’ object. It doesn’t have a body property. That’s the point of this test. Run the test and you should see this.

Have you ever succeeded at failing? You have now.

We test for the status code (500) and for the error ‘msg’ we set in blogpost.controller.js. Next we test for the ‘error’ property we set which contains the ‘errors’ property from Mongoose. This errors object is expected to tell us which property we forgot to include. This property is also an object which tells us which ‘kind’ of error we triggered. In this case it is the ‘required’ error, because the ‘body’ property is a required member of a BlogPost.

If we didn’t require a body in our model, this failure test would have failed. And, no, double negatives don’t equal a positive in this instance. Try it for yourself. Go into your blogpost.model.js and remove the “required: true” from the the “body” field. (Don’t forget to remove the comma after String or you’re going to have a bad time.) Run the test again. You should have 1 passing and 1 failing. Our app will send a 200 status code because it successfully added this body-less blog post to the database.

Our failure test failed

Don’t forget to go back and re-add the required: true (and the comma) to your model before you proceed.

Implementing new features

Let’s implement a new feature and test it, then we can wrap up this tutorial. I recommend pushing your code before you move onto this step. We just made a major change, not to our API itself, but we have two tests now and they work. You can always revert to this version of your code if you can’t get it to work after this. With automated testing, we can test our routes every time we make changes.

I don’t like that we have to create the URL ourselves. I’d rather just send the title, body, and tags and let the app create the URL for us. Let’s add this feature and test it. Open up blogpost.test.js and make these changes. First, remove the ‘url’ property and value from the NewBlogPost object. I just commented it out so you could see exactly what to remove.

// blogpost.test.js
const NewBlogPost = {
// url: 'my-first-blog-post', // you can remove this line
title: 'My First Blog Post',
body: 'This is some text. Lorem ipsum... etc...',
tags: ['blog', 'nodejs', 'api']
};

Directly underneath the NewBlogPost object, we need to add a little functionality to turn our title into a URL-friendly format.

// blogpost.test.js
const theURL = NewBlogPost.title.toLowerCase().split(' ').join('-');

This is some simple string manipulation that will take the title stored in our NewBlogPost, convert all alphabetic characters to lowercase, split all of the words (where ever there is a space character) into an array, then join all of those words back together with a dash between them.

Now we need to alter the test itself. On the line where we test the URL we need to change the string we are comparing the response against from ‘NewBlogPost.url’ (since that no long exists) to ‘theUrl’ (the variable we just defined.)

// blogpost.test.js// change this
expect(res.body.blogPost).to.have.property('url')
.and.be.an('string').eql(NewBlogPost.url);
// to this
expect(res.body.blogPost).to.have.property('url')
.and.be.an('string').eql(theURL);

Note: Go ahead and remove the ‘url’ property from NewBlogPost in the second test as well as we are no longer going to be sending a url with our HTTP requests. You don’t have to, but if you don’t, the failure test won’t be 100% accurate. You’d be testing if someone, somehow sent a request with a url field in it, which will never happen.

Open up blogpost.controller.js and enter these two lines at the very beginning of the publishPost() method.

const theURL = req.body.title.toLowerCase().split(' ').join('-');
req.body['url'] = theURL;

This is the exact same string manipulation we performed in our test with the only difference being we are manipulating the req.body.title. The second line is attaching this new string to the ‘url’ property of the req.body.

Go back and npm test your app. Both tests should pass with flying colors.

Note: we should be doing some input validation and sanitation on the title to ensure the resulting url will be valid because even though only we are ever going to be using this API, we might make mistakes in the future. We should be validating and sanitizing all inputs, but that’s an entirely different conversation.

Finally, push your code.

Wrap Up

Keep it 100

We now have a Node.js API that we can send POST requests to that will add a blog post to a MongoDB database collection. We also have automated tests that define what will happen when s POST request succeeds or fails.

Next we will set up and test the other three CRUD operations. We will be able to GET blog posts to display on our blog, UPDATE those posts with new data, and DELETE posts if we no longer want them.

Here’s the link to the repo on GitHub.

Learn JavaScript with my Solutions to Check.io Challenges

I’m here to learn myself. So, please leave a comment to let me know about any errors I may have made.

--

--