Harini Janakiraman
BuildShip
Published in
11 min readNov 30, 2022

--

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">
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>)
}
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.mjsimport { 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.mjsimport { 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))
<span class="hljs-keyword">const</span> res = []

doc_refs.forEach(<span class="hljs-function"><span class="hljs-params">todo</span> =&gt;</span> {
res.push({
<span class="hljs-attr">id</span>: todo.id,
...todo.data()
})
})

<span class="hljs-keyword">return</span> 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.jsimport TodoList from './components/todo-list.js'function App() {
return (
<div className="App">
<TodoList/>
</div>
)
}
export default AppThe TodoList component encapsulates all the back-end logic, handles state changes, and displays the content:src/components/todo-list.jsimport { 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([])
<span class="hljs-keyword">const</span> fetchData = <span class="hljs-keyword">async</span> () =&gt; {
setLoading(<span class="hljs-literal">true</span>)

<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> findAll()

setTodos([...res])
setLoading(<span class="hljs-literal">false</span>)
}

useEffect(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
fetchData()
}, [])

<span class="hljs-keyword">return</span> (
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">section</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>TODO<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>

{ loading &amp;&amp;
<span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>loading...<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
}

<span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
{todos.length &gt; 0 &amp;&amp; todos.map(todo =&gt; (
<span class="hljs-tag">&lt;<span class="hljs-name">TodoListItem</span> <span class="hljs-attr">todo</span>=<span class="hljs-string">{todo}/</span>&gt;</span>
))}
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
)
}export default TodoListsrc/components/todo-list-item.jsfunction TodoListItem(props) {
const todo = props.todo
<span class="hljs-keyword">return</span> (
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{todo.id}</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>{todo.name}<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span></span>
)
}export default TodoListItemThat’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.mjsimport { 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()
}

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.mjsimport { 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.jsimport { useState } from "react";function AddTodoBar(props) {<span class="hljs-keyword">const</span> [ newTodo, setNewTodo ] = useState(<span class="hljs-string">""</span>)

<span class="hljs-keyword">const</span> handleChange = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setNewTodo(e.target.value)

<span class="hljs-keyword">const</span> submit = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
props.createTodo({<span class="hljs-attr">name</span>: newTodo})
setNewTodo(<span class="hljs-string">""</span>)
}

<span class="hljs-keyword">return</span> (
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{newTodo}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleChange}</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{submit}</span>&gt;</span>Add todo<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
)
}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.jsimport { 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([])
<span class="hljs-keyword">const</span> fetchData = <span class="hljs-keyword">async</span> () =&gt; {
setLoading(<span class="hljs-literal">true</span>)

<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> findAll()

setTodos([...res])
setLoading(<span class="hljs-literal">false</span>)
}

<span class="hljs-keyword">const</span> createTodo = <span class="hljs-keyword">async</span> args =&gt; {
<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> create(args)

setTodos([...todos, {
<span class="hljs-attr">id</span>: res.id,
...args
}])
}

useEffect(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
fetchData()
}, [])

<span class="hljs-keyword">return</span> (
&lt;section&gt;
&lt;header&gt;
&lt;h2&gt;TODO&lt;/h2&gt;
&lt;/header&gt;

&lt;AddTodoBar createTodo={createTodo}/&gt;

{ loading &amp;&amp;
&lt;p&gt;loading...&lt;/p&gt;
}

&lt;ul&gt;
{todos.length &gt; 0 &amp;&amp; todos.map(todo =&gt; (
&lt;TodoListItem todo={todo}/&gt;
))}
&lt;/ul&gt;
&lt;/section&gt;
)
}export default TodoListYou 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.mjsimport { 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.mjsimport { 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.jsfunction TodoListItem(props) {
const todo = props.todo
<span class="hljs-keyword">const</span> submit = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> props.deleteTodo(todo.id)

<span class="hljs-keyword">return</span> (
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{todo.id}</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>{todo.name}<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{submit}</span>&gt;</span>delete<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span></span>
)
}export default TodoListItemAnd the parent logic to keep everything in sync:src/components/todo-list.jsimport { 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([])
<span class="hljs-keyword">const</span> fetchData = <span class="hljs-keyword">async</span> () =&gt; {
setLoading(<span class="hljs-literal">true</span>)

<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> findAll()

setTodos([...res])
setLoading(<span class="hljs-literal">false</span>)
}

<span class="hljs-keyword">const</span> createTodo = <span class="hljs-keyword">async</span> args =&gt; {
<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> create(args)

setTodos([...todos, {
<span class="hljs-attr">id</span>: res.id,
...args
}])
}

<span class="hljs-keyword">const</span> deleteTodo = <span class="hljs-keyword">async</span> id =&gt; {
<span class="hljs-keyword">await</span> del(id)

setTodos([...todos.filter(<span class="hljs-function"><span class="hljs-params">todo</span> =&gt;</span> todo.id != id)])
}

useEffect(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
fetchData()
}, [])

<span class="hljs-keyword">return</span> (
&lt;section&gt;
&lt;header&gt;
&lt;h2&gt;TODO&lt;/h2&gt;
&lt;/header&gt;

&lt;AddTodoBar createTodo={createTodo}/&gt;

{ loading &amp;&amp;
&lt;p&gt;loading...&lt;/p&gt;
}

&lt;ul&gt;
{todos.length &gt; 0 &amp;&amp; todos.map(todo =&gt; (
&lt;TodoListItem todo={todo} deleteTodo={deleteTodo}/&gt;
))}
&lt;/ul&gt;
&lt;/section&gt;
)
}export default TodoListYou 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 mochatest/index.mjsimport assert from 'assert'
import { create, findAll, findOne, update, del } from "../src/services/todo.mjs"
let todo_id = falsedescribe('React Database', () => {
describe('#createTodo()', () => {
it('create a todo list item', async () => {
const args = {
name: "buy groceries"
}
<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> create(args)
<span class="hljs-keyword">const</span> new_todo = <span class="hljs-keyword">await</span> findOne(res.id)

todo_id = res.id

<span class="hljs-keyword">assert</span>.equal(new_todo.name, args.name);
})
})describe('#updateTodo()', () => {
it('Update a todo list item', async () => {
const new_args = {
name: "do homework"
}
<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> update({
id: todo_id,
...new_args
})

<span class="hljs-keyword">const</span> new_todo = <span class="hljs-keyword">await</span> findOne(todo_id)

<span class="hljs-keyword">assert</span>.equal(new_todo.name, new_args.name);
})
})describe('#findAllTodo()', () => {
it('get all todo list items', async () => {
const res = await findAll()
<span class="hljs-keyword">assert</span>.equal(res.length, <span class="hljs-number">1</span>);
<span class="hljs-keyword">assert</span>.equal(res[<span class="hljs-number">0</span>].name, <span class="hljs-string">"do homework"</span>);
})
})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.

--

--

Harini Janakiraman
BuildShip

Co-founder and CEO of Rowy.io — Open-source low-code platform to empower developers build fast on Google Cloud Platform