DEV Community

Cover image for Build a Restful CRUD API with Node.js
Aris Zagakos
Aris Zagakos

Posted on • Updated on

Build a Restful CRUD API with Node.js

Table of Contents

  1. What CRUD API means
  2. Let's Start

What CRUD API means?

The CRUD paradigm stands for the four primitive database operations that are CREATE, READ, UPDATE and DELETE.

So, with the term CRUD API we mean, the API which have the ability to create, read, update and delete entities from a database. For this example, the entity is the employee.

Let's Start

API Endpoints are the following

Methods Urls Description
GET api/employees Get all employees
GET api/employees/id Get a specific employee
POST api/employees Create a new employee
PUT api/employees/id Update an existing employee
DELETE api/employees/id Delete an existing employee

We create the repository and install the dependencies.
The entry point is the server.js file.

 mkdir express-api
 cd express-api
 npm init
 npm install express helmet morgan body-parser monk joi dotenv --save 
 npm install nodemon --save-dev
Enter fullscreen mode Exit fullscreen mode

About the packages

express: It is a minimal and flexible Node.js web application framework.
helmet: It helps in securing HTTP headers in express applications.
morgan: It is an HTTP request logger middleware for Node. js
body-parser: It is responsible for parsing the incoming request bodies.
monk: A tiny layer that provides substantial usability improvements for MongoDB usage.
joi: It is an object schema description language and object validator.
dotenv: It loads environment variables from a .env file.
nodemon: It restarts automatically the node application when file changes in the directory have been detected.

Setup Express Web Server
./src/server.js

const express = require('express');
const morgan = require('morgan');
const helmet = require('helmet');
const bodyParser = require('body-parser');

require('dotenv').config();

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

app.use(helmet());
app.use(morgan('dev'));
app.use(bodyParser.json());

const port = process.env.PORT || 8080;
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});
Enter fullscreen mode Exit fullscreen mode

Create and configure the .env file
./.env
It contains all the enviroment variables that we use.
TEST_DB_URL variable is for test cases in order to prevent the insertion of test data in the database. Also, you can specify the port number you want.

DB_URL = localhost/my-employees
TEST_DB_URL = localhost/test-my-employees
PORT = 5000
Enter fullscreen mode Exit fullscreen mode

./src/db/schema.js
Create the data schema and define the validation rules that the properties name and job have to follow.

const Joi = require('joi');

const schema = Joi.object({
    name: Joi.string()
        .min(3)
        .max(30)
        .required(),
    job: Joi.string()
        .min(3)
        .max(30)
        .required(),
})

module.exports = schema;
Enter fullscreen mode Exit fullscreen mode

./src/db/connection.js
Connect to the database

const monk = require('monk');

let dbUrl = process.env.DB_URL;

if (process.env.NODE_ENV === 'test') {
  dbUrl = process.env.TEST_DB_URL;
}

const db = monk(dbUrl);

module.exports = db;
Enter fullscreen mode Exit fullscreen mode

./src/middlewares/index.js
Create the error middleware to handle the errors and give properly responses.

function notFound(req, res, next) {
    res.status(404);
    const error = new Error('Not Found', req.originalUrl);
    next(error);
}

function errorHandler(err, req, res, next){
    res.status(res.statusCode || 500);
    res.json({
        message: err.message,
        stack: err.stack
    });
}

module.exports = {
    notFound,
    errorHandler
}
Enter fullscreen mode Exit fullscreen mode

We import ./src/db/connection.js, ./src/db/schema.js and ./src/middlewares/index.js files in ./src/server.js

const express = require('express');
const morgan = require('morgan');
const helmet = require('helmet');
const bodyParser = require('body-parser');

const { notFound, errorHandler } = require('./middlewares');

require('dotenv').config();

const schema = require('./db/schema');
const db = require('./db/connection');
const employees = db.get('employees');

const app = express();

app.use(helmet());
app.use(morgan('dev'));
app.use(bodyParser.json());

app.use(notFound);
app.use(errorHandler);

const port = process.env.PORT || 8080;
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});
Enter fullscreen mode Exit fullscreen mode

Now, we code the API Endpoints

const express = require('express');
const morgan = require('morgan');
const helmet = require('helmet');
const bodyParser = require('body-parser');

const { notFound, errorHandler } = require('./middlewares');

require('dotenv').config();

const schema = require('./db/schema');
const db = require('./db/connection');
const employees = db.get('employees');

const app = express();

app.use(helmet());
app.use(morgan('dev'));
app.use(bodyParser.json());

