Skip to content

Instantly share code, notes, and snippets.

@wzulfikar
Last active August 20, 2022 21:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wzulfikar/9894181bb3463128bcba2d5cc91ef94b to your computer and use it in GitHub Desktop.
Save wzulfikar/9894181bb3463128bcba2d5cc91ef94b to your computer and use it in GitHub Desktop.
DEV.to Post: Typing for Next.js API
import { captureException } from '@sentry/nextjs';
import { StatusCodes } from 'http-status-codes';
import { NextApiRequest } from 'next';
import { z } from 'zod';
import { NextApiResponseWithData } from '.';
import { validateSchema } from './validateSchema';
type RootOptions = {
schema?: z.SomeZodObject;
};
type RootHandler = (
handler: (
req: NextApiRequest,
res: NextApiResponseWithData
) => void | Promise<void>,
options?: RootOptions
) => (
req: NextApiRequest,
res: NextApiResponseWithData
) => void | Promise<void>;
export const handle: RootHandler = (handler, options) => async (req, res) => {
try {
if (options?.schema) validateSchema(req, options.schema);
await handler(req, res); // Run the actual handler
} catch (e) {
captureException(e);
console.log('error caught in handler:', e);
res
.status(StatusCodes.INTERNAL_SERVER_ERROR)
.json({ success: false, error: (e as Error).message });
}
};
export { z } from "zod";
export * from "./handler";
export * from "./types";
import { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
export type NextApiResponseWithData<TData extends z.ZodTypeAny = z.ZodTypeAny> =
NextApiResponse<ApiResult<TData>> & {
data: (body: z.TypeOf<TData>) => void;
};
type ApiRequest<TSchema extends z.AnyZodObject = z.AnyZodObject> = Omit<
NextApiRequest,
"query" | "body"
> & {
body: z.infer<TSchema>["body"];
query: Partial<z.infer<TSchema>["query"]>;
};
type ApiResult<TData extends z.ZodTypeAny> =
| (TData extends void
? { success: true }
: { success: true; data: z.infer<TData> })
| { success: false; error: string };
type ApiResponse<TData extends z.ZodTypeAny> = NextApiResponseWithData<TData>;
export type ApiHandler<
TRequest extends z.AnyZodObject = z.AnyZodObject,
TResponse extends z.ZodTypeAny = z.ZodTypeAny
> = (
req: ApiRequest<TRequest>,
res: ApiResponse<TResponse>
) => void | Promise<void>;
import { NextApiRequest } from "next";
import { z } from "zod";
const errorMap: z.ZodErrorMap = (error, ctx) => {
let message = ctx.defaultError;
const { path, code } = error;
switch (code) {
case z.ZodIssueCode.invalid_type:
if (error.received === "undefined") {
message = `${path.join(".")} is required`;
} else {
const field = path.join(".");
const { expected, received } = error;
message = `Expected ${expected} got ${received}: ${field}`;
}
}
return { message };
};
export const validateSchema = (
req: NextApiRequest,
schema: z.SomeZodObject
) => {
const schemaPayload = {
query: req.query,
body: req.body,
};
const schemaResult = schema.safeParse(schemaPayload, { errorMap });
if (!schemaResult.success) {
throw new Error(schemaResult.error.issues[0].message);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment