Skip to content
This repository was archived by the owner on May 10, 2021. It is now read-only.

Expose event and context of Netlify Function in Next.js pages and API routes #119

Merged
merged 5 commits into from
Dec 22, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Expose event + context of Netlify Functions on the req object
When a page is being SSR-ed by a Netlify Function, allow users to access
the function's event and context parameters. These can be accessed as
a property on the `req` object in all SSR-ed pages and in API routes:
- req.netlifyFunction.event
- req.netlifyFunction.context

This allows users to access/leverage Netlify identity for their Next.js
page.
See: #20

It also allows users to modify the callbackWaitsForEmptyEventLoop
behavior.
See: #66 (comment))
  • Loading branch information
FinnWoelm committed Dec 15, 2020
commit c5375452369347b5a87c531d5df04ff6b498fefc
3 changes: 3 additions & 0 deletions cypress/fixtures/pages/api/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default async function context(req, res) {
res.json({ req, res });
}
11 changes: 11 additions & 0 deletions cypress/fixtures/pages/getServerSideProps/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const Context = ({ context }) => <pre>{JSON.stringify(context, 2, " ")}</pre>;

export const getServerSideProps = async (context) => {
return {
props: {
context,
},
};
};

export default Context;
46 changes: 46 additions & 0 deletions cypress/integration/default_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,31 @@ describe("getInitialProps", () => {
});

describe("getServerSideProps", () => {
it("exposes function context on the req object", () => {
cy.visit("/getServerSideProps/context");

cy.get("pre")
.first()
.then((json) => {
const {
req: {
netlifyFunction: { event, context },
},
} = JSON.parse(json.html());

expect(event).to.have.property("path", "/getServerSideProps/context");
expect(event).to.have.property("httpMethod", "GET");
expect(event).to.have.property("headers");
expect(event).to.have.property("multiValueHeaders");
expect(event).to.have.property("isBase64Encoded");
expect(context.done).to.be.undefined;
expect(context.getRemainingTimeInMillis).to.be.undefined;
expect(context).to.have.property("awsRequestId");
expect(context).to.have.property("callbackWaitsForEmptyEventLoop");
expect(context).to.have.property("clientContext");
});
});

context("with static route", () => {
it("loads TV shows", () => {
cy.visit("/getServerSideProps/static");
Expand Down Expand Up @@ -534,6 +559,27 @@ describe("API endpoint", () => {
cy.get("h1").should("contain", "Show #999");
cy.get("p").should("contain", "Flash Gordon");
});

it("exposes function context on the req object", () => {
cy.request("/api/context").then((response) => {
const {
req: {
netlifyFunction: { event, context },
},
} = response.body;

expect(event).to.have.property("path", "/api/context");
expect(event).to.have.property("httpMethod", "GET");
expect(event).to.have.property("headers");
expect(event).to.have.property("multiValueHeaders");
expect(event).to.have.property("isBase64Encoded");
expect(context.done).to.be.undefined;
expect(context.getRemainingTimeInMillis).to.be.undefined;
expect(context).to.have.property("awsRequestId");
expect(context).to.have.property("callbackWaitsForEmptyEventLoop");
expect(context).to.have.property("clientContext");
});
});
});

describe("Preview Mode", () => {
Expand Down
10 changes: 9 additions & 1 deletion lib/templates/createRequestObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const http = require("http");
// Based on API Gateway Lambda Compat
// Source: https://github.com/serverless-nextjs/serverless-next.js/blob/master/packages/compat-layers/apigw-lambda-compat/lib/compatLayer.js

const createRequestObject = ({ event }) => {
const createRequestObject = ({ event, context }) => {
const {
requestContext = {},
path = "",
Expand Down Expand Up @@ -52,6 +52,14 @@ const createRequestObject = ({ event }) => {
req.rawHeaders = [];
req.headers = {};

// Expose Netlify Function event and callback on request object.
// This makes it possible to access the clientContext, for example.
// See: https://github.com/netlify/next-on-netlify/issues/20
// It also allows users to change the behavior of waiting for empty event
// loop.
// See: https://github.com/netlify/next-on-netlify/issues/66#issuecomment-719988804
req.netlifyFunction = { event, context };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this name is fine. maybe netlifyFunctionData since the value itself is not really a function but 🤷‍♀️

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True! How about netlifyFunctionParams, since it's not just data but also some functions?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good!!!!!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's now netlifyFunctionParams :)


for (const key of Object.keys(multiValueHeaders)) {
for (const value of multiValueHeaders[key]) {
req.rawHeaders.push(key);
Expand Down
4 changes: 1 addition & 3 deletions lib/templates/netlifyFunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ exports.handler = async (event, context, callback) => {
console.log("[request]", path);

// Render the Next.js page
const response = await renderNextPage({
...event,
});
const response = await renderNextPage({ event, context });

// Convert header values to string. Netlify does not support integers as
// header values. See: https://github.com/netlify/cli/issues/451
Expand Down
4 changes: 2 additions & 2 deletions lib/templates/renderNextPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ const createRequestObject = require("./createRequestObject");
const createResponseObject = require("./createResponseObject");

// Render the Next.js page
const renderNextPage = (event) => {
const renderNextPage = ({ event, context }) => {
// The Next.js page is rendered inside a promise that is resolved when the
// Next.js page ends the response via `res.end()`
const promise = new Promise((resolve) => {
// Create a Next.js-compatible request and response object
// These mock the ClientRequest and ServerResponse classes from node http
// See: https://nodejs.org/api/http.html
const req = createRequestObject({ event });
const req = createRequestObject({ event, context });
const res = createResponseObject({
onResEnd: (response) => resolve(response),
});
Expand Down