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.
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.
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.
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:
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 Invite
button.
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.
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.
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:
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:
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
.
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.
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.
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
uuid
npm package. - 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 server/src/typeDefs.js
.
The resolver function is already assigned too. We can see this in the server/src/resolver.js
file:
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 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.
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:
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 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
Next, add the VideoSession route in the src/pages/index.tsx
file:
Finally, add the button to the src/pages/Home.tsx
page:
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:
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:
Open 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 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:
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:
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 StudentForm
and StudentsList
components from the same namespace. (Thanks again, index.tsx
!)
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.
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 server/src/services/aws/s3.js
.
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.
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:
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 HomeworksPage.tsx
under 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.
Populate the 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.
Populate the 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:
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.