Getting started with NodeJS and Express

nodejs Aug 18, 2019

In this tutorial you will learn how to setup a basic express rest api using nodejs, using mongodb as a database. We're going to be covering the basics on setting up the project structure, and adding our CRUD (Create/Read/Update/Delete) routes for our todo app. We will expand upon our project in the future, which will include handling CORS, authentication using JWT's (json web tokens), etc. And with that, let's begin.

p.s. I will be using the command line for creating directories and such, so be sure to use the relevant command in Windows, or use Windows Explorer. I will also use Postman at the end to test the api.

Getting Started

Make sure that you have nodejs installed. I recommend using nvm if you are on MacOS or Linux. For Windows, navigate to the nodejs website and download the installer.

First, create a folder where our project will live

$ mkdir nodejs-express-tutorial
$ cd nodejs-express-tutorial

Once you navigate into that folder, run the following command to generate the package.json.

$ npm init

After providing your social security to your package.json, we need to install a few packages from npm for our project to work:

$ npm install --save express mongoose
Package Purpose
express is the web server framework that we will use to build our api
mongoose is the ORM we will use to communicate to our MongoDB database

Now that we know what each of these packages is for, we can set up a basic folder structure:

mkdir src
cd src && touch index.js

After we run those commands, our folder structure should look like this:

├── package-lock.json
├── package.json
└── src
    └── index.js

To make our lives easier, let's update the start property in the package.json:

{
    "name": "nodejs-express-tutorial",
    "version": "1.0.0",
    "description": "",
    "main": "src/index.js",
    "scripts": {
        "start": "node src/index.js",
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "",
    "license": "ISC",
    "dependencies": {
        "express": "^4.16.4",
        "mongoose": "^5.4.2"
    }
}

Now that start is updated, when we run npm start it will know to run the node src/index.js command.

Now that our project structure is complete, let's get to creating our web server using express.

index.js

Our first step is to create our web server in index.js. Open the project in your favorite text editor, and open index.js.

const express = require('express');
const app = express();

app.get('/', (req, res)  =>  {
    res.json({ message: 'Welcome to the todo API!' });
});

app.listen(8080, () => {
    console.log('Server listening on port 8080');
});

The first line imports the express package that we installed earlier, and the second line initializes an express application using the express constructor.

The app.get('/', ...) creates our first route, and simply sends a json response containing a property called message. (req, res) is the incoming request, and the outgoing response we use to send our message.

app.listen(8080, ...) tells the express application start our web server and listen for requests on port 8080. The callback is called once the express app is listening, and logs a message to let us know it start successfully. From the parent directory, running npm start should yield:

$ npm start
Server listening on port 8080

Connecting our database

Now that we have our basic app set up, lets create a models folder with a file called todo.model.js where we will keep our mongoose models.

$ mkdir models && touch models/todo.model.js

Our folder structure should now look like the following:

├── package-lock.json
├── package.json
└── src
    ├── index.js
    └── models
        └── todo.model.js

To keep things simple for the moment, we will connect to our mongo database inside our index.js file:

const express = require('express');
const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/my_mongo_db');
mongoose.connection.on('error', err => {
    console.log(err);
    process.exit(1);
});

First we import mongoose with require('mongoose'); Then we connect to our mongo database by calling the mongoose.connect function and passing a mongo uri connection string; Mongoose will automatically create the my_mongo_db if it does not exist already.

The next line uses nodejs' event driven architecture by calling the on function from the connection object. In our case, the callback is executed whenever an error event is emitted from mongoose. Since our small application is useless without a database, we log the error, and exit the application. In the real world, we could attempt to reconnect to the database, but for now that's out of the scope of our project.

Todo model

Now that we have our database connection set up, we can create our todo model. Open todo.model.js and we have the following:

const mongoose = require('mongoose');

const Schema = mongoose.Schema;
const todoModel = new Schema({
    complete: { type: Boolean },
    message: { type: String }
});

exports.Todo = mongoose.model('todos', todoModel);

Like before, we import mongoose, and then get the Schema class from the module. Our todo will have two properties, complete, which will be a boolean value for whether the todo has been completed or not, and a message property will tell us what we need to do to complete this todo. The final thing to do is export the model so that we can start adding todos to our database.

Todo routes

Now that we have our database connection and model setup, we can begin writing our todo routes. Let's create a routes folder that contains todo.routes.js. We should now have:

├── package-lock.json
├── package.json
└── src
    ├── index.js
    └── models
        └── todo.model.js
    └── routes
        └── todo.routes.js

Open our new todo.routes.js and let's get right into it. We've already seen how to create a route on an app, but here we are going to use a router since we are creating sub-routes to our parent api route. Defining routes on a router is exactly the same as it is on an application, so let's define the routes we will use:

const express = require('express');

const router = express.Router();

router.get('/', (req, res) => {})
router.post('/', (req, res) => {})
router.get('/:id', (req, res) => {})
router.put('/:id', (req, res) => {})
router.delete('/:id', (req, res) => {})

This is our basic CRUD format for our todos. Here are the uses for each route:

Method Route Purpose
GET / Get ALL todos
POST / Create new todo
GET /:id Get ONE todo by id
PUT /:id Update todo by id
DELETE /:id Delete todo by id

Now that we know what each route is for, let's start speaking to our database:

const Todo = require('../models/todo.model');

router.get('/', (req, res) => {
    Todo.find({}, (err, todos) => {
        if (err) {
            console.error(err);
        }
        res.json({ list: todos });
    });
});
router.get('/:id', (req, res) => {
    const id = req.params.id;
    Todo.findById(id, (err, todo) => {
        if (err) {
            console.error(err);
            return res.status(500).end();
        }
        res.json(todo);
    });
});

We first import our model. This gives us access to start using mongoose's query functions (which are mostly direct translations from mongo's command line functions). In the router.get('/', ...), we want to find all todos in the database, so we pass an empty query object to the find function, and a callback with an error, and the list of todos. If there is an error, we log it out, and in this case we aren't so concerned if there is an error, because todos should be an empty array which is perfectly fine to pass back to the user.