/* Get all employees */
app.get('/', async (req, res, next) => {
    try {
        const allEmployees = await employees.find({});
        res.json(allEmployees);
    } catch(error) {
        next(error);
    }
});

/* Get a specific employee */
app.get('/:id', async (req, res, next) => {
    try {
        const { id } = req.params;
        const employee = await employees.findOne({
            _id: id
        });

        if(!employee) {
            const error = new Error('Employee does not exist');
            return next(error);
        }

    res.json(employee);
    } catch(error) {
        next(error);
    }
});

/* Create a new employee */
app.post('/', async (req, res, next) => {
    try {
        const { name, job } = req.body;
        const result = await schema.validateAsync({ name, job });

        const employee = await employees.findOne({
            name,
        })

        // Employee already exists
        if (employee) {
            res.status(409); // conflict error
            const error = new Error('Employee already exists');
            return next(error);
        } 

        const newuser = await employees.insert({
            name,
            job,
        });

        console.log('New employee has been created');
        res.status(201).json(newuser);
    } catch(error) {
        next(error);
    }
});

/* Update a specific employee */
app.put('/:id', async (req, res, next) => {
    try {
        const { id } = req.params;
        const { name, job } = req.body;
        const result = await schema.validateAsync({ name, job });
        const employee = await employees.findOne({
            _id: id
        });

        // Employee does not exist
        if(!employee) {
            return next();
        }

        const updatedEmployee = await employees.update({
            _id: id,
            }, {  
            $set: result},
            { upsert: true }
        );

        res.json(updatedEmployee);
    } catch(error) {
        next(error);
    }
});

/* Delete a specific employee */
app.delete('/:id', async (req, res, next) => {
    try {
        const { id } = req.params;
        const employee = await employees.findOne({
            _id: id
        });

        // Employee does not exist
        if(!employee) {
            return next();
        }
        await employees.remove({
            _id: id
        });

        res.json({
            message: 'Success'
        });

    } catch(error) {
        next(error);
    }
});

app.use(notFound);
app.use(errorHandler);

const port = process.env.PORT || 8080;
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});
Enter fullscreen mode Exit fullscreen mode

We go to the package.json file and replace the script section with the following

"scripts": {
    "start": "node src/server.js",
    "dev": "nodemon src/server.js"
  },
Enter fullscreen mode Exit fullscreen mode

The command npm start starts the Node.js application and the command npm run dev starts the Node.js application with the only difference that any change we do, will automatically be monitored by nodemon.

We "split" the ./src/server.js and create the ./src/app.js file.

./src/app.js

const express = require('express');
const morgan = require('morgan');
const helmet = require('helmet');
const bodyParser = require('body-parser');

const { notFound, errorHandler } = require('./middlewares');

require('dotenv').config();

const schema = require('./db/schema');
const db = require('./db/connection');
const employees = db.get('employees');

const app = express();

app.use(helmet());
app.use(morgan('dev'));
app.use(bodyParser.json());

/* Get all employees */
app.get('/', async (req, res, next) => {
    try {
        const allEmployees = await employees.find({});
        res.json(allEmployees);
    } catch(error) {
        next(error);
    }
});

/* Get a specific employee */
app.get('/:id', async (req, res, next) => {
    try {
        const { id } = req.params;
        const employee = await employees.findOne({
            _id: id
        });

        if(!employee) {
            const error = new Error('Employee does not exist');
            return next(error);
        }

    res.json(employee);
    } catch(error) {
        next(error);
    }
});

/* Create a new employee */
app.post('/', async (req, res, next) => {
    try {
        const { name, job } = req.body;
        const result = await schema.validateAsync({ name, job });

        const employee = await employees.findOne({
            name,
        })

        // Employee already exists
        if (employee) {
            res.status(409); // conflict error
            const error = new Error('Employee already exists');
            return next(error);
        } 

        const newuser = await employees.insert({
            name,
            job,
        });

        console.log('New employee has been created');
        res.status(201).json(newuser);
    } catch(error) {
        next(error);
    }
});

/* Update a specific employee */
app.put('/:id', async (req, res, next) => {
    try {
        const { id } = req.params;
        const { name, job } = req.body;
        const result = await schema.validateAsync({ name, job });
        const employee = await employees.findOne({
            _id: id
        });

        // Employee does not exist
        if(!employee) {
            return next();
        }

        const updatedEmployee = await employees.update({
            _id: id,
            }, {  
            $set: result},
            { upsert: true }
        );

        res.json(updatedEmployee);
    } catch(error) {
        next(error);
    }
});

