Building a CRUD API with NodeJs and MySQL

Building a CRUD API with NodeJs and MySQL

This article is on building a CRUD (Create, Read, Update, and Delete) API with NodeJs and MySQL.

Tools Used in building the API

  • NodeJs: is a runtime environment that runs on a JavaScript Engine (i.e. V8 engine).
  • Express: is a framework in NodeJs
  • MySQL: is a relational database management system (RDBMS) that is used to manage databases and servers.

Creating the Application

  1. Open up a terminal, create a directory for the application and navigate into the directory
    $ mkdir crud_api ; cd crud_api
    
  2. Initialize the application to have a package.json file
    $ npm init
    
  3. Install dependencies: We made use of express, mysql, jsonwebtoken, nodemailer, bcrpyt, joi, uuid and dotenv.
    $ npm i express mysql jsonwebtoken nodemailer bcrpyt joi uuid dotenv --save
    
  4. Structure the application Screenshot from 2022-10-15 12-30-39.png

Setting Up the Server

create the entry point of the application with the following contents in app.js file:

import express from "express";
import dotenv from "dotenv";
dotenv.config();

import userRouter from "./routes/userRoute.js"

const app = express();
const port = process.env.PORT || 5000;

app.use(express.json());

app.get('/', (_, res) =>{
    res.json({message: "This is a simple CRUD API"})
})

app.use("/api/v1/users", userRouter);

app.listen(port, () => {
    console.log(`App listening on port: http://localhost:${port}`);
});

Run the server on your terminal using this command:

$ node app.js
App listening on port: http://localhost:5000

Connecting to Database

Connect to the database with the following contents in db.js file:

import mysql from "mysql";
import dotenv from "dotenv";
dotenv.config();

const connection = mysql.createConnection({
  host: process.env.HOST,
  user: process.env.USER,
  port: process.env.DBPORT,
  database: process.env.DATABASE,
  password: process.env.PASSWORD,
});

connection.connect(function (err) {
  if (err) {
    return console.error("error: " + err.message);
  }

  console.log("Connected to MySQL server.");
});

export default connection;

It is essential to have your sensitive information stored in a .env file using this sample:

PORT=5000
SECRET="your-jwt-secret-token"
HOST=localhost
USER="host"
DBPORT=3306
DATABASE=crud_api
PASSWORD="your-password"

userEmail="nodemailer"
userPassword="nodemailer"
user="nodemailer"

On the terminal, start the server to make sure the database is connected:

$ node app.js
App listening on port: http://localhost:500
Connected to MySQL server.

Defining the Routes

we define our routes for different endpoints with the following contents in userRoutes.js file:

import express from "express";
const router = express.Router();
import middleware from "../middleware/auth.middleware.js";
import controllers from "../controllers/userController.js";

router.post("/", controllers.createUser);
router.post("/verify", controllers.verifyEmail);
router.post("/login", controllers.login);
router.get("/", controllers.getAllUsers);
router.get("/:id",middleware.authenticate, controllers.getAUser);
router.patch("/:id",middleware.authenticate, controllers.updateUser);
router.delete("/:id",middleware.authenticate, controllers.deleteUser);

export default router;

Creating the Controller Functions

we create the functions for different routes of the endpoints with the following contents in the userController.js file:

GET all users

const getAllUsers = (_, res) => {
    db.query(allUsers, (err, rows) => {
        if (err) {
            return res.status(500).json({
                message: "An error occurred, please contact the system Admin",
            });
        }
        return res.status(200).json(rows);
    });
};

Create a user

const createUser = async (req, res) => {
    const { first_name, last_name, email, phone_number, password } = req.body;
    const hashPassword = await bcrypt.hash(password, 10);

    // validating reg.body with joi
    await validate.validateSignUP.validateAsync(req.body);

    // checking if a user already has an account
    db.query(checkEmail, [req.body.email], (err, rows) => {
        if (err) {
            return res.status(500).json({
                message: "An error occurred, please contact the system Admin",
            });
        }

        if (rows.length) {
            return res.status(400).json({
                message: "User already exist",
            });
        }

        // creating a new user
        const users = {
            id: v4(),
            first_name: first_name,
            last_name: last_name,
            email: email,
            phone_number: phone_number,
            password: hashPassword,
        };
        db.query(
            newUser,
            [
                users.id,
                users.first_name,
                users.last_name,
                users.email,
                users.phone_number,
                users.password,
            ],
            (err, _) => {
                if (err) {
                    console.log(err);
                    return res.status(500).json({
                        message:
                            "An error occurred, please contact the system Admin",
                    });
                }
                // creating a payload
                const payload = {
                    id: users.id,
                    email: users.email,
                };

                const token = jwt.sign(payload, process.env.SECRET, {
                    expiresIn: "2h",
                });
                let mailOptions = {
                    from: process.env.user,
                    to: users.email,
                    subject: "Verify Email",
                    text: `Hi ${first_name}, Please verify your email.
           ${token}`,
                };
                smtp.sendMail(mailOptions);
                return res
                    .status(201)
                    .json({ message: "User created", token: token });
            }
        );
    });
};

Verify User's Email

