Skip to content

Instantly share code, notes, and snippets.

@DanyF-github
Created January 8, 2021 10:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DanyF-github/415876cd265ec9dd9bf65ed09c17cd9a to your computer and use it in GitHub Desktop.
Save DanyF-github/415876cd265ec9dd9bf65ed09c17cd9a to your computer and use it in GitHub Desktop.

2020 has been an atypical year for all of us. Many industries have had to "re-think" the way they do business and chances are that these strategies are not temporary but are here to stay.

One of these changes is how we learn. Many schools, universities, and academies around the world have experienced a rise in remote services, often relying on private solutions for providing these.

Today we'll take a look at how it's possible to build our own learning platform with video/audio capabilities, SMS notifications, and passwordless authentication.

Prerequisites

To build and run the application you'll need the following resources:

  • A Vonage API account.
  • A Vonage Video API account. Sign up free here.
  • A Virtual Phone Number. Once you have your Vonage API account see how you can get a number here.
  • A pair of key & secret for a Vonage Video API project. You can create a project from your Vonage Video API account page.
  • An AWS account and a key & secret pair.
  • An AWS S3 bucket to upload files.
  • Node 12 and NPM 6 installed in your system.

What We Will Build

We will build a web application that allows teachers to create instant video/audio classes that a student can join with just the link. Teachers will be able to create a list of students, identified by their phone numbers, and can later send them the link for the call through SMS.

The teacher can also create assignments. The students can later identify themselves using passwordless authentication and upload files that can be later reviewed by the teacher.

To keep things simple and be time-effective some capabilities, such as authentication (login and logout) and an actual database, have been left out. Instead, all the pages are publicly available and data will be stored in memory using JavaScript arrays.

If you’re interested in experiencing the final product yourself, I created a Github repository that you can clone locally. The repo has a final folder where you can see the finished example, and a starter one--with React, Express, and Apollo GraphQL already preconfigured--that you can use to follow along and build it step by step.

The demo code is divided into a server folder that contains an Apollo GraphQL Server with Express, and a client folder that contains a basic React application. The backend code is written in plain JavaScript while the frontend uses TypeScript, that way if you're not familiar with the differences between these two you can compare them side by side.

Before you start make sure to go into each folder and install dependencies using npm, as shown below:

https://gist.github.com/52fb50fb853eb0ff6291d2439b99be36

You also need to configure secrets. In the client folder all you need to do is to rename the .env file to .env.local.

In the server folder, on the other hand, rename app.envs file to .env you also need to replace the placeholder values in the file with your own AWS keys, S3 Bucket name, Vonage keys, and Vonage Virtual Number.

If you want to run the finished product, open two separate terminal windows and use npm to start both applications as shown below:

https://gist.github.com/da6cc9172035a6ff8b66f2f57864c413

A browser window will open automatically and you'll see the application in action there. The first thing you see is a window with a button that allows you to create a class. Before getting into it, head to the Students page and create a couple of students using valid phone numbers.

The Students Page

Now, head back to the main screen by clicking on the application title. Next, start a new class.

After a couple of seconds, you will be in a Vonage Video API session. Using the student list, you can send the students an SMS notification so that they can join the class by just clicking the Invite button.

Starting a class

Now let's say you want to create an assignment where students have to upload a PDF document. You can do that in a way that it's not required that they have an actual account, but they can authenticate just by using their phone.

To do so, head to the Homeworks page and create a new Homework by setting a description. Then, as a student, click on the Upload link.

Creating an Assignment

To upload the file, the student has to provide the same phone number that was used by the teacher at creation time. A verification code will be sent to the phone number and after providing it to the application, the student can upload the file.

Passwordless Login

Uploading a File

The teacher can see the files each student has uploaded per assignment by clicking the automatically generated UUID of the homework.

Seeing assignments

Getting Familiar with the Starting Code

