Docs Menu

Task Tracker (Web)

On this page

Check Out the Quick Start

If you prefer to explore on your own rather than follow a guided tutorial, check out the Web Quick Start. It includes copyable code examples and the essential information that you need to set up a MongoDB Realm application.

In this tutorial, you'll build a functional web application backed by the MongoDB Realm GraphQL API. The app uses React to define UI components and Apollo Client to run queries and mutations through the GraphQL API. We've already created most of the frontend application for you, so you don't need to know React to follow along.

The app is a task tracker that allows users to:

  • Register and log in with an email/password account.
  • View a list of their projects.
  • Add, view, and delete tasks for projects they are a member of.
  • Switch tasks between Open, In Progress, and Complete statuses
  • Add, view, and remove team members from projects.

This tutorial should take around 30 minutes.

Realm Web SDK and Sync

The Realm Web SDK does not support sync, so the app you'll build in this tutorial won't update in real time when data changes. However, data that you create from the web app will automatically sync to any of the other task tracker tutorial apps that use Realm Database.

Before you get started, you'll need the following:

Once you're set up with these prerequisites, you're ready to start the tutorial.

We've already put together a task tracker browser application that has most of the frontend code you'll need. You can clone the client application repository directly from GitHub:

git clone --branch start

In your terminal, run the following commands to navigate to the task tracker client application and install its dependencies:

cd realm-tutorial-web
npm install

The start branch is an incomplete version of the app that we will complete in this tutorial. To view the finished app, check out the final branch and update src/App.js with your Realm app ID.

The web client is a standard React web application written in JavaScript and scaffolded with Create React App. We encourage you to explore the files in the app for a few minutes before you continue the tutorial. This will help you to familiarize yourself with what the app contains and where you'll be working.

The project uses the following file structure:

├── index.js
├── App.js
├── RealmApp.js
├── TaskApp.js
├── components/
│ ├── ButtonGroup.js
│ ├── Card.js
│ ├── EditPermissionsModal.js
│ ├── Loading.js
│ ├── LoginScreen.js
│ ├── ProjectScreen.js
│ ├── SideBar.js
│ ├── StatusChange.js
│ ├── TaskContent.js
│ ├── TaskDetailModal.js
│ └── useChangeTaskStatusButton.js
└── graphql/
├── RealmApolloProvider.js
├── useProjects.js
├── useTaskMutations.js
└── useTasks.js

The /src/graphql directory contains all of the modules that you'll use to configure the Apollo GraphQL client and connect to your Realm app's GraphQL API. These files are only incomplete scaffolds - some are blank and others require you to make some modifications. This tutorial walks through adding the missing code in these files to connect the task tracker app to Realm.

The /src/components directory contains pre-built React components and hooks that handle local state management and UI rendering. The components import code from the files in /src/graphql and use them to interact with Realm. We've already completely implemented the UI portions, so you won't need to add any React-specific code to these files. We'll make sure to show you along the way how to modify the components and hooks to use the Realm GraphQL API.

React Components

React is a popular modern web application framework that uses a component model to maintain application state and intelligently render pieces of the UI. If you're not familiar with React or want to brush up on your knowledge, check out the official React website, which has excellent documentation and tutorials.

The client app needs to connect to your Realm app so that users can register and log in. In src/RealmApp.js, we import the Realm Web SDK to connect to Realm and handle these actions. The file exports a React context provider that encapsulates this behavior and makes it available to other components in the app.

Some of the functionality in RealmApp.js is not fully defined. You need to update the code to use the SDK to connect to your Realm app and handle user authentication.


The app client is the primary interface to your Realm app from the SDK. In src/App.js, replace "TODO" with your Realm App ID:

export const APP_ID = "<your Realm app ID here>";
Use Your App ID

Make sure to replace "TODO" with your app's unique App ID. You can find your App ID by clicking the copy button next to the name of your app in the lefthand navigation of the Realm UI.

The copy app id button in the UI

The app client provides methods that allow you to authenticate and register users through the email/password authentication provider. In src/RealmApp.js, the RealmAppProvider component wraps these functions and keeps the app client in sync with local React state.

