Something I like to do is evaluate various Database services to see what the latest and greatest offerings are. One name that came up in my research was HarperDB. HarperDB is a distributed SQL/NoSQL database with a built-in HTTP API, that can be deployed anywhere from edge to cloud. To stretch my SQL-muscles a bit, I decided to check out HarperDB and see what it was all about. We’ll build a basic app that performs all the standard CRUD operations (create, read, update, delete) and see what it’s like working with HarperDB.

Creating your Database

To get started, you first need to create an account on HarperDB, and create an organization for your account. Once created, you can spin up a new HarperDB Cloud Instance from your dashboard.

Clicking “Create New Cloud Instance” will open a dialog and prompt you to pick either a locally installed instance, or an instance with AWS or Verizon Wavelength. For mine, I selected AWS, and just left the rest of the options set to their default value.

What’s nice is that HarperDB lets you create a single tier for free in order to evaluate the service. Once your instance has been created and spun up, you can select it, and we can begin to make our first schema and table.

Our schema is basically where we can store the various tables we can interact with. I’ll call this schema “projects” and then create a table in it called “tasks”.

When you create a new table, you’ll be prompted to enter a name, and also a hashing attribute. The hashing attribute will be a property on our data that will be randomly generated, and will be unique to each entry.

With the table created, we can focus on building out our app to add data.

The App

Our app will be a simple React app, so let’s create that.

ionic start harper-app blank --type react
cd harper-app

With our project created, we first need to create a Provider for our app in order to make our lives a bit easier. The actual details here aren’t too important, so I won’t be going over it in details. But in summary, we’re providing the instance URL and login information for our app in our provider, then creating a hook in order to perform our queries against the database.

import { createContext, useContext, useCallback } from 'react';

export type HarperDBContextType = {
  url: string;
  user: string;
  password: string;
};
export type HarperDBProviderProps = {
  url: string;
  user: string;
  password: string;
  children: JSX.Element;
};
export const HarperDBContext = createContext<Partial<HarperDBContextType>>({});

export const HarperDBProvider = ({ url, user, password, children }: HarperDBProviderProps) => {
  return (
    <HarperDBContext.Provider value={{ user, password, url }}>
      {children}
    </HarperDBContext.Provider>
  );
};
export function useHarper() {
  const { url, user, password } = useContext(HarperDBContext);
  let abortController: AbortController;

  const executeQuery = useCallback(
    async ({ stringifiedQuery, signal }) => {
      try {
        const request = await fetch(`${url}`, {
          method: 'POST',
          signal,
          body: stringifiedQuery,
          headers: {
            'Content-Type': 'application/json',
            authorization: `Basic ${btoa(`${user}:${password}`)}`,
          },
        });

        const json = await request.json();
        const response = json.body || json;

        if (response.error) {
          return { error: response.message };
        }

        return { data: response };
      } catch (e: any) {
        return { error: e.message };
      }
    },
    [url, user, password]
  );
  const execute = async (sqlQuery: { operation: string; sql: string }) => {
    abortController = new AbortController();
    const queryBody = JSON.stringify(sqlQuery);
    const response = await executeQuery({
      stringifiedQuery: queryBody,
      signal: abortController.signal,
    });
    return response;
  };
  return { execute };
}

With this in place, we can import it in our index.tsx and set the props on the provider.

import { HarperDBProvider } from './harper-provider';
const url = process.env.react_app_url!;
const userName = process.env.react_app_username!;
const password = process.env.react_app_password!;

ReactDOM.render(
  <React.StrictMode>
    <HarperDBProvider url={url} user={userName} password={password}>
      <App />
    </HarperDBProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

Here I’m providing the URL, username, and password via environment variable, but a much more secure approach is recommend. Having this in place we can now make our requests using the execute function from our useHarper hook. So let’s add that functionality.

In src/pages/Home.tsx, let’s import the hook, and get a hold of the execute function.

const Home: React.FC = () => {
  const { execute } = useHarper();
  const [state, setState] = useState({ data: [], isLoading: true });

  return (...)
}

The execute function will allow us to pass our queries and request the appropriate data we need for our app. We’ve also included some state here in order to store that data once it’s been fetched. To request the data, we can use the useIonViewDidEnter hook, and create a loadData function that will get called.

  useIonViewDidEnter(() => {
    loadData();
  });
  const loadData = async () => {
    const { data } = await execute({
      operation: 'sql',
      sql: `SELECT * FROM project.tasks`,
    });
    setState({ data, isLoading: false });
  };

Here, loadData is calling execute, and we pass along an object with the type of operation we want to perform. Since I want to work with SQL, I’ll set the operation to sql, and then provide the correct SQL statement. Since the home page is where we’ll want to display all of the entries, we can use SELECT * FROM project.tasks. It’s worth mentioning that the casing of the SQL statements does not matter, which is something I learned along the way 🙃.

Once the data has been loaded, we can set it on our state, and then render it out

const Home: React.FC = () => {
  const { execute } = useHarper();
  const [state, setState] = useState({ data: [], isLoading: true });

  useIonViewDidEnter(() => {
    loadData();
  });
  const loadData = async () => {
    const { data } = await execute({
      operation: 'sql',
      sql: `SELECT * FROM project.tasks`,
    });
    setState({ data, isLoading: false });
  };
  return (
    <IonPage>
        <IonContent>
            <pre>{JSON.stringify(data, null 2)}</pre>
        </IonContent>
    </IonPage>
  )
}

From here on out, it’s standard SQL-practice for performing updates to your data. Meaning, if you know a bit of SQL, you should feel right at home with HarperDB.

Parting Thoughts

For a simple demo project like this, HarperDB might be overkill, but if you are building an app that needs a fast Database and a flexible API, HarperDB is a fantastic solution. Having the full power of SQL will help keep a consistent structure for larger apps, but there’s also the flexibility of NoSQL if that is how you prefer to work. And for those who are security minded, there’s a role-based, attribute-level security model in place to make sure only the correct users can access your data.

If you’re interested in seeing the app and exploring HarperDB more, here’s the link to my repo. All you need is a new instance from Harper Studio and you can explore how everything works for your self!

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