A React Database With Firebase In 10 Steps (2023)

With 18 millions downloads per week, React is the most used front-end framework in 2022. And if you’re reading this article, changes are you use React too! But front-end development isn’t always enough. Whether you need to authenticate users or call APIs, you can’t avoid databases.

Back-end development can be a pain though. At Rowy, we provide a content management system on top of Google Firebase―a popular backend-as-a-service provider―to help front-end developers use databases without back-end code. In the following article, you’ll learn how to use a serverless database in a React to-do app thanks to Rowy. Let’s dig in!

What’s A React Database

Unlike a JSON file or a spreadsheet, a database is a software system that stores information in data structures optimized for retrieval and manipulation

In a React project, you use databases for anything that involves storing data in a secure and efficient manner.

Why You Need A React Database

As already mentioned, you need databases to persist any kind of data across user sessions. Using the LocalStorage browser API simply isn’t good enough, because you will quickly run against storage limitations that will put your users at risk. Using a database is the only viable alternative in a production environment.

You will also need a database to develop app features people can use! Whether it’s a search feature, analytics, or API calls to manage data collections, front-end developers need to play with databases.

Back-end development is a full-time job, however. You’ll save much time by using a serverless approach like Firebase, Supabase, or Vercel. Rowy is a great alternative for front-end developers because it’s a visual content management system that looks like a spreadsheet, built on top of Firebase, that gives you all the powers of a NoSQL database without setting one up yourself. But an example speaks a thousand words, so let’s get to the code already.

How To Use A Database With React In 10 Steps

1. Create an account on Rowy

For the sake of simplicity, we are going to build a to-do app using Rowy. This will allow us to save hours on configuring a web server and a database.

First, follow the installation guide or use the Deploy shortcut to let Rowy guide you. It takes 5 minutes to get started:

Untitled

We name the project React Database. Rowy will take care of setting up Firestore, Firebase’s database management system.

You are ready to use Firebase with React as soon as your project is created:

Untitled

2. Create a database

We now create a database from scratch for our to-do app.

First, create a new table todo by clicking “Create a table”:

Untitled

Then, add a column to your table, name, which will describe what a to-do item is about as a short text:

Untitled

Finally, we can use Rowy’s spreadsheet interface to add a new to-do item “get groceries”:

Untitled

That’s it! We now have a database that contains a single document, and it only took a minute!

A NoSQL database like Firestore is a set of collections (called tables in Rowy) containing documents. In this example, our collection is a personal to-do list, and this collection contains all of our to-do tasks as single documents.

3. Obtain Your Firebase Config

Now that our back-end is ready, we need to start configuring the front-end. Firebase can be accessed through programmatic API calls from React, but you’ll first need to get your configuration right.

Head to your project’s Firebase Console and navigate to Project settings. In the first tab, you’ll find the following code that contains your Firebase configuration:

Untitled

Before you head to VSCode, go to the side panel. Click BuildFirestore database and navigate to the Rules tab. During development, we need to disable authentication to avoid running into permission issues.

Add the following security rule:

match /todo/{docId} {
    allow read, write: if true;
}

You will obtain something like this:

Untitled

We will talk more about user authentication in another article, but know that you’ll need some way to recognize your users to run an app in production.

For now, anyone can access your database’s API endpoints because of the security rules, so leaking your Firebase configuration is strongly not recommended!

4. Create A React App

Create a new React app:

npx create-react-app react-database

We won’t need much code to get started, so go ahead and empty your App function:

src/App.js

function App() {
  return (
    <div className="App">

    </div>
  )
}

export default App

5. Connect To The Database

Then, use the Firebase config you obtained in step 3 to connect to your Firestore database:

src/services/db.mjs

import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore"; 

let db = false;

export const getDb = () => {
    if(!db){
        const firebaseConfig = {
            apiKey: <API_KEY>,
            authDomain: <AUTH_DOMAIN>,
            projectId: <PROJECT_ID>,
            storageBucket: <STORAGE_BUCKET>,
            messagingSenderId: <MESSAGING_SENDER_ID>,
            appId: <APP_ID>
        }

        const app = initializeApp(firebaseConfig)

        db = getFirestore(app)
    }

    return db
}

This code implements a singleton pattern that requests a Firestore database connection to perform queries. This way, we don’t reconnect each time we send a request to avoid saturating our back-end server.

6. Read Documents

Our todo collection already contains an item, as you can see in Rowy. But now, we want to display it within our to-do app.

First, we create a standalone service to manage our calls with Firebase:

src/services/todo.mjs

