How to Build a Learning Platform With React, Express and Apollo GraphQL
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.
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.
Vonage API Account
To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.
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.
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
Before you start make sure to go into each folder and install dependencies using npm, as shown below:
If you want to run the finished product, open two separate terminal windows and use npm to start both applications as shown below:
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.
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
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
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.
The teacher can see the files each student has uploaded per assignment by clicking the automatically generated UUID of the homework.
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.
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:
The most important types are the
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:
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
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:
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.
If you want to know more about React and its integration with the Apollo server you can check the following links:
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.
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
apiSecret values from an already configured
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.
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:
- Initialize the opentok client.
- Create the session.
- Generate an ID for the session to be used as part of the URL. For this we will use the
- 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.
- Generate a token for the session.
- Return the data, honoring the format defined in the type definition for the mutation response.
The Mutation for starting the session, along with the response type, is already defined at
The resolver function is already assigned too. We can see this in the
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:
- Check that a UUID has been provided.
- Look for the videocall in the database.
- Initialize the opentok client.
- Use the session to generate a token for the student.
- Return data, honoring the format set in the type definition for the mutation.
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.
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
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.
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.
StartButton.tsx file and populate it as follows:
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:
Now simply create a new page under
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
Next, add the VideoSession route in the
Finally, add the button to the
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.
server/src/graphql/student.js file, and populate the resolver functions as follows:
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/sms.js file and populate the
sendSms function as follows:
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
Students, and inside it create three more files:
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.
StudentForm.tsx file as follows:
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.
StudentsList.tsx file as follows:
Now let’s expose both newly-created components in the
index.tsx as follows:
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
StudentsList components from the same namespace. (Thanks again,
We now should be able to create new students and view them in the list.
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.
Also add the newly created component to the Videocall index:
And finally, add the Attendees component to the VideoSession page:
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:
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
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:
Create React Components and Pages
Let’s start by creating a form for creating Homeworks and a simple table to list these.
Homeworks folder under
client/src/components and then create
HomeworkList.tsx inside it. Populate the first file as follows to create the form:
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.
Now, let’s expose the newly created components by creating an
index.tsx file under
client/src/components/Homeworks with the following content:
Then create the
client/src/pages/ as follows:
And don’t forget to add it to the
index.tsx file in the same folder:
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.
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.
PasswordlessLogin.tsx file as follows:
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.
HomeworkFileForm.tsx as follows:
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:
Again, don’t forget to add the newly created page to the
index.tsx in the same folder:
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:
Lastly, create the
ListHomeworkFilesPage.tsx file under
client/src/pages as shown below:
And for the last time, don’t forget to add the route to the
index.tsx file in the same folder:
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.