GraphQL API using Serverless + AWS AppSync + DynamoDB + Lambda resolvers + Cognito. [Part 1]
A couple of weeks ago, I started working on a project that implements all of this technologies together. It was a little hard to setup everything since all the documentation you can find online is not complete, it has errors, it is oriented to older versions, or it is only explained with a dummy very simple example not applicable to the real world.
So, to contribute a little, I had the idea of sharing my experience to anyone that wants to use this combination of technologies in order to build a real world project.
I’m not an expert, but I think that you could leverage this tips in order to save you a good amount of hours trying to resolve some (sometimes unbelievable) serverless issues.
The main goal is to write a tutorial that fully cover a real world fragment of a GraphQL API that involves:
- Offline development of all project (local DynamoDB database, local functions, endpoints and so on).
- Entities with different relations (one to one, one to many, many to many).
- Cognito AppSync authorization.
- Lambda functions as resolvers. (VTL request and response resolvers only with minimum logic).
- Jetpack plugin to correctly deploy only individual functions to Lambda without causing serverless freeze (this is a common serverless issue hard to solve when you have a lot of functions).
- Lambda layers.
In order to do all this, I will use the following Serverless plugins:
- serverless-offline plugin.
- serverless-dynamodb-local plugin.
- serverless-appsync-plugin plugin.
- serverless-appsync-offline plugin.
- serverless-jetpack plugin.
I’m going to split this tutorial in several parts, each part will cover a simple example, and then we will be adding extra features on the next part. I will give you access to a Github repository, in which you will find a commit containing the complete code of each part of the tutorial.
The tutorial is going to be composed by the following parts:
- Part 1: “Serverless basic configuration”.
Here we are going to configure the minimun necessary parts of serverless framework, in order to get a running offline/local AppSync service, with a query that returns a mocked list of Books.
- Part 2: “Mounting a local DynamoDB database”.
We are going to mount a DynamoDB database that runs offline and locally.
- Part 3: “Implementing queries and mutations that use our local DynamoDB database”.
We will implement our first mutation to create a new book and save it into that database, also we are going to modify our listBooks query so it returns the books from our mounted local DynamoDB database, and we are going to create another query to find a single book: getBook.
Also, we are going to use lambda layers in order to have all the third party libraries stored there, and keep the lambda functions as lightweight as it is possible.
- Part 4: “Book related entity: Authors”.
We are going to introduce a related entity: Authors. A book could have one or more authors, and we will have to be able to get the authors when we get a book, and get the books from an author, so we are going to introduce the corresponding queries, mutations and resolvers to list, get, create all those entities and they relations.
- Part 5: “Using Jetpack plugin to deploy”.
In this part we are going to implement a great serverless plugin: “Jetpack”. This plugin will significantly improve the way that serverless do the deploys to AWS. We are going to see how to deploy lambda functions individually and as lightweight as we can.
- Part 6: “Cognito integration”.
We are going to integrate Cognito and use it to authorize AppSync queries and mutations. Also, we will see how to handle this integration when running AppSync offline.
Well… Let’s start putting hands on code!
Serverless basic configuration
I will consider that you already understand how npm, yarn, nodejs, and all that stuff works. Also I assume you are familiarized on GraphQL and how it works.
The goal of this part of the tutorial, is to have a little mounted serverless project that can be runned offline and get a common GraphQL entity list using lambda as resolver.
In future parts, we will start complexing the things adding local DynamoDB, mutiple entities and relations, multiple lambda resolvers, cognito and so on…
Now, the goal should be to get a mocked list of one entity, let’s say a Book entity.
First of all, we must create a new folder for this project, let’s name it “sls_appsync_example”, then we are going to initialize an npm project and install serverless globally:
$ mkdir sls_appsync_example
$ cd sls_appsync_example
$ npm init
$ npm install -g firstname.lastname@example.org
(I’m using serverless 1.51, I had to downgrade from 1.52 due to deploy issues, so I recommend you that you follow this guide with the same exact version to avoid conflicts).
Now, we should install the very first packages we are going to need:
$ npm install --save-dev serverless-offline serverless-appsync-plugin serverless-appsync-offline
Once we have all this ready to use, we should start writing some serverless configuration and the current schema.graphql file, where we must define our entities.
I will guide you through each needed configuration file.
The first thing will be to configure the basics of serverless, so create a new serverless.yml file on the root directory, and copy this configuration. Let’s review each part of it.
- service: project service name.
- plugins: this will be the first basic plugins that we are going to use.
1. serverless-appsync-plugin to integrate AppSync with serverles.
2. serverless-appsync-offline to run and use AppSync locally without need of doing a deploy to AWS (we will cover deploys in detail on another part of this tutorial).
3. serverles-offline to fully run serverless offline in our local machine.
- provider: in this part we will configure the provider and its details: we are going to use aws (in my case in us-east-1 region, feel free to use your own region). I will use nodejs10.x runtime to execute lambda functions. The stage name will be passed as a parameter, but if it is no present, the default one will be “local” (we will reserve “dev” for AWS deployed versions of this project, and “local” for offline stage that will run on our machine).
The iamRoleStatements attribute, will configure needed permissions to this project (feel free if you want to do a more restrictive roles policy, but this one will be fine for this examples).
Then, we are going to configure some environment variables, that will be available in this configuration file, another serverless configurations files, and also inside lambda functions, here we will define region, stage (that are the same as we defined at the beginning of this “provider” section, and another variable ”APPSYNC_NAME” to define how we are going to call this AppSync API (note that it uses custom.defaultPrefix as part of the name, we will see this right below).
- custom: here we are going to configure:
1. appSync plugin configuration: for this first part, we are going to use the easier authenticationType that is “API_KEY” (this key is not going to be required when we test this local offline query).
Then we are going to include a file to define data sources, and another file to define mapping templates (we will cover this just in a moment), we could put all this configuration on this same file but I prefer to separate the configuration in order to not have a single huge file. By now at this part, we will only create the needed roles.
Also we are adding roles to AppSync, that are going to be defined in another file too (and included here at the end of this file).
2. appsync-offline will configure appsync-offline-plugin, in this part we will only configure the port where local AppSync is going to be listening.
3. defaultPrefix is going to be used to determine almost all resources names, this is very useful to have different names of the resources at each stage, in this case for example, if you look at “APPSYNC_NAME” env variable, this variable will be sls-appsync-demo-local-appsync at local stage, and if we deploy dev stage, this variable name (and the AWS dev AppSync API name) will be sls-appsync-demo-dev-appsync. This will be very useful once we deploy several stages to AWS.
- functions: This adds a configuration file where we’re going to define our lambda functions that will be use as resolvers.
- resources: here we are going to define other configurations, by now at this part, we will only create the needed roles and permissions for this project.
Now let’s create the schema.graphql file, and the let’s review the included configuration files
At this tutorial part we are going to define a very simple Book entity, with id and title. The only query will be listBooks, and will return a mocked array of books.
In this data-sources file we are going to define the data source of our listBooks query, this source will be lambda, and the function name will be “listBooks” (we are going to define that function in another following file). This means, that whenever the query listBooks is called, it will be a lambda function the responsible of returning the corresponding array of books.
Here we will define the lambda function responsible for returning the array of books, this function will be at /lambda-functions/book/listBooks/index.js. About package, we are going to cover that in detail in future parts of the tutorial, by now you only has to know that we will include all the files present at the lambda function defined folder.
Here we are going to review 3 files at once:
- resources/resolvers.yml: this will define how our AppSync request mapping template and response mapping template (one detail: mapping-template folder will be the default folder that AppSync plugin will check).
- mapping-templates/common-response.yml: this will check if the response has an error, and if so, it will format it and return it. If there is no error, it will only return the response converted to JSON format.
- mapping-templates/book/query.listBooks-request.vtl: this file will add extra info to event passed to lambda function, so I think it is the most important file here. At this part, we will only add a simple arguments. If there is any argument present in the GraphQL query, it will be passed to lambda event.arguments parameters (we are not going to have parameters right now, but we will see this works and how can we use it in detail at next tutorial parts).
This is the lambda function that will return the mocked book array list. See that is a very simple async function, and I added some console log so you can check at console the received event parameter (with an empty arguments array attribute by now), and the returned structure of books (two simple mocked books).
(I’m not going to cover resources/roles.yml file, you can see it at the repository, it is a simple configuration about roles and permissions).
Running and testing all this stuff locally
Well, now that we have cover (at least I tried) all this basic configurations for this simple purpose, we could see all this in action, let’s see how to do it!
- First of all, install Postman, we will need it.
- Second, run serverless locally
$ serverless offline start --printOutput --stage local
This will start a local serverless AppSync server. Using printOutput we can see all the executed lambda function console.log, and we define that our current stage is “local” using -stage local.
Now, we have a running version of our AppSync GraphQL API!
- Third, let’s test our listBooks query using Postman.
Well, that is all for this first part! Now you have locally mounted a GraphQL AppSync simple server, using Lambda as resolver.
In next parts we are going to add local DynamoDB database, mutations, read/write DynamoDB operations for queries and mutations, multiple kind of entities relations, lambda layers to use externa libraries, Jetpack plugin to do correct lambda deploys, AppSync Cognito authorization, and more things…
I hope this tutorial has been helpfull for anyone starting using Serverless framework with Appsync.
You can download the complete implementation code of this “part 1” at this GIT repo commit:
Just clone it into one folder, and then run:
$ npm install
$ serverless offline start --printOutput --stage local
You can continue reading next part: Mounting a local DynamoDB database.