Get started with Rowy in minutes
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!
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.
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.
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:
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:
We now create a database from scratch for our to-do app.
First, create a new table todo
by clicking “Create a table”:
Then, add a column to your table, name
, which will describe what a to-do item is about as a short text:
Finally, we can use Rowy’s spreadsheet interface to add a new to-do item “get groceries”:
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.
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:
Before you head to VSCode, go to the side panel. Click Build → Firestore 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:
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!
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
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.
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:
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".
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:
We can also check out Rowy to make sure the creation went as expected:
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)
}
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:
And we can see the doc in Rowy is deleted as well: the table is back to how it was in the beginning.
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
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.