If you want to follow along but you're not familiar with some of the technologies used here, we've got you covered. In this section, we will briefly describe what these are, how it is configured in the starter code, and provide some useful links so you can get more information. If you're already a pro with GraphQL and React, then you can skip this section and go straight to create classes, although you may want to read it anyway to know how these pieces fit together in the demo code.

Apollo GraphQL

GraphQL provides a query language and runtime for querying data from a server (commonly from multiple sources). It allows you to clearly describe the data and gives the client the power of asking exactly what it needs.

Apollo GraphQL is an industry-standard implementation of GraphQL. It provides server and client libraries that allow you to easily combine and consume databases, APIs, and microservices in a single graph.

The server folder is composed of a GraphQL server powered by Express. The configuration is in the server/index.js file. The most important pieces of the configuration are the Type Definitions and Resolvers.

Type Definitions is where GraphQL describes the data that a client can consume. This is done using types. Type Definitions are configured in the server/src/typeDefs.js file. Below are some examples of the types for the demo code:

https://gist.github.com/0c1b05d30fc56a30dabca53a71427011

The most important types are the Query and Mutation types, which actually expose what "queries" and "mutations" a client can perform with the data.

Below are the queries and mutations defined for the demo code:

https://gist.github.com/1ba6b92e871ca892065f8fdfdeb47f4e

The beauty with GraphQL is that you define the behavior of these queries and mutations using your own custom code, which allows you to retrieve the information from multiple databases, REST APIs, or even other GraphQL servers. That custom code you create is known as resolvers.

In the demo code, resolvers are assigned to each query and mutation in the server/src/resolvers.js file, while the actual resolver functions are located under the server/src/graphql folder. Currently, the resolvers are only throwing a NOT_IMPLEMENTED exception, but we will change that throughout this article.

https://gist.github.com/3135d2233efaca722167b9c347bda39b

Apollo also provides a library for client-side code that allows you to easily consume data from the server. It maintains a cache so that the client doesn't have to request data from the server if the data already exists.

If you want to know more about GraphQL and Apollo Graphql you can check the following links:

React

React is a JavaScript library for building user interfaces using a component-based approach. Each component can be reused and maintains its own state that automatically updates the user interface when changed.

This project uses functional components which provide a simple yet powerful way to write React components. It also uses hooks to provide additional functionality such as state and communication with the server.

The demo code features a basic React application written in TypeScript. It uses the Apollo Client library to connect with the server and also to provide a cache for storing the data retrieved from the server.

The whole application is wrapped inside the ApolloProvider which allows access to its context across all the components.

https://gist.github.com/e838aaf7d9dc167eff2c61b579ab9f65

If you want to know more about React and its integration with the Apollo server you can check the following links:

Creating Classes

Ok, if you want to follow along it's time to get our hands dirty. Get your favorite code editor and open the starter folder. The first thing we will do is add the ability to create new classes.

Since we have our code split into server and browser code, it makes sense to start setting up the backend code before working on what the user will see. So let's start by making a GraphQL mutation that creates a session in the Vonage Video API service.

Creating the Vonage Video API Service and the Resolver

To create an audio/video session in the Vonage Video API we will be using the opentok package, which is already installed. The first thing we need to do is to initialize the client by passing the API key and secret pair.

In the server/src/services/vonage/videoApi.js file, let's populate the initializeOpentok function. We will return a singleton instance of the opentok variable, this will ensure that the same instance is returned every time we call the function. Note how we are importing the key and secret we defined previously as an environment variable using the apiKey and apiSecret values from an already configured ../../utils/envs file.

https://gist.github.com/79f199f371d7bfb73fe73e3548324cad

The next step is to actually create the session. To do so we will use the opentok.createSession function. This function receives an object that sets the session as routed. A routed session means that we will use Vonage's Media Servers, which allows decreasing bandwidth usage in multiparty sessions and also permits us to enable advanced capabilities such as recordings and SIP interconnect.

https://gist.github.com/a34f38a8bdead0aac5f9ff12e43cd46e

Finally, we will be adding a function for generating JWT tokens that will be used to authenticate users in the context of a session and also set permissions.

