Building RESTful Api With Node.js, Express.Js And PostgreSQL

Building RESTful Api With Node.js, Express.Js And PostgreSQL
Building RESTful Api With Node.js, Express.Js And PostgreSQL

After my article about “Generating a Pdf with Nodejs and Cloudinary” i got few feedbacks from folks requesting that i write an article on Building RESTful Api’s with NodeJs. So i decided to put this piece with the hope that it will help somebody.

What we will build?

You’ll learn how to build a public bus transportation booking API — Such that Users book tickets for their trips, can see all their bookings and have the right to cancel any booking.

The only impossible journey is the one you never begin. 🙌— Tony Robbins

Table of Content

  • Getting Started
  • Prerequisite
  • Project Setup
  • Project Structure
  • Setting up Database(PostgrelSQL)
  • Server Helpers
  • Authentication & Authorization with JWT
  • Setup Controllers
  • Setup Routes
  • Server Setup
  • Testing with POSTMAN
  • Conclusion

Getting Started

In this post, I’ll explain how to connect and use PostgreSQL Database to store data. We will also learn how to write some basic [SQL](Structure Query Language) queries. SQL is a standard language for storing, manipulating and retrieving data in databases. To make this simple and so that we can have a better understanding of how SQL works, I’m not going to make use ORM(Object Relational Mapping) in this post. Check out this answer on stackoverflow to get a better understanding of ORM. ORM is basically a technique that allows us to query and manipulate data from the database with little knowledge of SQL. ORM packages expose some methods needed to query and manipulate the data in the database. We have several ORM packages that could be used with our database e.g Sequelize. Instead of using an ORM, we will use PG NodeJS package directly — PG is a NodeJs package for interfacing with the PostgreSQL database. Using PG alone will also give us the opportunity to understand some basic SQL queries as we will be querying and manipulating data in the DB using raw SQL queries.

Prerequisite

Although I’ll try to keep things simple. However, having some basic Knowledge about Nodejs before diving into this Tutorial is important, this post assumes that you have Nodejs and Npm installed in your PC, if you don’t kindly follow this guide.

Project Setup

  • Create a new project directory on your system, you can call it**transportApi**
  • Change working directly to the project and run npm init on your terminal or command prompt if you’re using a window system - Running npm init will prompt you with some questions to help set up your project

This is image title

When that is done, you should see package.json file in your project and it contains basic information about your project.

{
"name": "transportapi",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Beveloper",
"license": "ISC"
}

Installation of all dependencies for the project:

We are going to install the required dependencies needed for this project no fancy external packages e.g Morgan e.t.c.

  • expressjs — Expressjs is a nodejs web application framework that gives us the ability to create quick and easy APIs.
  • PG— PG is a NodeJs package for interfacing with the PostgreSQL database.
  • JSON Web Token (JWT)— is an open standard for securely transmitting information between parties as a JSON object. It’s pronounced jot, or as our Dutch friends would say, yaywaytay. JWT is commonly used for authorization. JWTs can be signed using a secret or a public/private key pair. Once a user is logged in, each subsequent request will require the JWT, allowing the user to access routes, services, and resources that are permitted with that token.
  • Body Parser — Parse incoming request bodies in a middleware before your handlers, available under the req.body property.
  • Moment — Is a lightweight JavaScript date library for parsing, validating, manipulating, and formatting dates.
  • Dotenv — Is a zero-dependency module that loads environment variables from a .env file into [process.env](https://nodejs.org/docs/latest/api/process.html#process_process_env)
  • CORS (Cross Origin Resource Sharing) — Is a node.js package that allows requests to skip the Same-origin policy and access resources from remote hosts.
  • Bcryptjs — is a secured way to help you hash passwords.
  • @babel/polyfill — is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments.
  • babel watch — This is needed for development. One thing that babel watch package does is to compile our code and reload the server each time we make changes to our code.

Run the following command to install all the above packages

$ npm install --save express pg moment body-parser dotenv jsonwebtoken cors make-runnable bcryptjs @babel/polyfill npm-run-all 
$ npm install --save-dev babel-core babel-cli babel-preset-env babel-watch babel-preset-es2015 babel-register

If all went well, you should see something similar to this

This is image title

You’ll notice that express and moment etc is under dependencies, that is because those are needed by the time we deploy our code to production. babel-cli, babel-preset-env and babel-watch etc are only needed during development.

Project Structure

Install PostgreSQL on your system. If you don’t have it installed on your PC walk through this guide

Set up your project structure using the following format;

TransportApi
 |-app
   |-controllers
   |-db
   |-helpers
   |-middlewares
   |-routes
 |-node_modules
 |-.babelrc
 |-.env
 |-env.js
 |-package-lock.json
 |-package.json
 |-server.js

This is image title

Set Up Database

Lets create our DB and name it transportApi. To create the DB, we can make use of any PostgreSQL client such as POSTICO for Mac Users or PgAdmin4 for window users.
If you need a guide on out to setup PostgrelSQL on your PC checkout this easy to read guide

Create TransportApi Table

First, let save our Database URL in the system environment. Your Database URL should be in the following format
postgres://{db_username}:{db_password}@{host}:{port}/{db_name}
e.g postgres://transportUser:develop@localhost:5235/transport

If you were unable to set your DB locally, the easy way out is to make use of any PostgreSQL cloud DB such as ElephantSQL. If you use cloud PostgreSQL DB, copy the URL and use it instead.
My port may differ from yours, so ensure you use the correct p.

ENVIRONMENT VARIABLE SETUP

Open the .env file located in the root directory, copy and paste the codes into it.

#.env
DATABASE_URL=postgres://transportUser:develop@localhost:5235/transport
PORT=5253
  • We defined a few environment variables (**_DATABASE_URL_** AND **_PORT_**) so we can use them later across our project.

Next, we need to create transportApi table in our DB. This is where we will start making use of pg package with basic SQL query to create the table.

Inside our db folder create a folder called **dev**and create three files named dbConnection.js, dbQuery.js and pool.js respectively.

The files should look like this.

pool.js

//db/dev/pool.js

import { Pool } from 'pg';

import dotenv from 'dotenv';

dotenv.config();

const databaseConfig = { connectionString: process.env.DATABASE_URL };
const pool = new Pool(databaseConfig);

export default pool;

Code Steps:

  • Import Pool object from pg. Check pg documentation to read more on pooling. We use this to connect to our PostgreSQL Db. Pool is the easiest and common way of using pg. You can also make use of their Client API to connect to the DB.
  • Import dotenv from dotenv and load it using dotenv.config() - what this does is to search for .env file in our project and load its content into the system environment so we can use node process.env to access those variables.
  • We create a new instance of Pool and pass in connectionString to its constructor. We use process.env.DATABASE_URL to get DATABASE_URL variable from the system environment.
  • We listened to pool connect event and console log connected to the db
//db/dev/dbQuery.js

import pool from './pool';

export default {
  /**
   * DB Query
   * @param {object} req
   * @param {object} res
   * @returns {object} object
   */
  query(quertText, params) {
    return new Promise((resolve, reject) => {
      pool.query(quertText, params)
        .then((res) => {
          resolve(res);
        })
        .catch((err) => {
          reject(err);
        });
    });
  },
};

Code Steps:

  • We created a new method query that takes in two arguments text - query text and params - values required by text. These two arguments are what is needed to query the DB. The method returns a promise and we will call it in our controller. Click here to read more about JavaScript Promise.
//db/dev/dbConnection.js

import pool from './pool';

pool.on('connect', () => {
  console.log('connected to the db');
});

/**
 * Create User Table
 * CREATE TABLE test
  (id SERIAL PRIMARY KEY, 
  name VARCHAR(100) UNIQUE NOT NULL, 
  phone VARCHAR(100));
 */
const createUserTable = () => {
  const userCreateQuery = `CREATE TABLE IF NOT EXISTS users
  (id SERIAL PRIMARY KEY, 
  email VARCHAR(100) UNIQUE NOT NULL, 
  first_name VARCHAR(100), 
  last_name VARCHAR(100), 
  password VARCHAR(100) NOT NULL,
  created_on DATE NOT NULL)`;

  pool.query(userCreateQuery)
    .then((res) => {
      console.log(res);
      pool.end();
    })
    .catch((err) => {
      console.log(err);
      pool.end();
    });
};

/**
 * Create Buses Table
 */
const createBusTable = () => {
  const busCreateQuery = `CREATE TABLE IF NOT EXISTS bus
    (id SERIAL PRIMARY KEY,
    number_plate VARCHAR(100) NOT NULL,
    manufacturer VARCHAR(100) NOT NULL,
    model VARCHAR(100) NOT NULL,
    year VARCHAR(10) NOT NULL,
    capacity integer NOT NULL,
    created_on DATE NOT NULL)`;

  pool.query(busCreateQuery)
    .then((res) => {
      console.log(res);
      pool.end();
    })
    .catch((err) => {
      console.log(err);
      pool.end();
    });
};

/**
 * Create Trip Table
 */
const createTripTable = () => {
  const tripCreateQuery = `CREATE TABLE IF NOT EXISTS trip
    (id SERIAL PRIMARY KEY, 
    bus_id INTEGER REFERENCES bus(id) ON DELETE CASCADE,
    origin VARCHAR(300) NOT NULL, 
    destination VARCHAR(300) NOT NULL,
    trip_date DATE NOT NULL,
    fare float NOT NULL,
    status float DEFAULT(1.00),
    created_on DATE NOT NULL)`;

  pool.query(tripCreateQuery)
    .then((res) => {
      console.log(res);
      pool.end();
    })
    .catch((err) => {
      console.log(err);
      pool.end();
    });
};

/**
 * Create Booking Table
 */
const createBookingTable = () => {
  const bookingCreateQuery = `CREATE TABLE IF NOT EXISTS booking(id SERIAL, 
    trip_id INTEGER REFERENCES trip(id) ON DELETE CASCADE,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    bus_id INTEGER REFERENCES bus(id) ON DELETE CASCADE,
    trip_date DATE, 
    seat_number INTEGER UNIQUE,      
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL,      
    created_on DATE NOT NULL,
    PRIMARY KEY (id, trip_id, user_id))`;
  pool.query(bookingCreateQuery)
    .then((res) => {
      console.log(res);
      pool.end();
    })
    .catch((err) => {
      console.log(err);
      pool.end();
    });
};

/**
 * Drop User Table
 */
const dropUserTable = () => {
  const usersDropQuery = 'DROP TABLE IF EXISTS users';
  pool.query(usersDropQuery)
    .then((res) => {
      console.log(res);
      pool.end();
    })
    .catch((err) => {
      console.log(err);
      pool.end();
    });
};


/**
 * Drop Bus Table
 */
const dropBusTable = () => {
  const busDropQuery = 'DROP TABLE IF EXISTS bus';
  pool.query(busDropQuery)
    .then((res) => {
      console.log(res);
      pool.end();
    })
    .catch((err) => {
      console.log(err);
      pool.end();
    });
};

/**
 * Drop Trip Table
 */
const dropTripTable = () => {
  const tripDropQuery = 'DROP TABLE IF EXISTS trip';
  pool.query(tripDropQuery)
    .then((res) => {
      console.log(res);
      pool.end();
    })
    .catch((err) => {
      console.log(err);
      pool.end();
    });
};

/**
 * Drop Bus Table
 */
const dropBookingTable = () => {
  const bookingDropQuery = 'DROP TABLE IF EXISTS booking';
  pool.query(bookingDropQuery)
    .then((res) => {
      console.log(res);
      pool.end();
    })
    .catch((err) => {
      console.log(err);
      pool.end();
    });
};


/**
 * Create All Tables
 */
const createAllTables = () => {
  createUserTable();
  createBusTable();
  createTripTable();
  createBookingTable();
};


/**
 * Drop All Tables
 */
const dropAllTables = () => {
  dropUserTable();
  dropBusTable();
  dropTripTable();
  dropBookingTable();
};

pool.on('remove', () => {
  console.log('client removed');
  process.exit(0);
});


export {
  createAllTables,
  dropAllTables,
};

require('make-runnable');

dbConnection.js

Code Steps:

  • Import Pool object from pg. Check pg documentation to read more on pooling. We use this to connect to our PostgreSQL Db. Pool is the easiest and common way of using pg. You can also make use of their Client API to connect to the DB.
  • We listened to pool connect event and console log connected to the db
  • We set up a createTables() function, inside the function is a query that creates userTbale, busTable, tripTable and bookingTable respectively with unique fields.
const createUserTable = () => {
   const userCreateQuery = `CREATE TABLE IF NOT EXISTS users
   (id SERIAL PRIMARY KEY,
   email VARCHAR(100) UNIQUE NOT NULL,
   first_name VARCHAR(100),
   last_name VARCHAR(100),
   password VARCHAR(100) NOT NULL,
   created_on DATE NOT NULL)`;

What the above does is to tell PostgreSQL DB to create users table if users table does not exist with fields listed and the same goes for the rest of the tables respectively.

Using the first Table(usersTable) as Case study, we observe the following;

  • We call pool query method with userCreateQuery as an argument and it returns a promise.
    We called pool.end() to close pool connection to the db.
  • We created another function called dropTables() - What this does it to delete users table.
    We set up a new query DROP TABLE IF EXISTS users, that drops users table if it exists in the DB.
  • We use pool.on('remove') to listened to pool remove event and use process.exit(0) to exit the node process.
  • Lastly, we require make-runnable package - We need this to be able to call and any of our two functions from the terminal.
    Note: You have to require make-runnable at the end. Remember we’d earlier installed make-runnable as a project dev-dependency.
    You’ll also notice that we used require instead of import, this is because we only want to run db.js file from the terminal alone and it is not directly part of our project so there is no point in compiling it.
    Finally, lets run createTables function to create our tables. To do this we need to add a command in our Package.json file.

In your start script remove the test command and add this

"scripts": {
"create-dev-tables": "babel-node ./app/db/dev/dbConnection createAllTables",
"start": "nodemon --watch . --exec babel-node -- server",
"setup": "npm-run-all -p start create-dev-tables"
},

Your package.json should know look like this
This is image title

Next, we run the command $ **npm run setup**

This is image title

After running the above, you should see something similar below;

This is image title

NOTE: Instead of going through the above process, you can also create tables directly either using any PostgreSQL client e.g pgAdmin, POSTICO or using PostgreSQL commands on the terminal.

SETUP HELPERS

Before diving to the core project, there’s need to create a few helper methods, that would foster resuability across our project.

Copy the following and paste it inside **_app/helpers/validations.js_**

//app/helpers/validation.js

import env from '../../env';

/**
   * isValidEmail helper method
   * @param {string} email
   * @returns {Boolean} True or False
   */
const isValidEmail = (email) => {
  const regEx = /\S+@\S+\.\S+/;
  return regEx.test(email);
};

/**
   * validatePassword helper method
   * @param {string} password
   * @returns {Boolean} True or False
   */
const validatePassword = (password) => {
  if (password.length <= 5 || password === '') {
    return false;
  } return true;
};
/**
   * isEmpty helper method
   * @param {string, integer} input
   * @returns {Boolean} True or False
   */
const isEmpty = (input) => {
  if (input === undefined || input === '') {
    return true;
  }
  if (input.replace(/\s/g, '').length) {
    return false;
  } return true;
};

/**
   * empty helper method
   * @param {string, integer} input
   * @returns {Boolean} True or False
   */
const empty = (input) => {
  if (input === undefined || input === '') {
    return true;
  }
};

export {
  isValidEmail,
  validatePassword,
  isEmpty,
  empty
};

validations.js

Code Steps:

  • isValidEmail() method uses regex(Regular Expression) in validating user’s provided email address.
    Check this guide to learn more about Regex
  • validatePassword — This method checks if the length of the password provided by a user at the time of signup or signin is less than or equal to 5 characters.
  • isEmpty method checks if any input provided by a user is undefined or empty.
  • We exports this methods so we can use them across our projects.

Copy the following and paste it inside **_app/helpers/status.js_**

//app/helpers/status.js

const successMessage = { status: 'success' };
const errorMessage = { status: 'error' };
const status = {
  success: 200,
  error: 500,
  notfound: 404,
  unauthorized: 401,
  conflict: 409,
  created: 201,
  bad: 400,
  nocontent: 204,
};

const trip_statuses = {
  active: 1.00,
  cancelled: 2.00,
}
export {
  successMessage,
  errorMessage,
  status,
  trip_statuses,
};

status.js

Code Steps:

  • Created an Object status and defined a few properties with values.
  • Success — This returns a status code of 200 when a user performs a post request
  • Error — This returns a status code of 500 when a user encounters a glitch
  • Notfound — As the name implies when a user tries to perform an undefined request a status code of 404 is returned, another name for forbidden😜.
  • Unauthorized — Such status occurs when a user accesses a path/page without been authorized, in this case a status code of 401 is returned.
  • Created — When data is created, a status code of 201 is returned.
  • Bad — A good use case is when an invalid email is provided, a status code of 400 is returned.
  • Nocontent — When a user submits an empty form, then a status code of 400 is returned.
  • Conflict — A good use case is when a user inputs an email that already exists. A 409 status code should be returned.
  • We exports this methods so we can use them across our projects.

AUTHORIZATION AND AUTHENTICATION_(JWT)_

As we proceed in thus tutorial our project would have a few routes that need protecting and some user’s that need authorizing. Much like myself at one point, you’re probably wondering how this can be achieved. Thankfully, we have JSON Web Tokens (JWT) (among other things) for that.

JSON Web Token (JWT) is an open standard that defines a compact and self-contained way of securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.

Authorization is the most common scenario for using JWT. Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token. Single sign-on is a feature that widely uses JWT nowadays, because of its small overhead and its ability to be easily used across different domains.

There are some advantages of using JWT for authorization:

  1. Purely stateless. No additional server or infra required to store session information.
  2. It can be easily shared among services.

In its compact form, JSON Web Tokens consist of three parts separated by dots (.), which are:

  • Header
  • Payload
  • Signature

Therefore, a JWT typically looks like the following.

xxxxx.yyyyy.zzzzz

Now, time for some code.

This is image title

AUTHENTICATION jwt.sign()

jwt.sign(payload, secretkey, [options, callback])

The first functionjwt.sign() will generate a JWT token, assign it to a user object, and then return that JWT token so we can pass it where ever we may need. It can be either asynchronous or synchronous depending if a callback is supplied. The payload parameter will be the user object in our case, the secretkey is made up by you, and it can be anything. The callback parameter is where we handle sending our token, and the options parameter will be where can set an expiration time among other things.

Copy the following codes and paste it _app/helpers/validations.js_

//app/helpers/validation.js

import env from '../../env';

/**
   * isValidEmail helper method
   * @param {string} email
   * @returns {Boolean} True or False
   */
const isValidEmail = (email) => {
  const regEx = /\S+@\S+\.\S+/;
  return regEx.test(email);
};

/**
   * validatePassword helper method
   * @param {string} password
   * @returns {Boolean} True or False
   */
const validatePassword = (password) => {
  if (password.length <= 5 || password === '') {
    return false;
  } return true;
};
/**
   * isEmpty helper method
   * @param {string, integer} input
   * @returns {Boolean} True or False
   */
const isEmpty = (input) => {
  if (input === undefined || input === '') {
    return true;
  }
  if (input.replace(/\s/g, '').length) {
    return false;
  } return true;
};

/**
   * empty helper method
   * @param {string, integer} input
   * @returns {Boolean} True or False
   */
const empty = (input) => {
  if (input === undefined || input === '') {
    return true;
  }
};

export {
  isValidEmail,
  validatePassword,
  isEmpty,
  empty
};

Code Steps:

Inside validation.js we have a function generateUserToken

This is image title

  • We imported jsonwebtoken(JWT) and dotenv from their respective modules.
  • We created a function generateUserToken , we used jwt.sign(..) in signing user’s token, by sending email, user_id, is_admin(will be discussed later ), first_name and last_name as the payload, we also passed our secret and setting token to expire in 3 days. .

**_AUTHORIZATION

jwt.verify()_**

jwt.verify(token, secretkey, [options, callback])
jwt.verify() will verify the users token when a protected route is accessed. It takes in the token as one parameter, the secret key that you defined in the jwt.sign() function, and then you have the options and callback parameters. The callback will be where we can access and send protected data.

Copy the following and paste it _app/middleware/verifyAuth.js_

//app/middleware/verifyAuth.js
 
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';
import {
  errorMessage, status,
} from '../helpers/status';

import env from '../../env';

dotenv.config();

/**
   * Verify Token
   * @param {object} req 
   * @param {object} res 
   * @param {object} next
   * @returns {object|void} response object 
   */

const verifyToken = async (req, res, next) => {
  const { token } = req.headers;
  if (!token) {
    errorMessage.error = 'Token not provided';
    return res.status(status.bad).send(errorMessage);
  }
  try {
    const decoded =  jwt.verify(token, process.env.SECRET);
    req.user = {
      email: decoded.email,
      user_id: decoded.user_id,
      is_admin: decoded.is_admin,
      first_name: decoded.first_name,
      last_name: decoded.last_name,
    };
    next();
  } catch (error) {
    errorMessage.error = 'Authentication Failed';
    return res.status(status.unauthorized).send(errorMessage);
  }
};

export default verifyToken;

verifyAuth.js

Code Steps:

Here, we create a new Auth object with verifyToken() method. What verifyToken() method does is basically to validate and decode user request token using the same secret key we used in signing the token. We used **const{token} = req.headers** to get the token from the request header and send it to jwt.verify(..) along with the secret we used in signing the token. If the token is valid, we retrieve **_email, user_id, is_admin,first_name, last_name_**from the token payload and query the DB to make sure the user exists in the DB. If the user exists in the DB, we created a new object property in the req object. We will use this to process other requests handler. Finally, since this method is a middleware, we used next() in moving to the next request handler. If any error occurred in here, we return an error message back to the user without having to move to the next request handler.

SETUP CONTROLLERS

Next, we need to setup our controllers for our projects and starting with is our admin and User controller.

adminController

Copy the following and paste it inside **_app/controllers/adminController.js_**

//app/controller/adminController.js

import moment from 'moment';

import dbQuery from '../db/dev/dbQuery';

import {
  hashPassword,
  isValidEmail,
  validatePassword,
  isEmpty,
  generateUserToken,
} from '../helpers/validations';

import {
  errorMessage, successMessage, status,
} from '../helpers/status';

/**
   * Create A Admin
   * @param {object} req
   * @param {object} res
   * @returns {object} reflection object
   */
const createAdmin = async (req, res) => {
  const {
    email, first_name, last_name, password,
  } = req.body;

  const { is_admin } = req.user;

  const isAdmin = true;
  const created_on = moment(new Date());

  if (!is_admin === false) {
    errorMessage.error = 'Sorry You are unauthorized to create an admin';
    return res.status(status.bad).send(errorMessage);
  }

  if (isEmpty(email) || isEmpty(first_name) || isEmpty(last_name) || isEmpty(password)) {
    errorMessage.error = 'Email, password, first name and last name field cannot be empty';
    return res.status(status.bad).send(errorMessage);
  }
  if (!isValidEmail(email)) {
    errorMessage.error = 'Please enter a valid Email';
    return res.status(status.bad).send(errorMessage);
  }
  if (!validatePassword(password)) {
    errorMessage.error = 'Password must be more than five(5) characters';
    return res.status(status.bad).send(errorMessage);
  }
  const hashedPassword = hashPassword(password);
  const createUserQuery = `INSERT INTO
      users(email, first_name, last_name, password, is_admin, created_on)
      VALUES($1, $2, $3, $4, $5, $6)
      returning *`;
  const values = [
    email,
    first_name,
    last_name,
    hashedPassword,
    isAdmin,
    created_on,
  ];

  try {
    const { rows } = await dbQuery.query(createUserQuery, values);
    const dbResponse = rows[0];
    delete dbResponse.password;
    const token = generateUserToken(dbResponse.email, dbResponse.id, dbResponse.is_admin, dbResponse.first_name, dbResponse.last_name);
    successMessage.data = dbResponse;
    successMessage.data.token = token;
    return res.status(status.created).send(successMessage);
  } catch (error) {
    if (error.routine === '_bt_check_unique') {
      errorMessage.error = 'Admin with that EMAIL already exist';
      return res.status(status.conflict).send(errorMessage);
    }
  }
};

/**
 * Update A User to Admin
 * @param {object} req 
 * @param {object} res 
 * @returns {object} updated user
 */
const updateUserToAdmin = async (req, res) => {
  const { id } = req.params;
  const { isAdmin } = req.body;

  const { is_admin } = req.user;
  if (!is_admin === true) {
    errorMessage.error = 'Sorry You are unauthorized to make a user an admin';
    return res.status(status.bad).send(errorMessage);
  }
  if (isAdmin === '') {
    errorMessage.error = 'Admin Status is needed';
    return res.status(status.bad).send(errorMessage);
  }
  const findUserQuery = 'SELECT * FROM users WHERE id=$1';
  const updateUser = `UPDATE users
        SET is_admin=$1 WHERE id=$2 returning *`;
  try {
    const { rows } = await dbQuery.query(findUserQuery, [id]);
    const dbResponse = rows[0];
    if (!dbResponse) {
      errorMessage.error = 'User Cannot be found';
      return res.status(status.notfound).send(errorMessage);
    }
    const values = [
      isAdmin,
      id,
    ];
    const response = await dbQuery.query(updateUser, values);
    const dbResult = response.rows[0];
    delete dbResult.password;
    successMessage.data = dbResult;
    return res.status(status.success).send(successMessage);
  } catch (error) {
    errorMessage.error = 'Operation was not successful';
    return res.status(status.error).send(errorMessage);
  }
};

export {
  createAdmin,
  updateUserToAdmin,
};

adminController.js
Code Steps:

Note: There are a few routes that needs an admin privilege to access

  • Here, we created two methods — createAdminand updateUserToAdmin. We also made use of async/await.
  • we Import moment, and the reason for that is because we’re going to be using it for date creation.
  • We imported our dbQuery from app/db/dev/dbQuery.js.
  • we also imported our status helper methods empty and errorMessage,successMessage, statusfrom app/helpers/status.js.
  • we also imported our validations helper methods hashPassword and comparePassword, isValidEmail, validPassword, isEmpty, generateUserTokenfrom app/helpers/validations.js.
  • We created a function createAdmin , in it we deconstructed our values email, first_name, last_name, is_admin, password from the request body.
  • Next, we created a variable named created_on, which holds the date of our created user table.
  • Next we check if our email is a valid, using our helper method **_isValidEmail._**
  • Also, we check if our password characters is more than 5.
  • Using our helper method hashPassword , we hash our password using the bcryptjs module.

usersController

Copy the following and paste it inside **_app/controllers/usersController.js_**

//app/controller/usersController.js

import moment from 'moment';

import dbQuery from '../db/dev/dbQuery';

import {
  hashPassword,
  comparePassword,
  isValidEmail,
  validatePassword,
  isEmpty,
  generateUserToken,
} from '../helpers/validations';

import {
  errorMessage, successMessage, status,
} from '../helpers/status';

/**
   * Create A User
   * @param {object} req
   * @param {object} res
   * @returns {object} reflection object
   */
const createUser = async (req, res) => {
  const {
    email, first_name, last_name, password,
  } = req.body;

  const created_on = moment(new Date());
  if (isEmpty(email) || isEmpty(first_name) || isEmpty(last_name) || isEmpty(password)) {
    errorMessage.error = 'Email, password, first name and last name field cannot be empty';
    return res.status(status.bad).send(errorMessage);
  }
  if (!isValidEmail(email)) {
    errorMessage.error = 'Please enter a valid Email';
    return res.status(status.bad).send(errorMessage);
  }
  if (!validatePassword(password)) {
    errorMessage.error = 'Password must be more than five(5) characters';
    return res.status(status.bad).send(errorMessage);
  }
  const hashedPassword = hashPassword(password);
  const createUserQuery = `INSERT INTO
      users(email, first_name, last_name, password, created_on)
      VALUES($1, $2, $3, $4, $5)
      returning *`;
  const values = [
    email,
    first_name,
    last_name,
    hashedPassword,
    created_on,
  ];

  try {
    const { rows } = await dbQuery.query(createUserQuery, values);
    const dbResponse = rows[0];
    delete dbResponse.password;
    const token = generateUserToken(dbResponse.email, dbResponse.id, dbResponse.is_admin, dbResponse.first_name, dbResponse.last_name);
    successMessage.data = dbResponse;
    successMessage.data.token = token;
    return res.status(status.created).send(successMessage);
  } catch (error) {
    if (error.routine === '_bt_check_unique') {
      errorMessage.error = 'User with that EMAIL already exist';
      return res.status(status.conflict).send(errorMessage);
    }
    errorMessage.error = 'Operation was not successful';
    return res.status(status.error).send(errorMessage);
  }
};

/**
   * Signin
   * @param {object} req
   * @param {object} res
   * @returns {object} user object
   */
const siginUser = async (req, res) => {
  const { email, password } = req.body;
  if (isEmpty(email) || isEmpty(password)) {
    errorMessage.error = 'Email or Password detail is missing';
    return res.status(status.bad).send(errorMessage);
  }
  if (!isValidEmail(email) || !validatePassword(password)) {
    errorMessage.error = 'Please enter a valid Email or Password';
    return res.status(status.bad).send(errorMessage);
  }
  const signinUserQuery = 'SELECT * FROM users WHERE email = $1';
  try {
    const { rows } = await dbQuery.query(signinUserQuery, [email]);
    const dbResponse = rows[0];
    if (!dbResponse) {
      errorMessage.error = 'User with this email does not exist';
      return res.status(status.notfound).send(errorMessage);
    }
    if (!comparePassword(dbResponse.password, password)) {
      errorMessage.error = 'The password you provided is incorrect';
      return res.status(status.bad).send(errorMessage);
    }
    const token = generateUserToken(dbResponse.email, dbResponse.id, dbResponse.is_admin, dbResponse.first_name, dbResponse.last_name);
    delete dbResponse.password;
    successMessage.data = dbResponse;
    successMessage.data.token = token;
    return res.status(status.success).send(successMessage);
  } catch (error) {
    errorMessage.error = 'Operation was not successful';
    return res.status(status.error).send(errorMessage);
  }
};

export {
  createUser,
  siginUser,
};

usersController.js

Code Steps:

  • Here, we created two methods — createUserand signUser. We also made use of async/await.
  • we Import moment, and the reason for that is because we’re going to be using it for date creation.
  • We imported our dbQuery from app/db/dev/dbQuery.js.
  • we also imported our status helper methods empty and errorMessage,successMessage, statusfrom app/helpers/status.js.
  • we also imported our validations helper methods hashPassword and comparePassword, isValidEmail, validPassword, isEmpty, generateUserTokenfrom app/helpers/validations.js.
  • We created a function createUser , in it we deconstructed our values email, first_name, last_name, password from the request body.
  • Next, we created a variable named created_on, which holds the date of our created user table.
  • Next we check if our email is a valid, using our helper method **_isValidEmail._**
  • Also, we check if our password characters is more than 5.
  • Using our helper method hashPassword , we hash our password using the bcryptjs module.
  • Next, we created a variable named created_on, which holds the date of our created bus table.
  • For our SQL query, we used INSERT INTO users(list_columns_here..) VALUES($1, $2, $3, $4, $5...) - what this does is create a new row in users table and insert the supplied values into its fields. values is an array of values that contains what we want to insert into the table. The elements inside values array must be in the same order as $1, $2, $3, $4, $5. We used returning * to return the created row. Remembered we created query method that takes in two arguments quertText and params inside app/db/dev/dbQuery.js, this is where we will use it. We called the method and send in createUserQuery and values as parameters.
  • we made use of try and catch, i 💕 it.
  • Using generateUserToken helper method we generated our user token.
    JWT will be discussed later in this post.
  • **__bt_check_unique_** — Checks for duplicate values and returns an error.

Since dbQuery.query returns a promise we make use of async/await to make our code looks sexy😘.

  • **_signUser_** - We set up SELECT * FROM users WHERE email = $1to get a user with a specific email**.**
  • Similar to createUser function, it checks for if email and password is incorrect, it also compare the passwords and ensures that both password and email inputed by the user are correct before signing them.
  • Also, a user gets a Token, when he signs in. Thats made possible by our generateUserToken.
  • And finally we exported our methods createUser and signUserrespectively, so we can use them across our project.

busController

Copy the following and paste it inside **_app/controllers/busController.js_**

//app/controllers/busControllers.js

import moment from 'moment';

import dbQuery from '../db/dev/dbQuery';

import {
  empty,
} from '../helpers/validations';


import {
  errorMessage, successMessage, status,
} from '../helpers/status';


/**
   * Add A Bus
   * @param {object} req
   * @param {object} res
   * @returns {object} reflection object
   */
const addBusDetails = async (req, res) => {
  const {
    number_plate, manufacturer, model, year, capacity,
  } = req.body;

  const created_on = moment(new Date());

  if (empty(number_plate) || empty(manufacturer) || empty(model) || empty(year)
  || empty(capacity)) {
    errorMessage.error = 'All fields are required';
    return res.status(status.bad).send(errorMessage);
  }
  const createBusQuery = `INSERT INTO
          bus(number_plate, manufacturer, model, year, capacity, created_on)
          VALUES($1, $2, $3, $4, $5, $6)
          returning *`;
  const values = [
    number_plate,
    manufacturer,
    model,
    year,
    capacity,
    created_on,
  ];
    
  try {
    const { rows } = await dbQuery.query(createBusQuery, values);
    const dbResponse = rows[0];
    successMessage.data = dbResponse;
    return res.status(status.created).send(successMessage);
  } catch (error) {
    errorMessage.error = 'Unable to add bus';
    return res.status(status.error).send(errorMessage);
  }
};

/**
   * Get All Buses
   * @param {object} req 
   * @param {object} res 
   * @returns {object} buses array
   */
const getAllBuses = async (req, res) => {
  const getAllBusQuery = 'SELECT * FROM bus ORDER BY id DESC';
  try {
    const { rows } = await dbQuery.query(getAllBusQuery);
    const dbResponse = rows;
    if (dbResponse[0] === undefined) {
      errorMessage.error = 'There are no buses';
      return res.status(status.notfound).send(errorMessage);
    }
    successMessage.data = dbResponse;
    return res.status(status.success).send(successMessage);
  } catch (error) {
    errorMessage.error = 'An error Occured';
    return res.status(status.error).send(errorMessage);
  }
};


export {
  addBusDetails,
  getAllBuses,
};

busController.js

Code Steps:

  • Here, we created two methods - addBusDetails()and getAllBuses(). We also made use of async/await.
  • we Import moment, and the reason for that is because we’re going to be using it for creating our date.
  • addbusDetails() - We imported our dbQuery from app/db/dev/dbQuery.js.
  • we also imported our helper methods empty and errorMessage,successMessage, statusfrom app/helpers/validations.js. and app/helpers/status.js respectively.
  • We created a function addBusDetails() , in it we deconstructed our values number_plate, manufacturer, model, year, capacity from the request body.
  • Next, we created a variable named created_on, which holds the date of our created user table.
  • We also check to see if the input fields are empty using our **_empty_** method, if its empty it sends an error “all fields are required” and returns a status code of 400.
  • For our SQL query, we used INSERT INTO bus(list_columns_here..) VALUES($1, $2, $3, $4, $5, $6 ...) - what this does is create a new row in bus table and insert the supplied values into its fields. values is an array of values that contains what we want to insert into the table. The elements inside values array must be in the same order as $1, $2, $3, $4, $5, $6. We used returning * to return the created row. Remembered we created query method that takes in two arguments quertText and params inside app/db/dev/dbQuery.js, this is where we will use it. We called the method and send in createBusQuery and values as parameters. Since dbQuery.query returns a promise we make use of async/await to make our code looks sexy😘.

tripController

Next, we’d be creating our tripController, similar to the bus Controller.

Copy the following and paste it inside **_app/controllers/tripController.js_**

// app/controllers/tripController.js

import moment from 'moment';

import dbQuery from '../db/dev/dbQuery';

import {
  isEmpty, empty,
} from '../helpers/validations';


import {
  errorMessage, successMessage, status, trip_statuses,
} from '../helpers/status';

/**
   * Create A Trip
   * @param {object} req
   * @param {object} res
   * @returns {object} reflection object
   */
const createTrip = async (req, res) => {
  const {
    bus_id, origin, destination, trip_date, fare,
  } = req.body;

  const { is_admin } = req.user;
  if (!is_admin === true) {
    errorMessage.error = 'Sorry You are unauthorized to create a trip';
    return res.status(status.bad).send(errorMessage);
  }

  const created_on = moment(new Date());

  if (empty(bus_id) || isEmpty(origin) || isEmpty(destination) || empty(trip_date) || empty(fare)) {
    errorMessage.error = 'Origin, Destination, Trip Date and Fare, field cannot be empty';
    return res.status(status.bad).send(errorMessage);
  }
  const createTripQuery = `INSERT INTO
          trip(bus_id, origin, destination, trip_date, fare, created_on)
          VALUES($1, $2, $3, $4, $5, $6)
          returning *`;
  const values = [
    bus_id,
    origin,
    destination,
    trip_date,
    fare,
    created_on,
  ];

  try {
    const { rows } = await dbQuery.query(createTripQuery, values);
    const dbResponse = rows[0];
    successMessage.data = dbResponse;
    return res.status(status.created).send(successMessage);
  } catch (error) {
    errorMessage.error = 'Unable to create trip';
    return res.status(status.error).send(errorMessage);
  }
};

/**
   * Get All Trips
   * @param {object} req
   * @param {object} res
   * @returns {object} trips array
   */
const getAllTrips = async (req, res) => {
  const getAllTripsQuery = 'SELECT * FROM trip ORDER BY id DESC';
  try {
    const { rows } = await dbQuery.query(getAllTripsQuery);
    const dbResponse = rows;
    if (!dbResponse[0]) {
      errorMessage.error = 'There are no trips';
      return res.status(status.notfound).send(errorMessage);
    }
    successMessage.data = dbResponse;
    return res.status(status.success).send(successMessage);
  } catch (error) {
    errorMessage.error = 'Operation was not successful';
    return res.status(status.error).send(errorMessage);
  }
};

/**
   * cancel A Trip
   * @param {object} req
   * @param {object} res
   * @returns {void} return Trip cancelled successfully
   */
const cancelTrip = async (req, res) => {
  const { tripId } = req.params;
  const { is_admin } = req.user;
  const { cancelled } = trip_statuses;
  if (!is_admin === true) {
    errorMessage.error = 'Sorry You are unauthorized to cancel a trip';
    return res.status(status.bad).send(errorMessage);
  }
  const cancelTripQuery = 'UPDATE trip SET status=$1 WHERE id=$2 returning *';
  const values = [
    cancelled,
    tripId,
  ];
  try {
    const { rows } = await dbQuery.query(cancelTripQuery, values);
    const dbResponse = rows[0];
    if (!dbResponse) {
      errorMessage.error = 'There is no trip with that id';
      return res.status(status.notfound).send(errorMessage);
    }
    successMessage.data = {};
    successMessage.data.message = 'Trip cancelled successfully';
    return res.status(status.success).send(successMessage);
  } catch (error) {
    errorMessage.error = 'Operation was not successful';
    return res.status(status.error).send(errorMessage);
  }
};

/**
 * filter trips by origin
 * @param {object} req
 * @param {object} res
 * @returns {object} returned trips
 */
const filterTripByOrigin = async (req, res) => {
  const { origin } = req.query;

  const findTripQuery = 'SELECT * FROM trip WHERE origin=$1 ORDER BY id DESC';
  try {
    const { rows } = await dbQuery.query(findTripQuery, [origin]);
    const dbResponse = rows;
    if (!dbResponse[0]) {
      errorMessage.error = 'No Trips with that origin';
      return res.status(status.notfound).send(errorMessage);
    }
    successMessage.data = dbResponse;
    return res.status(status.success).send(successMessage);
  } catch (error) {
    errorMessage.error = 'Operation was not successful';
    return res.status(status.error).send(errorMessage);
  }
};

/**
 * filter trips by destination
 * @param {object} req
 * @param {object} res
 * @returns {object} returned trips
 */
const filterTripByDestination = async (req, res) => {
  const { destination } = req.query;

  const findTripQuery = 'SELECT * FROM trip WHERE destination=$1 ORDER BY id DESC';
  try {
    const { rows } = await dbQuery.query(findTripQuery, [destination]);
    const dbResponse = rows;
    if (!dbResponse[0]) {
      errorMessage.error = 'No Trips with that destination';
      return res.status(status.notfound).send(errorMessage);
    }
    successMessage.data = dbResponse;
    return res.status(status.success).send(successMessage);
  } catch (error) {
    errorMessage.error = 'Operation was not successful';
    return res.status(status.error).send(errorMessage);
  }
};

export {
  createTrip,
  getAllTrips,
  cancelTrip,
  filterTripByOrigin,
  filterTripByDestination,
};

tripController.js

Code Steps:

The tripController is almost similar to that of the BusController earlier created, but with few additions

  • Here, we created two methods — createTrip(), getAllTrips()cancelTrips, filterTripByOrigin, filterTripByDestination.
  • CancelTrips — We set up UPDATE trip SET status=$1 WHERE id=$2 returning*to cancel trips according to trip ID.
  • filterTripByOrigin — We set up SELECT * FROM trip WHERE **_origin_**=$1 ORDER BY id DESC to filter the origin of a users trip from the descending order .
  • filterTripByDestination — We set up SELECT * FROM trip WHERE destination=$1 ORDER BY id DESC to filter the destination of a user trip from the descending order .

bookingController

Next, we’d consider the bookingController which seems to be an important part of our transportApi.

Copy the following and paste it inside **_app/controllers/bookingController.js_**

//app/controllers/bookingController.js

import moment from 'moment';

import dbQuery from '../db/dev/dbQuery';

import {
  empty,
} from '../helpers/validations';


import {
  errorMessage, successMessage, status,
} from '../helpers/status';


/**
   * Add A Booking
   * @param {object} req
   * @param {object} res
   * @returns {object} reflection object
   */
const createBooking = async (req, res) => {
  const {
    trip_id, bus_id, trip_date, seat_number,
  } = req.body;

  const {
    first_name, last_name, user_id, email,
  } = req.user;
  const created_on = moment(new Date());

  if (empty(trip_id)) {
    errorMessage.error = 'Trip is required';
    return res.status(status.bad).send(errorMessage);
  }
  const createBookingQuery = `INSERT INTO
          booking(user_id, trip_id, bus_id, trip_date, seat_number, first_name, last_name, email, created_on)
          VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9)
          returning *`;
  const values = [
    user_id,
    trip_id,
    bus_id,
    trip_date,
    seat_number,
    first_name,
    last_name,
    email,
    created_on,
  ];

  try {
    const { rows } = await dbQuery.query(createBookingQuery, values);
    const dbResponse = rows[0];
    successMessage.data = dbResponse;
    return res.status(status.created).send(successMessage);
  } catch (error) {
    if (error.routine === '_bt_check_unique') {
      errorMessage.error = 'Seat Number is taken already';
      return res.status(status.conflict).send(errorMessage);
    }
    errorMessage.error = 'Unable to create booking';
    return res.status(status.error).send(errorMessage);
  }
};

/**
   * Get All Bookings
   * @param {object} req 
   * @param {object} res 
   * @returns {object} buses array
   */
const getAllBookings = async (req, res) => {
  const { is_admin, user_id } = req.user;
  if (!is_admin === true) {
    const getAllBookingsQuery = 'SELECT * FROM booking WHERE user_id = $1';
    try {
      const { rows } = await dbQuery.query(getAllBookingsQuery, [user_id]);
      const dbResponse = rows;
      if (dbResponse[0] === undefined) {
        errorMessage.error = 'You have no bookings';
        return res.status(status.notfound).send(errorMessage);
      }
      successMessage.data = dbResponse;
      return res.status(status.success).send(successMessage);
    } catch (error) {
      errorMessage.error = 'An error Occured';
      return res.status(status.error).send(errorMessage);
    }
  }
  const getAllBookingsQuery = 'SELECT * FROM booking ORDER BY id DESC';
  try {
    const { rows } = await dbQuery.query(getAllBookingsQuery);
    const dbResponse = rows;
    if (dbResponse[0] === undefined) {
      errorMessage.error = 'There are no bookings';
      return res.status(status.bad).send(errorMessage);
    }
    successMessage.data = dbResponse;
    return res.status(status.success).send(successMessage);
  } catch (error) {
    errorMessage.error = 'An error Occured';
    return res.status(status.error).send(errorMessage);
  }
};

/**
   * Delete A Booking
   * @param {object} req 
   * @param {object} res 
   * @returns {void} return response booking deleted successfully
   */
const deleteBooking = async (req, res) => {
  const { bookingId } = req.params;
  const { user_id } = req.user;
  const deleteBookingQuery = 'DELETE FROM booking WHERE id=$1 AND user_id = $2 returning *';
  try {
    const { rows } = await dbQuery.query(deleteBookingQuery, [bookingId, user_id]);
    const dbResponse = rows[0];
    if (!dbResponse) {
      errorMessage.error = 'You have no booking with that id';
      return res.status(status.notfound).send(errorMessage);
    }
    successMessage.data = {};
    successMessage.data.message = 'Booking deleted successfully';
    return res.status(status.success).send(successMessage);
  } catch (error) {
    return res.status(status.error).send(error);
  }
};

/**
 * Update A User to Admin
 * @param {object} req 
 * @param {object} res 
 * @returns {object} updated user
 */
const updateBookingSeat = async (req, res) => {
  const { bookingId } = req.params;
  const { seat_number } = req.body;

  const { user_id } = req.user;

  if (empty(seat_number)) {
    errorMessage.error = 'Seat Number is needed';
    return res.status(status.bad).send(errorMessage);
  }
  const findBookingQuery = 'SELECT * FROM booking WHERE id=$1';
  const updateBooking = `UPDATE booking
        SET seat_number=$1 WHERE user_id=$2 AND id=$3 returning *`;
  try {
    const { rows } = await dbQuery.query(findBookingQuery, [bookingId]);
    const dbResponse = rows[0];
    if (!dbResponse) {
      errorMessage.error = 'Booking Cannot be found';
      return res.status(status.notfound).send(errorMessage);
    }
    const values = [
      seat_number,
      user_id,
      bookingId,
    ];
    const response = await dbQuery.query(updateBooking, values);
    const dbResult = response.rows[0];
    delete dbResult.password;
    successMessage.data = dbResult;
    return res.status(status.success).send(successMessage);
  } catch (error) {
    if (error.routine === '_bt_check_unique') {
      errorMessage.error = 'Seat Number is taken already';
      return res.status(status.conflict).send(errorMessage);
    }
    errorMessage.error = 'Operation was not successful';
    return res.status(status.error).send(errorMessage);
  }
};

export {
  createBooking,
  getAllBookings,
  deleteBooking,
  updateBookingSeat,
};

bookingController.js

Code steps:

The bookingController is almost similar to that of the BusController earlier created, but with few additions

  • Here, we created four methods — createBooking(), getAllBooking, deleteBooking, and updateBookingSeat.
    The first two methods createBooking and getAllBooking are same with the previous explanations in our busController.
  • DeleteBooking — We set up UPDATE trip SET status=$1 WHERE id=$2 returning*to cancel trips according to trip ID.
  • UpdateBookingSeat— We set up UPDATE booking SET seat_number=$1 WHERE user_id=$2 returning*to update bookings according to the users ID and the seat number of a user, during booking.

If you have gotten this far, you deserve a thumbs up!

SETUP ROUTES

Next, we’d consider setting up our routes for every individual controller created.

BusRoutes

Copy the following and paste it in **_app/routes/busRoute.js._**

//app/routes/busRoute.js

import express from 'express';

import { addBusDetails, getAllBuses } from '../controllers/busController';
import verifyAuth from '../middlewares/verifyAuth';

const router = express.Router();

// buses Routes

router.post('/buses', verifyAuth, addBusDetails);
router.get('/buses', verifyAuth, getAllBuses);
export default router;

busRoute.js

Code Steps:

  • We imported express.
  • Next, we imported all methods created in our busController in **_app/controllers/busController.js._**
  • We declared a variable(router), that holds our express router.
  • We defined our routes using our router variable and passing request methods(POST, PUT,PATCH,GET) with matching routes e.g
    **_router.post('/buses', addBusDetails), router.get('/buses', getAllBuses)etc_**.

NOTE:

  • Although the routes are same but the request methods differs, therefore handles different request.
  • The same pattern applies to all routes in this tutorial
  • Each request to any of the endpoint will pass through **verifyAuth** middleware before processing the actual request. With this new setup, a user without a valid token will get an error.

BookingRoutes

//app/routes/bookingRoute.js
import express from 'express';

import { createBooking, getAllBookings, deleteBooking, updateBookingSeat } from '../controllers/bookingController';
import verifyAuth from '../middlewares/verifyAuth';

const router = express.Router();

// bookings Routes

router.post('/bookings', verifyAuth, createBooking);
router.get('/bookings', verifyAuth, getAllBookings);
router.delete('/bookings/:bookingId', verifyAuth, deleteBooking);
router.put('/bookings/:bookingId', verifyAuth, updateBookingSeat);


export default router;

bookingRoute.js

TripRoutes

//app/routes/tripRoute.js

import express from 'express';

import { createTrip, getAllTrips, cancelTrip, filterTripByOrigin, filterTripByDestination } from '../controllers/tripController';
import verifyAuth from '../middlewares/verifyAuth';

const router = express.Router();

// trips Routes

router.post('/trips', verifyAuth, createTrip);
router.get('/trips', verifyAuth, getAllTrips);
router.patch('/trips/:tripId', verifyAuth, cancelTrip);
router.get('/trips/origin', verifyAuth, filterTripByOrigin);
router.get('/trips/destinatiovan', verifyAuth, filterTripByDestination);

export default router;

tripRoute.js

SETTING UP OUR SERVER

Now that we’ve our Controller and Route setup, lets see how to put everything together.

Copy the following codes and paste it in **_app/server.js._**

//server.js

import express from 'express';
import 'babel-polyfill';
import cors from 'cors';
import env from './env';
import usersRoute from './app/routes/usersRoute';
import seedRoute from './app/routes/seedRoute';
import adminRoute from './app/routes/adminRoute';
import tripRoute from './app/routes/tripRoute';
import busRoute from './app/routes/busRoute';
import bookingRoute from './app/routes/bookingRoute';
import familyRoute from './app/routes/familyRoute';

const app = express();

// Add middleware for parsing URL encoded bodies (which are usually sent by browser)
app.use(cors());
// Add middleware for parsing JSON and urlencoded data and populating `req.body`
app.use(express.urlencoded({ extended: false }));
app.use(express.json());

app.use('/api/v1', usersRoute);
app.use('/api/v1', familyRoute);
app.use('/api/v1', seedRoute);
app.use('/api/v1', adminRoute);
app.use('/api/v1', tripRoute);
app.use('/api/v1', busRoute);
app.use('/api/v1', bookingRoute);


app.listen(env.port).on('listening', () => {
  console.log(`🚀 are live on ${env.port}`);
});


export default app;

server.js

Code Steps:

**.**We installed babel-polyfill npm package and imported it — We need this here so that node runtime will recognize async/await and Promise.

**.**We imported the necessary packages from their respective modules

**.**Next, we imported all the routes from our routes folder

**.**Adding cors middleware for parsing URL encoded bodies (which are usually sent by browser)

.Express.urlencoded is added as a middleware for parsing JSON and urlencoded data and populating req.body.

**.**Next, we versioned our Api **_'/api/v1', userRoute_**and appended the respective routes.

**.**Lastly we listened to our port so we can start the server.

TESTING WITH POSTMAN

Create User — POST api/v1/signup

This is image title

Create Admin— POST api/v1/admin/signup

This is image title

Copy Token and put it in the Header

This is image title

Create Buses— POST api/v1/buses

This is image title

Get Buses— GET api/v1/buses/

This is image title

Create Trip— POST api/v1/trips

This is image title

Get Trips— GET api/v1/trips

This is image title

Cancel Trips — PATCH api/v1/trips/:tripId

This is image title

Create Booking— POST api/v1/bookings

This is image title

Get Bookings— GET api/v1/bookings

This is image title

CONCLUSION

We just successfully built our Transport Backend App.
If you came thus far congrats, ✌.
Surely, you’ve got questions or issues as you went through this tutorial, kindly drop you comments and you’d be responded to ASAP.
Once again thanks for reading, Clap, and share!👌

Checkout the complete code here

Suggest:

JavaScript Programming Tutorial Full Course for Beginners

Learn JavaScript - Become a Zero to Hero

Javascript Project Tutorial: Budget App

Top 10 JavaScript Questions

E-Commerce JavaScript Tutorial - Shopping Cart from Scratch

JavaScript for React Developers | Mosh