import { getDocs, collection } from "firebase/firestore"; 
import { getDb } from "./db.mjs"

const collection_name = "todo"

export const findAll = async () => {
    const doc_refs = await getDocs(collection(getDb(), collection_name))

    const res = []

    doc_refs.forEach(todo => {
        res.push({
            id: todo.id, 
            ...todo.data()
        })
    })

    return res
}

The findAll function will fetch all the documents within the todo collection and return them as an array of to-do tasks, ready to be consumed by our user interface.

Let’s add a TodoList component that will display all the to-do tasks:

src/App.js

import TodoList from './components/todo-list.js'

function App() {
  return (
    <div className="App">
      <TodoList/>
    </div>
  )
}

export default App

The TodoList component encapsulates all the back-end logic, handles state changes, and displays the content:

src/components/todo-list.js

import { useState, useEffect } from 'react'
import { findAll } from '../services/todo.mjs'
import TodoListItem from './todo-list-item.js'

function TodoList() {
    const [loading, setLoading] = useState(false)
    const [todos, setTodos] = useState([])

    const fetchData = async () => {
        setLoading(true)

        const res = await findAll()

        setTodos([...res])
        setLoading(false)
    }

    useEffect(() => {
        fetchData()
    }, [])

    return (
        <section>
            <header>
                <h2>TODO</h2>
            </header>

            { loading && 
                <p>loading...</p>
            }

            <ul>
            {todos.length > 0 && todos.map(todo => (
                <TodoListItem todo={todo}/>
            ))}
            </ul>
        </section>
    )
}

export default TodoList

src/components/todo-list-item.js

function TodoListItem(props) {
    const todo = props.todo

    return (
        <li key={todo.id}>
            <h3>{todo.name}</h3>
        </li>
    )
}

export default TodoListItem

That’s it! Just yarn start the React app and you’ll see your todo items stored in Rowy:

Untitled

Similarly, you can also query Firestore to get a single document by id:

src/services/todo.mjs

import { doc, getDoc } from "firebase/firestore"; 
import { getDb } from "./db.mjs"

const collection_name = "todo"

export const findOne = async id => {
    const d = await getDoc(doc(getDb(), collection_name, id)) 
    return d.data()
}

If you want to learn more about querying data from Firestore for a React app, including how to filter, sort, and paginate your results, check out our article "Query Firestore Data With React In 10 Steps".

7. Create A New Document

You can always use Rowy to create a document directly without coding, but it’ll be more interesting to allow users to create to-do tasks themselves from within your React app.

Add a function to add a document to the todo collection:

src/services/todo.mjs

import { addDoc, collection } from "firebase/firestore"; 
import { getDb } from "./db.mjs"

const collection_name = "todo"

export const create = args => addDoc(collection(getDb(), collection_name), args)

And then call the function when the user clicks the Add button:

src/components/add-todo-bar.js

import { useState } from "react";

function AddTodoBar(props) {

    const [ newTodo, setNewTodo ] = useState("")

    const handleChange = (e) => setNewTodo(e.target.value)

    const submit = () => {
        props.createTodo({name: newTodo})
        setNewTodo("")
    }

    return (
        <div>
            <input type="text" value={newTodo} onChange={handleChange} />
            <button onClick={submit}>Add todo</button>
        </div>
    )
}

export default AddTodoBar;

Here, we pass the createTodo function from the parent component TodoList to avoid the complexity of using React stores. When the user clicks, the button, the value from the input is used as the name of the new to-do tasks.

This is the code in the TodoList component:

src/components/todo-list.js

import { useState, useEffect } from 'react'
import { findAll, create } from '../services/todo.mjs'
import AddTodoBar from './add-todo-bar.js'
import TodoListItem from './todo-list-item.js'

function TodoList() {
    const [loading, setLoading] = useState(false)
    const [todos, setTodos] = useState([])

    const fetchData = async () => {
        setLoading(true)

        const res = await findAll()

        setTodos([...res])
        setLoading(false)
    }

    const createTodo = async args => {
        const res = await create(args)

        setTodos([...todos, {
            id: res.id,
            ...args
        }])
    }

    useEffect(() => {
        fetchData()
    }, [])

    return (
        <section>
            <header>
                <h2>TODO</h2>
            </header>

            <AddTodoBar createTodo={createTodo}/>

            { loading && 
                <p>loading...</p>
            }

            <ul>
                {todos.length > 0 && todos.map(todo => (
                    <TodoListItem todo={todo}/>
                ))}
            </ul>
        </section>
    )
}

export default TodoList

You can now enter a new to-do in the input field and have it added to the to-do list:

Untitled

We can also check out Rowy to make sure the creation went as expected:

Untitled

8. Update An Existing Document

For this example, we decide to keep the user interface simple and forgo the feature of updating to-do tasks, but here is how you update an existing document with the Firestore API:

src/services/todo.mjs

import { doc, setDoc } from "firebase/firestore"; 
import { getDb } from "./db.mjs"

const collection_name = "todo"

export const update = args => {
    const {id, ...params} = args 
    return setDoc(doc(getDb(), collection_name, id), params)
}

9. Delete A Document

All things must come to an end, and documents aren’t different.

To delete a document, you call the deleteDoc function of Firebase SDK:

src/services/todo.mjs

import { doc, deleteDoc } from "firebase/firestore"; 
import { getDb } from "./db.mjs"

const collection_name = "todo"

export const del = id => deleteDoc(doc(getDb(), collection_name, id))

We then code a button to delete each corresponding to-do task:

src/components/todo-list-item.js

function TodoListItem(props) {
    const todo = props.todo

    const submit = () => props.deleteTodo(todo.id)

    return (
        <li key={todo.id}>
            <h3>{todo.name}</h3>
            <button onClick={submit}>delete</button>
        </li>
    )
}

export default TodoListItem

And the parent logic to keep everything in sync:

src/components/todo-list.js

import { useState, useEffect } from 'react'
import { findAll, create, del } from '../services/todo.mjs'
import AddTodoBar from './add-todo-bar.js'
import TodoListItem from './todo-list-item.js'

function TodoList() {
    const [loading, setLoading] = useState(false)
    const [todos, setTodos] = useState([])

    const fetchData = async () => {
        setLoading(true)

        const res = await findAll()

        setTodos([...res])
        setLoading(false)
    }

    const createTodo = async args => {
        const res = await create(args)

        setTodos([...todos, {
            id: res.id,
            ...args
        }])
    }

    const deleteTodo = async id => {
        await del(id)

        setTodos([...todos.filter(todo => todo.id != id)])
    }

    useEffect(() => {
        fetchData()
    }, [])

    return (
        <section>
            <header>
                <h2>TODO</h2>
            </header>

            <AddTodoBar createTodo={createTodo}/>

            { loading && 
                <p>loading...</p>
            }

            <ul>
                {todos.length > 0 && todos.map(todo => (
                    <TodoListItem todo={todo} deleteTodo={deleteTodo}/>
                ))}
            </ul>
        </section>
    )
}

export default TodoList

You can click on the delete button to remove a to-do card:

Untitled

And we can see the doc in Rowy is deleted as well: the table is back to how it was in the beginning.

Untitled

10. Unit testing everything

An important part of dealing with database is testing. Database testing not only makes sure your database logic works as expected, but will also speed your development workflow.

We use the mocha library to perform unit tests:

yarn add mocha

test/index.mjs

import assert from 'assert'
import { create, findAll, findOne, update, del } from "../src/services/todo.mjs"

let todo_id = false

describe('React Database', () => {
  describe('#createTodo()', () => {
    it('create a todo list item', async () => {
        const args = {
            name: "buy groceries"
        }

        const res = await create(args)
        const new_todo = await findOne(res.id)

        todo_id = res.id

            assert.equal(new_todo.name, args.name);
    })
  })

  describe('#updateTodo()', () => {
    it('Update a todo list item', async () => {
        const new_args = {
            name: "do homework"
        }

        const res = await update({
          id: todo_id, 
          ...new_args
        })

        const new_todo = await findOne(todo_id)

            assert.equal(new_todo.name, new_args.name);
    })
  })

  describe('#findAllTodo()', () => {
    it('get all todo list items', async () => {
        const res = await findAll()

            assert.equal(res.length, 1);
            assert.equal(res[0].name, "do homework");
    })
  })

  describe('#deleteTodo()', () => {
    it('delete a todo list item', async () => {
        await del(todo_id)
    })
  })
})

Lastly, run the unit tests with the mocha command:

yarn mocha

Subscribe To Rowy

It’s that simple! You now have a basic to-do app with a full-blown back-end you can build upon.

Rowy offers all the benefits of a feature-complete database management system, with a low learning curve and built-in best practices for database testing! It’s the perfect way to minimize your costs of switching to a secure database.

The best part? We’ve got plenty of templates to get you up and running in 2 minutes! So don’t hesitate and try Rowy for free.

Get started with Rowy in minutes

Continue reading

Browse all