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.
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.
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:
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 .
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.
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:
export const ERROR_TYPE = {
PRISMA: "PRISMA",
DEFAULT: "DEFAULT",
};
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.
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.