https://gist.github.com/ab3bfe14cf58853420278368d2f4b8a7

Now that we have the functionality in place, all that remains is to actually expose that to the clients. To do so, we will create a pair of mutations that the React client can consume in order to allow teachers to create sessions and students to join these.

Let's open the server/src/graphql/videoApi.js file and populate the placeholder resolvers.

For creating sessions these are the steps we will follow:

  1. Initialize the opentok client.
  2. Create the session.
  3. Generate an ID for the session to be used as part of the URL. For this we will use the uuid npm package.
  4. Save the session in persistent storage. To keep things simple we will store things in memory using arrays defined in server/src/services/db/index.js, but in a real-world application, an actual database makes more sense.
  5. Generate a token for the session.
  6. Return the data, honoring the format defined in the type definition for the mutation response.

https://gist.github.com/54591acf87d4a3a7698c08cc9fcf17ee

The Mutation for starting the session, along with the response type, is already defined at server/src/typeDefs.js.

https://gist.github.com/953423cd0f4797c2b616032db3f12b99

The resolver function is already assigned too. We can see this in the server/src/resolver.js file:

https://gist.github.com/67155a90b9e043fcd357aa35e3e0e524

Next, we need to create a resolver function that allows students to join an already created session. To do so, these are the steps we will follow:

  1. Check that a UUID has been provided.
  2. Look for the videocall in the database.
  3. Initialize the opentok client.
  4. Use the session to generate a token for the student.
  5. Return data, honoring the format set in the type definition for the mutation.

https://gist.github.com/4e2571d9fe97fcda789f9447b92e0bf0

Same as with the previous function, the resolver is already connected with the type definition. The only difference is that this time instead of a mutation, it's a query.

https://gist.github.com/4b5abcdcafd9618b282fb3d50cfe7dfc

Now we're ready to build the user interface.

Adding the User Interface

First, let's create a couple of React components. Inside the client/src/components/ folder create a new Videocall folder.

Now create a file named Room.tsx inside the newly created folder. This is the component that will host the session.

To build the component we will use the opentok-react npm package. The component will receive an uuid property that will be used in the query to retrieve the information about the session.

https://gist.github.com/f51ebb507f9acfd4f9bdb53876f6eb17

Next, let's add a button to create the session. Here, we will explore a powerful feature of Apollo client: the cache.

Currently, the Room component attempts to retrieve the session details from the server based on the UUID of an already created session.

Since we also get those same details when creating the session, it doesn't make sense to do a second request when joining. Instead we will write it to the cache so that the Room component can get it from there and doesn't have to make a new request to the server.

Create a StartButton.tsx file and populate it as follows:

https://gist.github.com/d295c0ade7e1e61bbf889f3b52eb3e5d

Before getting into adding the pages, let's create an index.tsx file under client/src/components/Videocall that will expose both components under the same import:

https://gist.github.com/848e8546e56e1e0c8648bf5bde924309

Now simply create a new page under client/src/pages/ named VideoSession.tsx, and then add the Room component. Note how we don't need to specify the Room file but just import it at the folder level. This is thanks to the index.tsx file we have just added

https://gist.github.com/2bb611c96632a5e0c58651b55538dfa0

Next, add the VideoSession route in the src/pages/index.tsx file:

https://gist.github.com/f30aaa4450ebf65d6f34419d6a3afd96

Finally, add the button to the src/pages/Home.tsx page:

https://gist.github.com/f69631f601082cc4cf0673d48de4f3e2

Creating a List of Students

The next step is allowing a teacher to create a list of students. The whole idea is that when a call is started, the teacher can review the list and send SMS notifications to the students to invite them to the call.

As with the classes, we will start by making the required mutations and queries in the GraphQL server. Then we will add the user interface.

Setting Up Mutations and Queries

Let's start working on the server code by allowing a teacher to create a student. To keep things simple we will be storing students in an array, but in a real-world application, a database would make more sense.

