Building an app these days often requires some kind of data layer, whether storing data locally using LocalStorage or using some remote database solution. In this blog post, we will look at Realm, from MongoDB, as our storage solution for our web app and discover how we can start to make use of it in an Ionic React app, with either GraphQL or direct DB operations. But before we look at the code, let’s understand what Realm is and how it relates to MongoDB.

What Is Realm?

In the simplest terms, Realm is a platform made up of two parts. One is a local database solution for mobile that is an alternative to using something like SQLite. Realm will store the data locally on the device, but also sync it back with a MongoDB Atlas instance. Realm also offers a hosted application service that can handle data syncing across clients, as well as serverless functions for actions like authentication.

For the Web SDK, Realm makes heavy use of the Atlas data layer. Instead of interacting with a local store, all the read/write operations are performed on the Atlas instance directly. Realm’s serverless functions are still used for actions like authentication and user management.

In our app, we’ll use Ionic and React to interact with a Realm/Atlas instance and make a quick task tracker app. There are some prerequisites involved, but the Mongo docs cover everything in this excellent page. Following this tutorial will have you set up a free Atlas Database cluster and a free Realm hosted application.

When your database and hosted app has been set up, you can clone the project below and play around with it.

git clone https://github.com/mhartington/ionic-realm-demo
cd ionic-realm-demo
# Optionally check out the graphql-less version
git checkout graphql-less
npm install
ionic serve

With all the setup taken care of, let’s actually build something.

Accessing Realm From Your App

To start accessing Realm in your app, we first must make sure the Realm instance is available. To do so, we’re going to make use of React’s Context API and hooks. In the src directory, there is a Realm.tsx file, let’s open that up.

import { createContext, useContext, useEffect, useState } from 'react';
import * as Realm from 'realm-web';

const RealmAppContext = createContext<Partial<Realm.App>>({});

export const useRealmApp = () => {
  const app = useContext(RealmAppContext);
  if (!app) {
    throw new Error( `You must call useRealmApp() inside of a <RealmAppProvider />`);
  }
  return app;
};

export const RealmAppProvider = ({ appId, children, }: { appId: any; children: JSX.Element; }) => {
  const [app, setApp] = useState(new Realm.App(appId));
  const [currentUser, setCurrentUser] = useState(app.currentUser);

  useEffect(() => {
    setApp(new Realm.App(appId));
  }, [appId]);

  const logIn = async (credentials: Realm.Credentials<any>) => {
    const user = await app.logIn(credentials);
    setCurrentUser(app.currentUser);
    return user;
  };
  const logOut = async () => {
    await app.currentUser?.logOut();
    setCurrentUser(app.currentUser);
  };

  const wrapped = { ...app, currentUser, logIn, logOut };
  return (
    <RealmAppContext.Provider value={wrapped}>
      {children}
    </RealmAppContext.Provider>
  );
};

While this may seem like a lot, the basics of what is happening here can be broken down into three parts:

  • Create “context” for our Realm instance
  • Create a “provider” which will populate our context with the values needed
  • Create a hook that exposes our context to the rest of the app.

Context

const RealmAppContext = createContext<Partial<Realm.App>>({});

Our context here is going to be a specialized React component whose value will be populated later on. This is where the Realm API will be mapped to and be exposed to our app.

Provider

The provider component is doing most of the heavy lifting here. When we use it, we set an app ID prop on it, and it will create a new Realm instance. We then create some state that is local to the component, like app, and currentUser to track when a user has logged in and out. This way we don’t have any accidental permission errors. We can provide some functions like login and logout to manage that currentUser and then create a wrapper around the built- in methods from Realm by destructuring the app instance and padding the new login/logout functions.

After the setup, we can finally render our context and set its value:

return (
  <RealmAppContext.Provider value={wrapped}>
    {children}
  </RealmAppContext.Provider>
);

RealmAppContext is our context from earlier, and all we’re doing is setting the value to be the wrapped Realm API we just created.

Hook

export const useRealmApp = () => {
  const app = useContext(RealmAppContext);
  if (!app) {
    throw new Error(`You must call useRealmApp() inside of a <RealmAppProvider />`);
  }
  return app;
};

With the provider and context handled, we now need a way to expose things at an application level. That is where our hook comes in. We can get our context by calling useContext and passing the context as an argument. Then, if the context has been set already, we can just return it!

Our Code In Action

With the hook, context, and provider established, we can start to see how our app’s functionality is set up. For instance, in src/pages/Login.tsx, to register or login to the app we use the useRealmApp hook and call our functions directly from it.


import { useRealmApp } from '../Realm'; import * as Realm from 'realm-web'; import './Login.css'; export function Login() { const app = useRealmApp(); const login = async () => { await app.logIn!( Realm.Credentials.emailPassword(loginInfo.email, loginInfo.password) ); }; const register = async () => { await app.emailPasswordAuth!.registerUser( loginInfo.email, loginInfo.password ); await login(); }; return (...); }

With our hook, calling logIn will use the same app services that we created at the start of this. The best part is that we don’t need to cover every feature or Realm in the hook, just the stateful features that make sense to an app. We can still import Realm itself and call methods like Realm.Credentials.emailPassword to create the right payload for our login request.

Getting The Code

There’s a lot more going on in this app, and I highly suggest you check it out and start to know what you can do with Realm. To get the code, simply clone the repo and follow the backend tutorial on Realm’s docs.

git clone https://github.com/mhartington/ionic-realm-demo
cd ionic-realm-demo
# Optionally check out the graphql-less version
git checkout graphql-less
npm install
ionic serve

There are two flavors of the app, one that uses GraphQL and one that uses the direct API from Atlas itself. Check out both versions and let us know which one is your favorite down in the comments 😀. Happy coding!

Sign up for the Ionic Newsletter to get the latest news and updates!