const verifyEmail = async (req, res) => {
    const { token } = req.query;
    jwt.verify(token, process.env.SECRET, (err, payload) => {
        if (err) {
            return res.status(400).json({ message: "Bad Request" });
        }
        req.email = payload.email;
    });
    db.query(verifyMail, [req.email], (err, rows) => {
        if (err) {
            return res.status(500).json({
                message: "An error occurred, please contact the system Admin",
            });
        }

        if (rows[0].is_verified) {
            return res.status(200).json({
                message: "user verified already",
            });
        }
        db.query(updateVerified, [req.email]);
        return res.status(200).json({ message: "User verified successfully" });
    });
};

Login the user

const login = async (req, res) => {
    const { email, password } = req.body;

    // validate with joi
    await validate.validateSignIn.validateAsync(req.body);

    //  checking email and password match
    if (email && password) {
        db.query(loginUser, [email], (err, rows) => {
            if (err) {
                return res.status(500).json({
                    message:
                        "An error occurred, please contact the system Admin",
                });
            }
            if (!rows.length) {
                return res.status(400).json({
                    message: "email address not found.",
                });
            }
            const passMatch = bcrypt.compare(password, rows[0].password);
            if (!passMatch) {
                return res.status(400).json({ message: "incorrect details" });
            }
            if (!rows[0].is_verified) {
                return res.status(400).json({
                    message: "Unverified account.",
                });
            }

            // creating a payload
            const payload = {
                id: rows[0].id,
                email: rows[0].email,
            };

            const token = jwt.sign(payload, process.env.SECRET_TOKEN, {
                expiresIn: "1h",
            });
            return res.status(200).json({
                message: "User logged in successfully",
                token: token,
            });
        });
    }
};

Get a user by id

const getAUser = (req, res) => {
    const { id } = req.params;
    db.query(getUser, [id], (err, rows) => {
        if (err) {
            return res.status(500).json({
                message: "An error occurred, please contact the system Admin",
            });
        }
        if (!rows.length) {
            return res.status(404).json({
                message: "User not found",
            });
        }
        if (rows[0].id !== req.userId) {
            return res.status(403).json({ message: "Unauthorized" });
        }
        return res.status(200).json(rows);
    });
};

Update a user

const updateUser = async (req, res) => {
    const { id } = req.params;
    const { first_name, last_name, phone_number, password } = req.body;
    db.query(findUser, [id], (err, rows) => {
        if (err) {
            return res.status(500).json({
                message: "An error occurred, please contact the system Admin",
            });
        }
        if (!rows.length) {
            return res.status(404).json({
                message: "User not found",
            });
        }
        if (rows[0].id !== req.userId) {
            return res.status(403).json({ message: "Unauthorized" });
        }
        if (first_name) {
            db.query("UPDATE users SET first_name = ? WHERE id = ?", [
                first_name,
                id,
            ]);
        }
        if (last_name) {
            db.query("UPDATE users SET last_name WHERE id = ?", [id]);
        }
        if (phone_number) {
            db.query("UPDATE users SET phone_number WHERE id = ?", [
                phone_number,
                id,
            ]);
        }
        if (password) {
            const hashPassword = bcrypt.hash(password, 10);
            db.query("UPDATE users SET password WHERE id = ?", [
                hashPassword,
                id,
            ]);
        }
        return res.status(200).json({
            message: "Update was successful",
        });
    });
};

Delete a user

const deleteUser = async (req, res) => {
    const { id } = req.params;
    db.query(getUser, [id], (err, rows) => {
        if (err) {
            return res.status(500).json({
                message: "An error occurred, please contact the system Admin",
            });
        }
        if (!rows.length) {
            return res.status(404).json({
                message: "User not found",
            });
        }
        if (rows[0].id !== req.userId) {
            return res.status(403).json({ message: "Unauthorized" });
        }
        db.query(removeUser, [id]);
        return res.status(410).json({
            message: "User successfully deleted",
        });
    });
};

There is a need to export every content in a file to make them usable in another file by simply importing them.

Authentication and Authorization

In this CRUD API, a user that is not logged in cannot access most of the endpoints, so it is necessary to authenticate and authorize the user in every application to ensure that the user has an account and it is verified and also to prevent a user from accessing an account that does not belong to them. We used jsonwebtoken to authenticate and authorize a user:

import Jwt from "jsonwebtoken";
import dotenv from "dotenv";
dotenv.config();

const authenticate = async (req, res, next) => {
    try {
        const authorization = req.headers.authorization;
        if (!authorization) {
            return res.status(401).json({ message: "Access Denied" });
        }
        const authenticationArr = authorization.split(" ");
        if (authenticationArr[0] !== "Bearer") {
            return res.status(401).json({ message: "Access Denied" });
        }
        const token = authenticationArr[1];
        if (!token) {
            return res.status(401).json({ message: "Access Denied" });
        }
        Jwt.verify(token, process.env.SECRET, (err, payload) => {
            if (err) {
                return res.status(400).json({message: "Bad Request"});
            } else {
                req.userId = payload.id;
            }
        });

        next();
    } catch (err) {
        return res.status(500).json({ message: err.message });
    }
};

export default { authenticate };

Testing Our API with Postman

Below is the request and response of every endpoint in the application Postman

Conclusion

In this article, we built a CRUD API using NodeJs and MySQL. You can find the complete source code in the GitHub link.

Thank you for reading

Contributors

Abiodun Shittu

Ugochukwu Chioma