# Using Firestore with Next.js

> Learn how to start using Firebase Firestore in your Next.js and React application

*Published: 2022-03-18*
*Canonical: https://makerkit.dev/blog/tutorials/firestore-nextjs*

---

Next.js is a serverless framework that can be used with any database you
prefer, and it's one of the reasons why it's so popular among developers.

## Why we use Firestore with Next.js
Firestore is a common choice among Next.js developers due to
it being performant, scalable, simple, real-time, offline-first, and with a
generous free tier. There's a lot to like.

Firestore is a serverless database that we can use from both the client and
the server. Communicating with Firestore right from the browser
is likely one of its biggest strengths: you no longer need an API to read
and write to your database because it can be accessed directly via the SDK.

The possibility of not having to write an API is a huge time-saver for
developers, and further help to front-end developers who want to write
full-stack applications from scratch.

If you're wondering how we can keep our database secure despite being used
from a client (good question!), you may want to take a look at [this guide for
writing Security Rules for your Firestore database](/blog/tutorials/in-depth-guide-firestore-security-rules).

One of the biggest criticisms of Firestore was the fact that it added a
significant additional size to client-side bundles due to its heavy SDK.

Fortunately, The newly released version 9 of Firebase made massive
improvements in terms of size, which can go even further when using the
Lite version.

