Navigation

Task Tracker (Web)

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.
  • Add, view, and delete tasks.
  • Switch tasks between Open, In Progress, and Complete statuses

This tutorial should take around 30 minutes.

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.

Download the Complete Source Code

We host this tutorial application’s complete and ready-to-run source code on GitHub. Just follow the instructions in README.md to get started. Don’t forget to update the /src/realm/RealmApp.tsx file with your App ID, which you can find in the Realm UI.

Prerequisites

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

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

A. Clone the Client App Repository

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

git clone git@github.com:mongodb-university/realm-tutorial.git

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 main branch is a finished version of the app as it should look after you complete this tutorial. To remove the Realm-specific source code that you’ll define in this tutorial, check out the todo branch:

git checkout todo

Note

The realm-tutorial repo contains task tracker client applications for multiple platforms. The project root for this tutorial is located in the web subdirectory.

B. Explore the App Structure & Components

The web client is a standard React web application written in TypeScript 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:

src/
├── index.tsx
├── realm/
│   ├── RealmApp.tsx
│   ├── RealmApolloProvider.tsx
│   ├── schema.graphql
│   └── operations.graphql
├── components/
│   ├── App.tsx
│   ├── Board.tsx
│   ├── LoginScreen.tsx
│   ├── Navbar.tsx
│   ├── TaskCard.tsx
│   ├── TaskDetail.tsx
│   ├── TaskLists.tsx
│   └── TaskView.tsx
└── hooks/
   ├── useDraftTask.tsx
   ├── useTaskLists.tsx
   └── useTasks.tsx

Realm & Apollo

The /src/realm directory contains all of the modules that you’ll use to connect the application to Realm. 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.

React Components & Hooks

The /src/components and /src/hooks directories contain pre-built React components and hooks that handle local state management and UI rendering. The components import code from the files in /src/realm and use them to interact with Realm. We’ve already completely implemented these so you won’t need to add any code to these files. We’ll make sure to show you along the way how these components and hooks use the code that you write.

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.

C. Connect to Your MongoDB Realm App

The client app needs to connect to your Realm app so that users can register and log in. In src/realm/RealmApp.tsx, 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.tsx is not fully defined. You need to update the code to use the SDK to connect to your Realm app and handle user authentication.

1

Create a Realm App Client

The app client is the primary interface to your Realm app from the SDK. In src/realm/RealmApp.tsx, add the following code immediately below the imports at the top of the file to create the app client:

src/realm/RealmApp.tsx
const REALM_APP_ID = "<Your App ID>"
const app = new RealmWeb.App({ id: REALM_APP_ID });

Use Your App ID

Make sure to replace <Your App ID> 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.

../../_images/task-tracker-web-copy-appid.png
2

Complete the Registration & Authentication Functions

The app client provides methods that allow you to authenticate and register users through the email/password authentication provider. In src/realm/RealmApp.tsx, the RealmApp 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.

How We Use It

In /components/App.tsx, 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.

function RequireAuthentication() {
  const app = useRealmApp();
  if (!app) {
    return <div>Loading</div>;
  }
  return app.user ? (
    <RealmApolloProvider>
      <Board />
    </RealmApolloProvider>
  ) : (
    <LoginScreen />
  );
}

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

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

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

D. Define the GraphQL Schema & Operations

1

Define the GraphQL Schema

A GraphQL schema defines all of the types, enums, and scalars that a GraphQL API supports. Realm automatically generates a GraphQL schema for you, and you can use it to generate useful TypeScript types for your data.

To get your schema, navigate to the GraphQL page in the Realm UI and click the Schema tab. You should see a generated GraphQL schema that matches the schemas you defined earlier.

Click the GraphQL Schema button to download the schema as schema.graphql. When the download completes, copy the downloaded schema into src/realm/, overwriting the empty schema.graphql file that’s already there.

Alternatively, copy the schema from the UI and paste it directly into /src/realm/schema.graphql.

2

Define GraphQL Operations

In addition to generating types from a schema file, you can also generate functions and GraphQL objects for operations such as queries and mutations. You can split operations across multiple files, but in this tutorial we’ll define all of the CRUD operations that the app uses to work with tasks and user documents in a single file.

Open /src/realm/operations.graphql and paste in the following GraphQL operations.

