In this blog post, we’ll explore how to build and deploy GraphQL APIs using AWS AppSync and the Serverless Framework. This tutorial is designed to be easy to follow for both beginners and experienced developers. By the end, you’ll have a working GraphQL API deployed on AWS.
Table of Contents
1. Introduction to GraphQL
2. Understanding AWS AppSync
3. Setting Up Your AWS Environment
4. Creating a GraphQL API with AWS AppSync Console
5. Define the API Schema
6. Adding Data Sources
7. Create Resolver
8. Testing Your GraphQL API
9. Deploying the API Using the Serverless Framework
10. Conclusion
Introduction to GraphQL
GraphQL is an open-source query and data manipulation language developed by Facebook, GraphQL allows you a more flexible and feasible way to fetch and manipulate data compared to traditional API calls. It allows you to request the exact data you need. With GraphQL you as a client can specify the structure of data you want to receive, and the server will return only that data.
GraphQL is built with a strongly typed schema. That defines the type and relationships of data that can be queried. Clients interact with schema using mutations, queries, and subscriptions.
Key Concept of GraphQL:
1. Query Language for APIs: GraphQL allows you to specify exactly what data you want in a single request. You can fetch the data you need in a single query and avoid multiple roundtrips to the server.
2. Single Endpoint: Not like REST APIs, which often require multiple endpoints for different resources, GraphQL APIs have only a single endpoint. Clients send their queries to the endpoint that GraphQL provides, and the server will process the query and return the appropriate response you need.
3. Strongly Types Schema: The schema we use in GraphQL is defined using GraphQL Schema Definition Language (SDL). This schema definition includes various types, queries, mutations, and subscriptions. Each type can have fields, which can themselves be complex types or simple scalar types like strings, integers, and booleans.
4. Queries: GraphQL queries allow you as a client to request specific fields you need from a resource and nested resource in a single request. A query mirrors the shape of the response the client expects. Below is a simple query example:
{ posts { id title Content }
This query will fetch posts and retrieve their ID, title, and content of their posts.
5. Mutations: Mutations allow clients to modify data on the server side. They are similar to queries; queries allow clients to fetch data, but mutations allow clients to create, update, and delete data. Mutation also specifies the data that should be returned after modification. Here’s an example of a mutation to create a new post:
mutation CreatePost { createPost(title: "Sample Post", content: "This is a sample Post", author: "Jone") { id title Content author }
6. Subscriptions: Subscriptions allow clients to receive updated data in real time from the server. This is useful for applications that need to react to changes in data, such as chat apps or live sports scores. Subscriptions use WebSockets to push updates to the clients whenever specified events occur.
subscription { newPost { id title content } }
7. Resolvers: Resolver is noting it’s a function that connects GraphQL queries and mutations to your backend data. When a query is made, GraphQL calls the appropriate resolver to fetch the requested data. Resolver can fetch data from databases, APIs, or any other data source.
Advantages of GraphQL
1. GraphQL runs faster: GraphQL is significantly faster than other communication APIs because it allows you to narrow down your request query by selecting only the fields you want to query.
2. Best for complex systems and microservices: We can combine multiple systems behind GraphQL’s API. It unifies them and hides their complexity. The GraphQL server is also used to fetch data from the existing systems and package it up in the GraphQL response format. This is most beneficial for legacy infrastructures or third-party APIs that are enormous in size and difficult to maintain and handle. When we have to migrate from a monolithic backend application to a microservice architecture, the GraphQL API can help us to handle communication between multiple microservices by merging them into one GraphQL schema.
3. Efficient Data Fetching: The main advantage of GraphQl over REST is that REST responses contain too much data or sometimes not enough data, which creates the need for another request. GraphQL solves this problem by fetching only the exact and specific data in a single request.
4. Single End Point: Having a single endpoint simplifies the client-server interaction; clients do not need to manage multiple endpoints for different resources; we can manage multiple resources in a single endpoint.
5. Real-Time Data: Built-in support for subscriptions allows clients to receive real-time updates, which is essential for many modern applications.
Understanding AWS AppSync
AWS AppSync is a managed service that uses GraphQL to make it easy for applications to get exactly the data they need. It automatically scales your GraphQL API and provides real-time updates with built-in data synchronization.
Key Features of AWS AppSync
1. Managed GraphQL Service: This service abstracts away all of the runtime semantics and operational challenges of a GraphQL API, making it painless for developers.
2. Real-Time Data: It supports GraphQL subscriptions. This way, you can build real-time applications simply.
3. Integration with AWS Services: Integration is no challenge since the API is perfectly compatible with AWS services such as DynamoDB, Lambda, and Cognito which turn your machine into a platform for applications of any scale.
Now that we’ve covered the fundamentals of GraphQL and AppSync, let’s take a step-by-step approach to creating a backend application for our blog posts.
Step 1: Setting Up Your AWS Environment
Before we dive into creating our GraphQL API, we need to set up our AWS environment. This includes creating an AWS account and configuring the necessary services.
Step-by-Step: Setting Up AWS
1. Log in to the AWS Management Console: If you don’t have an AWS account, sign up for a free account. Once you have an account, log in to the AWS Management Console.
2. Navigate to AppSync: In the AWS Management Console, search for “AppSync” in the AWS services search bar and click on it.
Step 2: Creating a GraphQL API
Now that our AWS environment is set up, let’s create our GraphQL API using the AppSync console. We’ll start by defining a simple schema and then add data sources and resolvers.
Step-by-Step: Creating a GraphQL API
1. Create a new API: Click “Create API” and choose “Start from scratch.” This will allow us to define our own schema and resolvers.
2. Define the API name: Enter a name for your API (e.g., “MyBlogPostAPI”). This will be the name used to identify your API in the AWS Management Console.
Step 3: Define the API schema
In the AppSync console, you’ll be prompted to define your API schema. You can start with a sample schema or create your own. For this guide, let’s start with a simple schema for a Blog Post application.
type Post { id: ID! title: String! content: String! author: String! } type Query { posts: [Post] } type Mutation { createPost(title: String!, content: String!, authorId: String!): Post } type Subscription { newPost: Post }schema { query: Query mutation: Mutation subscription: Subscription }
This schema defines a Post type with id, title,content, and author fields. It also defines a Query type for fetching posts and a Mutation type for creating posts.
1. Save the schema: Click “Save” to save your schema. AppSync will validate your schema and provide feedback if there are any errors.
Step 4: Adding Data Sources
A data source is a persistent storage system (like DynamoDB, RDS, etc.) where the data for your API resides. Resolvers connect the fields in your schema to your data source.
Step-by-Step: Adding Data Sources
1. Navigate to Data Sources: Click on the “Data Sources” tab in the AppSync console and then click “Create Data Source.”
2. Select DynamoDB: For this example, choose DynamoDB as your data source and enter a name for it (e.g., “BlogPostTable”).
If you don’t already have a DynamoDB table, you can create one through the DynamoDB console. Navigate to the DynamoDB service, click “Create table,” and configure it with a partition key of pk and sort key of sk(type: String).
{ "TableName": "PostTable", "AttributeDefinitions": [ { "AttributeName": "pk", "AttributeType": "S" }, { "AttributeName": "sk", "AttributeType": "S" } ], "KeySchema": [ { "AttributeName": "pk", "KeyType": "HASH" }, { "AttributeName": "sk", "KeyType": "RANGE" } ] }
Step 5: Creating Resolvers
Resolvers are functions that handle requests for data. There are two types of resolvers unit and pipeline resolvers.
1. Unit resolvers: A unit resolver is composed of code that defines a single request and response handler that are executed against a data source. The request handler takes a context object as an argument and returns the request payload used to call your data source. The response handler receives a payload back from the data source with the result of the executed request. The response handler transforms the payload into a GraphQL response to resolve the GraphQL field.
2. Pipeline Resolvers: When implementing pipeline resolvers, the general structure they follow includes:
- Before Step: Preprocesses request data before it moves through the resolver. This allows initial operations to be performed.
- Function(s): After the before step, the request is passed to a list of functions. Each function has its own request and response handler:
1. Request Handler: Performs operations against the data source.
2. Response Handler: Processes the data source’s response before passing it to the next function.
3. Functions execute serially in the order defined by the developer. The final result is passed to the after step.
3. After Step: A handler function that performs final operations on the response from the last function before passing it to the GraphQL response.
We’ll need to create a resolver for each field in our schema that interacts with our data source. We’ll use unit resolvers and AppSync JavaScript (APPSYNC_JS) as the resolver runtime.
Add a resolver for createPost:
1. In your API, choose the Schema tab.
2. In the Resolvers pane, find the createPost field under the Mutation type, then choose Attach.
3. Choose your data source, then choose Create.
4. In your code editor, replace the code with this snippet:
import { util } from "@aws-appsync/utils"; import * as ddb from "@aws-appsync/utils/dynamodb"; function request(ctx) { const { input } = ctx.arguments; const id = util.autoId() const sk = `${id}`; const item = { id, ...input }; const key = { pk: "POST", sk }; return ddb.put({ key, item }); } function response(ctx) { return ctx.result; } export { request, response };
To Add resolver for posts:
1. In your API, choose the Schema tab.
2. In the Resolvers pane, find the posts field under the Query type, then choose Attach.
3. Choose your data source, then choose Create.
4. In your code editor, replace the code with this snippet:
import * as ddb from '@aws-appsync/utils/dynamodb' export function request(ctx) { return ddb.get({ key: { pk: "POST" } }) } export const response = (ctx) => ctx.result
Testing Your GraphQL API
AWS AppSync provides a built-in GraphQL playground to test your API. This playground allows you to run queries and mutations against your API and see the results in real-time.
Step-by-Step: Testing the API
1. Navigate to the Queries Tab: In the AppSync console, go to the “Queries” tab. This will open the GraphQL playground.
Run a Query: Enter the following query to fetch all posts:
query ListPost { posts { id title Content author } }
2. Click the “Run” button to execute the query. You should see an empty list if no posts have been created yet.
Run a Mutation: Enter the following mutation to create a new post:
mutation CreatePost { createPost(title: "Sample Post", content: "This is a sample Post", author: "Jone") { id title Content author }
1. Click the “Run” button to execute the mutation. You should see the new post in the response.
2. Run the Query Again: Run the ListPost query again to see the newly created post in the list.
Deploying Blog Post Applications with Serverless
The Serverless Framework simplifies the deployment and management of serverless applications. It allows you to define the infrastructure as code and deploy it with a single command.
Now we will deploy our Blog post application using the serverless framework here are the steps we need to follow.
Step-by-Step: Deploying with Serverless
1. Install Serverless Framework: If you haven’t already, install it globally using npm.
npm install -g serverless |
2. Create a Serverless Project: Initialize a new Serverless project.
serverless create –template aws-nodejs –path graphql-appsync cd graphql-appsync |
3. Instal Dependencies: Install the AWS AppSync plugin for Serverless.
npm install serverless-appsync-plugin |
4. Configure serverless.yml: Edit the serverless.yml file to include the AppSync plugin and configure your API.
- First of all, set up the service name as blog-post and specify that you are using version 3 of the Serverless Framework.
service: blog-post frameworkVersion: “3” |
- The provider section defines AWS as the cloud provider, sets the region to us-east-1, and specifies Node.js version 20.x as the runtime environment for your application.
provider: name: aws region: us-east-1 runtime: nodejs20.x |
- plugins section includes the AppSync plugin to enable integration with AWS AppSync.
plugins: – serverless-appsync-plugin |
- The appSync section configures your AppSync API, naming it blog-post-api, using API_KEY for authentication, and defining an API key that expires after one month.
appSync: name: blog-post-api authentication: type: “API_KEY” apiKeys: – name: blog-post-key expiresAfter: 1M |
- The resolvers section specifies how to handle GraphQL operations:
- Mutation.createPost: Defines a unit resolver for the createPost mutation, using postTable as the data source and referencing the code in resolvers/createPost.js.
- Query.posts: Defines a unit resolver for the posts query, also using postTable as the data source, with the implementation in resolvers/getAllPost.js.
resolvers: Mutation.createPost: kind: UNIT dataSource: postTable code: resolvers/createPost.js Query.posts: kind: UNIT dataSource: postTable code: resolvers/getAllPost.js |
- The resources section defines a DynamoDB table named BlogPost with a primary key (pk) of type string and specifies the key schema for the table.
resources: Resources: MyTable: Type: AWS::DynamoDB::Table Properties: TableName: BlogPost AttributeDefinitions: – AttributeName: pk AttributeType: S – AttributeName: sk AttributeType: S KeySchema: – AttributeName: pk KeyType: HASH – AttributeName: sk KeyType: RANGE BillingMode: PAY_PER_REQUEST |
5. Create Schema File: Create a file named schema.graphql in the root of your project and paste the above schema.
6. Create Resolves: Create a folder named resolvers in the root of your project and create two js files one is createPost.js and another one is getAllPost.js and paste the resolvers code that we define above.
7. Deploy Your Service: Run the deploy command to deploy your API.
serverless deploy |
- This command will package and deploy your service to AWS. The Serverless Framework will provide the API endpoint after deployment.
- Retrieve the Endpoint: After deployment, the Serverless Framework will output the API endpoint. You can use this endpoint to interact with your GraphQL API.
Full Code:
service: blog-post frameworkVersion: "3" provider: name: aws region: us-east-1 runtime: nodejs20.x plugins: - serverless-appsync-plugin appSync: name: blog-post-api authentication: type: "API_KEY" apiKeys: - name: blog-post-key expiresAfter: 1M schema: "schema.graphql" dataSources: postTable: type: AMAZON_DYNAMODB description: "Blog Post Table" config: tableName: BlogPost resolvers: Mutation.createPost: kind: UNIT dataSource: postTable code: resolvers/createPost.js Query.posts: kind: UNIT dataSource: postTable code: resolvers/getAllPost.js resources: Resources: MyTable: Type: AWS::DynamoDB::Table Properties: TableName: BlogPost AttributeDefinitions: - AttributeName: pk AttributeType: S - AttributeName: sk AttributeType: S KeySchema: - AttributeName: pk KeyType: HASH - AttributeName: sk KeyType: RANGE BillingMode: PAY_PER_REQUEST
Conclusion
Following this guide, you have successfully set up a GraphQL API with the help of AWS AppSync and managed to deploy it utilizing Serverless Framework.
You’ve learned how to add and connect data sources, write resolvers, and carry out the testing of your API.
With this configuration, it is possible to create highly flexible and scalable APIs that are capable of complex query resolution.
AWS AppSync and the Serverless Framework work together to provide a strong toolkit for building modern apps, facilitating real-time data handling, and simplifying development.