This post is also available in: esEspañol (Spanish)

This Express tutorial assumes that you have Node.js installed and Mongo DB. Here is a link to the github repo. 

Express is an awesome framework for creating fast web APIs to interface with your SPA and because it’s also in Javascript, your entire codebase can be developed in 1 language. The makes it easier to use all of the same familiar tools if you’re integrating with React JS or Angular.

In this tutorial, we’ll create a simple blog API that returns a list of blog posts to the user. In later posts, we’ll cover JWT authentication and user relationships with MongoDB but for now, we’ll focus on basic CRUD.

Project Setup with NPM

In order to create our application, we’re first going to have to setup out npm configuration and make sure that we specify that our project uses the ES6 syntax.

Create a project folder in the command line by running

mkdir ExpressAPIBlog && cd ExpressAPIBlog

Now that you are in the directory, create a package.json and add you dev dependencies

npm init
npm install --save-dev babel-cli babel-preset-stage-0 babel-preset-env

This will allow for you to compile your ES6 code to ES5 so that it is understood by Express.JS and Node.

Now in order to actually build your application, you have to add each of these dependencies with npm.

npm install body-parser cuid express limax mongoose sanatize-html

Now that everything is installed, create a file called server.js

import express from 'express';
import bodyParser from 'body-parser';
let app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
    extended: false
}));
app.get('/', (req, res) => res.send('Hello World!'));

app.listen(3005, () => {
    console.log('server started - 3005');
});

Then add a script to your package.json file to run the server

