Featured Image

NodeJS and Typescript Setup for REST API - Part 2

In this article we will cover the following topics for NodeJS and Typescript Setup for REST API - Part 2

Introduction

In this article, we will learn how to setup NodeJS and Typescript for REST API. Specifically, we will continue from where we left in Part 1. Here are the things we will learn in this article:

  • Prisma ORM and database operations.
  • Writing validators for the request data.
  • Error handling.

For the sake of this blog, we will create a simple API for a user resource. You can find the complete code for this part in Part 2.

Creating API handlers

First, we need to create a handlers folder in src folder. This folder will contain all the API handlers for our application.

Now, we need to create a user.ts file in handlers folder. This file will contain the API handlers for the user resource.

file_type_typescript src/handlers/user.ts
import { Request, Response, NextFunction } from "express";
import prisma from "../db";
import { createHandlerError } from "../module/error";

export const createUser = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    const { name, email } = req.body;
    const user = await prisma.user.create({
      data: {
        name,
        email,
      },
    });

    res.status(201).json({ data: user });
  } catch (error) {
    const newError = await createHandlerError(error);
    next(newError);
  }
};

export const getUser = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    const user = await prisma.user.findUnique({
      where: {
        id: req.params.id,
      },
    });
    res.status(200).json({ data: user });
  } catch (error) {
    const newError = await createHandlerError(error);
    next(newError);
  }
};

export const updateUser = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    const user = await prisma.user.update({
      where: { id: req.params.id },
      data: req.body,
    });
    res.status(200).json({ data: user });
  } catch (error) {
    const newError = await createHandlerError(error);
    next(newError);
  }
};

export const deleteUser = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    const user = await prisma.user.delete({
      where: { id: req.params.id },
    });
    res.status(200).json({ data: user });
  } catch (error) {
    const newError = await createHandlerError(error);
    next(newError);
  }
};

Please don’t focus on catch block for now. We will learn about it in the next part of this blog.

Finally, we need to add these handlers to the router.

file_type_typescript src/router.ts
import { Router } from "express";
import { createUser, deleteUser, getUser, updateUser } from "./handlers/user";

const router = Router();

router.get("/", (req, res) => {
  res.send("Hello World");
});

router.post("/user", createUser);
router.get("/user/:id", getUser);
router.put("/user/:id", updateUser);
router.delete("/user/:id", deleteUser);

export default router;

From this point, we can start the server with npm run dev and test our API endpoints. JFYI, routes are prefixed with /api in this example. We haven’t created createHandlerError function yet. We will create it in the next part of this blog. It might give you an error if you try to test the API endpoints, so you can comment out that lines for now.

Input Validation and Error handling

So far, we have created the API handlers for the user resource but we haven’t validated the input data for these handlers. Now, we need to validate the input data for these handlers. We will use express-validator for this purpose. We are going to use some custom validation functions to validate the input data and that will work as middleware in the routes.

Now we need to create a validation folder in src folder. This folder will contain all the validation functions for our application. We need to create a user.ts file in validation folder and add the following code to it:

file_type_typescript src/validation/user.ts
import { body } from "express-validator";

export const createUserValidator = [
  body("name").exists().isString(),
  body("email").exists().isEmail(),
];

Now to catch the validation errors, we need to create a middleware. We will create a handleInputErrors middleware in module/middleware.ts file .

file_type_typescript src/module/middleware.ts
import { validationResult } from "express-validator";

export const handleInputErrors = (req, res, next) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  } else {
    next();
  }
};

Add these validator and error handler middleware to the routes in router.ts file. Make sure you use these validators before the handlers.

file_type_typescript src/router.ts
router.post("/user", createUserValidator, handleInputErrors, createUser);

And there you go! You have successfully created the API handlers and validated the input data for them.

Remember, we have mentioned that we will learn about the catch block in the next part of this blog. catch block is used to catch the errors that are thrown in the handlers. We will create a createHandlerError function in module/error.ts file to create the error object. Before that, we need to create a constant.ts file in module folder and add the following code to it:

file_type_typescript src/module/constant.ts
export const ERROR_TYPE = {
  PRISMA: "PRISMA",
  DEFAULT: "DEFAULT",
};
file_type_typescript src/module/error.ts
import { Prisma } from "@prisma/client";
import { ERROR_TYPE } from "./constant";

export const createHandlerError = async (error: any) => {
  const newError = error;
  console.log(error.constructor);
  const prismaErrors = [
    Prisma.PrismaClientInitializationError,
    Prisma.PrismaClientUnknownRequestError,
    Prisma.PrismaClientRustPanicError,
    Prisma.PrismaClientValidationError,
    Prisma.PrismaClientKnownRequestError,
  ];

  if (prismaErrors.includes(error.constructor)) {
    newError.type = ERROR_TYPE.PRISMA;
    newError.message = "Database error";
  } else {
    newError.type = ERROR_TYPE.DEFAULT;
    newError.message = "Internal server error";
  }

  return newError;
};

Now, we need to add an error handler middleware in router.ts file to handle the errors for the routes that don’t have any validators or any other error that we might throw in the handlers.

file_type_typescript src/router.ts
router.use((err, req, res, next) => {
  if (err.type === ERROR_TYPE.PRISMA) {
    res.status(500).json({ message: err.message });
  } else {
    res.status(500).json({ message: "Internal server error" });
  }
});

And there you go! You have successfully created the API handlers, validated the input data for them and handled the errors for them.

You can find the complete code for this part in Part 2.