Skip to content

Instantly share code, notes, and snippets.

@misterjohannesson
Created July 16, 2020 07:56
Show Gist options
  • Save misterjohannesson/af465e6f3f904675be48cc620b41a604 to your computer and use it in GitHub Desktop.
Save misterjohannesson/af465e6f3f904675be48cc620b41a604 to your computer and use it in GitHub Desktop.
NextJS API Pages server-side wrapper
import post from "pages/api/post";
import serverApiHandler from "utils/serverApiHandler";
const IdExample = () => <div></div>
IdExample.getInitialProps = async ctx => {
const { query } = ctx;
let id = query.id;
let data = null;
if (process.browser) {
data = await fetch("/api/post", {
method: "POST",
body: JSON.stringify({ name: id })
}).then(r => r.json());
} else {
data = await serverApiHandler(ctx, post, {}, "POST", { name: id });
}
return {
source: process.browser ? "browser" : "server",
data
};
};
export default IdExample;
import { ServerResponse, IncomingMessage, IncomingHttpHeaders } from "http";
import { ParsedUrlQuery } from "querystring";
class _ServerApiResponse {
constructor(res: ServerResponse) {
const proto = { ..._ServerApiResponse.prototype };
Object.assign(proto, Object.getPrototypeOf(res));
Object.setPrototypeOf(this, proto);
Object.assign(this, res);
}
statusCode: number;
statusMessage: string;
data: any;
status = (int: number) => {
this.statusCode = int;
return this;
};
send = (obj: any) => {
this.data = obj;
};
json = (obj: any) => {
this.data = obj;
};
setPreviewData = (data: string | object, options?: { maxAge?: number }) => {
throw Error("NOT YET IMPLEMENTED");
return this;
};
clearPreviewData = () => {
throw Error("NOT YET IMPLEMENTED");
return this;
};
}
type ServerApiResponse = _ServerApiResponse & ServerResponse;
export const ServerApiResponse: new (
data: ServerResponse
) => ServerApiResponse = _ServerApiResponse as any;
export class _ServerApiRequest {
constructor(req: IncomingMessage, query: ParsedUrlQuery = {}) {
Object.assign(this, req);
this.query = query;
this.cookies = this.headers.cookie
?.split("; ")
.reduce<{ [key: string]: string }>((acc, set) => {
const [key, value] = set.split("=");
acc[key] = value;
return acc;
}, {});
}
headers: IncomingHttpHeaders;
query;
cookies;
body;
env;
}
type ServerApiRequest = _ServerApiRequest & IncomingMessage;
export const ServerApiRequest: new (
req: IncomingMessage,
query: ParsedUrlQuery
) => ServerApiRequest = _ServerApiRequest as any;
import { NextApiHandler, NextPageContext } from "next";
import { ServerApiRequest, ServerApiResponse } from "types/ServerApiHandler";
/**
* This helper function wraps calling the api pages when on the server side.
* To avoid having to construct a network call from the server to the server.
*
* Pass the Next Page Context from getInitialProps or getServerProps
* and the default export api page.
* It is possible to mock a POST or a PUT, simply add the method param and the possible body.
*
* Example:
* ```typescript
* import post from "pages/api/post";
* //...
* Component.getInitialProps = async ctx => {
* if (!process.browser) {
* const getData = await serverApiHandler(ctx, post);
* const postData = await serverApiHandler(ctx, post, {}, "POST", { name: id });
* ```
*
* @param context: NextPageContext
* @param fn: NextApiHandler
* @param query: object
* @param method: string
* @param body: any
*/
export default async (
{ req, res }: NextPageContext,
fn: NextApiHandler,
query: { [key: string]: string } = {},
method: "GET" | "POST" | "PUT" | "DELETE" = "GET",
body?: any
) => {
if (process.browser) {
throw Error("Server API Handler can only be used server-side");
}
//Create mock instances of the HTTP request and response.
const request = new ServerApiRequest(req, query);
const response = new ServerApiResponse(res);
request.method = method;
if (body) request.body = body;
//Execute the passed api page function. (this includes middleware)
await fn(request, response);
console.debug("Server fetched API data", response.data);
return response.data;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment