Business Productivity

Quickly launch an Amazon Chime SDK application with AWS Amplify

Building a server-less full stack meeting application enabled with the Amazon Chime SDK for Javascript requires you to configure a back end cloud infrastructure to allow for the creation and deletion of Amazon Chime meetings and attendees within the application. This walkthrough shows how to leverage AWS Amplify to simplify and speed up the creation of the back end infrastructure, making it faster and simpler to create a meeting application powered by the Amazon Chime SDK for JavaScript.

This walkthrough will guide you through the steps of building a basic server-less full stack meeting web application with collaborative audio and video capabilities by leveraging the Amazon Chime SDK React Components to build the front end and Amplify to bootstrap back end resources. The Amazon Chime SDK React Components Library is built on the Amazon Chime SDK for JavaScript. The React Components Library features pre-built React components – such as VideoGrid, MeetingControls, and DeviceSelection – which are already integrated with the Amazon Chime SDK for Javascript and provide an interface for builders to quickly create any type of web application that requires audio or video collaboration capabilities. Refer to the Amazon Chime SDK React Component Library meeting demo for an example of building a meeting application with Amazon Chime SDK React Components.

The first part of this walkthrough shows how to build a basic responsive User Interface (UI) by integrating Amazon Chime SDK React Components into your web application. The second part of the walkthrough will show you how to use Amplify to configure and generate required backend resources – such as an AWS AppSync API, AWS Lambda function, and Amazon DynamoDB Tables. Amplify reduces the amount of time it takes to configure and create back end resources in your AWS account by providing an easy-to-use CLI that automatically generates and configures all of the required back end resources.

Solution Overview

The application’s front end consists of two basic components which rely on the Amazon Chime SDK React Component Library. The first component is a basic home page consisting of a UI form for taking in the meeting title and attendee name as input. The second component is a simple meeting view that displays attendees’ videos and access basic meeting controls – such as choosing alternative audio and video devices or leaving the meeting.

The application’s back end infrastructure is configured and provisioned with the Amplify CLI. The back end infrastructure is comprised of a GraphQL-based API and multiple data sources to resolve the API calls. Data sources are resources in your AWS account that GraphQL APIs can interact with. Resolvers are data sources that fulfill a GraphQL API request and return a response. Behind the scenes, Amplify configures a GraphQL API in AWS AppSync and connects the API to either the AWS Lambda or Amazon DynamoDB resolvers that are also created by Amplify.

Once the back end is provisioned and your API is ready to use, the newly created APIs are called from the client code to initialize a new meeting with audio and video capabilities.

When the application is completed, you have the option of using Amplify to automatically provision both an Amazon Simple Storage Service (Amazon S3) bucket and Amazon CloudFront instance to host your server-less application.

Demo Setup Walkthrough

Prerequisites

Please note, deploying and receiving traffic from the demos created in this walkthrough can incur AWS charges. For more information on Amazon Chime pricing, please refer to the Amazon Chime official documentation. 

Before getting started, you must have the following:

  • An AWS account
  • Node.js v12+
  • NPM 6.11 or higher

Key Steps

  1. Create a starter React application
  2. Initialize and configure Amplify in your project directory
  3. Create the application front end using the Amazon Chime SDK React Component Library
  4. Use Amplify to create a Lambda handler and a GraphQL-based API
  5. Call the GraphQL APIs from within your React application
  6. Optional: Use Amplify to host your application on S3 and CloudFront

Create a starter React application

  1. First, create a basic app using:
    $ npx create-react-app react-sample --template typescript
  2. Change directory into your newly created React app:
    $ cd react-sample
  3. Install dependencies:
    $ npm install
  4. Next, install the Amazon Chime SDK React Component Library and peer dependencies at the root directory of your React application:
    $ npm install --save amazon-chime-sdk-component-library-react amazon-chime-sdk-js styled-components styled-system aws-amplify && npm install --save-dev @types/styled-components

Install and Configure Amplify

  1. Install Amplify CLI onto your machine:
    $ npm install -g @aws-amplify/cli
  2. Then, configure Amplify and create a new AWS Identity and Access Management (IAM) User:
    $ amplify configure
  3. Now, initialize your project as a Amplify project by running:
    $ amplify init
    
    ? Enter a name for the project: (amplifyDemo) 
    ? Enter a name for the environment: dev 
    ? Choose your default editor: Visual Studio Code
    ? Choose the type of app that youre building: javascript 
    ? What JavaScript framework are you using: React
    ? Source Directory Path: src 
    ? Distribution Directory Path: dist 
    ? Build Command: npm run-script build 
    ? Start Command: npm run-script serve 
    ? Do you want to use an AWS profile? Yes 
    ? Please choose the profile you want to use [Your AWS Profile] - Choose the AWS Profile configured in the previous step

Create the application front end using the Amazon Chime SDK React Component Library

  1. Remove all of the extraneous files in ./public except for index.html
  2. Replace ./public/index.html with the following html starter code:
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>Amazon Chime SDK Amplify Demo</title>
        <link rel="icon" href="data:,">
      </head>
    
      <body>
        <div id="root"></div>
      </body>
    </html>
  3. Create a new directory ./src/components/
  4. Create the Meeting component in src/components/Meeting.tsx. This starter code shows how to use the ControlBar, AudioInputControl, VideoInputControl, and AudioOutputControl components from the Amazon Chime SDK React Library. Copy the following code into src/components/Meeting.tsx:
    import React, { FC } from 'react';
    
    import {
      AudioInputControl,
      AudioOutputControl,
      ControlBar,
      ControlBarButton,
      Phone,
      useMeetingManager,
      MeetingStatus,
      useMeetingStatus,
      VideoInputControl,
      VideoTileGrid
    } from 'amazon-chime-sdk-component-library-react';
    import { endMeeting } from '../utils/api';
    
    const Meeting: FC = () => {
      const meetingManager = useMeetingManager();
      const meetingStatus = useMeetingStatus();
    
      const clickedEndMeeting = async () => {
        const meetingId = meetingManager.meetingId;
        if (meetingId) {
          await endMeeting(meetingId);
          await meetingManager.leave();
        }
      }
      
      return (
          <div style={{marginTop: '2rem', height: '40rem', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center'}}>
            <VideoTileGrid/>
            {meetingStatus === MeetingStatus.Succeeded ? 
              <ControlBar
                layout="undocked-horizontal"
                showLabels
              >
                <AudioInputControl />
                <VideoInputControl />
                <AudioOutputControl />
                <ControlBarButton icon={<Phone />} onClick={clickedEndMeeting} label="End" />
              </ControlBar> 
              :
              <div/>
            }
          </div>
      );
    };
    
    export default Meeting;
     
  5. Create the MeetingForm component in ./src/components/MeetingForm.tsx. This component is responsible for getting the meeting title, attendee name, and region. Submitting the form calls the API to create or join an existing meeting using the data in the form. The joinMeeting function placeholder will be replaced in a later step. Copy the following code into ./src/components/MeetingForm.tsx:
    import React, { ChangeEvent, FC, FormEvent, useState } from 'react';
    
    import {
      Flex,
      FormField,
      Input,
      PrimaryButton,
      useMeetingManager,
    } from 'amazon-chime-sdk-component-library-react';
    import { addAttendeeToDB, addMeetingToDB, createMeeting, getAttendeeFromDB, getMeetingFromDB, joinMeeting } from '../utils/api';
    
    const MeetingForm: FC = () => {
      const meetingManager = useMeetingManager();
      const [meetingTitle, setMeetingTitle] = useState('');
      const [attendeeName, setName] = useState('');
    
      function getAttendeeCallback() {
        return async (chimeAttendeeId: string, externalUserId?: string) => {
          const attendeeInfo: any = await getAttendeeFromDB(chimeAttendeeId);
          const attendeeData = attendeeInfo.data.getAttendee;
          return {
            name: attendeeData.name
          };
        }
      }
    
    //Placeholder - we'll replace this function implementation later
      const clickedJoinMeeting = async (event: FormEvent) => {
        event.preventDefault();
      };
    
      return (
        <form>
          <FormField
            field={Input}     
            label='Meeting Id'
            value={meetingTitle}
            fieldProps={{
              name: 'Meeting Id',
              placeholder: 'Enter a Meeting ID',
            }}
            onChange={(e: ChangeEvent<HTMLInputElement>) => {
              setMeetingTitle(e.target.value);
            }}
          />
          <FormField
            field={Input}
            label="Name"
            value={attendeeName}
            fieldProps={{
              name: 'Name',
              placeholder: 'Enter your Attendee Name'
            }}
            onChange={(e: ChangeEvent<HTMLInputElement>) => {
              setName(e.target.value);
            }}
          />
          <Flex
            container
            layout="fill-space-centered"
            style={{ marginTop: '2.5rem' }}
          >
              <PrimaryButton label="Join Meeting" onClick={clickedJoinMeeting} />
          </Flex>
        </form>
      );
    };
    
    export default MeetingForm;
  6. Remove all of the extraneous code in ./src except for aws-exports.js and./index.tsx.
  7. Configure Amplify on the client code so that we can call the GraphQL APIs. Replace ./index.tsx with the following starter code:
    import ReactDOM from 'react-dom';
    import { ThemeProvider } from 'styled-components';
    import {
      MeetingProvider,
      lightTheme
    } from 'amazon-chime-sdk-component-library-react';
    import Meeting from './components/Meeting';
    import MeetingForm from './components/MeetingForm';
    
    import Amplify from 'aws-amplify';
    import awsconfig from './aws-exports';
    Amplify.configure(awsconfig);
    
    window.addEventListener('load', () => {
      ReactDOM.render(
      <ThemeProvider theme={lightTheme}>
        <MeetingProvider>
          <MeetingForm />
          <Meeting/>
        </MeetingProvider>
      </ThemeProvider>
      , document.getElementById('root'));
    });

Create back end resources for your React Application using Amplify

Amplify provides a CLI command to generate a predefined set of back end resources, referred to as a “category”. By adding the ‘function’ and ‘api’ categories to your Amplify application, Amplify automatically generates the required back end resources needed to run your server-less application. When you add categories and make updates to your back end configuration using the Amplify CLI, the configuration in ./aws-exports.js will update automatically.

  1. Adding the ‘function’ category configures a Lambda function, which can resolve API requests. From the project’s root level, add the ‘function’ category using the Amplify CLI and answer the prompted questions as follows:
$ amplify add function

? Select which capability you want to add: Lambda function (serverless function)
? Provide an AWS Lambda function name: reactSampleLambda
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: Hello World
? Do you want to configure advanced settings? No
? Do you want to edit the local lambda function now? Yes

Replace */amplify/backend/function/reactSampleLambda/src/index.js with the code provided in ./lambda/index.js. Then, go back to the terminal and press enter to finish the setup.

2. Adding the ’api’ category will create a GraphQL API in AWS AppSync. Add the ‘api’ category using the Amplify CLI and answer the prompted questions as follows:

$ amplify add api

? Please select from one of the below mentioned services: GraphQL
? Provide API name: reactSampleApi
? Choose the default authorization type for the API: API key
? Enter a description for the API key:
? After how many days from now the API key should expire (1-365): 7
? 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)

? Do you want to edit the schema now? Yes

When the editor opens, replace the schema file with the contents of the following schema:

type Meeting @model(mutations: {create: "createMeetingGraphQL", delete: "deleteMeetingGraphQL"}, subscriptions: null) @key(fields: ["title"]){
  meetingId: String!
  title: String!
  data: String!
}

type Attendee @model(mutations: {create: "createAttendeeGraphQL", delete: "deleteAttendeeGraphQL"}, subscriptions: null) @key(fields: ["attendeeId"]){
  attendeeId: String!
  name: String!
}

type Query {
  createChimeMeeting(title: String, name: String, region: String): Response @function(name: "reactSampleLambda-${env}")
  joinChimeMeeting(meetingId: String, name: String): Response @function(name: "reactSampleLambda-${env}")
  endChimeMeeting(meetingId: String): Response  @function(name: "reactSampleLambda-${env}")
}

type Response {
  statusCode: String!
  headers: String
  body: String
  isBase64Encoded: String
}

3. Then, to push the changes to the cloud, run the following command – you may accept all of the defaults for the prompt:

$ amplify push

4. Lastly, modify the IAM Role policy to include Amazon Chime Full Access to allow your Lambda function to call Amazon Chime APIs:

    1.  Navigate to the AWS Console using the same account that you used to configure Amplify.
    2. Navigate to IAM
    3. Click on Roles in the side menu.
    4. Find the role created and click on the name of the Role – {projectName}LambdaRoleXXX-{environment}
    5. Click on the “Attach Policies” button
    6. Type in the search box: AmazonChimeFullAccess
    7. Click on the checkbox for: AmazonChimeFullAccess
    8. Click on “Attach Policy” at the bottom right of the screen

Connecting your front end to your back end using GraphQL

Once you have pushed to your AWS account, you can check out the generated GraphQL APIs inside the ./src/graphql/* directories. You should see ./src/graphql/mutations.js and ./src/graphql/queries.js. These files contain the GraphQL APIs that have been generated by Amplify that you can now use within your application.

  1. Create a new directory ./src/utils/
  2. Create a new file called api.ts in ./src/utils/api.ts. Copy the code below into the new file:
import { API, graphqlOperation } from 'aws-amplify';
import { createAttendeeGraphQL, createMeetingGraphQL, deleteMeetingGraphQL } from '../graphql/mutations';
import { createChimeMeeting, getAttendee, endChimeMeeting, getMeeting, joinChimeMeeting } from '../graphql/queries';


export async function createMeeting(title: string, attendeeName: string, region: string) {
  const joinInfo: any = await API.graphql(graphqlOperation(createChimeMeeting, {title: title, name: attendeeName, region: region }));
  const joinInfoJson = joinInfo.data.createChimeMeeting;
  const joinInfoJsonParse = JSON.parse(joinInfoJson.body);
  return joinInfoJsonParse;
}

export async function joinMeeting(meetingId: string, name: string) {
  const joinInfo: any = await API.graphql(graphqlOperation(joinChimeMeeting, {meetingId: meetingId, name: name}));
  const joinInfoJson = joinInfo.data.joinChimeMeeting;
  const joinInfoJsonParse = JSON.parse(joinInfoJson.body);
  return joinInfoJsonParse;
}

export async function endMeeting(meetingId: string) {
  const endInfo: any = await API.graphql(graphqlOperation(endChimeMeeting, {meetingId: meetingId}));
  const endInfoJson = endInfo.data.endChimeMeeting;
  await API.graphql(graphqlOperation(deleteMeetingGraphQL, {input: {title: meetingId}}));
  return endInfoJson;
}

export async function addMeetingToDB(title: string, meetingId: string, meetingData: string) {
  await API.graphql(graphqlOperation(createMeetingGraphQL, {input: {title: title, meetingId: meetingId, data: meetingData, }}));
}

export async function addAttendeeToDB(attendeeID: string, attendeeName: string) {
  await API.graphql(graphqlOperation(createAttendeeGraphQL, {input: {attendeeId: attendeeID, name: attendeeName }}));
}

export async function getMeetingFromDB(title: string) {
  const meetingInfo = await API.graphql(graphqlOperation(getMeeting, {title: title }));
  return meetingInfo;
}

export async function getAttendeeFromDB(attendeeId: string) {
  const attendeeInfo = await API.graphql(graphqlOperation(getAttendee, {attendeeId: attendeeId }));
  return attendeeInfo;
}

3. Open the ./src/components/MeetingForm.tsx file. Copy this implementation of the getAttendeeCallback and joinMeeting function into the joinMeeting method placeholder in ./src/components/MeetingForm.tsx.


const clickedJoinMeeting = async (event: FormEvent) => {
  event.preventDefault();

  meetingManager.getAttendee = getAttendeeCallback();
  const title = meetingTitle.trim().toLocaleLowerCase();
  const name = attendeeName.trim();

// Fetch the Meeting via AWS AppSync - if it exists, then the meeting has already
// been created, and you just need to join it - you don't need to create a new meeting
  const meetingResponse: any = await getMeetingFromDB(title);
  const meetingJson = meetingResponse.data.getMeeting;
  try {
    if (meetingJson) {
      const meetingData = JSON.parse(meetingJson.data);
      const joinInfo = await joinMeeting(meetingData.MeetingId, name);
      await addAttendeeToDB(joinInfo.Attendee.AttendeeId, name);

      await meetingManager.join({
        meetingInfo: meetingData,
        attendeeInfo: joinInfo.Attendee
      });
    } else {
      const joinInfo = await createMeeting(title, name, 'us-east-1');
      await addMeetingToDB(title, joinInfo.Meeting.MeetingId, JSON.stringify(joinInfo.Meeting));       await addAttendeeToDB(joinInfo.Attendee.AttendeeId, name);

      await meetingManager.join({
        meetingInfo: joinInfo.Meeting,
        attendeeInfo: joinInfo.Attendee
      });
    }
  } catch (error) {
    console.log(error);
  }
  
  // At this point you can let users setup their devices, or start the session immediately
  await meetingManager.start();
};

4. From here, you can now run the meeting client locally. To do so, navigate to the repo’s root level directory and run this command:

$ npm install && npm run build && npm run start

Congratulations – you should now have successfully set up a full stack meeting application using the Amazon Chime SDK React Component Library and Amplify. After joining a meeting, press the video icon to turn on your local video tile.

Optional: Hosting your application using Amplify

You can use Amplify to set up a S3 bucket and CloudFormation stack to host your application in just a few minutes.

Add the hosting category from the Amplify CLI:

$ amplify add hosting

? Select the plugin module to execute: Amazon CloudFront and S3
? Select the environment setup: PROD (S3 with CloudFront using HTTPS)
? hosting bucket name: <Default Value>

Then, publish your changes to the cloud:

$ amplify publish

Once finished, navigate to the endpoint that is generated to try the server-less application.

Cleanup

To avoid incurring any unintended charges as a result of deploying the demo application, it is important to delete any resources in your AWS account that you are not using.

When you first called ‘amplify init,’ you created a local Amplify environment. You can have multiple environments on your AWS account, but calling ‘amplify remove <category>’ removes the corresponding resources in your local Amplify environment.

$ amplify remove { api | function | hosting }

Removing the category in your local Amplify environment does not affect the resources created in the cloud. Once you’ve removed the category locally, remove the resources from your AWS account by publishing those changes to the cloud:

$ amplify push

Alternatively, you can clean up your entire Amplify environment in the cloud and all local files created by Amplify by running:

$ amplify delete

Next Steps

In this blog post, we demonstrated how you can quickly set up the back end stack for a basic Amazon Chime meeting application built using the Amazon Chime SDK React Component Library. Refer to the documentation below for further information.