In this article, I want to introduce you to Firestore using Next.js at the
time of writing one of the most popular React frameworks, and the framework we built
[MakerKit](https://makerkit.dev/ai-saas-starter) on.

## Setting Up Firestore with Next.js

If you haven't yet configured your application, you may want to get started
using our article about [setting up the Firestore Emulators with Next.js](/blog/tutorials/setting-up-firebase-emulators-nextjs).

We use `reactfire`; it's a [Typescript library by Firebase](https://github.com/FirebaseExtended/reactfire) designed to
help developers use Firebase with any React codebase.

### Firestore Provider

We need to initialize Firestore and write a provider to wrap the children that use the Firestore hooks.

The code below is a simplified version of what MakerKit does to initialize a
Firestore instance:

 ```tsx {% title="FirebaseFirestoreProvider.tsx" %}
import { FirestoreProvider, useFirebaseApp } from 'reactfire';
import { useMemo } from 'react';

import {
  enableIndexedDbPersistence,
  connectFirestoreEmulator,
  initializeFirestore,
} from 'firebase/firestore';

import { isBrowser } from '~/core/generic';

export default function FirebaseFirestoreProvider({
  children,
  useEmulator,
}: React.PropsWithChildren<{ useEmulator?: boolean }>) {
  const firestore = useFirestore();

  // connect to emulator if enabled
  if (useEmulator) {
    const host = getFirestoreHost();
    const port = Number(getFirestorePort());

    try {
     connectFirestoreEmulator(firestore, host, port);
    } catch (e) {
      // this may happen on re-renderings
    }
  }

  const enablePersistence = isBrowser();

  // We enable offline capabilities by caching Firestore in IndexedDB
  // NB: if you don't want to cache results, please remove the next few lines
  if (enablePersistence) {
    enableIndexedDbPersistence(firestore)
  }

  return <FirestoreProvider sdk={firestore}>{children}</FirestoreProvider>;
}

function getFirestoreHost() {
  return process.env.NEXT_PUBLIC_FIREBASE_EMULATOR_HOST ?? 'localhost';
}

function getFirestorePort() {
  return process.env.NEXT_PUBLIC_FIRESTORE_EMULATOR_PORT ?? 8080;
}

function useFirestore() {
  const app = useFirebaseApp();

  return useMemo(() => initializeFirestore(app, {}), [app]);
}
```

If we use the provider above in `_app.tsx`, every page can have access to
Firestore. Adding the Firebase providers may not be the best solution depending on what your application does and how lightweight you want your pages.

### Our first Firestore hook to read a document

The library `Reactfire` helps us with writing queries to our Firestore database
thanks to a list of predefined React hooks.

For example, one common query in SaaS applications is to fetch the
organization/project a user belongs to. In MakerKit, we wrote it similarly
to the code below:

 ```tsx {% title="use-fetch-organization.ts" %}
import { useFirestore, useFirestoreDocData } from 'reactfire';
import { doc, DocumentReference } from 'firebase/firestore';
import { Organization } from '~/lib/organizations/types/organization';

type Response = Organization & { id: string; };

export function useFetchOrganization(organizationId: string) {
  const firestore = useFirestore();
  const organizationsPath = `/organizations`;

  const ref = doc(
    firestore,
    organizationsPath,
    organizationId
  ) as DocumentReference<Response>;

  return useFirestoreDocData(ref, { idField: 'id' });
}
```

We imported the `Firestore` instance using the React hook `useFirestore`. By using the function `doc` we can get a reference to a Firestore document at the path specified. In this case, we're fetching a reference of the path
`/organizations/${organizationId}`.

Using the hook `useFirestoreDocData,` we can directly query and retrieve the document's data.

By adding the property `idField`, we're also adding the `id` of the document to the data object. Adding the `id` field is the reason why we merge
the interface of the response with the interface `{ id: string }`.

#### Reading a list of documents using Firestore

To read a list of documents from a Firestore collection, we can use the
`useFirestoreCollectionData` hook.

 ```tsx {% title="use-fetch-user-organizations.ts" %}
import { useFirestore, useFirestoreCollectionData } from 'reactfire';
import { collection, CollectionReference, where, query } from
'firebase/firestore';

export function useFetchUserOrganizations(userId: string) {
  const firestore = useFirestore();
  const organizationsPath = `organizations`;

  const ref = collection(
    firestore,
    organizationsPath
  ) as CollectionReference<Organization>;

  const userPath = `members.${userId}`;
  const operator = '!=';

  // adding the constraint where. This means: retrieve the organizations
  // where the field members[userId] is not null, eg: it exists
  const constraint = where(userPath, operator, null);
  const organizationsQuery = query(ref, constraint);

  return useFirestoreCollectionData(organizationsQuery);
}
```

### Using document queries in Firestore

[Reactfire](/docs/next-fire/api/reactfire)'s hooks are async and therefore re-render based on the
status of the hook, which can be one of the following: `loading`, `error`, or
`success`.

 ```tsx {% title="organization/index.ts" %}
const Organization: React.FC<{
  id: string
}> = ({ id }) => {
  const { data: organization, status }
    = useFetchOrganization(id);

  // warn users there was an error
  if (status === 'error') {
    return <p>Ops! Error encountered while loading organization data...</p>
  }

  // show a loading indicator
  if (status === 'loading') {
    return <p>Loading organization data...</p>
  }

  // all good! display organization data
  return <OrganizationDetail organization={organization} />;
}
```

### Using document writes in Firestore

Unlike queries, `Reactfire` does not provide any hook to write to documents.

But we can easily make our own by using `useState` (NB: the following is a
a simplified example, but you'll get the gist).

 ```tsx {% title="use-create-organization.ts" %}
import { useCallback, useState } from "react";

import {
  collection,
  addDoc,
} from 'firebase/firestore';

export function useCreateOrganization() {
  const firestore = useFirestore();
  const [state, setState] = useState();

  const mutation = useCallback(async (organization: Organization) => {
    setState('loading');

    const organizationsPath = `/organizations`;
    const collectionReference =
      collection(firestore, organizationsPath);

    try {
      await addDoc(
        collectionReference,
        organization
      );

      setState('success');
    } catch (e) {
      setState('error');
    }
  }, [firestore]);

  return [
    mutation,
    state,
  ] as [
    typeof mutation,
    typeof state,
  ];
}
```

Using the pattern above, we send the mutation as the first element of the
array, and the state of the mutation as the second element.

The pattern can be used as follows:

 ```tsx {% title="OrganizationForm.tsx" %}
function OrganizationForm() {
  const [
    createOrganization,
    createOrganizationState
  ] = useCreateOrganization();

  if (createOrganizationState === 'error') {
    return <p>Ops, we couldn't create this organization</p>;
  }

  if (createOrganizationState === 'loading') {
    return <p>Creating your organization. Please Wait....</p>;
  }

  if (createOrganizationState === 'success') {
    return <p>Yay! It worked.</p>;
  }

  return (
    <OrganizationForm onSubmit={createOrganization} />
  );
}
```

You can also use the same patterns for deleting or updating a document.

### Should you use Firestore with your Next.js app?

OK, we've seen that Firestore is solid. But is it the best choice
for your application? Let's talk about this.

Many developers start using Firestore, only to meet certain obstacles along the
way: rigidity of the data model, need for full-text search, highly relational
data structures, and so on.

The above are all signs that Firestore may not have been
the best choice for your application or you as a developer (as you may
prefer relational databases, for example).

Below is my non-exhaustive checklist for choosing (or not) Firestore:

- I don't need a full-text search, OR I am ready to integrate and duplicate
data using a dedicated search engine (Algolia, ElasticSearch, MeilliSearch,
TypeSense, etc.)
- I am OK with duplicating data, and I know how and where to do so
- I don't need to make a large number of reads, or I am able
to load documents progressively (for example, using pagination)
- I need/want real-time updates
- I need/want offline persistence

## Final Words

Firestore is a robust database: easy to start with, secure, and scale.
But, at the same time, it's not always the correct choice, so it's worth remembering
if you're embarking on starting a SaaS using it.

For example, MakerKit uses Firestore extensively: while it's OK for most
applications, it won't be the perfect choice for several use-cases, and
that's fine.

If you need any help or want to know more about how we use Firestore in MakerKit,
[please reach out and let's talk](/contact).

Want to learn more? Check out our [free Makerkit course](/courses/nextjs-turbo/introduction) to build your SaaS.
