Gathering detailed insights and metrics for @as-integrations/aws-lambda
Gathering detailed insights and metrics for @as-integrations/aws-lambda
Gathering detailed insights and metrics for @as-integrations/aws-lambda
Gathering detailed insights and metrics for @as-integrations/aws-lambda
@aws-cdk/aws-apigatewayv2-integrations-alpha
This module is deprecated. All constructs are now available under aws-cdk-lib/aws-apigatewayv2-integrations
@whatwg-node/server
Fetch API compliant HTTP Server adapter
@aws-cdk/aws-apigatewayv2-alpha
This module is deprecated. All constructs are now available under aws-cdk-lib/aws-apigatewayv2
@fastify/aws-lambda
Inspired by aws-serverless-express to work with Fastify with inject functionality.
An integration to use AWS Lambda as a hosting service with Apollo Server
npm install @as-integrations/aws-lambda
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
46 Stars
159 Commits
9 Forks
5 Watching
12 Branches
5 Contributors
Updated on 23 Nov 2024
TypeScript (97.55%)
JavaScript (1.38%)
Shell (1.07%)
Cumulative downloads
Total Downloads
Last day
-8.6%
7,500
Compared to previous day
Last week
4%
41,414
Compared to previous week
Last month
1.8%
173,958
Compared to previous month
Last year
93.1%
1,816,799
Compared to previous year
1
1
@as-integrations/aws-lambda
Apollo Server runs as a part of your Lambda handler, processing GraphQL requests. This package allows you to easily integrate Apollo Server with AWS Lambda. This integration comes with built-in request handling functionality for ProxyV1, ProxyV2, and ALB events with extensible typing. You can also create your own integrations via a Custom Handler and submitted as a PR if others might find them valuable.
First, install Apollo Server, graphql-js, and the Lambda handler package:
1npm install @apollo/server graphql @as-integrations/aws-lambda
Then, write the following to server.mjs
. (By using the .mjs extension, Node treats the file as a module, allowing us to use ESM import
syntax.)
1import { ApolloServer } from '@apollo/server'; 2import { 3 startServerAndCreateLambdaHandler, 4 handlers, 5} from '@as-integrations/aws-lambda'; 6 7// The GraphQL schema 8const typeDefs = `#graphql 9 type Query { 10 hello: String! 11 } 12`; 13 14// A map of functions which return data for the schema. 15const resolvers = { 16 Query: { 17 hello: () => 'world', 18 }, 19}; 20 21// Set up Apollo Server 22const server = new ApolloServer({ 23 typeDefs, 24 resolvers, 25}); 26 27export default startServerAndCreateLambdaHandler( 28 server, 29 handlers.createAPIGatewayProxyEventV2RequestHandler(), 30);
As with all Apollo Server 4 integrations, the context resolution is done in the integration. For the Lambda integration, it will look like the following:
1import { ApolloServer } from '@apollo/server'; 2import { 3 startServerAndCreateLambdaHandler, 4 handlers, 5} from '@as-integrations/aws-lambda'; 6 7type ContextValue = { 8 isAuthenticated: boolean; 9}; 10 11// The GraphQL schema 12const typeDefs = `#graphql 13 type Query { 14 hello: String! 15 isAuthenticated: Boolean! 16 } 17`; 18 19// Set up Apollo Server 20const server = new ApolloServer<ContextValue>({ 21 typeDefs, 22 resolvers: { 23 Query: { 24 hello: () => 'world', 25 isAuthenticated: (root, args, context) => { 26 // For context typing to be valid one of the following must be implemented 27 // 1. `resolvers` defined inline in the server config (not particularly scalable, but works) 28 // 2. Add the type in the resolver function. ex. `(root, args, context: ContextValue)` 29 // 3. Propagate the type from an outside definition like GraphQL Codegen 30 return context.isAuthenticated; 31 }, 32 }, 33 }, 34}); 35 36export default startServerAndCreateLambdaHandler( 37 server, 38 handlers.createAPIGatewayProxyEventV2RequestHandler(), 39 { 40 context: async ({ event }) => { 41 // Do some parsing on the event (parse JWT, cookie, auth header, etc.) 42 return { 43 isAuthenticated: true, 44 }; 45 }, 46 }, 47);
For mutating the event before passing off to @apollo/server
or mutating the result right before returning, middleware can be utilized.
Note, this middleware is strictly for event and result mutations and should not be used for any GraphQL modification. For that, plugins from
@apollo/server
would be much better suited.
For example, if you need to set cookie headers with a V2 Proxy Result, see the following code example:
1import { 2 startServerAndCreateLambdaHandler, 3 handlers, 4} from '@as-integrations/aws-lambda'; 5import type { APIGatewayProxyEventV2 } from 'aws-lambda'; 6import { server } from './server'; 7 8async function regenerateCookie(event: APIGatewayProxyEventV2) { 9 // ... 10 return 'NEW_COOKIE'; 11} 12 13export default startServerAndCreateLambdaHandler( 14 server, 15 handlers.createAPIGatewayProxyEventV2RequestHandler(), 16 { 17 middleware: [ 18 // Both event and result are intended to be mutable 19 async (event) => { 20 const cookie = await regenerateCookie(event); 21 return (result) => { 22 result.cookies.push(cookie); 23 }; 24 }, 25 ], 26 }, 27);
If you want to define strictly typed middleware outside of the middleware array, the easiest way would be to extract your request handler into a variable and utilize the typeof
keyword from Typescript. You could also manually use the RequestHandler
type and fill in the event and result values yourself.
1import { 2 startServerAndCreateLambdaHandler, 3 middleware, 4 handlers, 5} from '@as-integrations/aws-lambda'; 6import type { 7 APIGatewayProxyEventV2, 8 APIGatewayProxyStructuredResultV2, 9} from 'aws-lambda'; 10import { server } from './server'; 11 12const requestHandler = handlers.createAPIGatewayProxyEventV2RequestHandler(); 13 14// Utilizing typeof 15const cookieMiddleware: middleware.MiddlewareFn<typeof requestHandler> = ( 16 event, 17) => { 18 // ... 19 return (result) => { 20 // ... 21 }; 22}; 23 24// Manual event filling 25const otherMiddleware: middleware.MiddlewareFn< 26 RequestHandler<APIGatewayProxyEventV2, APIGatewayProxyStructuredResultV2> 27> = (event) => { 28 // ... 29 return (result) => { 30 // ... 31 }; 32}; 33 34export default startServerAndCreateLambdaHandler(server, requestHandler, { 35 middleware: [ 36 // cookieMiddleware will always work here as its signature is 37 // tied to the `requestHandler` above 38 cookieMiddleware, 39 40 // otherMiddleware will error if the event and result types do 41 // not sufficiently overlap, meaning it is your responsibility 42 // to keep the event types in sync, but the compiler may help 43 otherMiddleware, 44 ], 45});
In some situations, a middleware function might require the execution end before reaching Apollo Server. This might be a global auth guard or session token lookup.
To achieve this, the request middleware function accepts ResultType
or Promise<ResultType>
as a return type. Should middleware resolve to such a value, that result is returned and no further execution occurs.
1import { 2 startServerAndCreateLambdaHandler, 3 middleware, 4 handlers, 5} from '@as-integrations/aws-lambda'; 6import type { 7 APIGatewayProxyEventV2, 8 APIGatewayProxyStructuredResultV2, 9} from 'aws-lambda'; 10import { server } from './server'; 11 12const requestHandler = handlers.createAPIGatewayProxyEventV2RequestHandler(); 13 14// Utilizing typeof 15const sessionMiddleware: middleware.MiddlewareFn<typeof requestHandler> = ( 16 event, 17) => { 18 // ... check session 19 if (!event.headers['X-Session-Key']) { 20 // If header doesn't exist, return early 21 return { 22 statusCode: 401 23 body: 'Unauthorized' 24 } 25 } 26}; 27 28export default startServerAndCreateLambdaHandler(server, requestHandler, { 29 middleware: [ 30 sessionMiddleware, 31 ], 32});
Each of the provided request handler factories has a generic for you to pass a manually extended event type if you have custom authorizers, or if the event type you need has a generic you must pass yourself. For example, here is a request that allows access to the lambda authorizer:
1import { 2 startServerAndCreateLambdaHandler, 3 middleware, 4 handlers, 5} from '@as-integrations/aws-lambda'; 6import type { APIGatewayProxyEventV2WithLambdaAuthorizer } from 'aws-lambda'; 7import { server } from './server'; 8 9export default startServerAndCreateLambdaHandler( 10 server, 11 handlers.createAPIGatewayProxyEventV2RequestHandler< 12 APIGatewayProxyEventV2WithLambdaAuthorizer<{ 13 myAuthorizerContext: string; 14 }> 15 >(), // This event will also be applied to the MiddlewareFn type 16);
When invoking a lambda manually, or when using an event source we don't currently support (feel free to create a PR), a custom request handler might be necessary. A request handler is created using the handlers.createHandler
function which takes two function arguments eventParser
and resultGenerator
, and two type arguments EventType
and ResultType
.
eventParser
ArgumentThere are two type signatures available for parsing events:
This helper object has 4 properties that will complete a full parsing chain, and abstracts some of the work required to coerce the incoming event into a HTTPGraphQLRequest
. This is the recommended way of parsing events.
parseHttpMethod(event: EventType): string
Returns the HTTP verb from the request.
Example return value: GET
parseQueryParams(event: EventType): string
Returns the raw query param string from the request. If the request comes in as a pre-mapped type, you may need to use URLSearchParams
to re-stringify it.
Example return value: foo=1&bar=2
parseHeaders(event: EventType): HeaderMap
Import from here: import {HeaderMap} from "@apollo/server"
;
Return an Apollo Server header map from the event. HeaderMap
automatically normalizes casing for you.
parseBody(event: EventType, headers: HeaderMap): string
Return a plaintext body. Be sure to parse out any base64 or charset encoding. Headers are provided here for convenience as some body parsing might be dependent on content-type
If the helper object is too restrictive for your use-case, the other option is to create a function with (event: EventType): HTTPGraphQLRequest
as the signature. Here you can do any parsing and it is your responsibility to create a valid HTTPGraphQLRequest
.
resultGenerator
ArgumentThere are two possible result types, success
and error
, and they are to be defined as function properties on an object. Middleware will always run, regardless if the generated result was from a success or error. The properties have the following signatures:
success(response: HTTPGraphQLResponse): ResultType
Given a complete response, generate the desired result type.
error(e: unknown): ResultType
Given an unknown type error, generate a result. If you want to create a basic parser that captures everything, utilize the instanceof type guard from Typescript.
1error(e) { 2 if(e instanceof Error) { 3 return { 4 ... 5 } 6 } 7 // If error cannot be determined, panic and use lambda's default error handler 8 // Might be advantageous to add extra logging here so unexpected errors can be properly handled later 9 throw e; 10}
1import { 2 startServerAndCreateLambdaHandler, 3 handlers, 4} from '@as-integrations/aws-lambda'; 5import type { APIGatewayProxyEventV2 } from 'aws-lambda'; 6import { HeaderMap } from '@apollo/server'; 7import { server } from './server'; 8 9type CustomInvokeEvent = { 10 httpMethod: string; 11 queryParams: string; 12 headers: Record<string, string>; 13 body: string; 14}; 15 16type CustomInvokeResult = 17 | { 18 success: true; 19 body: string; 20 } 21 | { 22 success: false; 23 error: string; 24 }; 25 26const requestHandler = handlers.createRequestHandler< 27 CustomInvokeEvent, 28 CustomInvokeResult 29>( 30 { 31 parseHttpMethod(event) { 32 return event.httpMethod; 33 }, 34 parseHeaders(event) { 35 const headerMap = new HeaderMap(); 36 for (const [key, value] of Object.entries(event.headers)) { 37 headerMap.set(key, value); 38 } 39 return headerMap; 40 }, 41 parseQueryParams(event) { 42 return event.queryParams; 43 }, 44 parseBody(event) { 45 return event.body; 46 }, 47 }, 48 { 49 success({ body }) { 50 return { 51 success: true, 52 body: body.string, 53 }; 54 }, 55 error(e) { 56 if (e instanceof Error) { 57 return { 58 success: false, 59 error: e.toString(), 60 }; 61 } 62 console.error('Unknown error type encountered!', e); 63 throw e; 64 }, 65 }, 66); 67 68export default startServerAndCreateLambdaHandler(server, requestHandler);
No vulnerabilities found.
No security vulnerabilities found.