Open the server/src/graphql/student.js file, and populate the resolver functions as follows:

https://gist.github.com/7a81f3ee4f1d415aa42c6a16071280cc

Next, let's add the Vonage magic to send notifications. To do so we will use the @vonage/server-sdk npm package which is already preinstalled and initialized as a singleton instance in the server/src/services/vonage/vonage.js file:

https://gist.github.com/7b09e5992e97d33059d80924cc078184

Open the server/src/services/vonage/sms.js file and populate the sendSms function as follows:

https://gist.github.com/d382f097076ac09636d5721fb5ee307b

Adding the User Interface

First, let's create some components that we will later reuse when creating students and inviting them to a video session.

Create a new folder under client/src/components named Students, and inside it create three more files: index.tsx, StudentForm.tsx and StudentsList.tsx.

When creating the form we will adopt a similar approach to the one used when creating a class, where after calling the mutation that creates the student in the server we are also updating the local cache to prevent subsequent requests to the server.

For the actual form we will use controlled components so that its values are managed by React's state. Since we're using functional components, we will use the useState hook to provide a state to the formit.

Populate the StudentForm.tsx file as follows:

https://gist.github.com/11cac022c8517b09204002f918d41542

When creating the list of students, we will add an actions property that will be an array of "actions" that can be applied to a student.

For each action, we will add a button in the table under the "Actions" column, that will trigger a custom function. Think of actions such as "edit", "delete" or "disable". We will later use this property to "invite" a student to a class.

Populate the StudentsList.tsx file as follows:

https://gist.github.com/910e601a59d999d6e308eb8b8049ae27

Now let's expose both newly-created components in the index.tsx as follows:

https://gist.github.com/5dfebbda8b9990a783e9eb4190799eb2

And now let's create the client/src/pages/StudentPage.tsx page, and then add the route in the client/src/page/index.tsx index. Note how we are importing both StudentForm and StudentsList components from the same namespace. (Thanks again, index.tsx!)

https://gist.github.com/18f0bad6e1825c0cf20d852e0dee4f98

We now should be able to create new students and view them in the list.

Inviting Students

The whole idea of having students is to be able to invite them to a call. Remember the actions property we talked about earlier? Here's where that feature will shine, as it will allow us to provide that functionality to the list of students while allowing us to reuse the very same component we created before.

Let's create a new component called Attendees.tsx under client/src/components/Videocall/. In this new component, we will create a custom action that will trigger the inviteStudent mutation.

https://gist.github.com/3bfdd27dffa5b262ccc3593060fad795

Also add the newly created component to the Videocall index:

https://gist.github.com/65b813e33c67566b252a1d884ac93d32

And finally, add the Attendees component to the VideoSession page:

https://gist.github.com/f82a18f926020e3d79ff6deafade129c

Now create a couple of students using valid phone numbers, start a class, and click on the “invite” button to invite them.

Creating and Sending Assignments

The final step in our demo is allowing students to send assignments. To make sure that we are able to identify which student a homework file belongs to, we will use passwordless login based on the phone number used to register the student.

Set Up Mutations and Queries

The first thing we need to do is allow for actual homework and homework files to be created. We also need to give users the ability to upload files. We will be using an S3 bucket with Presigned POST Requests for the latter.

Let's start with the resolvers for creating and retrieving homework and homework files. Open the server/src/graphql/homework.js file, under server, and populate the resolvers as follow:

https://gist.github.com/b418edd493c537ef670964ae9183802c

Next, let's add a mutation for pre-signing a POST request that can be used later in the client-side code to upload the file to S3. To do so, we are using the aws-sdk npm package. The service is already configured in server/src/services/aws/s3.js.

https://gist.github.com/31a3c7fa2aaaf5a8fde61416866b56a9

So all we need to do is to actually consume the service in a new mutation. Open the server/src/graphql/s3.js file, and populate the presignDocument resolver function as follows:

https://gist.github.com/7afbf7dbc7fb1a3d6316e9c3f9c4e757

