Skip to content

Instantly share code, notes, and snippets.

@giuseppeg
Last active August 2, 2021 21:52
Show Gist options
  • Save giuseppeg/45483f1d4b0e5e5ddd10b3f1eb0a61b6 to your computer and use it in GitHub Desktop.
Save giuseppeg/45483f1d4b0e5e5ddd10b3f1eb0a61b6 to your computer and use it in GitHub Desktop.
import { NextApiRequest as DefaultNextApiRequest, NextApiResponse } from "next";
/**
* // Route /pages/api/posts/[id]
*
* const api = defineApi<{ id: string }>()
* .get<{ query: { foo: "1" } }, "foo" | "bar">(
* async function handler(req, res) {
* const session = await getSession({ req });
* if (!session) {
* throw new ApiError(401, "unauthorized")
* }
*
* return {
req.query.foo === "1" ? "foo" : "bar"
* }
* }
* )
* .post<{body:{foo: "hi"}}, void>(
* async function handler(req, res) {
* req.body.foo
* }
* )
*
* export default api.create()
*
*/
export type ApiResponse<
ResponseData = Record<string, unknown>,
ResponseError = { status: number; message: string }
> =
| { result: true; data: ResponseData }
| { result: false; data: ResponseError };
type NextApiRequest<RequestOwnTypings> = Omit<
DefaultNextApiRequest,
"query" | "body"
> &
RequestOwnTypings;
type NextApiHandler<
RequestOwnTypings,
ResponseDataOwnTypings,
ResponseErrorOwnTypings
> = (
req: NextApiRequest<RequestOwnTypings>,
res: NextApiResponse<
ApiResponse<ResponseDataOwnTypings, ResponseErrorOwnTypings>
>
) => ResponseDataOwnTypings | Promise<ResponseDataOwnTypings>;
export const defineApi = <RouteParams extends Record<string, string>>() => {
const handlers: { [key: string]: NextApiHandler<unknown, unknown, unknown> } =
{};
const methodHandler = {
get: <ApiRequest, ResponseData, ResponseError = { message: string }>(
handler: NextApiHandler<
ApiRequest & { query: RouteParams },
ResponseData,
ResponseError
>
) => {
handlers["GET"] = handler;
return methodHandler;
},
post: <ApiRequest, ResponseData, ResponseError = { message: string }>(
handler: NextApiHandler<
ApiRequest & { query: RouteParams },
ResponseData,
ResponseError
>
) => {
handlers["POST"] = handler;
return methodHandler;
},
put: <ApiRequest, ResponseData, ResponseError = { message: string }>(
handler: NextApiHandler<
ApiRequest & { query: RouteParams },
ResponseData,
ResponseError
>
) => {
handlers["PUT"] = handler;
return methodHandler;
},
delete: <ApiRequest, ResponseData, ResponseError = { message: string }>(
handler: NextApiHandler<
ApiRequest & { query: RouteParams },
ResponseData,
ResponseError
>
) => {
handlers["DELETE"] = handler;
return methodHandler;
},
create: () => async (req, res) => {
if (typeof handlers[req.method] === "function") {
try {
const data = await handlers[req.method](req, res);
if (data) {
res.json({ result: true, data });
}
if (data === null) {
res.json({ result: true, data: {} });
}
} catch (error) {
const status = error instanceof ApiError ? error.status : 500;
const message =
error instanceof ApiError || process.env.NODE_ENV === "development"
? error.message || "unknown error"
: "unknown error";
res.status(status).json({ result: false, data: { status, message } });
}
} else {
res
.status(404)
.json({ result: false, data: { status: 404, message: "not found" } });
}
},
};
return methodHandler;
};
export class ApiError extends Error {
status: number;
constructor(status: number, message: string) {
super(message);
this.status = status;
}
}
export function swrFetcher(url: string, options = {}) {
return fetch(url, { credentials: "same-origin", ...options })
.then((response) => response.json())
.then(({ result, data }) => {
if (!result) {
throw new ApiError(data.status, data.message);
}
return data;
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment