Create Rest Api Using Express Node Web Framework

03 Jul 2024 - Syed Muhammad Shahrukh Hussain

A complete guide for creation of REST API endpoints (GET, POST, PUT and DELETE) on Node.js with a tester application to tests the endpoints using Node.js http library.

Brief

Nodejs

Node.js® is a JavaScript runtime environment that is free, open-source, and cross-platform, enabling developers to build servers, web applications, command-line tools, and scripts.

Express

Express is a versatile and lightweight Node.js web application framework offering a comprehensive range of features for both web and mobile applications.

Framework Components

Request

The request is your HTTP request. An HTTP request has a URL with one of the following method (POST, GET, PUT and DELETE). A request is an external component with the framework util it is hits the server where its context is created.

Response

The response is your HTTP response. An HTTP response in an internal component within the framework. This object is created by framework and passed on the router for population of response for the request.

Router

This is the internal component of the framework which is responsible for handling the requests and providing resources for the response. A router is passed in the request and response object. Router is a middle-ware and you can use more than one router for handling request matching a URL.

Router is an optional component, but by using the router you ease the management of the request.

Request that don’t a route are handled as a 404 error which the framework provides.

Listener

This component is responsible for binding the IP and PORT to listen for requests. Registers the routers for handling the request and providing the response.

Setup

Create a directory for the package or application

mkdir restapi-example

Optionally you can initialize the package, which will create package.json

cd rrestapi-example
npm init -y
npm install --save express

Router script

Create a file name default_router.js which expose endpoints for GET, POST, PUT and DELETE. These endpoints operate on a array of user ids. GET return all the user ids, POST adds a new user id, PUT update a user id and DELETE deletes user id.

const express = require('express')
const bodyParser = require('body-parser');
const router = express.Router()

router.use( bodyParser.json() );       // to support JSON-encoded bodies

let userData = [{ 'user': '1' }, { 'user': '2' }];

router.get('/', (req, res) => {
  res.json(userData);
})

router.post('/', (req, res) => {
  //console.log(req.body);
  userData[userData.length] = req.body; // add new user
  res.json({'res':true})
})

router.put('/:userId/:newUserId', (req, res) => {
  for (var  i = 0; i < userData.length; i++)
  {
    if (userData[i].user == req.params.userId)
    {
      userData[i].user = req.params.newUserId; // update user id
    }
  }
  res.json({'res':true})
})

router.delete('/:userId', (req, res) => {
  for (var  i = 0; i < userData.length; i++)
  {
    if (userData[i].user == req.params.userId)
    {
      userData.splice(i, 1);   // delete user
      i--; // element is delete and new length is evaluated so decrement i
    }
  }
  res.json({'res':true})
})

module.exports = router

Application Listener

Create a file named app.js

const express = require('express')
const default_router = require('./default_router')
const app = express()
const port = 3000

app.use('/', default_router)

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

Tester

The tester.js using nodejs http library to make request to the endpoints create and assert response to verify the endpoints are working.

const http = require('node:http');

url = "http://localhost:3000/"

function test_get() {
  http.get(url, (res) => {
    const { statusCode } = res;
    const contentType = res.headers['content-type'];

    let error;
    // Any 2xx status code signals a successful response but
    // here we're only checking for 200.
    if (statusCode !== 200) {
      error = new Error('Request Failed.\n' +
                        `Status Code: ${statusCode}`);
    } else if (!/^application\/json/.test(contentType)) {
      error = new Error('Invalid content-type.\n' +
                        `Expected application/json but received ${contentType}`);
    }
    if (error) {
      console.error(error.message);
      // Consume response data to free up memory
      res.resume();
      return;
    }

    res.setEncoding('utf8');
    let rawData = '';
    res.on('data', (chunk) => { rawData += chunk; });
    res.on('end', () => {
      try {
        const parsedData = JSON.parse(rawData);
        console.log(parsedData);
      } catch (e) {
        console.error(e.message);
      }
    });
  }).on('error', (e) => {
    console.error(`Got error: ${e.message}`);
  });
}