Now it's time to set up the Vonage magic for passwordless authentication. To do this, we will use the Verify API. First, let's create the service. Open the server/src/services/vonage/verify.js file, and populate the verifyRequest and checkCode functions as follows:

https://gist.github.com/05047c9778cc52dc179050f7399d6bbc

Finally, let's expose these services through GraphQL. Open the server/src/graphql/vonage.js file and populate the verifyRequestResolver and checkCodeResolver resolver functions as follows:

https://gist.github.com/71212f534e16f3387cc68b33f146ad9f

Create React Components and Pages

Let's start by creating a form for creating Homeworks and a simple table to list these.

Create a Homeworks folder under client/src/components and then create HomeworkForm.tsx and HomeworkList.tsx inside it. Populate the first file as follows to create the form:

https://gist.github.com/cd8776342240988652da17b5538c5bb5

And then populate the HomeworkList.tsx file as follows to create a simple table that lists the created homework. Note that we are also setting a couple of Links under the "Identifier" "Action" columns. These links will allow a teacher to review the homework files of a given homework and allow students to upload the actual files.

We will work on the pages these links will open in a moment.

https://gist.github.com/fd223caddb6bf9f6a5b025dc80e02321

Now, let's expose the newly created components by creating an index.tsx file under client/src/components/Homeworks with the following content:

https://gist.github.com/8febc78a344535406a00b13400804cee

Then create the HomeworksPage.tsx under client/src/pages/ as follows:

https://gist.github.com/227265400b81bbc7c82459bdaa689c96

And don't forget to add it to the index.tsx file in the same folder:

https://gist.github.com/872ba1ed4dbf4e9ca66030a2812e0ead

Implementing a Passwordless Login

For the passwordless login let's create two new components: one that will serve as a login page, and another one that will have the form that students will see after authenticating.

Create PasswordlessLogin.tsx and HomeworkFileForm.tsx under client/src/components/Homeworks.

First, let's focus on creating the login form. To do so, our component will define one mutation for creating a verification request and another for making the actual verification.

The user interface will consist of a text box that requests the phone number and a button for initiating the request. After a requestId has been successfully returned by the server we want to show an additional text field for entering the code and a button for verification.

Populate the PasswordlessLogin.tsx file as follows:

https://gist.github.com/84bd96de089422906373485e3bcd5940

Next, create the form for uploading the file. This form will slightly differ from the ones we have previously built in this tutorial because it will be an uncontrolled form. Also, some extra steps need to be taken to upload the file to S3 before calling the mutate function.

Populate the HomeworkFileForm.tsx as follows:

https://gist.github.com/a57f0c68307d9b62c075b6f919d4b8ce

Now let's create a page that will show a different component depending on if the student has logged in or not. Create the client/src/pages/AddHomeworkFilePage.tsx file and populate as follows:

https://gist.github.com/63d32bb865d95ae6b8437da635ac9d04

Again, don't forget to add the newly created page to the index.tsx in the same folder:

https://gist.github.com/e265f3aa0268ba6146735886c17b813d

Creating a List of the Homework Files

The last thing we need to do is allow the teacher to actually check the homework files students have sent. To do so we will simply create a HomeworkFileList component similar to the ones we have just created for Students and Homeworks.

Create a new client/src/components/Homeworks/HomeworkFileList.tsx and populate it as follows to create the list of homework files:

https://gist.github.com/3e40a2ab14b07631319f0c03a91036a4

Lastly, create the ListHomeworkFilesPage.tsx file under client/src/pages as shown below:

https://gist.github.com/cda695b46d2e01c0843a431432edf35e

And for the last time, don't forget to add the route to the index.tsx file in the same folder:

https://gist.github.com/66531085582f8fdad73ad4b63a2691f0

Conclusion

And that's it! Hopefully, this post has given you an idea of what you can do to adapt to the "new normality" and how the cool stuff that is being developed at Vonage can help you to achieve it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment