~/blog/crud-rest-api-nodejs-using-expressjs-mongodb
Published on

CRUD REST API Node.js Using Express.js and MongoDB

book7 minutes read

Node.js rest api

Regarding my previous post, Automated Deployment Node.js Using Github Actions in this post, I want to create a tutorial for a CRUD API for a to-do list app using Node.js with the Web Framework Express.js and MongoDB as the database.

Preparation

The list of tools I used while creating this tutorial are:

node.js v15.0.1
npm v7.0.3
yarn v1.22.5
mongodb v3.6.3

Alright... let’s go ahead and create a new directory and initialize the package.json for the project

mkdir nodejs-todo-app
cd nodejs-todo-app
yarn init -y

Then install the required packages

yarn add express dotenv cors mongoose

Before moving on to the steps, let’s create the .env file in the root directory first.

PORT=1337
MONGODB=mongodb://localhost:27017/nama_database

We’ll proceed step by step, starting with creating the model, handler / controller (for Create, Read, Update, Delete processes), then route, and the main file, index.js.

Create Model

First, we create the model for the project. Here, we use the mongoose library for the communication with MongoDB.

Create a models folder in the root directory

mkdir models

Then create the file models/todo.js

// models/todo.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const model = new Schema({
  // This field contains the activities to be done
  text: {
    type: String,
    required: true
  },
  // This field indicates whether the activity has been completed
  checked: {
    type: Boolean,
    required: true
  }
}, {
  // This option is optional
  // It will automatically creates the creation date and update date every time a document is created or updated
  timestamps: {
    createdAt: 'created_at',
    updatedAt: 'updated_at'
  }
});

module.exports = mongoose.model('todos', model);

Create Handler

Next, we create the handler to process create, read, update, and delete data

Create a handlers folder in the root directory

mkdir handlers

Then create the file handlers/todo.js, and import the model we created earlier.

// handlers/todo.js

const modelTodo = require('../models/todo');

// Create Process

// Read All Process

// Read By ID Process

// Update Process

// Delete Process

Create

Add the create function by adding the following code:

// Create Process
exports.create = async (req, res) => {
  // Retrieve data from the text and checked fields
  const { text, checked } = req.body;

  try {
    // Send an error response if the text or checked field is not found
    if (text === undefined || checked === undefined) {
      return res.status(422).json({ message: 'Parameter yang dimasukkan tidak lengkap.' });
    }

    // Create a new document
    const todo = await modelTodo.create({ text, checked });

    // Send a response with the payload of the newly created document
    return res.status(201).json(todo);
  }
  catch (error) {
    console.log(error);
    return res.status(500).json(error);
  }
}

Read All

Add the function to read all or retrieve all existing data by adding the following code:

// Read All Process
exports.getAll = async (req, res) => {
  try {
    // Retrieve all data
    const todos = await modelTodo.find();

    // If we don't add .status(xxx), the response code will be 200
    return res.json(todos);
  }
  catch (error) {
    console.log(error);
    return res.status(500).json(error);
  }
}

Read By ID

Add the function to read by ID or retrieve data based on the ID by adding the following code:

// Read By ID Process
exports.getOne = async (req, res) => {
  // Retrieve the id parameter from the URL
  const { id } = req.params;

  try {
    // If the id parameter is not present, send an error response
    if (id === undefined) {
      return res.status(422).json({ message: 'Parameter id tidak ditemukan.' });
    }
  
    // Retrieve data based on id
    const todo = await modelTodo.findOne({ _id: id });

    // If data is not found, send a 404 status response
    if ( ! todo) {
      return res.status(404).json({ message: 'Data tidak ditemukan.' });
    }
  
    return res.json(todo);
  }
  catch (error) {
    console.log(error);
    return res.status(500).json(error);
  }
}

Update

The update process is quite similar to the create process, except that we need to obtain the ID parameter to locate the document that we want to modify

// Update Process
exports.update = async (req, res) => {
  try {
    // Retrieve the id parameter
    const { id } = req.params;

    // Retrieve data from the fields text and checked
    const { text, checked } = req.body;

    // Send an error response if the fields text or checked are not found
    if (text === undefined || checked === undefined) {
      return res.status(422).json({ message: 'Parameter yang dimasukkan tidak lengkap.' });
    }

    // Update the data
    const todo = await modelTodo.updateOne(
      {
        _id: id
      },
      {
        text,
        checked
      });

    // If no changes are made to a document, it means the document was not found
    // Send a 404 status response
    if (todo.nModified === 0) {
      return res.status(404).json({ message: 'Data tidak ditemukan.' });
    }

    return res.json({ message: 'Sukses.' });
  }
  catch (error) {
    console.log(error);
    return res.status(500).json(error);
  }
}

Delete

Lastly, the process is delete

// Delete Process
exports.delete = async (req, res) => {
  // Retrieve the id parameter
  const { id } = req.params;

  try {
    // delete the data
    const todo = await modelTodo.deleteOne({ _id: id });

    // If no deleted document, it means the document was not found
    // Send a 404 status response
    if (todo.deletedCount === 0) {
      return res.status(404).json({ message: 'Data tidak ditemukan.' })
    }

    return res.json({ message: 'Sukses.' });
  }
  catch (error) {
    console.log(error);
    return res.status(500).json(error);
  }
}

Create Route

The next step is to set up the routing by creating a routes directory and then creating the index.js and todo.js files.

mkdir routes

Create the file routes/todo.js:

// routes/todo.js

const express = require('express');
const router = express.Router();

// Import the handler we created earlier
const todoHandler = require('../handlers/todo');

// This route maps to http://localhost:1337/todos
router.route('/')
  // Route for the Read All process
  .get(todoHandler.getAll)
  // Route for the Create process
  .post(todoHandler.create);

// This route maps to http://localhost:1337/todos/:id
router.route('/:id')
  // Route for the Read By ID process
  .get(todoHandler.getOne)
  // Route for the Update process
  .put(todoHandler.update)
  // Route for the Delete process
  .delete(todoHandler.delete);

module.exports = router;

Create the file routes/index.js:

// routes/index.js

const express = require('express');
const router = express.Router();

// mport the todo routes we created earlier
const todoRoutes = require('./todo');

// This route maps to http://localhost:1337/
router.get('/', async (req, res) => {
  res.json({ message: 'Halo!' });
});
// This route maps to http://localhost:1337/todos
router.use('/todos', todoRoutes);

module.exports = router;

Create the Main File

Finally, let's create the main file to run the server. Create a file named index.js in the root directory of the project:

// ./index.js

// to load the .env file
require('dotenv').config()

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

const port = process.env.PORT || 1337;
const dbUrl = process.env.MONGODB;
// Import the index.js route file we created earlier
// to import an index.js file inside a folder, you only need to reference the folder name
const routes = require('./routes');

const app = express();
// open the connection to MongoDB
const mongooseOptions = {
  useNewUrlParser: true,
  useUnifiedTopology: true
};
mongoose.connect(dbUrl, mongooseOptions);

const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
  // MongoDB is successfully connected
  console.log('MongoDB connected.');
});
// finished opening the connection

app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(routes);

app.listen(port, () => {
  console.log('The magic happen on port :' + port);
});

Run the Server

Finally, try running the server using node, pm2, or nodemon:

$ node index.js
The magic happen on port :1337
MongoDB connected.

If you see this message, the server is running successfully.

Testing

You can test the API using postman, curl, or httpie.

I have created a Postman collection to make testing easier; you can download it here.

You can check the this project repository on my GitHub here.

That's it for now, see you~