"scripts": {
    "start": "babel-node server.js --presets env",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

Database Setup with Mongoose

Now that express is working, let’s set up a database connection.

First off, let’s create a config file that specifies our connection to the database instance

mkdir config && touch config/index.js

Open the file and add the port number and database connection string.

export default {
    "port": 3005,
    "mongoUrl": "mongodb://localhost:27017/express-api-blog",
    "bodyLimit": "100kb"
}

Once that is done, create a database file that connections your express app to Mongo DB.

mkdir db && touch db/connect.js
import mongoose from 'mongoose';
import config from '../config/index';

mongoose.Promise = global.Promise;

const connectToDb = async () => {
    try {
        await mongoose.connect(config.mongoUrl, { useMongoClient: true });
        
    }
    catch (err) {
        
      console.log(err);
    }
}

export default connectToDb;

Here you create an asynchronous function that tries to connect to the DB and if it fails, it consoles out the error.

Creating the Model

Next we have to create the Model for the post. This represents each post that goes into the database.

mkdir models && touch post.js

Inside of this model we will add a title along with the post content, a date the post was created at, a slug that will be generated based on the title and a cuid which is an id number. Even though all Mongoose documents have a _id field by default, cuid is another great way to have collision resistant ids

import mongoose from 'mongoose';
const Schema = mongoose.Schema;

const postSchema = new Schema({
    title: { type: 'String', required: true },
    content: { type: 'String', required: true },
    slug: { type: 'String', required: true },
    cuid: { type: 'String', required: true },
    dateAdded: { type: 'Date', default: Date.now, required: true },
});

let Post = mongoose.model('Post', postSchema);

export default Post;

Express Controller Actions

Now that we have our database setup and the model that we would like to use to populate the database, now we must figure out a way to allow the user to send requests to the API to create new posts. We could place all of the requests inside of the server.js file but it’s much cleaner to create a controller file.

mkdir controllers && touch controllers/post.controller.js

Inside of this controller we are going to need 5 CRUD functions. A get all, get by Id, create, update and delete.

import Post from '../models/post';
import cuid from 'cuid';
import slug from 'limax';
import sanitizeHtml from 'sanitize-html';

const PostController = {};

We’ve created a controller object to make it easy to export all of the functions that we’ll be creating.

The first function will actually be the getAll() function.

PostController.getAll = async (req, res) => {
    try{
        await Post.find().sort('-dateAdded').exec((err, posts) => {
            if (err) {
                res.status(500).send(err);
            }
            res.json({ posts });
        });
    }
    catch(err){
        res.send(err);
    }
}

This function takes in the request, tries the function and returns a a list of posts or an err depending on where the error takes place.

Then we have the getPost function. This finds the post by it’s cuid and returns the post if it exists.

PostController.getPost = async (req, res) => {
    try{
        Post.findOne({ cuid: req.params.cuid }).exec((err, post) => {
            if (err) {
                res.status(500).send(err);
            }
            res.json({ post });
        });
    }
    catch(err){

    }
}

Next, we have the addPost function. This checks if either the title or content is not present, creates the post, sanitizes the content, then saves the Post to the database.

PostController.addPost = async (req, res) => {
    try {
        if (!req.body.post.title || !req.body.post.content) {
            res.status(403).end();
        }

        const newPost = new Post(req.body.post);

        // Sanitize inputs
        newPost.title = sanitizeHtml(newPost.title);
        newPost.content = sanitizeHtml(newPost.body);

        newPost.slug = slug(newPost.title.toLowerCase(), { lowercase: true });
        newPost.cuid = cuid();

        newPost.save((err, saved) => {
            if (err) {
                res.status(500).send(err);
            }
            res.json({ post: saved });
        });
    }
    catch (err) {
        console.log(err);
    }
}

UpdatePost is very similar to addPost except it finds the post by it’s cuid, then updates the fields.

PostController.updatePost = async (req, res) => {
    try {
        if (!req.body.post.title || !req.body.post.content) {
            res.status(403).end();
        }
        Post.findOne({ cuid: req.params.cuid }).exec((err, post) => {
            // Handle database errors
            if (err) {
                res.status(500).send(err);
            } else {
                post.title = req.body.post.title || post.title;
                post.content = req.body.post.content || post.content;
                console.log('Post about to be saved');
                // Save 
                post.save((err, saved) => {
                    if (err) {
                        res.status(500).send(err)
                    }
                    res.json({ post: saved });
                });
            }
        });
    }
    catch (err) {
        console.log(err);
    }
}

For the DELETE Request, we have deletePost. This takes the cuid and deletes the post with that id. At the end, we also export the controller.

PostController.deletePost = async (req, res) => {
    try {
        Post.findOne({ cuid: req.params.cuid }).exec((err, post) => {
            if (err) {
                res.status(500).send(err);
            }

            post.remove(() => {
                res.status(200).end();
            });
        });
    }
    catch (err) {
        console.log(err);
    }
}

export default PostController;

Express Routing

Even though we now have our controller set up, we’ll need a way to access the routes in our controller. This can easily be done by creating a routing file. In your command line run

mkdir routes && touch routes/posts.routes.js

Inside of your server.js file, add

import posts from './routes/posts.routes';


app.use('/api', posts);

Now open your posts.routes file and import the express router and your controller.

import { Router } from 'express';
import PostController from '../controllers/post.controller';
const router = new Router();

This will allow for you to map get, post, put and delete requests to your controller methods.

// Get all Posts
router.get('/posts', (req, res) => {
    PostController.getAll(req, res);
});

// Get one post by cuid
router.get('/posts/:cuid', (req, res) =>{
    PostController.getPost(req,res);
});

// Add a new Post
router.post('/posts', (req, res) => {
    PostController.addPost(req, res);
});

router.put('/posts/:cuid', (req, res) => {
    PostController.updatePost(req, res);
});

// Delete a post by cuid
router.delete('/posts/:cuid', (req, res) => {
    PostController.deletePost(req, res);
});
export default router;

Once this is done, we should be ready to test our API.

Testing API Routes with Insomnia

Lets start off by creating a few posts. Run the server with

npm run-script start

and create a few posts with

Express Blog API Create

Express Blog API Create

Do this several time and then do a get request to return all posts

Get Request Express Blog API

Get Request Express Blog API

You can also use a tool like Compass to view all of your MongoDB Documents

MongoCompass Expres Blog API

MongoCompass Expres Blog API

Now let’s try testing out getting a post by it’s id.

GetByCUID Express Blog API

GetByCUID Express Blog API

Also by passing in the cuid and the modified contents, we can update the post.

Put Request Blog API Express

Put Request Blog API Express

Finally we have the DELETE Request. This can be done just by passing in the cuid.

Delete-Request-Express-Blog-API

Delete-Request-Express-Blog-API

Conclusion

Currently setting up a REST API with ES6 currently requires a lot of tooling to compile the Javascript but it offers the ability to write cleaning, more maintainable code. In later posts, we’ll discuss adding user management and authentication to the API as well as build both a React and Angular client. To keep up with the project, visit the link to the github repo.

 

Codebrains Newsletter

Get weekly dev news and tutorials.

Powered by ConvertKit