Getting started with NodeJS and Express
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
afterconst 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.