query GetAllTasks {
  tasks {
    _id
    name
    status
    assignee {
      _id
      name
      image
      user_id
    }
  }
}

mutation AddTask($task: TaskInsertInput!) {
  task: insertOneTask(data: $task) {
    _id
    name
    status
    assignee {
      _id
      name
      image
      user_id
    }
  }
}

mutation UpdateTask($taskId: ObjectId!, $updates: TaskUpdateInput!) {
  task: updateOneTask(query: { _id: $taskId }, set: $updates) {
    _id
    name
    status
    assignee {
      _id
      name
      image
      user_id
    }
  }
}

mutation DeleteTask($taskId: ObjectId!) {
  deletedTask: deleteOneTask(query: { _id: $taskId }) {
    _id
    name
    status
    assignee {
      _id
      name
      image
      user_id
    }
  }
}

query GetUser($userId: String!) {
  user(query: { user_id: $userId }) {
    _id
    _partition
    name
    image
    user_id
  }
}

Run Operations in GraphiQL

You can run these operations directly in the Realm UI using the GraphiQL explorer on the GraphQL page. To run them, paste the operations into the operations pane and define variables in the variables pane. Then, click run and select the operation you want to execute.

../../_images/task-tracker-web-graphiql.png
3

Configure GraphQL Code Generator

Now that we’ve defined our GraphQL schema and operations, we can use graphql-codegen to automatically generate code. This tool is highly configurable and can generate many different types of code depending on your needs. In this tutorial, we’ll use it to generates TypeScript types & React hooks for the schema and operations.

To configure graphql-codegen, open the codegen.yml file in the project root and paste in the following configuration:

codegen.yml
schema: src/realm/schema.graphql
documents: src/**/operations.graphql
overwrite: true
generates:
  src/types.ts:
    plugins:
      - typescript
      - typescript-operations
  src/graphql-operations.ts:
    preset: import-types
    presetConfig:
      typesPath: ./types
    plugins:
      - typescript-react-apollo
    config:
      withHooks: true
      withComponent: false
      withHOC: false
4

Run GraphQL Code Generator

Once you’ve updated codegen.yml, execute the following command in your shell to run graphql-codegen:

npm run generate

If you’ve set everything up correctly, this will generate two files:

  • src/types.ts, which includes TypeScript types that reflect your GraphQL schema.
  • src/graphql-operations.ts, which includes custom Apollo hooks for each operation as well as additional helper objects and TypeScript types.

How We Use It

We import the generated types defined in /src/types.ts from multiple components throughout the app that need to use data from the GraphQL API. For example, in /src/components/TaskView.tsx we import the generated Task type and use it to type the component’s task prop.

/src/components/TaskView.tsx
import { Task } from "../types";

interface TaskViewProps {
  task: Task;
}

export function TaskView({ task }: TaskViewProps) {
  const { assignee, name } = task;
  const status = task.status as TaskStatus;
  ...
}

We use the custom Apollo hooks generated in /src/graphql-operations.ts call the GraphQL API as part of the task actions defined in useTasks().

/src/components/TaskView.tsx
import { GetAllTasksQuery } from "./../types";
import {
  useGetAllTasksQuery,
  useAddTaskMutation,
  useUpdateTaskMutation,
  useDeleteTaskMutation,
} from "./../graphql-operations";

export function useTasks() {
  const [tasks, setTasks] = React.useState<Task[]>([]);

  // Query for Tasks
  const { loading } = useGetAllTasksQuery({ onCompleted: (data: GetAllTasksQuery) => {
    if(data?.tasks) {
      setTasks(data.tasks as Task[])
    }
  }});

  // Create Task Mutation Functions
  const [addTaskMutation] = useAddTaskMutation();
  const [updateTaskMutation] = useUpdateTaskMutation();
  const [deleteTaskMutation] = useDeleteTaskMutation();

  ...
}

The custom query and mutation hooks are lightweight wrappers around Apollo’s useQuery() and useMutation() hooks. For example, you could define useAddTaskMutation() yourself with the following code:

import { Task, TaskInsertInput } from "../types";
import { useMutation } from "@apollo/react-hooks";
import gql from 'graphql-tag';

