Sponsored Content

DEV Community

Ayush Mishra
Ayush Mishra

Posted on Originally published at ayushtech.hashnode.dev on

Why Zod Crashed My Node.js Server

Today, when I was creating my project, there was an interesting bug occurred in my Postman. When using Zod, it crash my server completely.

I am first time using the Zod in my project. Although it is made for the TypeScript, but it also works well with the JavaScript.

Basically, Zod in simple language use to check the inputs of data which you are moving in the backend, like if it is good or not. Even the input come in req.body, req.params, req.headers, etc., but generally use for the input validation of the input come in body and params from the outside.

I was think this saves my lot of effort and make code more scalable. Through its working, I am at least able to remove that input checks in every controller or business logic to validate the input is correct or not.

The Setup and The Crash

But it cause the server crash. I was create a specific middleware for the Zod with name of validator, so it call the specific validator file in the folder written by using Zod as an universal middleware. So, I can add the check in the router and things become easy.

Here is the code of my Zod schema setup to validate the inputs:

import { z } from "zod";

const usernameRule = z
    .string({ required_error: "Username is required" })
    .trim()
    .toLowerCase()
    .min(3, { message: "Username must be at least 3 characters long" })
    .max(20, { message: "Username cannot exceed 20 characters" });

const emailRule = z
    .string({ required_error: "Email is required" })
    .trim()
    .email({ message: "Invalid email address format" });

export const registerSchema = z.object({
    username: usernameRule,
    email: emailRule,
});

Enter fullscreen mode Exit fullscreen mode

When I check the things in the Postman to test the setup is working or not, interesting thing happen. I got the internal server error (500) status code and message inside my controller. I think maybe due to wrong any configuration. I check the flow end to end properly and cross verify the configuration on internet and even the AI. I don't get the bug even in the middleware. Then, after lot of struggle, I found it.

The Investigation: throw vs next()

It was due to these line in the code: throw new ApiError(). Generally this line simple throw the error. Here ApiError() is my custom error class, not the global error handler itself. But in middleware case, I miss one thing that is important, which is next() as a wrapper. But question here is why throw work in the controller, but not in middleware, which cause my Zod crash and things bad?

Basically I found it that, middleware is a check comes in middle and move the cycle further of the request so the controller do its work. So I found that concept effect in real life after search that Express next() is not only used for the moving cycle further for only controller or next middleware, but also to pass errors to the global error handler. That's why we need to use next() upon the throw to pass it to my global error handler.

Here is the code of the fixed validation middleware using the try/catch and next() wrapper:

import { ApiError } from "../error/ApiErrors.error.js";

export const validate = (schema, source = "body") => (req, res, next) => {
    try {
        const result = schema.safeParse(req[source]);

        if (result.success === true) {
            req[source] = result.data;
            return next(); 
        }

        const errorMessage =
            result.error.issues?.[0]?.message ||
            result.error.errors?.[0]?.message ||
            "Invalid input data";

        return next(new ApiError(400, errorMessage));

    } catch (error) {
        console.error("VALIDATION MIDDLEWARE CRASHED:", error);
        return next(new ApiError(500, "Validation middleware crashed"));
    }
};

Enter fullscreen mode Exit fullscreen mode

The Hidden Magic of asyncHandler

Whereas I encounter this problem in this case first time because my old wrapper done this job for me as a sake of the error, which name is asyncHandler.

When the authentication middleware called, since I don't use the next() in middleware, this thing cause server crash and async error. But my asyncHandler wrapper catch it and do the next() to the error, cause the things work. After the test and code is in production, this thing I cannot catch if this problem not happen with me today.

Here is the code of the asyncHandler that was hiding the magic from me:

const asyncHandler = (requestHandler) =>{
    return (req,res,next) => {
        Promise.resolve(requestHandler(req,res,next)).catch((err)=>next(err))
    }
}

export {asyncHandler}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)