Deploying with AWS Lambda
How to deploy Apollo Server with AWS Lambda
AWS Lambda is a service that allows users to run code without provisioning or managing servers. Cost is based on the compute time that is consumed, and there is no charge when code is not running.
This guide explains how to setup Apollo Server 2 to run on AWS Lambda using Serverless Framework. To use CDK and SST instead, follow this tutorial.
Prerequisites
The following must be done before following this guide:
Setup an AWS account
Install the serverless framework from NPM
npm install -g serverless
Setting up your project
Setting up a project to work with Lambda isn't that different from a typical NodeJS project.
First, install the apollo-server-lambda
package:
1npm install apollo-server-lambda graphql
Next, set up the schema's type definitions and resolvers, and pass them to the ApolloServer
constructor like normal. Here, ApolloServer
must be imported from apollo-server-lambda
. It's also important to note that this file must be named graphql.js
, as the config example in a later step depends on the filename.
1// graphql.js
2
3const { ApolloServer, gql } = require('apollo-server-lambda');
4const {
5 ApolloServerPluginLandingPageLocalDefault
6} = require('apollo-server-core');
7
8// Construct a schema, using GraphQL schema language
9const typeDefs = gql`
10 type Query {
11 hello: String
12 }
13`;
14
15// Provide resolver functions for your schema fields
16const resolvers = {
17 Query: {
18 hello: () => 'Hello world!',
19 },
20};
21
22const server = new ApolloServer({
23 typeDefs,
24 resolvers,
25 csrfPrevention: true,
26 cache: 'bounded',
27 plugins: [
28 ApolloServerPluginLandingPageLocalDefault({ embed: true }),
29 ],
30});
31
32exports.graphqlHandler = server.createHandler();
Finally, pay close attention to the last line. This creates an export named graphqlHandler
with a Lambda function handler.
Deploying with the Serverless Framework
Serverless is a framework that makes deploying to services like AWS Lambda simpler.
Configuring the Serverless Framework
Serverless uses a config file named serverless.yml
to determine what service to deploy to and where the handlers are.
For the sake of this example, the following file can just be copied and pasted into the root of your project.
1# serverless.yml
2
3service: apollo-lambda
4provider:
5 name: aws
6 runtime: nodejs14.x
7functions:
8 graphql:
9 # this is formatted as <FILENAME>.<HANDLER>
10 handler: graphql.graphqlHandler
11 events:
12 - http:
13 path: /
14 method: post
15 cors: true
16 - http:
17 path: /
18 method: get
19 cors: true
Running Locally
Using the serverless
CLI, we can invoke our function locally to make sure it is running properly. As with any GraphQL "server", we need to send an operation for the schema to run as an HTTP request. You can store a mock HTTP request locally in a query.json
file:
1{
2 "httpMethod": "POST",
3 "path": "/",
4 "headers": {
5 "content-type": "application/json"
6 },
7 "requestContext": {},
8 "body": "{\"operationName\": null, \"variables\": null, \"query\": \"{ hello }\"}"
9}
1serverless invoke local -f graphql -p query.json
Deploying the Code
After configuring the Serverless Framework, all you have to do to deploy is run serverless deploy
If successful, serverless
should output something similar to this example:
1> serverless deploy
2Serverless: Packaging service...
3Serverless: Excluding development dependencies...
4Serverless: Uploading CloudFormation file to S3...
5Serverless: Uploading artifacts...
6Serverless: Uploading service .zip file to S3 (27.07 MB)...
7Serverless: Validating template...
8Serverless: Updating Stack...
9Serverless: Checking Stack update progress...
10..............
11Serverless: Stack update finished...
12Service Information
13service: apollo-lambda
14stage: dev
15region: us-east-1
16stack: apollo-lambda-dev
17api keys:
18 None
19endpoints:
20 POST - https://ujt89xxyn3.execute-api.us-east-1.amazonaws.com/dev/
21 GET - https://ujt89xxyn3.execute-api.us-east-1.amazonaws.com/dev/
22functions:
23 graphql: apollo-lambda-dev-graphql
What does serverless
do?
First, it builds the functions, zips up the artifacts, and uploads the artifacts to a new S3 bucket. Then, it creates a Lambda function with those artifacts, and if successful, outputs the HTTP endpoint URLs to the console.
Managing the resulting services
The resulting S3 buckets and Lambda functions can be viewed and managed after logging in to the AWS Console.
To find the created S3 bucket, search the listed services for S3. For this example, the bucket created by Serverless was named
apollo-lambda-dev-serverlessdeploymentbucket-1s10e00wvoe5f
To find the created Lambda function, search the listed services for
Lambda
. If the list of Lambda functions is empty, or missing the newly created function, double check the region at the top right of the screen. The default region for Serverless deployments isus-east-1
(N. Virginia)
If you changed your mind, you can remove everything from your AWS account with npx serverless remove
.
Customizing HTTP serving
apollo-server-lambda
is built on top of apollo-server-express
. It combines the HTTP server framework express
with a package called @vendia/serverless-express
that translates between Lambda events and Express requests. By default, this is entirely behind the scenes, but you can also provide your own express app with the expressAppFromMiddleware
option to createHandler
:
1const { ApolloServer } = require('apollo-server-lambda');
2const express = require('express');
3
4exports.handler = server.createHandler({
5 expressAppFromMiddleware(middleware) {
6 const app = express();
7 app.use(someOtherMiddleware);
8 app.use(middleware);
9 return app;
10 }
11});
Configuring the underlying Express integration
Because apollo-server-lambda
is built on top of apollo-server-express
, you can specify the same options that apollo-server-express
accepts in getMiddleware
(or applyMiddleware
, other than app
) as the expressGetMiddlewareOptions
option to createHandler
. The default value of this option is {path: '/'}
(and this value of path
will be used unless you explicitly override it). For example:
1exports.handler = server.createHandler({
2 expressGetMiddlewareOptions: {
3 disableHealthCheck: true,
4 }
5});
Getting request info
Your ApolloServer's context
function can read information about the current operation from both the original Lambda data structures and the Express request and response created by @vendia/serverless-express
. These are provided to your context
function as event
, context
, and express
options.
The event
object contains the API Gateway event (HTTP headers, HTTP method, body, path, ...). The context
object (not to be confused with the context
function itself!) contains the current Lambda Context (Function Name, Function Version, awsRequestId, time remaining, ...). express
contains req
and res
fields with the Express request and response. The object returned from your context
function is provided to all of your schema resolvers in the third context
argument.
1const { ApolloServer, gql } = require('apollo-server-lambda');
2const {
3 ApolloServerPluginLandingPageLocalDefault
4} = require('apollo-server-core');
5
6// Construct a schema, using GraphQL schema language
7const typeDefs = gql`
8 type Query {
9 hello: String
10 }
11`;
12
13// Provide resolver functions for your schema fields
14const resolvers = {
15 Query: {
16 hello: () => 'Hello world!',
17 },
18};
19
20const server = new ApolloServer({
21 typeDefs,
22 resolvers,
23 csrfPrevention: true,
24 cache: 'bounded',
25 context: ({ event, context, express }) => ({
26 headers: event.headers,
27 functionName: context.functionName,
28 event,
29 context,
30 expressRequest: express.req,
31 }),
32 plugins: [
33 ApolloServerPluginLandingPageLocalDefault({ embed: true }),
34 ],
35});
36
37exports.graphqlHandler = server.createHandler();