/* Delete a specific employee */
app.delete('/:id', async (req, res, next) => {
    try {
        const { id } = req.params;
        const employee = await employees.findOne({
            _id: id
        });

        // Employee does not exist
        if(!employee) {
            return next();
        }
        await employees.remove({
            _id: id
        });

        res.json({
            message: 'Success'
        });

    } catch(error) {
        next(error);
    }
});

app.use(notFound);
app.use(errorHandler);

module.exports = app;
Enter fullscreen mode Exit fullscreen mode

./src/server.js

const app = require('./app');

const port = process.env.PORT || 8080;
app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});
Enter fullscreen mode Exit fullscreen mode

Last step is to refactor our code and create ./src/routes/employees.

./src/routes/employees.js

const express = require('express');
const schema = require('../db/schema');
const db = require('../db/connection');

const employees = db.get('employees');

const router = express.Router();

/* Get all employees */
router.get('/', async (req, res, next) => {
  try {
    const allEmployees = await employees.find({});
    res.json(allEmployees);
  } catch (error) {
    next(error);
  }
});

/* Get a specific employee */
router.get('/:id', async (req, res, next) => {
  try {
    const { id } = req.params;
    const employee = await employees.findOne({
      _id: id,
    });

    if (!employee) {
      const error = new Error('Employee does not exist');
      return next(error);
    }

    res.json(employee);
  } catch (error) {
    next(error);
  }
});

/* Create a new employee */
router.post('/', async (req, res, next) => {
  try {
    const { name, job } = req.body;
    const result = await schema.validateAsync({ name, job });

    const employee = await employees.findOne({
      name,
    });

    // Employee already exists
    if (employee) {
      const error = new Error('Employee already exists');
      res.status(409); // conflict error
      return next(error);
    }

    const newuser = await employees.insert({
        name,
        job,
    });

    res.status(201).json(newuser);
  } catch (error) {
    next(error);
  }
});

/* Update a specific employee */
router.put('/:id', async (req, res, next) => {
  try {
    const { id } = req.params;
    const { name, job } = req.body;
    const result = await schema.validateAsync({ name, job });
    const employee = await employees.findOne({
      _id: id,
    });

    // Employee does not exist
    if (!employee) {
      return next();
    }

    const updatedEmployee = await employees.update({
      _id: id,
    }, { $set: result },
    { upsert: true });

    res.json(updatedEmployee);
  } catch (error) {
    next(error);
  }
});

/* Delete a specific employee */
router.delete('/:id', async (req, res, next) => {
  try {
    const { id } = req.params;
    const employee = await employees.findOne({
      _id: id,
    });

    // Employee does not exist
    if (!employee) {
      return next();
    }
    await employees.remove({
      _id: id,
    });

    res.json({
      message: 'Employee has been deleted',
    });
  } catch (error) {
    next(error);
  }
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

and the ./src/app.js file looks like this

const express = require('express');
const morgan = require('morgan');
const helmet = require('helmet');
const bodyParser = require('body-parser');

const { notFound, errorHandler } = require('./middlewares');

const app = express();

require('dotenv').config();

app.use(helmet());
app.use(morgan('dev'));
app.use(bodyParser.json());

const employees = require('./routes/employees');

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

app.use(notFound);
app.use(errorHandler);

module.exports = app;

Enter fullscreen mode Exit fullscreen mode

You can check the whole project in my github repository express-api

Top comments (6)

Collapse
 
davidmcmurrey2022 profile image
David McMurrey

Thanks for this well-designed presentation, Aris. I followed it carefully but got this error:

at Function.executeUserEntryPoint as runMain {
code: 'MODULE_NOT_FOUND',
requireStack: [ 'C:\Users\David McMurrey\Desktop\express-api\server.js' ]

I'm using Windows 10 or 11. Any ideas how to fix?

David ( admin@mcmassociates.io )

Collapse
 
sabbirsobhani profile image
Sabbir Sobhani

I have a question. Can we make a CRUD or RESTful API using only core Nodejs?

Collapse
 
zagaris profile image
Aris Zagakos

Hi Sabbir. Sure, you can use NodeJS core HTTP built-in module and create a web server, instead of using express or any other framework.

Collapse
 
sabbirsobhani profile image
Sabbir Sobhani

Thank You, Aris.

Collapse
 
juanfabiorey profile image
juanfabiorey

Oh great, and high detailed. Thanks.

Collapse
 
mhenrk profile image
Marcio Henrique

This is amazing.. I was trying to understand api concepts and you teach me much more than I wish