
Three Ways to Deploy a Serverless GraphQL API
Published:
How to deploy Apollo Server and GraphQL Yoga on serverless functions with Netlify Functions, Serverless Framework, and AWS Amplify.
Outline
- Introduction
- Pros and Cons
- Deployment Providers
- Serve Apollo Server Locally
- Deploy Apollo Server Lambda with Netlify
- Deploy Apollo Server Lambda with Serverless Framework
- Deploy Apollo Server Lambda with Amplify
- Serve GraphQL Yoga Locally
- Deploy GraphQL Yoga with Netlify
- Deploy GraphQL Yoga with Serverless Framework
- Deploy GraphQL Yoga with Amplify
- Discover Related Articles
All of this project’s code can be found in the First Look monorepo on my GitHub.
Introduction
There are a wide range of different options for GraphQL servers with varying deployment methods. Typical methods for hosting your server include virtual machines (AWS EC2, Digital Ocean Droplet) or containers (Cloud Run, AWS ECS). However, if your server does not need to persist state, then you can host it using a serverless function. This includes:
- Bundling your application into a zip file
- Storing that static file somewhere with blob storage like an S3 bucket
- Serving that file using an API gateway and serverless function like an AWS Lambda
Pros and Cons
The benefits include removing large amounts of operations work such as managing the operating system of underlying VMs or creating optimized Dockerfiles. You also no longer need to worry about scaling your application or paying for idle time when your server is not being used.
Since the server state cannot be persisted, the drawbacks include removing the ability to use websockets or subscriptions. The functions will only work for basic GET
and POST
methods. You also need to write your code with the syntax of the underlying runtime which requires keeping in mind information such as the available Node version and compatible dependencies.
Deployment Providers
This article looks at three different methods of deploying two different GraphQL servers: Apollo Server and GraphQL Yoga. They will be deployed with Netlify Functions, Serverless Framework, and AWS Amplify. All three use the same underlying technology, AWS Lambda functions. They differ in terms of the conventions and abstractions around creating, developing, and deploying those functions.
Netlify Functions
Netlify Functions are scripts that you can write and deploy directly on Netlify. Netlify lets you deploy serverless Lambda functions without an AWS account, and with function management handled directly within Netlify. It requires the least amount of knowledge of AWS, but also gives you the least amount of control over the underlying infrastructure.
Lambda functions can be added to a project by creating a JavaScript file in a configured functions directory. The function endpoint is determined by its filename or the name of its dedicated parent directory. It exports a handler
that receives an event object similar to what you would receive from AWS API Gateway.
Serverless Framework
The Serverless Framework is an open source framework for building applications on AWS Lambda. It provides a CLI for developing and deploying AWS Lambda functions, along with the AWS infrastructure resources they require.
The resources and functions are defined in a file called serverless.yml
which includes:
- The
provider
for the Noderuntime
and AWSregion
- The
handler
andevents
for yourfunctions
Once the project is defined in code it can be deployed with the sls deploy
command. This command creates a CloudFormation stack defining any necessary resources such as API gateways or S3 buckets.
Amplify
AWS Amplify is a set of tools and services to help frontend web and mobile developers build fullstack applications with AWS infrastructure. It includes a CLI for creating and deploying CloudFormation stacks along with a Console and Admin UI for managing frontend web apps, backend environments, CI/CD, and user data.
Amplify provides open source libraries in various languages including:
The amplify init
command creates a boilerplate project that is setup for generating CloudFormation templates. amplify add api
configures a Lambda handler and API gateway to serve the function. amplify push
uploads the stack templates to an S3 bucket and calls the CloudFormation API to create or update resources in the cloud.
Serve Apollo Server Locally
Apollo Server is an open-source, spec-compliant GraphQL server. Apollo Server Core implements the core logic of Apollo Server.
mkdir apollo-servercd apollo-serveryarn init -yyarn add apollo-server graphqltouch index.jsecho 'node_modules\n.DS_Store' > .gitignore
ApolloServer
apollo-server
exports a base version of the ApolloServer
constructor which requires two parameters, typeDefs
for a schema definition and resolvers
for a set of resolvers. The listen
method is used to launch the server.
const { ApolloServer, gql } = require('apollo-server')
const typeDefs = gql`type Query { hello: String }`const resolvers = { Query: { hello: () => "Hello from Apollo on Localhost!" }}
const server = new ApolloServer({ typeDefs, resolvers })
server.listen().then( ({ url }) => { console.log(`Server ready at ${url}`) })
ApolloServer
is usually imported either from a batteries-included apollo-server
package or from integration packages like apollo-server-lambda
.
Run test queries on Apollo Server Locally
Start the server with node index.js
.
node index.js
Open localhost:4000 and run the hello
query.
query HELLO_QUERY { hello }
curl --request POST \ --url http://localhost:4000/ \ --header 'content-type: application/json' \ --data '{"query":"{ hello }"}'
Apollo Server Final Project Structure
├── .gitignore├── index.js└── package.json
Deploy Apollo Server Lambda with Netlify
In Apollo Server 3, apollo-server-lambda
is implemented as a wrapper around apollo-server-express
and uses serverless-express
to parse AWS Lambda events into Express requests. This will cause problems with Netlify Functions, so instead we will install v2.
mkdir -p apollo-server-netlify/netlify/functionscd apollo-server-netlifyyarn init -yyarn add apollo-server-lambda@2 graphqltouch netlify/functions/index.jsecho 'node_modules\n.DS_Store\n.netlify' > .gitignore
ApolloServer
Each JavaScript file to be deployed as a synchronous serverless Lambda function must export a handler
method. Netlify provides the event
and context
parameters when the serverless function is invoked.
const { ApolloServer, gql } = require('apollo-server-lambda')
const typeDefs = gql`type Query { hello: String }`const resolvers = { Query: { hello: () => "Hello from Apollo on Netlify!" }}
const server = new ApolloServer({ typeDefs, resolvers })
exports.handler = server.createHandler()
Run test queries on Apollo Server Lambda Netlify Locally
Start the development server with netlify dev
.
netlify dev
Open localhost:8888/.netlify/functions/index and run the hello
query.
query HELLO_QUERY { hello }
curl --request POST \ --url http://localhost:8888/.netlify/functions/index \ --header 'content-type: application/json' \ --data '{"query":"{ hello }"}'
Create GitHub Repo and Connect to Netlify
git initgit add .git commit -m "apollo server lambda netlify"gh repo create ajcwebdev-apollo-server-netlifygit push -u origin mainnetlify deploy --prod
Run test queries on Apollo Server Lambda Netlify
Open ajcwebdev-apollo-server-netlify.netlify.app/.netlify/functions/index
and run the hello
query.
query HELLO_QUERY { hello }
curl --request POST \ --url https://ajcwebdev-apollo-server-netlify.netlify.app/.netlify/functions/index \ --header 'content-type: application/json' \ --data '{"query":"{ hello }"}'
Apollo Server Lambda Netlify Final Project Structure
├── .gitignore├── netlify│ └── functions│ └── index.js└── package.json
Deploy Apollo Server Lambda with Serverless Framework
mkdir apollo-server-serverlesscd apollo-server-serverlessyarn init -yyarn add apollo-server-lambda graphqltouch index.js serverless.ymlecho 'node_modules\n.DS_Store\n.serverless' > .gitignore
ApolloServer
const { ApolloServer, gql } = require('apollo-server-lambda')
const typeDefs = gql`type Query { hello: String }`const resolvers = { Query: { hello: () => 'Hello from Apollo on Serverless Framework!' }}
const server = new ApolloServer({ typeDefs, resolvers })
exports.handler = server.createHandler()
Serverless Framework Configuration
The handler is formatted as <FILENAME>.<HANDLER>
. I have included us-west-1
for the region, feel free to enter the AWS region closest to your current location.
service: ajcwebdev-apollo-serverprovider: name: aws runtime: nodejs14.x region: us-west-1functions: graphql: handler: index.handler events: - http: path: / method: post cors: true - http: path: / method: get cors: true
Upload to AWS with sls deploy
sls deploy
service: ajcwebdev-apollo-serverstage: devregion: us-west-1stack: ajcwebdev-apollo-server-devresources: 12api keys: Noneendpoints: POST - https://q6b9hu1h71.execute-api.us-west-1.amazonaws.com/dev/ GET - https://q6b9hu1h71.execute-api.us-west-1.amazonaws.com/dev/functions: graphql: ajcwebdev-apollo-server-dev-graphqllayers: None
You can see all of your AWS resources in the various AWS consoles.
CloudFormation
Lambda Function
Lambda Application
API Gateway
Run test queries on Apollo Server Lambda Serverless
Open q6b9hu1h71.execute-api.us-west-1.amazonaws.com/dev/
and run the hello
query.
query HELLO_QUERY { hello }
curl --request POST \ --url https://q6b9hu1h71.execute-api.us-west-1.amazonaws.com/dev/ \ --header 'content-type: application/json' \ --data '{"query":"{ hello }"}'
Apollo Server Lambda Serverless Final Project Structure
├── .gitignore├── index.js├── package.json└── serverless.yml
Deploy Apollo Server Lambda with Amplify
mkdir apollo-server-amplifycd apollo-server-amplifyamplify init
? Enter a name for the project ajcwebdevapolloThe following configuration will be applied:
Project information| Name: ajcwebdevapollo| Environment: dev| Default editor: Visual Studio Code| App type: javascript| Javascript framework: none| Source Directory Path: src| Distribution Directory Path: dist| Build Command: npm run-script build| Start Command: npm run-script start
? Initialize the project with the above configuration? YesUsing default provider awscloudformation? Select the authentication method you want to use: AWS profile
For more information on AWS Profiles, see:https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
? Please choose the profile you want to use default
CloudFormation
Create backend with amplify add api
amplify add api
? Please select from one of the below mentioned services: REST? Provide a friendly name for your resource to be used as a label for this category in the project: ajcwebdevapollo? Provide a path (e.g., /book/{isbn}): /graphql? Choose a Lambda source Create a new Lambda function? Provide an AWS Lambda function name: apolloserver? Choose the runtime that you want to use: NodeJS? Choose the function template that you want to use: Hello World? Do you want to access other resources created in this project from your Lambda function? N? Do you want to edit the local lambda function now? N? Restrict API access: N? Do you want to add another path? N
cd amplify/backend/function/apolloserver/srcyarn add apollo-server-lambda graphqlcd ../../../../../
ApolloServer
const { ApolloServer, gql } = require('apollo-server-lambda')
const typeDefs = gql`type Query { hello: String }`const resolvers = { Query: { hello: () => 'Hello from Apollo on Amplify!' }}
const server = new ApolloServer({ typeDefs, resolvers, context: ({ event, context }) => ({ headers: event.headers, functionName: context.functionName, event, context, }),})
exports.handler = server.createHandler()
Upload to AWS with amplify push
Deploy the function containing the GraphQL API.
amplify push
✔ Successfully pulled backend environment dev from the cloud.
Current Environment: dev
┌──────────┬─────────────────┬───────────┬───────────────────┐│ Category │ Resource name │ Operation │ Provider plugin │├──────────┼─────────────────┼───────────┼───────────────────┤│ Function │ apolloserver │ Create │ awscloudformation │├──────────┼─────────────────┼───────────┼───────────────────┤│ Api │ ajcwebdevapollo │ Create │ awscloudformation │└──────────┴─────────────────┴───────────┴───────────────────┘? Are you sure you want to continue? Yes
All resources are updated in the cloud
REST API endpoint: https://kl21tioy61.execute-api.us-east-1.amazonaws.com/dev
Lambda Function
Lambda Application
API Gateway
Run test queries on Apollo Server Lambda Amplify
Open kl21tioy61.execute-api.us-east-1.amazonaws.com/dev/graphql
and run the hello
query.
query HELLO_QUERY { hello }
curl --request POST \ --url https://kl21tioy61.execute-api.us-east-1.amazonaws.com/dev/graphql \ --header 'content-type: application/json' \ --data '{"query":"{ hello }"}'
Apollo Server Lambda Amplify Final Project Structure
├── .gitignore└── amplify └── backend ├── api │ └── ajcwebdevapollo │ ├── ajcwebdevapollo-cloudformation-template.json │ ├── api-params.json │ └── parameters.json └── function └── apolloserver ├── apolloserver-cloudformation-template.json ├── function-parameters.json └── src ├── event.json ├── index.js └── package.json
Serve GraphQL Yoga Locally
GraphQL Yoga is a fully-featured GraphQL Server with a focus on easy setup and performance. Originally created by Prisma, it is now maintained by Dotan and the Guild. It includes features aimed at providing a great developer experience including file uploading, GraphQL subscriptions support with WebSockets, and TypeScript typing.
graphql-yoga
is built on other packages that provide functionality required for building a GraphQL server such as web server frameworks like express
and apollo-server
, GraphQL subscriptions with graphql-subscriptions
and subscriptions-transport-ws
, GraphQL engine & schema helpers including graphql.js
and graphql-tools
, and an interactive GraphQL IDE with graphql-playground
.
mkdir graphql-yogacd graphql-yogayarn init -yyarn add graphql-yogatouch index.jsecho 'node_modules\n.DS_Store' > .gitignore
GraphQLServer
GraphQLServer
is a constructor
with a props
argument that accepts a wide array of fields. We will only be using a handful including:
typeDefs
- Contains GraphQL type definitions in SDL or file path to type definitionsresolvers
- Contains resolvers for the fields specified intypeDefs
schema
- An instance ofGraphQLSchema
const { GraphQLServer } = require('graphql-yoga')
const typeDefs = `type Query { hello: String }`const resolvers = { Query: { hello: () => 'Hello from GraphQL Yoga on Localhost!' }}
const server = new GraphQLServer({ typeDefs, resolvers })
server.start( () => console.log('Server is running on localhost:4000'))
If you provide typeDefs
and resolvers
but omit the schema
, graphql-yoga
will construct the GraphQLSchema
instance using makeExecutableSchema
from graphql-tools
.
Run test queries on GraphQL Yoga Locally
node index.js
Open http://localhost:4000 and run the hello
query.
query HELLO_QUERY { hello }
curl --request POST \ --url http://localhost:4000/ \ --header 'content-type: application/json' \ --data '{"query":"{ hello }"}'
GraphQL Yoga Final Project Structure
├── .gitignore├── index.js└── package.json
Deploy GraphQL Yoga with Netlify
mkdir -p graphql-yoga-netlify/netlify/functionscd graphql-yoga-netlifyyarn init -yyarn add graphql-yogatouch netlify/functions/index.jsecho 'node_modules\n.DS_Store\n.netlify' > .gitignore
GraphQLServerLambda
const { GraphQLServerLambda } = require('graphql-yoga')
const typeDefs = `type Query { hello: String }`const resolvers = { Query: { hello: () => 'Hello from GraphQL Yoga on Netlify!' }}
const lambda = new GraphQLServerLambda({ typeDefs, resolvers })
exports.handler = lambda.handler
Run test queries on GraphQL Yoga Netlify Locally
Start the development server with netlify dev
.
netlify dev
Open localhost:8888/.netlify/functions/index and run the hello
query.
curl --request POST \ --url http://localhost:8888/.netlify/functions/index \ --header 'content-type: application/json' \ --data '{"query":"{ hello }"}'
Create GitHub Repo and Connect GraphQL Yoga to Netlify
git initgit add .git commit -m "graphql yoga netlify"gh repo create ajcwebdev-graphql-yoga-netlifygit push -u origin mainnetlify deploy --prod
Run test queries on GraphQL Yoga Netlify
Open ajcwebdev-graphql-yoga-netlify.netlify.app/.netlify/functions/index
and run the hello
query.
query HELLO_QUERY { hello }
curl --request POST \ --url https://ajcwebdev-graphql-yoga-netlify.netlify.app/.netlify/functions/index \ --header 'content-type: application/json' \ --data '{"query":"{ hello }"}'
GraphQL Yoga Netlify Final Project Structure
├── .gitignore├── netlify│ └── functions│ └── index.js└── package.json
Deploy GraphQL Yoga with Serverless Framework
mkdir graphql-yoga-serverlesscd graphql-yoga-serverlessyarn init -yyarn add graphql-yogatouch index.js serverless.ymlecho 'node_modules\n.DS_Store\n.serverless' > .gitignore
GraphQLServerLambda
const { GraphQLServerLambda } = require('graphql-yoga')
const typeDefs = `type Query { hello: String }`const resolvers = { Query: { hello: () => 'Hello from GraphQL Yoga on Serverless Framework!' }}
const lambda = new GraphQLServerLambda({ typeDefs, resolvers })
exports.handler = lambda.graphqlHandlerexports.playground = lambda.playgroundHandler
Serverless Framework Configuration
service: yoga-exampleprovider: name: aws runtime: nodejs14.x region: us-west-1functions: graphql: handler: index.handler events: - http: path: / method: post cors: true playground: handler: index.playground events: - http: path: / method: get cors: true
Upload to AWS with sls deploy
sls deploy
service: yoga-examplestage: devregion: us-west-1stack: yoga-example-devresources: 16api keys: Noneendpoints: POST - https://vptcz65b06.execute-api.us-west-1.amazonaws.com/dev/ GET - https://vptcz65b06.execute-api.us-west-1.amazonaws.com/dev/functions: graphql: yoga-example-dev-graphql playground: yoga-example-dev-playgroundlayers: None
Run test queries on GraphQL Yoga Serverless
Open vptcz65b06.execute-api.us-west-1.amazonaws.com/dev/
and run the hello
query.
query HELLO_QUERY { hello }
curl --request POST \ --url https://vptcz65b06.execute-api.us-west-1.amazonaws.com/dev/ \ --header 'content-type: application/json' \ --data '{"query":"{ hello }"}'
GraphQL Yoga Serverless Final Project Structure
├── .gitignore├── index.js├── package.json└── serverless.yml
Deploy GraphQL Yoga with Amplify
mkdir graphql-yoga-amplifycd graphql-yoga-amplifyamplify init
? Enter a name for the project ajcwebdevgraphqlyogaThe following configuration will be applied:
Project information| Name: ajcwebdevgraphqlyoga| Environment: dev| Default editor: Visual Studio Code| App type: javascript| Javascript framework: none| Source Directory Path: src| Distribution Directory Path: dist| Build Command: npm run-script build| Start Command: npm run-script start
? Initialize the project with the above configuration? YesUsing default provider awscloudformation? Select the authentication method you want to use: AWS profile
For more information on AWS Profiles, see:https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
? Please choose the profile you want to use default
Create backend with amplify add api
amplify add api
? Please select from one of the below mentioned services: REST? Provide a friendly name for your resource to be used as a label for this category in the project: ajcwebdevyoga? Provide a path (e.g., /book/{isbn}): /graphql? Choose a Lambda source: Create a new Lambda function? Provide the AWS Lambda function name: graphqlyoga? Choose the function runtime that you want to use: NodeJS? Choose the function template that you want to use: Hello World? Do you want to access other resources created in this project from your Lambda function? N? Do you want to edit the local lambda function now? N? Restrict API access: N? Do you want to add another path? N
cd amplify/backend/function/graphqlyoga/srcyarn add graphql-yogacd ../../../../../
GraphQLServerLambda
const { GraphQLServerLambda } = require('graphql-yoga')
const typeDefs = `type Query { hello: String }`const resolvers = { Query: { hello: () => 'Hello from GraphQL Yoga on Amplify!' }}
const lambda = new GraphQLServerLambda({ typeDefs, resolvers })
exports.handler = lambda.handler
Upload to AWS with amplify push
Deploy the function containing the GraphQL API.
amplify push
✔ Successfully pulled backend environment dev from the cloud.
Current Environment: dev
┌──────────┬───────────────┬───────────┬───────────────────┐│ Category │ Resource name │ Operation │ Provider plugin │├──────────┼───────────────┼───────────┼───────────────────┤│ Function │ graphqlyoga │ Create │ awscloudformation │├──────────┼───────────────┼───────────┼───────────────────┤│ Api │ ajcwebdevyoga │ Create │ awscloudformation │└──────────┴───────────────┴───────────┴───────────────────┘? Are you sure you want to continue? Yes
All resources are updated in the cloud
REST API endpoint: https://zmvy0jw9dc.execute-api.us-east-1.amazonaws.com/dev
CloudFormation
Lambda Function
Lambda Application
API Gateway
Run test queries on GraphQL Yoga Amplify
Open zmvy0jw9dc.execute-api.us-east-1.amazonaws.com/dev/graphql
and run the hello
query.
query HELLO_QUERY { hello }
curl --request POST \ --url https://zmvy0jw9dc.execute-api.us-east-1.amazonaws.com/dev/graphql \ --header 'content-type: application/json' \ --data '{"query":"{ hello }"}'
GraphQL Yoga Amplify Final Project Structure
├── .gitignore└── amplify └── backend ├── api │ └── ajcwebdevyoga │ ├── ajcwebdevyoga-cloudformation-template.json │ ├── api-params.json │ └── parameters.json └── function └── graphqlyoga ├── amplify.state ├── graphqlyoga-cloudformation-template.json ├── function-parameters.json └── src ├── event.json ├── index.js └── package.json
You’re still here? Wow, didn’t think anyone would actually make it to the end.