In our get by id route, we get the id from the req.params property on the incoming request object. In our case, since we made the route /:id, the id will be located at req.params.id. If we were to name it /:todoId, then we would have used req.param.todoId. Since we know the id, we use the findById function, which takes in a string, and a callback with an error and the found todo object. In this case if there is an error, we need to let the user know. We send a 500 status to let the user know there was an internal error and it was not their fault. However, if there is no error and, the todo is null, we can send a 404, which lets the user know the todo they requested was not found.

From here, we can create the rest of the routes, POST /, PUT /:id, and DELETE /:id. Go ahead and try them on your own, or if you want to skip ahead I have put them below.

  • Note: Before continuing, be sure to go back to the top of our index.js after const app = express() and add the following lines:
app.use(express.json());
app.use(express.urlencoded({ extended:  false }));

In order for the POST and PUT requests to work, we must be able to read the body of the request, so we add the json and urlencoded middleware to parse the body for us, and be able to access it using req.body.

router.post('/', (req, res) => {
    const todoToCreate = req.body;
    const todo = new Todo(todoToCreate);

    // Save the request
    todo.save();
    res.json(todo);
});
router.put('/:id', (req, res) => {
    const id = req.params.id;
    const todoToUpdate = req.body;
	
    Todo.findById(id, (err, todo) => {
        if (err) {
            console.error(err);
            return res.status(500).end();
        }
		
        todo.complete = todoToUpdate.complete;
        todo.message = todoToUpdate.message;
        todo.save();

        res.json(todo);
    });
});
router.delete('/:id', (req, res) => {
    const id = req.params.id;

    Todo.findByIdAndDelete(id, (err) => {
        if (err) {
            return res.status(500).end();
        }
        res.end();
    })
});

At the end of this file, we will export the router:

export.todoRouter = router;

Final Steps

Now that we have exported the todo routes, we can go back to our index.js and use the routes on the main express app. We will first import the routes using the following line:

const { todoRouter } = require('./routes/todo.routes');

After we import the routes, we will use them on the main app, under the path /todos, like so:

app.use('/todos', TodoRoutes);

Our final index.js will look like the following:

const express = require('express');
const mongoose = require('mongoose');

const { todoRouter } = require('./routes/todo.routes');

mongoose.connect('mongodb://localhost:27017/my_mongo_db');
mongoose.connection.on('error', err => {
    console.log(err);
    process.exit(1);
});

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.get('/', (req, res) => {
    res.json({ message: 'Welcome to the todo API!' });
});

app.use('/todos', TodoRoutes);

app.listen(8080, () => {
    console.log('Server listening on port 8080');
});

And with that, our todo api is complete! Now that it's done, let start the web server using npm start and open Postman to test the routes.

Chris Santos

Full-stack javascript engineer. Blogger, Nomad, Do-It-Yourself-er — author of express project generator Xpresso