function test_post(postData) {
  const options = {
    hostname: "localhost",
    port: 3000,
    path: '/',
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(postData),
    },
  };
  let req = http.request(options, (res) => {
    const { statusCode } = res;
    const contentType = res.headers['content-type'];

    let error;
    // Any 2xx status code signals a successful response but
    // here we're only checking for 200.
    if (statusCode !== 200) {
      error = new Error('Request Failed.\n' +
                        `Status Code: ${statusCode}`);
    } else if (!/^application\/json/.test(contentType)) {
      error = new Error('Invalid content-type.\n' +
                        `Expected application/json but received ${contentType}`);
    }
    if (error) {
      console.error(error.message);
      // Consume response data to free up memory
      res.resume();
      return;
    }

    res.setEncoding('utf8');
    let rawData = '';
    res.on('data', (chunk) => { rawData += chunk; });
    res.on('end', () => {
      try {
        const parsedData = JSON.parse(rawData);
        console.log(parsedData);
      } catch (e) {
        console.error(e.message);
      }
    });

  }).on('error', (e) => {
    console.error(`Got error: ${e.message}`);
  });
  req.write(postData);
  req.end();
}

function test_delete(user_id) {
  const options = {
    hostname: "localhost",
    port: 3000,
    path: "/" + user_id,
    method: 'DELETE',
  };
  let req = http.request(options, (res) => {
    const { statusCode } = res;
    const contentType = res.headers['content-type'];

    let error;
    // Any 2xx status code signals a successful response but
    // here we're only checking for 200.
    if (statusCode !== 200) {
      error = new Error('Request Failed.\n' +
                        `Status Code: ${statusCode}`);
    } else if (!/^application\/json/.test(contentType)) {
      error = new Error('Invalid content-type.\n' +
                        `Expected application/json but received ${contentType}`);
    }
    if (error) {
      console.error(error.message);
      // Consume response data to free up memory
      res.resume();
      return;
    }

    res.setEncoding('utf8');
    let rawData = '';
    res.on('data', (chunk) => { rawData += chunk; });
    res.on('end', () => {
      try {
        const parsedData = JSON.parse(rawData);
        console.log(parsedData);
      } catch (e) {
        console.error(e.message);
      }
    });

  }).on('error', (e) => {
    console.error(`Got error: ${e.message}`);
  });
  req.end();
}

function test_put(user_id, new_user_id) {
  const options = {
    hostname: "localhost",
    port: 3000,
    path: "/" + user_id + "/" + new_user_id,
    method: 'PUT',
  };
  let req = http.request(options, (res) => {
    const { statusCode } = res;
    const contentType = res.headers['content-type'];

    let error;
    // Any 2xx status code signals a successful response but
    // here we're only checking for 200.
    if (statusCode !== 200) {
      error = new Error('Request Failed.\n' +
                        `Status Code: ${statusCode}`);
    } else if (!/^application\/json/.test(contentType)) {
      error = new Error('Invalid content-type.\n' +
                        `Expected application/json but received ${contentType}`);
    }
    if (error) {
      console.error(error.message);
      // Consume response data to free up memory
      res.resume();
      return;
    }
    res.setEncoding('utf8');
    let rawData = '';
    res.on('data', (chunk) => { rawData += chunk; });
    res.on('end', () => {
      try {
        const parsedData = JSON.parse(rawData);
        console.log(parsedData);
      } catch (e) {
        console.error(e.message);
      }
    });

  }).on('error', (e) => {
    console.error(`Got error: ${e.message}`);
  });
  req.end();
}

/// TESTING

// List the users
test_get();

// Create a new user with id 3
test_post("{\"user\": 3}");

// List the updated users
test_get();

// Delete a user with id 3
test_delete(3);

// List the updated users
test_get();

// Update user with id 2 to 3
test_put(2, 3);

// List the updated users
test_get();

Sources