jstack icon indicating copy to clipboard operation
jstack copied to clipboard

Bug: Error handler not catching zodError

Open rtrampox opened this issue 10 months ago • 0 comments

When there's a validation error on the input schema, the error thrown by zod is not catched, and instead an Internal Server Error is shown.

src/server/jstack.ts:

import { jstack } from "jstack";
import { db } from "./db";
import { getCurrentSession } from "./sessions";
import { HTTPException } from "hono/http-exception";

interface Env {}

export const j = jstack.init<Env>();

/**
 * Type-safely injects database into all procedures
 * @see https://jstack.app/docs/backend/middleware
 *
 * For deployment to Cloudflare Workers
 * @see https://developers.cloudflare.com/workers/tutorials/postgres/
 */
const databaseMiddleware = j.middleware(async ({ next }) => {
  return await next({ db });
});

/**
 * Middleware to check if the current session is valid
 * @see https://jstack.app/docs/backend/middleware
 */
const authMiddleware = j.middleware(async ({ next, c }) => {
  const session = await getCurrentSession(c);
  if (!session) {
    throw new HTTPException(401, { message: "Unauthorized" });
  }

  return await next({ session });
});

/**
 * Public (unauthenticated) procedures
 */
export const publicProcedure = j.procedure.use(databaseMiddleware);

/**
 * Protected (authenticated) procedures
 */
export const protectedProcedure = j.procedure.use(databaseMiddleware).use(authMiddleware);

src/server/index.ts:

import { j } from "./jstack";
import { authRouter } from "./routers/auth-router";

/**
 * This is your base API.
 * Here, you can handle errors, not-found responses, cors and more.
 *
 * @see https://jstack.app/docs/backend/app-router
 */
const api = j.router().basePath("/api").use(j.defaults.cors).onError(j.defaults.errorHandler);

/**
 * This is the main router for your server.
 * All routers in /server/routers should be added here manually.
 */
const appRouter = j.mergeRouters(api, {
  auth: authRouter,
});

export type AppRouter = typeof appRouter;

export default appRouter;

src/server/routers/auth-router.ts:

export const authRouter = j.router({
  emailExists: publicProcedure.input(emailExistsSchema).post(async ({ c, input, ctx }) => {
    const { email } = input;
    const { db } = ctx;

    const userQuery = await db.select({ email: Users.email }).from(Users).where(eq(Users.email, email));
    const user = userQuery.pop();

    return c.json({ exists: !!user });
  }),
})

This request returns Internal Server Error.

curl -X POST http://localhost:5173/api/auth/emailExists -H "Content-Type: application/json" -d "{}"

and in the console, the zodError gets logged:

POST /api/auth/emailExists 500 in 168ms
[Error [ZodError]: [
  {
    "code": "invalid_type",
    "expected": "object",
    "received": "undefined",
    "path": [],
    "message": "Required"
  }
]] {
  issues: [Array],
  addIssue: [Function (anonymous)],
  addIssues: [Function (anonymous)]
}

ps: While testing, I've seen that errors related to the JSON input also returns Internal Server Error.

rtrampox avatar Feb 23 '25 19:02 rtrampox