How to Build and Deploy a GraphQL Server in AWS Lambda Using Node.js and CloudFormation
GraphQL is becoming increasingly popular as an alternative to REST APIs for building flexible and efficient APIs. When combined with serverless compute platforms like AWS Lambda, you can build highly scalable GraphQL servers that can handle huge volumes of traffic with ease.
In this in-depth tutorial, we‘ll walk through the process of building a production-ready GraphQL server using Node.js and Apollo Server, and deploying it to AWS Lambda using CloudFormation. We‘ll also look at best practices and optimization techniques to ensure your GraphQL server is performant, secure and maintainable.
Why GraphQL and AWS Lambda?
Before diving into the implementation, let‘s take a step back and understand why GraphQL and AWS Lambda are a great fit.
GraphQL offers several benefits over traditional REST APIs:
AWS Lambda is a serverless compute service that lets you run code without provisioning or managing servers. You only pay for the compute time you consume, and there is no charge when your code is not running.
The benefits of using AWS Lambda for GraphQL servers are:
Setting Up the Node.js Project
Let‘s get started by creating a new directory for our project and initializing a new Node.js project:
mkdir graphql-lambda-demo
cd graphql-lambda-demo
npm init -y
Next, we‘ll install the required dependencies:
npm install apollo-server-lambda graphql
We‘re using the apollo-server-lambda
package which is an Apollo Server integration for AWS Lambda.
Building the GraphQL Server
Create a new file named index.js
with the following code:
const { ApolloServer, gql } = require(‘apollo-server-lambda‘);
const typeDefs = gql`
type Query {
hello: String
}
`;
const resolvers = {
Query: {
hello: () => ‘Hello world!‘,
},
};
const server = new ApolloServer({ typeDefs, resolvers });
exports.handler = server.createHandler();
Let‘s break this down:
gql
template literal tag. Our schema has a single Query
type with a hello
field that returns a String.hello
resolver simply returns the string "Hello world!".ApolloServer
by passing in our schema definition and resolvers.createHandler
method from ApolloServer
takes care of parsing the incoming request, executing our resolvers, and sending back the response.To run the server locally, add the following script to package.json
:
"scripts": {
"start": "node index.js"
}
Now run npm start
and visit http://localhost:4000
in your browser to explore the GraphQL playground.
Deploying to AWS Lambda using CloudFormation
Now that our GraphQL server is working, let‘s deploy it to AWS Lambda. We‘ll use AWS CloudFormation to define and provision the required resources.
Create a file named template.yaml
with the following contents:
AWSTemplateFormatVersion: ‘2010-09-09‘
Transform: AWS::Serverless-2016-10-31
Description: GraphQL server
Resources:
Api:
Type: AWS::Serverless::Api
Properties:
StageName: dev
GraphQLFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./
Handler: index.handler
Runtime: nodejs12.x
Events:
GraphQL:
Type: Api
Properties:
Path: /graphql
Method: ANY
RestApiId:
Ref: Api
Outputs:
ApiUrl:
Description: URL of your GraphQL API
Value: !Sub "https://${Api}.execute-api.${AWS::Region}.amazonaws.com/dev/graphql"
This CloudFormation template defines two resources:
handler
function exported from index.js
The function is triggered by requests to the /graphql
path of the API.
Finally, we output the API URL which can be used to access our GraphQL server.
To deploy the stack, run:
aws cloudformation deploy --template-file template.yaml --stack-name graphql-api
Once the deployment completes, the GraphQL API url will be displayed in the output. Open this URL in your browser and you should see the GraphQL playground.
Automating Deployment with a Script
Having to run the aws CLI command every time we want to deploy can get tedious. Let‘s write a simple bash script to automate this:
#!/bin/bash
# Package code
zip -r code.zip index.js node_modules
# Deploy CloudFormation stack
aws cloudformation deploy \
--stack-name graphql-api \
--template-file template.yaml \
--capabilities CAPABILITY_IAM
# Clean up
rm code.zip
This script does the following:
- Zips the code and dependencies into a deployment package
- Deploys the CloudFormation stack
- Cleans up the generated zip file
To use the script, make it executable using chmod +x deploy.sh
and then run ./deploy.sh
Optimizing Lambda Performance
A few tips to optimize the performance of your GraphQL Lambda:
GraphQLSchema
constructor for creating your schema to prevent the schema from being re-constructed on every invocation. Export your schema from a separate file and import it.Securing Your GraphQL API
Adding authentication and authorization to your GraphQL API is crucial for protecting sensitive data. Here are some options:
Local Development and Testing
For local development and testing, I highly recommend using the serverless-offline plugin along with apollo-server-testing. This lets you run your Lambda function locally and write integration tests for your GraphQL resolvers.
Install the dependencies:
npm install -D serverless-offline apollo-server-testing
Update your serverless.yml
:
plugins:
- serverless-offline
Create a tests
directory and add a test file:
const { ApolloServer } = require("apollo-server-lambda");
const { createTestClient } = require("apollo-server-testing");
const { typeDefs, resolvers } = require("../schema");
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ event, context }) => ({
headers: event.headers,
functionName: context.functionName,
event,
context
})
});
const { query, mutate } = createTestClient(server);
describe("Query", () => {
test("hello", async () => {
const res = await query({
query: `query { hello }`
});
expect(res.data.hello).toBe("Hello world!");
});
});
This test uses the createTestClient
function to create a test client that can be used to send queries and mutations to our server.
To run the tests, add the following script to package.json
:
"scripts": {
"test": "jest"
}
And run using npm test
Monitoring and Observability
To ensure the smooth operation of your GraphQL server, it‘s critical to have proper monitoring and observability in place.
Some key metrics to track:
AWS CloudWatch is a good starting point for monitoring your Lambda functions and API Gateway. Make sure to enable detailed metrics for API Gateway and logs for Lambda.
For more advanced monitoring, consider using a third-party service like Epsagon or AWS X-Ray to get tracing and performance insights.
Conclusion
In this post, we looked at how to build a GraphQL server using Node.js and Apollo Server and deploy it to AWS Lambda using CloudFormation.
We also covered best practices around performance optimization, security, testing, and monitoring. By following these tips and techniques, you‘ll be able to build production-ready, scalable GraphQL APIs on AWS Lambda.
The complete code for this tutorial is available on GitHub.
If you have any questions or feedback, feel free to leave a comment below. Happy coding!