These wrapping functions already have the state update calls but don't currently use the app client you created. You need to update the functions to actually call the SDK authentication and registration methods.

export const RealmAppProvider = ({ appId, children }) => {
const [app, setApp] = React.useState(new Realm.App(appId));
React.useEffect(() => {
setApp(new Realm.App(appId));
}, [appId]);
// Wrap the Realm.App object's user state with React state
const [currentUser, setCurrentUser] = React.useState(app.currentUser);
async function logIn(credentials) {
await app.logIn(credentials);
// If successful, app.currentUser is the user that just logged in
async function logOut() {
// Log out the currently active user
await app.currentUser?.logOut();
// If another user was logged in too, they're now the current user.
// Otherwise, app.currentUser is null.
const wrapped = {, currentUser, logIn, logOut };
return (
<RealmAppContext.Provider value={wrapped}>
How We Use It

In src/App.js, we use the useRealmApp() hook to determine when the main application is ready to render. We also check for an authenticated user and always render exclusively the login screen unless a user is logged in. This guarantees that only authenticated users can access the rest of the app.

const RequireLoggedInUser = ({ children }) => {
// Only render children if there is a logged in user.
const app = useRealmApp();
return app.currentUser ? children : <LoginScreen />;

In /components/LoginScreen.js, we use the wrapped authentication methods that you defined to log user in and register new users.

Find the the handleLogin function and add the following code to process a emailPassword credential by calling the logIn() method.

const handleLogin = async () => {
setError((e) => ({ ...e, password: null }));
try {
await app.logIn(Realm.Credentials.emailPassword(email, password));
} catch (err) {
handleAuthenticationError(err, setError);

Next, find the handleRegistrationAndLogin function and add the following code to create a emailPassword credential.

const handleRegistrationAndLogin = async () => {
const isValidEmailAddress = validator.isEmail(email);
setError((e) => ({ ...e, password: null }));
if (isValidEmailAddress) {
try {
// Register the user and, if successful, log them in
await app.emailPasswordAuth.registerUser(email, password);
return await handleLogin();
} catch (err) {
handleAuthenticationError(err, setError);
} else {
setError((err) => ({ ...err, email: "Email is invalid." }));

A GraphQL schema defines all of the types, enums, and scalars that a GraphQL API supports. Realm automatically generates a GraphQL schema for you that includes definitions for your schema types as well as a set of CRUD query and mutation resolvers for each type.

Generated TypeScript Types

You can use graphql-codegen to generate TypeScript types based on your app's GraphQL schema.

Open src/graphql/useTaskMutations.js and find the TODO mutation definitions. These are all of the mutations that the app uses to create and modify User and Task documents.

Fill in the code with the following mutation definitions:

  • AddTaskMutation

    const AddTaskMutation = gql`
    mutation AddTask($task: TaskInsertInput!) {
    addedTask: insertOneTask(data: $task) {
  • UpdateTaskMutation

    const UpdateTaskMutation = gql`
    mutation UpdateTask($taskId: ObjectId!, $updates: TaskUpdateInput!) {
    updatedTask: updateOneTask(query: { _id: $taskId }, set: $updates) {
  • DeleteTaskMutation

    const DeleteTaskMutation = gql`
    mutation DeleteTask($taskId: ObjectId!) {
    deletedTask: deleteOneTask(query: { _id: taskId }) {

Now that you've defined the mutations, you need to call them in their respective hooks. The mutation hooks are lightweight wrappers around Apollo's useMutation() hook that allow you to pass dynamic variables to the queries.

In src/graphql/useTaskMutations.js file, find the following functions below your mutation definitions and update them to execute their respective mutations:

  • useAddTask

    function useAddTask(project) {
    const [addTaskMutation] = useMutation(AddTaskMutation, {
    // Manually save added Tasks into the Apollo cache so that Task queries automatically update
    // For details, refer to
    update: (cache, { data: { addedTask } }) => {
    fields: {
    tasks: (existingTasks = []) => [
    data: addedTask,
    fragment: TaskFieldsFragment,
    const addTask = async (task) => {
    const { addedTask } = await addTaskMutation({
    variables: {
    task: {
    _id: new ObjectId(),
    _partition: project.partition,
    status: "Open",
    return addedTask;
    return addTask;
  • useUpdateTask

    function useUpdateTask(project) {
    const [updateTaskMutation] = useMutation(UpdateTaskMutation);
    const updateTask = async (task, updates) => {
    const { updatedTask } = await updateTaskMutation({
    variables: { taskId: task._id, updates },
    return updatedTask;
    return updateTask;
  • useDeleteTask

    function useDeleteTask(project) {
    const [deleteTaskMutation] = useMutation(DeleteTaskMutation);
    const deleteTask = async (task) => {
    const { deletedTask } = await deleteTaskMutation({
    variables: { taskId: task._id },
    return deletedTask;
    return deleteTask;

You've defined GraphQL CRUD operations and created custom query mutation hooks for tasks. However, these hooks must be wrapped in an Apollo context provider that makes an ApolloClient object available.

In src/graphql/RealmApolloProvider.js, we export a React component that provides the ApolloClient object but the function that instantiates the client is incomplete. You need to update the file to create a client that can connect to your app's GraphQL API.


The RealmApolloProvider component should call createRealmApolloClient() to instantiate the client. Update the component with the following code to create an ApolloClient object that connects to your app:

export default function RealmApolloProvider({ children }) {
const app = useRealmApp();
const [client, setClient] = React.useState(createRealmApolloClient(app));
React.useEffect(() => {
}, [app]);
return <ApolloProvider client={client}>{children}</ApolloProvider>;

The createRealmApolloClient() function now instantiates a client object, but you won't be able to run any GraphQL queries or mutations just yet. Every GraphQL request must include an Authorization header that specifies a valid user access token. The current client does not include any Authorization headers, so all requests it makes will fail.

To fix this, update the createRealmApolloClient() function to include the current user's access token in an Authorization header with every request:

const createRealmApolloClient = (app) => {
const link = new HttpLink({
// Realm apps use a standard GraphQL endpoint, identified by their App ID
uri: `${}/graphql`,
// A custom fetch handler adds the logged in user's access token to GraphQL requests
fetch: async (uri, options) => {
if (!app.currentUser) {
throw new Error(`Must be logged in to use the GraphQL API`);
// Refreshing a user's custom data also refreshes their access token
await app.currentUser.refreshCustomData();
// The handler adds a bearer token Authorization header to the otherwise unchanged request
options.headers.Authorization = `Bearer ${app.currentUser.accessToken}`;
return fetch(uri, options);
const cache = new InMemoryCache();
return new ApolloClient({ link, cache });

As defined by our data model, a Project is an embedded object inside of a User document. This means that we can use custom user data to access a given user's list of projects directly from their Realm.User object.

In the file /graphql/useProjects.js, add the following code to retrieve the current user's projects:

export default function useProjects() {
const app = useRealmApp();
if (!app.currentUser) {
throw new Error("Cannot list projects if there is no logged in user.");
const projects = app.currentUser.customData.memberOf;
return projects;

The file src/components/EditPermissionsModal.js defines a hook named useTeamMembers() that returns a list of the current user's team members. It should also return functions that add and remove team members, but you'll need to define them. The task tracker backend application already defines server-side Realm functions that handle the logic, so you just need to call them.


You can call your app's Realm functions by name as asynchronous methods on the User.functions object. All function calls return a Promise. For example, to call a function named myFunction as the current user, you would write await app.currentUser.functions.myFunction().

Update the useTeamMembers() hook's return object to include addTeamMember() and removeTeamMember() helper functions that accept an email address and pass it their respective server-side functions:

function useTeamMembers() {
const [teamMembers, setTeamMembers] = React.useState(null);
const [newUserEmailError, setNewUserEmailError] = React.useState(null);
const app = useRealmApp();
const { addTeamMember, removeTeamMember, getMyTeamMembers } = app.currentUser.functions;
const updateTeamMembers = () => {
// display team members on load
React.useEffect(updateTeamMembers, []);
return {
errorMessage: newUserEmailError,
addTeamMember: async (email) => {
const { error } = await addTeamMember(email);
if (error) {
return { error };
} else {
removeTeamMember: async (email) => {
await removeTeamMember(email);

The task tracker app is now fully configured so you can start it up to start tracking tasks.

To start the app, navigate to the project root in your shell and then enter the following command:

npm run start

If the app starts successfully, you should see output that resembles the following:

Compiled successfully!
You can now view task-tracker in the browser.
Local: http://localhost:3000
On Your Network:

Open your browser to http://localhost:3000 to access the app.

If the app builds successfully, here are some things you can try in the app:

  • Create a user with email
  • Explore the app, then log out or launch a second instance of the app in an incognito browser window
  • Create another user with email
  • Navigate to's project
  • Add, update, and remove some tasks
  • Click "Manage Team"
  • Add to your team
  • Log out and log in as
  • See two projects in the projects list
  • Navigate to's project
  • Collaborate by adding, updating, and removing some new tasks

Now that you've validated that the app is working, it's time to deploy the app. In this section, you're going to create a production build with Create React App's build script and deploy the app to be hosted with Realm Static Hosting.

To create your production bundle, run the command:

npm run build

Once the build finishes, validate it's working by running:

npx serve -s build

You will see a new browser window pop up running the app on localhost:5000. The app should look and behave the same as the development version you made in the previous step.

You can find the production bundle's code with your repository in a newly generated folder build.

First, navigate to the Hosting section of the Realm UI. You can find it on the side menu under Manage. Once you're on the Hosting page, click the Enable Hosting button.

Now that you have hosting enabled, replace the default index.html with the production bundle you built in the last step. Delete the index.html file by clicking the check box next to the file and then Actions > Delete.

To add your app, open a file browser on your computer, and navigate to the build folder in your app's root folder. Select all the build folder's contents, and drag and drop it over to the Realm Hosting UI's Files page.

Once the files finish uploading, click the Review & Deploy button at the top of the page. A dialog will open with your changes. Review the changes and click the Deploy button at the bottom of the dialogue to start the deployment of your app.

Realm deploys your site to <Your-App-Id> Deployment can take up to a few minutes. You can track the status on the Hosting page.

Once deployment finishes, navigate to your app's new URL, <Your-App-Id> Here you'll find the app with the same backend and data that you were working with before, now live on the Internet!

For more detailed information about hosting your web app with Realm, refer to our documentation on hosting a single page application.

Deploy Using the Realm CLI

You can also deploy the app frontend with the Realm CLI. Before you're able to deploy the frontend from the Realm CLI, you must first enable hosting in the Realm UI, as discussed at the beginning of the Deploy Using the Realm UI section.

Once you've enabled hosting in the UI, you need to have the realm-tutorial-backend repository handy, which you used in the backend setup.

In the backend directory, create the folder hosting/files. This is where you will place your assets for Realm Hosting.

Also create a config file for the app. This should be located at hosting/config.json, and have the following contents:

"enabled": true,
"default_response_code": 200,
"default_error_path": "/index.html",

Copy the contents of the build folder from the realm-tutorial-web directory to the hosting/files folder in the backend. You can do this with the command:

cp -a path/to/realm-tutorial-web/build/. path/to/realm-tutorial-backend/hosting/files

Now you're ready to deploy the app using the Realm CLI. To deploy, run the command:

realm-cli push --remote=<Your-App-Id> --include-hosting

Realm then deploys your site to <Your-App-Id>

You just built and deployed a functional task tracker web application built with MongoDB Realm. Great job!

Now that you have some hands-on experience with MongoDB Realm, consider these options to keep practicing and learn more:

Give Feedback

How did it go? Use the Give Feedback tab at the bottom right of the page to let us know if this tutorial was helpful or if you had any issues.

Give Feedback
© 2021 MongoDB, Inc.


  • Careers
  • Legal Notices
  • Privacy Notices
  • Security Information
  • Trust Center
© 2021 MongoDB, Inc.