type AddTaskMutation = { task: Task };
type AddTaskMutationVariables = { task: TaskInsertInput };
function useAddTaskMutation() {
  return useMutation<AddTaskMutation, AddTaskMutationVariables>(gql`
    mutation AddTask($task: TaskInsertInput!) {
      task: insertOneTask(data: $task) {
        _id
        name
        status
        assignee {
          _id
          name
          image
          user_id
        }
      }
    }
  `);
}

Later in the function, we use the functions returned from the mutation hooks to execute the mutations.

/src/components/TaskView.tsx
export function useTasks() {
  ...

  const addTask = async (task: Task) => {
    const variables = {
      task: {
        status: task.status,
        name: task.name,
        assignee: task.assignee ? { link: task.assignee.user_id } : undefined,
      },
    };
    const currentTasks = [...tasks];
    try {
      const result = await addTaskMutation({ variables });
      const task = result.data?.task as Task;
      setTasks([...tasks, task]);
    } catch (err) {
      setTasks(currentTasks);
      throw new Error("Unable to add task");
    }
  };
}

E. Connect Apollo to the GraphQL API

We’ve defined GraphQL CRUD operations and used the code generator to create custom query and mutation hooks. However, these hooks must be wrapped in an Apollo context provider that makes an ApollClient object available.

In src/realm/RealmApolloProvider.tsx, 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.

1

Instantiate an ApolloClient

The RealmApolloProvider component calls createApolloClient() to instantiate the client. Update the function with the following code to create an ApolloClient object that connects to your app:

src/realm/RealmApolloProvider.tsx
function createApolloClient(realmAppId: string, user: RealmWeb.User): ApolloClient<NormalizedCacheObject> {
  const graphql_url = `https://realm.mongodb.com/api/client/v2.0/app/${realmAppId}/graphql`;
  const httpLink = new HttpLink({ uri: graphql_url });

  return new ApolloClient({
    link: httpLink,
    cache: new InMemoryCache(),
  });
}
2

Authenticate GraphQL Requests

The createApolloClient() 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 createApolloClient() function to include the current user’s access token in an Authorization header with every request:

src/realm/RealmApolloProvider.tsx
function createApolloClient(realmAppId: string, user: RealmWeb.User): ApolloClient<NormalizedCacheObject> {
  const graphql_url = `https://realm.mongodb.com/api/client/v2.0/app/${realmAppId}/graphql`;
  const httpLink = new HttpLink({ uri: graphql_url });
  const authorizationHeaderLink = setContext(async (_, { headers }) => ({
    headers: {
      ...headers,
      Authorization: `Bearer ${user.accessToken}`,
    },
  }));

  return new ApolloClient({
    link: authorizationHeaderLink.concat(httpLink),
    cache: new InMemoryCache(),
  });
}

Try It Out

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

Start the App

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:  http://191.175.1.124:3000

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

Register a New User

You need to register a user account before you can log in and use the tracker. On the login screen, click Register one now and enter the email address and password that you want to log in with. This is just a sample app that does not send any validation emails, so feel free to use a fake email address and/or simple password.

../../_images/task-tracker-web-register-user.png

Add Some Tasks

Once you register, the app automatically logs you in. You can now add tasks and use the tracker. To add a task, click Add Task at the bottom of any of the lists, enter a name in the draft task that appears, then click Add.

../../_images/task-tracker-web-add-task.png

Move Tasks Around

You can change the status of a task by dragging it between lists. You can also click on a task to open a detailed view with buttons that change the task’s status and allow you to delete the task entirely.

../../_images/task-tracker-web-move-task.png

Check Out the Logs

Whenever you add, update, or delete a task, the client app sends a GraphQL request to Realm. You can see a history of requests on the Logs page of the Realm UI. Each GraphQL log entry shows the operation, compute usage, and rule evaluation summary.

../../_images/task-tracker-web-graphql-logs.png

What’s Next?

You just built 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:

  • Extend the task tracker app with additional features. For example, you could:
    • allow users to change a task’s assignee
    • allow users to log in using another authentication provider
    • support multiple projects that each have their own set of tasks
  • Follow another tutorial to build a mobile app for the task tracker. We have task tracker tutorials for the following platforms:
  • Dive deeper into the docs to learn more about MongoDB Realm. You’ll find information and guides on features like:

Leave Feedback

How did it go? Please let us know if this tutorial was helpful or if you had any issues by using the feedback widget on the bottom right of the page.