Front-End Web & Mobile
Amplify Framework adds support for AWS Lambda functions and Amazon DynamoDB custom indexes in GraphQL schemas
Written by Kurt Kemple, Sr. Developer Advocate at AWS, Nikhil Dabhade, Sr. Product Manager at AWS, & Me!
The Amplify Framework is an open source project for building cloud-enabled mobile and web applications. Today, we’re happy to announce new features for the Function and API categories in the Amplify CLI.
It’s now possible to add an AWS Lambda function as a data source for your AWS AppSync API using the GraphQL transformer that’s included in the Amplify CLI. You can also grant permissions for interacting with AWS resources from the Lambda function. This updates the associated IAM execution role policies without needing you to perform manual IAM policy updates.
The GraphQL transformer also includes a new @key directive that simplifies the syntax for creating custom indexes and performing advanced query operations with Amazon DynamoDB. This streamlines the process of configuring complex key structures to fit various access patterns when you’re using DynamoDB as a data source.
Adding a Lambda function as a data source for your AWS AppSync API
The new @function directive in the GraphQL transform library provides an easy mechanism to call a Lambda function from a field in your AWS AppSync API. To connect a Lambda data source, add the @function directive to a field in your annotated GraphQL schema that’s managed by the Amplify CLI. You can also create and deploy the Lambda functions by using the Amplify CLI.
Let’s look at how you can use this feature.
What are we building?
In this blog post, we’re creating a React JavaScript application that uses a Lambda function as a data source for your GraphQL API. The Lambda function writes to storage which in this case is DynamoDB. In addition, we will illustrate how you can easily grant create/read/update/delete permissions for interacting with AWS resources (such as DynamoDB) from a Lambda function.
Setting up the project
Pre-requisites
Download, install and configure the Amplify CLI.
Next, create your project if you don’t already have one. We’re creating a React application here, but you can choose to create a project with any other Amplify-supported framework, such as Angular, Vue or Ionic.
Download, install and configure the Amplify CLI.
The ‘amplify init’ command initializes the project, sets up deployment resources in the cloud, and makes your project ready for Amplify.
Adding storage to your project
Next, we will setup the backend to add storage using Amazon DynamoDB for your React JavaScript application.
Adding a function to your project
Next, we’re adding a Lambda function by using the Amplify CLI. We will also grant permissions for the Lambda function to be able to interact with the DynamoDB table that we created in the previous step.
This opens the Hello world function template file ‘index.js’ in the editor that you selected during the ‘amplify init’ step.
Auto populating environment variables for your Lambda function
The Amplify CLI adds the environment variables as comments to your index.js files at the top for ease of reference. The environment variables represent the AWS resources that the Lambda function interacts with. In this case, the AWS resource is DynamoDB. We want to have the Lambda function add an entry to the DynamoDB table with the parameters that we pass to the GraphQL API. Add the following code to the Lambda function that uses the environment variables that represent the DynamoDB table and AWS Region:
/* Amplify Params - DO NOT EDIT
You can access the following resource attributes as environment variables from your Lambda function
var environment = process.env.ENV
var region = process.env.REGION
var storageTeststorageName = process.env.STORAGE_TESTSTORAGE_NAME
var storageTeststorageArn = process.env.STORAGE_TESTSTORAGE_ARN
Amplify Params - DO NOT EDIT */
var AWS = require('aws-sdk');
var region = process.env.REGION
var storageTeststorageName = process.env.STORAGE_TESTSTORAGE_NAME
AWS.config.update({region: region});
var ddb = new AWS.DynamoDB({apiVersion: '2012-08-10'});
var ddb_table_name = storageTeststorageName
var ddb_primary_key = 'id';
function write(params, context){
ddb.putItem(params, function(err, data) {
if (err) {
console.log("Error", err);
} else {
console.log("Success", data);
}
});
}
exports.handler = function (event, context) { //eslint-disable-line
var params = {
TableName: ddb_table_name,
Item: AWS.DynamoDB.Converter.input(event.arguments)
};
console.log('len: ' + Object.keys(event).length)
if (Object.keys(event).length > 0) {
write(params, context);
}
};
After you replace the function, jump back to the command line and press Enter to continue.
Next, run the ‘amplify push’ command to deploy your changes to the AWS Cloud.
Adding and updating the Lambda execution IAM role for Amplify managed resources
When you run the ‘amplify push’ command, the IAM execution role policies that are associated with the permissions you granted earlier are updated automatically to allow the Lambda function to interact with DynamoDB.
Setting up the API
After completing the function setup, the next step is to add a GraphQL API to your project:
This will open the schema.graphql file in the editor you selected during the ‘amplify init’ step.
Replace the annotated schema template located in your <project-root>/amplify/backend/api/<api-name>/schema.graphql file with the following code:
Check to see if the updates to your schema are compiled successfully by running the following command:
Now that your API is configured, run the amplify push command to deploy your changes to create the corresponding AWS backend resources.
When you’re prompted about code generation for your API, choose Yes. You can accept all default options. This generates queries, mutations, subscriptions, and boilerplate code for the Amplify libraries to consume. For more information, see Codegen in the Amplify CLI documentation.
Accessing the function from your project
Now that your function and API are configured, you can access them through the API class, which is part of the Amplify JavaScript Library.
Open App.js and add the following imports and call to the Amplify API as shown below:
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { addEntry } from './graphql/mutations';
import awsconfig from './aws-exports';
import { API, graphqlOperation } from "aws-amplify";
API.configure(awsconfig);
class App extends React.Component {
constructor() {
super();
this.state = {};
}
componentDidMount() {
this.addData();
}
async addData() {
const entry = {id:“1”, email:“email@example.com”, createdAt:“2019-5-29”}
const data = await API.graphql(graphqlOperation(addEntry, entry))
console.log(data)
this.setState({data});
}
}
export default App;
Running the app
Now that you have your application code complete, run the application and verify that the API call outputs “Success”.
Setting Amazon DynamoDB custom indexes in your GraphQL schemas
When you’re building an application on top of DynamoDB, it helps to first think about access patterns. The new @key directive, which is a part of the GraphQL transformer in the Amplify CLI, makes it simple to configure complex key structures in DynamoDB that can fit your access patterns.
Let’s say we’re using DynamoDB as a backend for your GraphQL API. The initial GraphQL schemas that we can use to represent @model types Customer and Item are as shown here:
Access patterns
For example, let’s say that this application needs to facilitate the following access patterns:
- Get customers by email – email is the primary key.
- Get Items by status and by createdAt – orderId is the primary key
Let’s walkthrough how you would accomplish these use cases and call the APIs for these queries in your React JavaScript application.
We’re assuming that you completed the pre-requisites and created your React JavaScript application as shown in the first section.
Creating an API
First, we will create a GraphQL API by using the ‘amplify add api’ command:
This will open the schema.graphql file under <myproject>/amplify/backend/api/myproject/schema.graphql
Modifying the schema.graphql file
Let’s dive in to the details with respect to the new @key directive.
Querying by primary key
Add the following Customer @model type to your schema.graphql
For Customer @model type, a @key without a name specifies the key for the DynamoDB table’s primary index. Here the hash key for the table’s primary index is email. You can only provide one @key without a name per @model type.
Querying by composite keys (one or more fields are sort key)
Let’s break down the above Item @model type.
DynamoDB lets you query by at most two attributes. We added three fields to our first key directive ‘@key(fields: [“orderId”, “status”, “createdAt”])‘. The first field ‘orderId is the hash key as expected, but the sort key is the new composite key named status#createdAt that is made of the status and createdAt fields. This enables us to run queries using more than two attributes at a time.
Run the ‘amplify push’ command to deploy your changes to the AWS Cloud. Because we have the @key directive, it creates the DynamoDB tables for Customer and Item–with the primary indexes, sort keys and generate resolvers that inject composite key values during queries and mutation.
The file <myproject>/src/graphlql/queries.js will contain the auto generated queries for our intended access patterns “Get customers by email” and “Get Items by status and by createdAt”.
Accessing the API from your application
Now that your API is configured, you can access it through the API class, which is part of the Amplify JavaScript Library. We will call the query for “Get Items by status and by createdAt”
Open App.js and add the following imports and call to the Amplify API as shown below:
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { itemsByStatusAndCreatedAt } from './graphql/queries';
import awsconfig from './aws-exports';
import { API, graphqlOperation } from "aws-amplify";
API.configure(awsconfig);
class App extends React.Component {
constructor() {
super();
this.state = {};
}
componentDidMount() {
this.fetchData();
}
async fetchData() {
const entry = {status:'PENDING', createdAt: {beginsWith:"2019"}};
const data = await API.graphql(graphqlOperation(itemsByStatusAndCreatedAt, entry));
this.setState({data});
}
render() {
return (
<div className="App">
<p>
{JSON.stringify(this.state.data, null, 4)}
</p>
</div>
);
}
}
export default App;
To learn more, see GraphQL Transform.
Feedback
We hope you like these new features! As always, let us know how we’re doing, and submit any requests in the Amplify Framework GitHub Repository. You can read more about AWS Amplify on the AWS Amplify website.