From c41038bdebd31a499818902965dcf2587a63c6ca Mon Sep 17 00:00:00 2001 From: Daniel Abdelsamed Date: Wed, 25 Jan 2023 20:54:11 -0500 Subject: [PATCH 1/5] Updated documentation with new event and package info --- docs/source/deployment/lambda.mdx | 290 ++++++++++++++++++++++-------- 1 file changed, 215 insertions(+), 75 deletions(-) diff --git a/docs/source/deployment/lambda.mdx b/docs/source/deployment/lambda.mdx index e91dc05422e..05d8efae09a 100644 --- a/docs/source/deployment/lambda.mdx +++ b/docs/source/deployment/lambda.mdx @@ -60,30 +60,6 @@ const server = new ApolloServer({ }); ``` -```js title="server.js" -import { ApolloServer } from '@apollo/server'; - -// The GraphQL schema -const typeDefs = `#graphql - type Query { - hello: String - } -`; - -// A map of functions which return data for the schema. -const resolvers = { - Query: { - hello: () => 'world', - }, -}; - -// Set up Apollo Server -const server = new ApolloServer({ - typeDefs, - resolvers, -}); -``` - Now we can import the `startServerAndCreateLambdaHandler` function from [`@as-integrations/aws-lambda`](https://www.npmjs.com/package/@as-integrations/aws-lambda), passing in our `ApolloServer` instance: @@ -92,32 +68,12 @@ Now we can import the `startServerAndCreateLambdaHandler` function from [`@as-in ```ts title="src/server.ts" import { ApolloServer } from '@apollo/server'; -import { startServerAndCreateLambdaHandler } from '@as-integrations/aws-lambda'; //highlight-line - -const typeDefs = `#graphql - type Query { - hello: String - } -`; - -const resolvers = { - Query: { - hello: () => 'world', - }, -}; - -const server = new ApolloServer({ - typeDefs, - resolvers, -}); - -// This final export is important! -export const graphqlHandler = startServerAndCreateLambdaHandler(server); //highlight-line -``` - -```js title="server.js" -import { ApolloServer } from '@apollo/server'; -import { startServerAndCreateLambdaHandler } from '@as-integrations/aws-lambda'; //highlight-line +// highlight-start +import { + startServerAndCreateLambdaHandler, + handlers, +} from '@as-integrations/aws-lambda'; +// highlight-end const typeDefs = `#graphql type Query { @@ -137,7 +93,13 @@ const server = new ApolloServer({ }); // This final export is important! -export const graphqlHandler = startServerAndCreateLambdaHandler(server); //highlight-line +// highlight-start +export const graphqlHandler = startServerAndCreateLambdaHandler( + server, + // We will be using the Proxy V2 handler + handlers.createAPIGatewayProxyEventV2RequestHandler() +); +// highlight-end ``` @@ -206,13 +168,19 @@ You can store a mock HTTP requests locally by creating a `query.json` file, like ```json title="query.json" { "version": "2", - "httpMethod": "POST", - "path": "/", "headers": { - "content-type": "application/json" + "content-type": "application/json", }, - "requestContext": {}, + "isBase64Encoded": false, "rawQueryString": "", + "requestContext": { + "http": { + "method": "POST", + }, + // Other requestContext properties omitted for brevity + }, + "rawPath": "/", + "routeKey": "/", "body": "{\"operationName\": null, \"variables\": null, \"query\": \"{ hello }\"}" } ``` @@ -284,42 +252,177 @@ If you ever want to remove the S3 bucket or Lambda functions that Serverless cre serverless remove ``` -## Customizing HTTP behavior +## Middleware -The `@as-integrations/aws-lambda` package is compatible with the following event types `APIGatewayProxyEvent`, `APIGatewayProxyEventV2`, `ALBEvent`. This supports a wide range of services like API Gateway HTTP Proxy APIs, API Gateway REST Proxy APIs, Lambda Function URLs, and Application Load Balancers. However, it does not let you customize HTTP behavior directly or support other AWS products that invoke Lambda functions (e.g., S3 or DynamoDB). +In order to impelment event and result mutations, type-safe middleware can be passed to the `startServerAndCreateLambdaHandler` call. The API is as follows: -If you want to customize your HTTP behavior, you can couple Apollo Server's Express integration (i.e., [`expressMiddleware`](../api/express-middleware)) with the [`@vendia/serverless-express`](https://github.com/vendia/serverless-express) package. The `@vendia/serverless-express` library translates between Lambda events and Express requests. Despite their similar names, the Serverless CLI and the `@vendia/serverless-express` package are unrelated. +```ts -You can update your Apollo Server setup to the following to have a fully functioning Lambda server that works in a variety of AWS features: +import { middleware, startServerAndCreateLambdaHandler, handlers } from "@as-integrations/aws-lambda"; - +const requestHandler = handlers.createAPIGatewayProxyEventV2RequestHandler() + +// All middleware must be async and will be strictly typed based on the `requestHandler`'s types +const middlewareFn: middleware.MiddlewareFn = async (event) => { + // event is intended to be mutable and can be updated here + + // optional result mutation, otherwise function returns `Promise` + return async (result) => { + // result is intended to be mutable and can be updated here + + // `Promise` return + } +} + +startServerAndCreateLambdaHandler(..., ..., { + middleware: [middlewareFn], +}) +``` + +An example use case would be for cookie modificaiton. The `APIGatewayProxyStructuredResultV2` type contains a property `cookies` that then mutate to multiple `Set-Cookie` headers. ```ts -const { ApolloServer } = require('@apollo/server'); -const { expressMiddleware } = require('@apollo/server/express4'); -const serverlessExpress = require('@vendia/serverless-express'); -const express = require('express'); -const { json } = require('body-parser'); -const cors = require('cors'); +import { + startServerAndCreateLambdaHandler, + middleware, + handlers, +} from '@as-integrations/aws-lambda'; +import { server } from './server'; +import { refreshCookie } from './cookies'; + +const requestHandler = handlers.createAPIGatewayProxyEventV2RequestHandler(); + +// Utilizing typeof +const cookieMiddleware: middleware.MiddlewareFn = async ( + event, +) => { + // Access existing cookies and produce a refreshed one + const cookie = refreshCookie(event.cookies); + return async (result) => { + // Assure proper initialization of the cookies property on the result + result.cookies = result.cookies ?? []; + // Result is mutable so it can be updated here + result.cookies.push(cookie); + }; +}; -const server = new ApolloServer({ - typeDefs: 'type Query { x: ID }', - resolvers: { Query: { x: () => 'hi!' } }, + +export default startServerAndCreateLambdaHandler(server, requestHandler, { + middleware: [ + cookieMiddleware, + ], }); +``` -server.startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests(); +More use-cases and API information can be found in the [library's README](https://github.com/apollo-server-integrations/apollo-server-integration-aws-lambda#middleware). -const app = express(); -app.use(cors(), json(), expressMiddleware(server)); -exports.graphqlHandler = serverlessExpress({ app }); +## Event extensions + +In many cases, API Gateway events will have an authorizer infront of them that contains custom state that will be used for authorization during GraphQL resolution. All of the handlers that are packged with the library contain a generic type which allows you to explicitly extend the base event type. By passing an event with authorization information, that event type will be used during the creation of `contextValue` and for `middleware`. Below is an example, and more information can be found in the [library's README](https://github.com/apollo-server-integrations/apollo-server-integration-aws-lambda/blob/main/README.md#event-extensions). + + +```ts +import { + startServerAndCreateLambdaHandler, + middleware, + handlers, +} from '@as-integrations/aws-lambda'; +import type { APIGatewayProxyEventV2WithLambdaAuthorizer } from 'aws-lambda'; +import { server } from './server'; + +export default startServerAndCreateLambdaHandler( + server, + handlers.createAPIGatewayProxyEventV2RequestHandler< + APIGatewayProxyEventV2WithLambdaAuthorizer<{ + myAuthorizerContext: string; + }> + >(), +); ``` +## Custom request handling + +In order to support all event types from AWS Lambda (including custom ones), a request handler creation utility is exposed as `handlers.createHandler(eventParser, resultGenerator)`. This function returns a fully typed request handler that can be passed as the second argument to the `startServerAndCreateLambdaHandler` call. Below is an example and the exact API is documented in the [library's README](https://github.com/apollo-server-integrations/apollo-server-integration-aws-lambda/blob/main/README.md#custom-request-handlers). + + + + ```ts + import { + startServerAndCreateLambdaHandler, + handlers, + } from '@as-integrations/aws-lambda'; + import type { APIGatewayProxyEventV2 } from 'aws-lambda'; + import { HeaderMap } from '@apollo/server'; + import { server } from './server'; + + type CustomInvokeEvent = { + httpMethod: string; + queryParams: string; + headers: Record; + body: string; + }; + + type CustomInvokeResult = + | { + success: true; + body: string; + } + | { + success: false; + error: string; + }; + + const requestHandler = handlers.createRequestHandler< + CustomInvokeEvent, + CustomInvokeResult + >( + { + parseHttpMethod(event) { + return event.httpMethod; + }, + parseHeaders(event) { + const headerMap = new HeaderMap(); + for (const [key, value] of Object.entries(event.headers)) { + headerMap.set(key, value); + } + return headerMap; + }, + parseQueryParams(event) { + return event.queryParams; + }, + parseBody(event) { + return event.body; + }, + }, + { + success({ body }) { + return { + success: true, + body: body.string, + }; + }, + error(e) { + if (e instanceof Error) { + return { + success: false, + error: e.toString(), + }; + } + console.error('Unknown error type encountered!', e); + throw e; + }, + }, + ); + + export default startServerAndCreateLambdaHandler(server, requestHandler); + ``` + -The setup enables you to customize your HTTP behavior as needed. -## Using request information + +## Using event information You can use the [`context` function](../data/context/#the-context-function) to get information about the current operation from the original Lambda data structures. @@ -394,3 +497,40 @@ exports.handler = serverlessExpress({ app }); ``` + + +## Customizing HTTP behavior + +The `@as-integrations/aws-lambda` package is compatible with the following event types `APIGatewayProxyEvent`, `APIGatewayProxyEventV2`, `ALBEvent`. This supports a wide range of services like API Gateway HTTP Proxy APIs, API Gateway REST Proxy APIs, Lambda Function URLs, and Application Load Balancers. However, it does not let you customize HTTP behavior directly or support other AWS products that invoke Lambda functions (e.g., S3 or DynamoDB). + +If you want to customize your HTTP behavior, you can couple Apollo Server's Express integration (i.e., [`expressMiddleware`](../api/express-middleware)) with the [`@vendia/serverless-express`](https://github.com/vendia/serverless-express) package. The `@vendia/serverless-express` library translates between Lambda events and Express requests. Despite their similar names, the Serverless CLI and the `@vendia/serverless-express` package are unrelated. + +You can update your Apollo Server setup to the following to have a fully functioning Lambda server that works in a variety of AWS features: + + + +```ts +const { ApolloServer } = require('@apollo/server'); +const { expressMiddleware } = require('@apollo/server/express4'); +const serverlessExpress = require('@vendia/serverless-express'); +const express = require('express'); +const { json } = require('body-parser'); +const cors = require('cors'); + +const server = new ApolloServer({ + typeDefs: 'type Query { x: ID }', + resolvers: { Query: { x: () => 'hi!' } }, +}); + +server.startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests(); + +const app = express(); +app.use(cors(), json(), expressMiddleware(server)); + +exports.graphqlHandler = serverlessExpress({ app }); +``` + + + +The setup enables you to customize your HTTP behavior as needed. + From 1dd8e2fcf4ffce1b471b773491b5a1b7d43ceeb7 Mon Sep 17 00:00:00 2001 From: Daniel Abdelsamed Date: Wed, 25 Jan 2023 21:02:26 -0500 Subject: [PATCH 2/5] spelling --- docs/source/deployment/lambda.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/deployment/lambda.mdx b/docs/source/deployment/lambda.mdx index 05d8efae09a..d61e12cf64a 100644 --- a/docs/source/deployment/lambda.mdx +++ b/docs/source/deployment/lambda.mdx @@ -254,7 +254,7 @@ If you ever want to remove the S3 bucket or Lambda functions that Serverless cre ## Middleware -In order to impelment event and result mutations, type-safe middleware can be passed to the `startServerAndCreateLambdaHandler` call. The API is as follows: +In order to implement event and result mutations, type-safe middleware can be passed to the `startServerAndCreateLambdaHandler` call. The API is as follows: ```ts @@ -279,7 +279,7 @@ startServerAndCreateLambdaHandler(..., ..., { }) ``` -An example use case would be for cookie modificaiton. The `APIGatewayProxyStructuredResultV2` type contains a property `cookies` that then mutate to multiple `Set-Cookie` headers. +An example use case would be for cookie modification. The `APIGatewayProxyStructuredResultV2` type contains a property `cookies` that then mutate to multiple `Set-Cookie` headers. ```ts import { @@ -319,7 +319,7 @@ More use-cases and API information can be found in the [library's README](https: ## Event extensions -In many cases, API Gateway events will have an authorizer infront of them that contains custom state that will be used for authorization during GraphQL resolution. All of the handlers that are packged with the library contain a generic type which allows you to explicitly extend the base event type. By passing an event with authorization information, that event type will be used during the creation of `contextValue` and for `middleware`. Below is an example, and more information can be found in the [library's README](https://github.com/apollo-server-integrations/apollo-server-integration-aws-lambda/blob/main/README.md#event-extensions). +In many cases, API Gateway events will have an authorizer in front of them that contains custom state that will be used for authorization during GraphQL resolution. All of the handlers that are packaged with the library contain a generic type which allows you to explicitly extend the base event type. By passing an event with authorization information, that event type will be used during the creation of `contextValue` and for `middleware`. Below is an example, and more information can be found in the [library's README](https://github.com/apollo-server-integrations/apollo-server-integration-aws-lambda/blob/main/README.md#event-extensions). ```ts From f1fa867e9b0a9a2440e7fa6404dd2ec9fb4d8448 Mon Sep 17 00:00:00 2001 From: Daniel Abdelsamed Date: Wed, 25 Jan 2023 21:09:35 -0500 Subject: [PATCH 3/5] Minor verbiage tweaks --- docs/source/deployment/lambda.mdx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/source/deployment/lambda.mdx b/docs/source/deployment/lambda.mdx index d61e12cf64a..70a354fd6a1 100644 --- a/docs/source/deployment/lambda.mdx +++ b/docs/source/deployment/lambda.mdx @@ -501,9 +501,7 @@ exports.handler = serverlessExpress({ app }); ## Customizing HTTP behavior -The `@as-integrations/aws-lambda` package is compatible with the following event types `APIGatewayProxyEvent`, `APIGatewayProxyEventV2`, `ALBEvent`. This supports a wide range of services like API Gateway HTTP Proxy APIs, API Gateway REST Proxy APIs, Lambda Function URLs, and Application Load Balancers. However, it does not let you customize HTTP behavior directly or support other AWS products that invoke Lambda functions (e.g., S3 or DynamoDB). - -If you want to customize your HTTP behavior, you can couple Apollo Server's Express integration (i.e., [`expressMiddleware`](../api/express-middleware)) with the [`@vendia/serverless-express`](https://github.com/vendia/serverless-express) package. The `@vendia/serverless-express` library translates between Lambda events and Express requests. Despite their similar names, the Serverless CLI and the `@vendia/serverless-express` package are unrelated. +If you want to customize your HTTP routing behavior, you can couple Apollo Server's Express integration (i.e., [`expressMiddleware`](../api/express-middleware)) with the [`@vendia/serverless-express`](https://github.com/vendia/serverless-express) package. The `@vendia/serverless-express` library translates between Lambda events and Express requests. Despite their similar names, the Serverless CLI and the `@vendia/serverless-express` package are unrelated. You can update your Apollo Server setup to the following to have a fully functioning Lambda server that works in a variety of AWS features: From 709da59382e7f7352ee75a2aeeb7b3ae19031f1d Mon Sep 17 00:00:00 2001 From: Daniel Abdelsamed Date: Fri, 27 Jan 2023 16:02:55 -0500 Subject: [PATCH 4/5] Update from feedback --- docs/source/deployment/lambda.mdx | 159 ++++++++++++++++-------------- 1 file changed, 83 insertions(+), 76 deletions(-) diff --git a/docs/source/deployment/lambda.mdx b/docs/source/deployment/lambda.mdx index 70a354fd6a1..356ec408d03 100644 --- a/docs/source/deployment/lambda.mdx +++ b/docs/source/deployment/lambda.mdx @@ -62,7 +62,7 @@ const server = new ApolloServer({ -Now we can import the `startServerAndCreateLambdaHandler` function from [`@as-integrations/aws-lambda`](https://www.npmjs.com/package/@as-integrations/aws-lambda), passing in our `ApolloServer` instance: +Now we can import the `startServerAndCreateLambdaHandler` function and `handlers` object from [`@as-integrations/aws-lambda`](https://www.npmjs.com/package/@as-integrations/aws-lambda), passing in our `ApolloServer` instance: @@ -256,30 +256,34 @@ If you ever want to remove the S3 bucket or Lambda functions that Serverless cre In order to implement event and result mutations, type-safe middleware can be passed to the `startServerAndCreateLambdaHandler` call. The API is as follows: + + ```ts import { middleware, startServerAndCreateLambdaHandler, handlers } from "@as-integrations/aws-lambda"; +import {server} from "./server"; -const requestHandler = handlers.createAPIGatewayProxyEventV2RequestHandler() +const requestHandler = handlers.createAPIGatewayProxyEventV2RequestHandler(); -// All middleware must be async and will be strictly typed based on the `requestHandler`'s types +// Middleware is an async function whose type is based on your request handler. Middleware +// can read and mutate the incoming event. Additionally, returning an async function from your +// middleware allows you to read and mutate the result before it's sent. const middlewareFn: middleware.MiddlewareFn = async (event) => { - // event is intended to be mutable and can be updated here - - // optional result mutation, otherwise function returns `Promise` + // read or update the event here + // optionally return a callback to access the result return async (result) => { - // result is intended to be mutable and can be updated here - - // `Promise` return + // read or update the result here } } -startServerAndCreateLambdaHandler(..., ..., { +startServerAndCreateLambdaHandler(server, requestHandler, { middleware: [middlewareFn], -}) +}); ``` -An example use case would be for cookie modification. The `APIGatewayProxyStructuredResultV2` type contains a property `cookies` that then mutate to multiple `Set-Cookie` headers. + + +One use case for middleware is cookie modification. The `APIGatewayProxyStructuredResultV2` type contains a property `cookies` which can be pushed to. This allows you to set multiple `set-cookie` headers in the response. ```ts import { @@ -299,7 +303,7 @@ const cookieMiddleware: middleware.MiddlewareFn = async ( // Access existing cookies and produce a refreshed one const cookie = refreshCookie(event.cookies); return async (result) => { - // Assure proper initialization of the cookies property on the result + // Ensure proper initialization of the cookies property on the result result.cookies = result.cookies ?? []; // Result is mutable so it can be updated here result.cookies.push(cookie); @@ -321,6 +325,7 @@ More use-cases and API information can be found in the [library's README](https: In many cases, API Gateway events will have an authorizer in front of them that contains custom state that will be used for authorization during GraphQL resolution. All of the handlers that are packaged with the library contain a generic type which allows you to explicitly extend the base event type. By passing an event with authorization information, that event type will be used during the creation of `contextValue` and for `middleware`. Below is an example, and more information can be found in the [library's README](https://github.com/apollo-server-integrations/apollo-server-integration-aws-lambda/blob/main/README.md#event-extensions). + ```ts import { @@ -341,82 +346,84 @@ export default startServerAndCreateLambdaHandler( ); ``` + + ## Custom request handling In order to support all event types from AWS Lambda (including custom ones), a request handler creation utility is exposed as `handlers.createHandler(eventParser, resultGenerator)`. This function returns a fully typed request handler that can be passed as the second argument to the `startServerAndCreateLambdaHandler` call. Below is an example and the exact API is documented in the [library's README](https://github.com/apollo-server-integrations/apollo-server-integration-aws-lambda/blob/main/README.md#custom-request-handlers). - ```ts - import { - startServerAndCreateLambdaHandler, - handlers, - } from '@as-integrations/aws-lambda'; - import type { APIGatewayProxyEventV2 } from 'aws-lambda'; - import { HeaderMap } from '@apollo/server'; - import { server } from './server'; - - type CustomInvokeEvent = { - httpMethod: string; - queryParams: string; - headers: Record; - body: string; - }; +```ts +import { + startServerAndCreateLambdaHandler, + handlers, +} from '@as-integrations/aws-lambda'; +import type { APIGatewayProxyEventV2 } from 'aws-lambda'; +import { HeaderMap } from '@apollo/server'; +import { server } from './server'; + +type CustomInvokeEvent = { + httpMethod: string; + queryParams: string; + headers: Record; + body: string; +}; + +type CustomInvokeResult = + | { + success: true; + body: string; + } + | { + success: false; + error: string; + }; - type CustomInvokeResult = - | { - success: true; - body: string; +const requestHandler = handlers.createRequestHandler< + CustomInvokeEvent, + CustomInvokeResult +>( + { + parseHttpMethod(event) { + return event.httpMethod; + }, + parseHeaders(event) { + const headerMap = new HeaderMap(); + for (const [key, value] of Object.entries(event.headers)) { + headerMap.set(key, value); } - | { - success: false; - error: string; + return headerMap; + }, + parseQueryParams(event) { + return event.queryParams; + }, + parseBody(event) { + return event.body; + }, + }, + { + success({ body }) { + return { + success: true, + body: body.string, }; - - const requestHandler = handlers.createRequestHandler< - CustomInvokeEvent, - CustomInvokeResult - >( - { - parseHttpMethod(event) { - return event.httpMethod; - }, - parseHeaders(event) { - const headerMap = new HeaderMap(); - for (const [key, value] of Object.entries(event.headers)) { - headerMap.set(key, value); - } - return headerMap; - }, - parseQueryParams(event) { - return event.queryParams; - }, - parseBody(event) { - return event.body; - }, }, - { - success({ body }) { + error(e) { + if (e instanceof Error) { return { - success: true, - body: body.string, + success: false, + error: e.toString(), }; - }, - error(e) { - if (e instanceof Error) { - return { - success: false, - error: e.toString(), - }; - } - console.error('Unknown error type encountered!', e); - throw e; - }, + } + console.error('Unknown error type encountered!', e); + throw e; }, - ); + }, +); - export default startServerAndCreateLambdaHandler(server, requestHandler); - ``` +export default startServerAndCreateLambdaHandler(server, requestHandler); +``` @@ -499,7 +506,7 @@ exports.handler = serverlessExpress({ app }); -## Customizing HTTP behavior +## Customizing HTTP routing behavior If you want to customize your HTTP routing behavior, you can couple Apollo Server's Express integration (i.e., [`expressMiddleware`](../api/express-middleware)) with the [`@vendia/serverless-express`](https://github.com/vendia/serverless-express) package. The `@vendia/serverless-express` library translates between Lambda events and Express requests. Despite their similar names, the Serverless CLI and the `@vendia/serverless-express` package are unrelated. From c013fcfbaf74397aeeb0d52e245733cd82d1cb79 Mon Sep 17 00:00:00 2001 From: Trevor Scheer Date: Mon, 6 Feb 2023 15:08:25 -0800 Subject: [PATCH 5/5] prettier spacing nit --- docs/source/deployment/lambda.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/deployment/lambda.mdx b/docs/source/deployment/lambda.mdx index 356ec408d03..e034ebfc494 100644 --- a/docs/source/deployment/lambda.mdx +++ b/docs/source/deployment/lambda.mdx @@ -261,7 +261,7 @@ In order to implement event and result mutations, type-safe middleware can be pa ```ts import { middleware, startServerAndCreateLambdaHandler, handlers } from "@as-integrations/aws-lambda"; -import {server} from "./server"; +import { server } from "./server"; const requestHandler = handlers.createAPIGatewayProxyEventV2RequestHandler();