Efficient AppSync backend API development for mobile/Web app with AWS Amplify

Efficient AppSync backend API development for mobile/Web app with AWS Amplify

Why GraphQL and App Sync

GraphQL has taken the API world by storm as an alternative to REST APIs. One of its main benefits is to enable clients to query for just the data they need.

All the types that are exposed in an API are written down in a schema using the GraphQL Schema Definition Language (SDL). The schema works well with the Javascript type system. This schema serves as the contract between the client and the server to define how data can be accessed by the client. Once the schema is defined, the teams working on frontend and backend can do their work independently since they both are aware of the definite structure of the data that’s sent over the network. Frontend teams can easily test their applications by mocking the required data structures. Once the server is ready, the switch can be flipped for the client apps to load the data from the actual API.

AWS offers its own GraphQL serverless solution: AppSync. AWS AppSync enables developers to interact with their data by using a managed GraphQL service. AppSync integrates seamlessly with modern tools and frameworks, including React, React Native, iOS, and Android.

AWS Amplify is a rapid development tool to generate a AppSync backend infrastructure with CloudFormation template.

Demo Application

We will explore through a sample e-commerce application the process of developing a DynamoDB backed backend App Sync API. A subset of the data model will be used for demo purpose (see figure 1).

Figure 1: E-commerce data model subset
GraphQL schema definition

The first step is to create the GraphQL schema definition for your backend data structure. AWS Amplify CLI can generate a DynamoDB-backed backend infrastructure from a GraphQL schema. For that purpose, some directives are added to make to guide the mapping of the model to DynamoDB. Like @model to specify the storing types (tables) and @connection to specify relationships between @model object types. Below is the schema definition for our e-commerce application.


type Order @model {
  id: ID!
  status: OrderStatus!
  dateOrderPlaced: AWSDateTime!
  orderDetails: String
  items: [OrderItem] @connection(name: "OrderOrderItems")
}

type OrderItem @model {
  id: ID!
  quantity: Int!
  price: Float!
  otherItemDetails: String
  order: Order @connection(name: "OrderOrderItems")
  product: Product @connection(name: "OrderItemProduct")
}

type Product @model {
  id: ID!
  name: String
  price: Float!
  size: String
  description: String
  orderItems: [OrderItem] @connection(name: "OrderProduct")
}

enum OrderStatus {
  CANCELED
  COMPLETED
  ONHOLD
  PROCESSING
  PENDING_PAYMENT
}
DynamoDB-backed App Sync backend generation with AWS Amplify

AWS Amplify first has to installed, configured and initialised.

$ npm install --save aws-amplify
$ amplify configure
$ amplify init

The “amplify init” should be issued in the root folder of the application and creates the basic infrastructure to manage the backend resources which can go beyond the API (see figure 2).

Figure 2: amplify init

The AppSync API infrastructure can then be created with the schema definition (see example of running the commands in figures 3 and 4-1). The add command create the CloudFormation template for the API and the push command provision the resources in the cloud. Thus the AppSync API is ready to be used (see figure 4-2).

$ amplify add api
$ amplify push
Figure 3: amplify add api
Figure 4-1: amplify push
Figure 4-2: Generated API

During the push command Amplify CLI can generate queries (read-only fetch) and mutations (write followed by a fetch), and subscriptions (long-lived requests that receive data in response to events) in the front-end. Amplify supports React/React Native, Angular and Vue frontends.

Components of the generated AppSync API

Data Source

An AppSync data source is a persistent storage system (DynamoDB or RDS) or a trigger (AWS Lambda), along with credentials for accessing that system or trigger. In the example, the data sources are the DynamoDB tables generated by AWS Amplify CLI (see figure 5).

Figure 5: Data sources
Resolver

A resolver performs an operation (query or mutation) against a predefined data source. It is comprised of a request and a response mapping template, a data source name, and a version. The templates are written in Apache Velocity Template Language (VTL). Amplify generates resolvers for each CRUD operation across each model. Below is an example of resolver (see figures 6-1, 6-2 and 6-3).

Figure 6-1: Resolver for CreateProduct – Request template
Figure 6-2: Resolver for CreateProduct – Request template
Figure 6-3: Resolver for CreateProduct – Response template
Updated schema

The generated GraphQL schema does not include the directives and the queries and mutations have been added. The JSON version of the schema is added in the frontend as part of the integration with the backend. Below is an excerpt of the generated schema and a call to the mutation createProduct in the query interface offered by AWS AppSync (see figure 7).


schema {
  query: Query
  mutation: Mutation
}
…
type Mutation {
  createOrder(input: CreateOrderInput!): Order
  createOrderItem(input: CreateOrderItemInput!): OrderItem
  createProduct(input: CreateProductInput!): Product
  deleteOrder(input: DeleteOrderInput!): Order
  deleteOrderItem(input: DeleteOrderItemInput!): OrderItem
  deleteProduct(input: DeleteProductInput!): Product
  updateOrder(input: UpdateOrderInput!): Order
  updateOrderItem(input: UpdateOrderItemInput!): OrderItem
  updateProduct(input: UpdateProductInput!): Product
}
…
input CreateProductInput {
  description: String
  name: String
  price: Float!
  size: String
}
Figure 7: Testing of the mutation createProduct

AppSync API call from the frontend with React

AWS Amplify facilitates the integration of the backend with JavaScript frameworks like React, Angular and Vue. It generates a configuration file, aws-exports.js, in your project root src folder. Import the file to configure your app to work with your AWS AppSync GraphQL backend.


import Amplify from 'aws-amplify';
import config from './aws-exports';

Amplify.configure(config);

The Amplify CLI generates all GraphQL statements (queries, mutations and subscriptions) and for JavaScript applications saves it in src/graphql folder. In order to call the mutation createProduct, import the generated mutation and execute it with API.graphQL :


import { API, graphqlOperation } from 'aws-amplify';
import {createProduct} from './graphql/mutations';
.... 
state = {
  name: 'Chips',
  price: 10.0,
  description: 'Original salty chips',
  created: false
}
async createProduct() {
  try {
const response = await API.graphql(graphqlOperation(createProduct, {
      input: {
        name: this.state.name,
        price: this.state.price,
        description: this.state.description
      }
    }));
    this.setState({created: true});
    console.log(response);
  } catch (error) {
    console.log(error);
  }
}

The result of a successful call is an JavaScript object with the data field as seen in figure 8.

Figure 8: Console log of a mutation call

Conclusion

Creating a DynamoDB-backed GraphQL AppSync API with Amplify is fairly easy as demonstrated in this article. AWS Amplify CLI facilitates the integration with a Javascript frontend. In a followup article, I will explore how to create a RDS GraphQL AppSync API using an AWS Lambda data source.

References

AWS re:Invent 2018: Ten tips And Tricks for Improving Your GraphQL API with AppSync (MOB401)

AWS AppSync documentation 

AWS Amplify API Documentation

defodji.sogbohossou@shinesolutions.com
1 Comment
  • Defodji Sogbohossou
    Posted at 06:20h, 26 March Reply

    @Nicolas Silva
    To be able to sort the results of a query by some field, a new parameter sortField has been added to the @connection directive (see https://aws-amplify.github.io/docs/cli/graphql).
    directive @connection(name: String, keyField: String, sortField: String) on FIELD_DEFINITION
    Below is an example:
    type Order @model {
    id: ID!
    status: OrderStatus!
    dateOrderPlaced: AWSDateTime!
    orderDetails: String
    items: [OrderItem] @connection(name: “OrderOrderItems”, sortField: “price”)
    }

Leave a Reply

%d bloggers like this: