Add GraphQL API

Create GraphQL API

The API category of the Amplify Framework uses AWS AppSync, a managed service that uses GraphQL to make it easy for applications to get exactly the data they need. With AppSync, you can build scalable applications, including those requiring real-time updates, on a range of data sources such as NoSQL data stores, relational databases, HTTP APIs, and your custom data sources with AWS Lambda.

  1. Run this Amplify CLI command to add API:
amplify api add

We are going to:

  • Create GraphQL API
  • Use default name, e.g. traveldeals
  • Use Amazon Cognito User Pool for default authorization type
  • Do you want to edit the schema now? - Yes

Amplify will deploy with AppSync and generate table in Amazon DynamoDB.

? Please select from one of the below mentioned services: GraphQL
? Provide API name: traveldeals
? Choose the default authorization type for the API Amazon Cognito User Pool
Use a Cognito user pool configured as a part of this project.
? Do you want to configure advanced settings for the GraphQL API No, I am done.
? Do you have an annotated GraphQL schema? No
? Choose a schema template: Single object with fields (e.g., “Todo” with ID, name, description)

The following types do not have '@auth' enabled. Consider using @auth with @model
	 - Todo
Learn more about @auth here: https://docs.amplify.aws/cli/graphql-transformer/directives#auth


GraphQL schema compiled successfully.

Edit your schema at traveldeals/amplify/backend/api/traveldeals/schema.graphql or place .graphql files in a directory at traveldeals/amplify/backend/api/traveldeals/schema
? Do you want to edit the schema now? Yes
Please edit the file in your editor: traveldeals/amplify/backend/api/traveldeals/schema.graphql
Successfully added resource traveldeals locally
  1. If your IDE/Editor did not open a Schema file, open it manually: traveldeals/amplify/backend/api/traveldeals/schema.graphql.

Replace its content with this Schema:

type Deal @model
    @auth(rules: [
        { allow: owner, operations: [create, delete, update] },
    ]) {
    id: ID!
    name: String!
    category: String!
}

The Amplify CLI provides GraphQL directives to enhance your schema with additional capabilities. Object types that are annotated with @model are top-level entities in the generated API. Objects annotated with @model are stored in Amazon DynamoDB and are capable of being protected via @auth.

Object types that are annotated with @auth are protected by a set of authorization rules giving you additional controls than the top level authorization on an API. You may use the @auth directive on object type definitions and field definitions in your project’s schema.

  1. Run this Amplify CLI command to push Backend changes to the Cloud:
amplify push

Confirm your decision, when prompted.

It will deploy GraphQL API with AppSync and create DynamoDB table Deal model. Also, Amplify CLI will generate JavaScript code, which can be used to access GraphQL endpoints in the Web Application code:

✔ Successfully pulled backend environment dev from the cloud.

Current Environment: dev

| Category | Resource name       | Operation | Provider plugin   |
| -------- | ------------------- | --------- | ----------------- |
| Api      | traveldeals         | Create    | awscloudformation |
| Auth     | traveldealsXXXXXXXX | No Change | awscloudformation |
? Are you sure you want to continue? Yes

GraphQL schema compiled successfully.

Edit your schema at traveldeals/amplify/backend/api/traveldeals/schema.graphql or place .graphql files in a directory at traveldeals/amplify/backend/api/traveldeals/schema
? Do you want to generate code for your newly created GraphQL API Yes
? Choose the code generation language target javascript
? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested] 2
  Updating resources in the cloud. This may take a few minutes...

While the backend is pushed, you can continue with following sections to modify Web Application. However, functionality will not be available until amplify push is complete.

Add Deal Creation and Browsing to Web Application

  1. Install required NPM modules:
npm install --save react-router-dom faker

When we added api category, Amplify CLI used provided GraphQL schema to generate following files:

traveldeals/src/graphql
traveldeals/src/graphql/mutations.js
traveldeals/src/graphql/queries.js
traveldeals/src/graphql/schema.json
traveldeals/src/graphql/subscriptions.js

  1. To run a GraphQL query from Web Application, you can import generated queries and execute them with API.graphql:
import * as queries from './graphql/queries';
import * as mutations from './graphql/mutations';
import * as subscriptions from './graphql/subscriptions';

// Create a Deal
await API.graphql(graphqlOperation(mutations.createDeal, { input: { name, category }}));

// List all Deals
await API.graphql(graphqlOperation(queries.listDeals, { limit: 1000 }));

// Subscribe to Deal creation updates
await API.graphql(graphqlOperation(subscriptions.onCreateDeal)).subscribe({
  next: (dealData) => {
    // ...
  }
});

This is a complete code of src/App.js that includes Deal Creation, Browsing Deals and Authentication, that was set up on the previous step:

import React from 'react';
import './App.css';

import PropTypes from 'prop-types';
import { BrowserRouter as Router, Route, NavLink, Link } from 'react-router-dom';
import { Divider, Form, Icon, Input, Modal, Button, Card, Menu, Dropdown, 
  Container, Header, Segment, Placeholder } from 'semantic-ui-react';

import Amplify, { Auth } from 'aws-amplify';
import API, { graphqlOperation } from '@aws-amplify/api';
import { AuthState, onAuthUIStateChange } from '@aws-amplify/ui-components';
import { AmplifyAuthenticator, AmplifySignUp } from '@aws-amplify/ui-react';

import * as queries from './graphql/queries';
import * as mutations from './graphql/mutations';
import * as subscriptions from './graphql/subscriptions';

import faker from 'faker';

import awsconfig from './aws-exports';
Amplify.configure(awsconfig);

const CATEGORIES = ['Outdoors', 'Cities'];
const COLORS = ['orange', 'yellow', 'green', 'blue', 'violet', 'purple', 'pink'];

function DealCardImage({dealName, minHeight, fontSize}) {
  function dealColor(name) {
    if (!name) name = '';
    return COLORS[Math.floor(name.length % COLORS.length)];
  }

  return (
    <Segment style={{minHeight, display: 'flex'}} inverted color={dealColor(dealName)} vertical>
      <Header style={{margin: 'auto auto', fontSize}}>{dealName}</Header>
    </Segment>
  );
}

DealCardImage.propTypes = {
  dealName: PropTypes.string,
  minHeight: PropTypes.number,
  fontSize: PropTypes.number
};

function DealCreation() {
  const [modalOpen, setModalOpen] = React.useState(false);
  const [name, setName] = React.useState();
  const [category, setCategory] = React.useState();
  
  function handleOpen() {
    handleReset();
    setModalOpen(true);
  };

  function handleReset() {
    setName(faker.address.city())
    setCategory(CATEGORIES[Math.floor(Math.random() * CATEGORIES.length)]);
  }

  function handleClose() {
    setModalOpen(false);
  };

  async function handleSave(event) {
    event.preventDefault();
    await API.graphql(graphqlOperation(mutations.createDeal, { input: { name, category }}));
    handleClose();
  };

  const options = CATEGORIES.map(c => ({ key: c, value: c, text: c}));

  return (
    <Modal
      closeIcon
      size='small'
      open={modalOpen}
      onOpen={handleOpen}
      onClose={handleClose}
      trigger={<p><Icon name='plus'/>Create new Deal</p>}>
      <Modal.Header>Create new Deal</Modal.Header>
      <Modal.Content>
        <Form>
            <Form.Field>
              <label>Deal Name</label>
              <Input fluid type='text' placeholder='Set Name' name='name' value={name || ''}
                onChange={(e) => { setName(e.target.value); } }/>
            </Form.Field>
            <Form.Field>
              <label>Category</label>
              <Dropdown fluid placeholder='Select Category' selection options={options} value={category}
                onChange={(e, data) => { setCategory(data.value); } }/>
            </Form.Field>
            {name ? (
              <DealCardImage dealName={name} minHeight={320} fontSize={48}/>
            ) : (
              <Segment style={{minHeight: 320}} secondary/>
            )}
        </Form>
      </Modal.Content>
      <Modal.Actions>
        <Button content='Cancel' onClick={handleClose}/>
        <Button primary labelPosition='right' content='Reset' icon='refresh' onClick={handleReset}/>
        <Button positive labelPosition='right' icon='checkmark' content='Save' href='/'
          disabled = {!(name && category)} 
          onClick={handleSave}/>
      </Modal.Actions>
    </Modal>
  );
};

function DealsListCardGroup({ items, pageViewOrigin, cardStyle }) {
  function dealCards() {
    return items
      .map(deal =>
        <Card
          key={deal.id}
          as={Link} to={{ pathname: `/deals/${deal.id}`, state: { pageViewOrigin } }}
          style={cardStyle}>

          <DealCardImage dealName={deal.name} minHeight={140} fontSize={24}/>
          <Card.Content>
            <Card.Header>{deal.name}</Card.Header>
            <Card.Meta><Icon name='tag'/> {deal.category}</Card.Meta>
          </Card.Content>
        </Card>
      );
  };

  return (
    <Card.Group centered>
      {dealCards()}
    </Card.Group>
  );
};

DealsListCardGroup.propTypes = {
  items: PropTypes.array,
  pageViewOrigin: PropTypes.string,
  cardStyle: PropTypes.object
};

function DealsList() {
  const [deals, setDeals] = React.useState([]);

  React.useEffect(() => {
    async function fetchData () {
      const result = await API.graphql(graphqlOperation(queries.listDeals, { limit: 1000 }));
      const deals = result.data.listDeals.items;
      setDeals(deals);
    }
    fetchData();
  }, []);

  React.useEffect(() => {
    let dealSubscription;
    async function fetchData() {
      dealSubscription = await API.graphql(graphqlOperation(subscriptions.onCreateDeal)).subscribe({
        next: (dealData) => {
          const newDeal = dealData.value.data.onCreateDeal;
          setDeals([...deals, newDeal]);
        }
      });
    }
    fetchData();

    return () => {
      if (dealSubscription) {
        dealSubscription.unsubscribe();
      }
    };
  });

  document.title = 'Travel Deals';
  return (
    <Container style={{ marginTop: 70 }}>
      <DealsListCardGroup items={deals} pageViewOrigin='Browse'/>
    </Container>
  );
};

function DealDetails({ id, locationState }) {
  const [deal, setDeal] = React.useState({});
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    async function loadDealInfo() {
      const dealResult = await API.graphql(graphqlOperation(queries.getDeal, { id }));
      const deal = dealResult.data.getDeal;
      setDeal(deal);
      setLoading(false);
      document.title = `${deal.name} - Travel Deals`;
    };
    loadDealInfo();

    return () => {
      setDeal({});
      setLoading(true);
    };
  }, [id, locationState]);

  return (
    <Container>
      <NavLink to='/'><Icon name='arrow left'/>Back to Deals list</NavLink>
      <Divider hidden/>
      <Card key={deal.id} style={{ width: '100%', maxWidth: 720, margin: 'auto' }}>
        {loading ? (
          <Placeholder fluid style={{minHeight: 320}}>
            <Placeholder.Image/>
          </Placeholder>
        ) : (
          <DealCardImage dealName={deal.name} minHeight={320} fontSize={48}/>
        )}
        {loading ? (
          <Placeholder>
            <Placeholder.Line/>
            <Placeholder.Line/>          
          </Placeholder>
        ) : (
          <Card.Content>
            <Card.Header>{deal.name}</Card.Header>
            <Card.Meta><Icon name='tag'/> {deal.category}</Card.Meta>
          </Card.Content>
        )}

      </Card>
      <Divider hidden/>
    </Container>
  );
};

DealDetails.propTypes = {
  id: PropTypes.string,
  locationState: PropTypes.object
};

function AuthStateApp() {  
  const [authState, setAuthState] = React.useState();
  const [user, setUser] = React.useState();

  React.useEffect(() => {
    onAuthUIStateChange((nextAuthState, authData) => {
      setAuthState(nextAuthState);
      setUser(authData);
    });
  }, []);

  document.title = 'Travel Deals';
  return authState === AuthState.SignedIn && user ? (
      <div className='App'>
        <Router>
          <Menu fixed='top' color='teal' inverted>
            <Menu.Menu>
              <Menu.Item header href='/'><Icon name='globe'/>Travel Deals</Menu.Item>
            </Menu.Menu>
            <Menu.Menu position='right'>
              <Menu.Item link><DealCreation/></Menu.Item>
              <Dropdown item simple text={user.username}>
                <Dropdown.Menu>
                  <Dropdown.Item onClick={() => Auth.signOut()}><Icon name='power off'/>Log Out</Dropdown.Item>
                </Dropdown.Menu>
              </Dropdown>
            </Menu.Menu>
          </Menu>
          <Container style={{ marginTop: 70 }}>
            <Route path='/' exact component={() => 
              <DealsList/>
            }/>
            <Route path='/deals/:dealId' render={props => 
              <DealDetails id={props.match.params.dealId} locationState={props.location.state}/>
            }/>
          </Container>
        </Router>
      </div>
  ) : (
      <AmplifyAuthenticator>
        <AmplifySignUp slot='sign-up' formFields={[
            { type: 'username' },
            { type: 'password' },
            { type: 'email' }
          ]}/>
      </AmplifyAuthenticator>
  );
};

export default AuthStateApp;

Create and Browse Deals

Functionality is available only after amplify push is complete.

Now you can create new Deals:

Create new Deal button in Web Application

Create new Deal dialog in Web Application

Name and Category will be randomly generated. You can generated another one by clicking Reset or set your own values.

And list all of the created items:

List of created deals in Web Application