ReactJS y FaunaDB

Table of contents
- Introducción
- Proyecto ReactJS
- Instalación y configuración de TailwindCSS
- Componente Form
- Componente Todo
- Componente TodoApp
- FaunaDB - Creación de la base de datos
- FaunaDB - Creación del ‘key secret’
- Instalación y configuración de fauna package
- Explicación de las funciones principales:
- Probar la aplicación
- Conclusión

Introducción
Fauna DB es una base de datos distribuida y globalmente accesible que ofrece una solución serverless para el almacenamiento de datos. Combina características de bases de datos documentales y relacionales, diseñada específicamente para el desarrollo de aplicaciones modernas.
En este tutorial, aprenderás los conceptos básicos para integrar la base de datos en tu aplicación ReactJS y realizar operaciones CRUD.
Proyecto ReactJS
Vamos a utilizar ViteJS para crea el proyecto ReactJS, abre una consola y escribe el siguiente comando:
npm create vite@latest reactjs-fauna -- --template react
Este comando generará un directorio reactjs-fauna
, entra e instala las dependencias necesarias con el comando npm install
. Cuando finalice, puedes ejecutar el comando npm run dev
para correr el proyecto.
Instalación y configuración de TailwindCSS
Para añadirle estilos a nuestro proyecto, vamos a utilizar el framework TailwindCSS. Instalamos las dependencias necesarias con el siguiente comando:
npm install -D tailwindcss postcss autoprefixer
Para inicializar TailwindCSS corremos el comando:
npx tailwindcss init -p
Esto creará dos ficheros, tailwind.config.js
y postcss.config.js
. Abre el fichero tailwind.config.js
y actualiza como sigue:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
Ahora abre el fichero src/index.css
y añade las directivas de tailwind como sigue:
@tailwind base;
@tailwind components;
@tailwind utilities;
Ahora asegúrate que el archivo CSS esté importado en el archivo principal del proyecto (en este caso src/main.jsx
)
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import './index.css' // IMPORTAMOS LOS ESTILOS DE TAILWINDCSS
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
Finalmente vamos a instalar lucide-react que es un biblioteca de íconos para react, basada en el proyecto Lucide. Ofrece una colección de iconos de código abierto, simples y consistentes que pueden ser fácilmente integrados en aplicaciones React.
Lo instalas con el siguiente comando:
npm install lucide-react
Ya tenemos todo listo para comenzar a crear los componentes de nuestro proyecto.
Componente Form
El componente Form.jsx nos permitirá añadir tareas a la lista de tareas. Crea un fichero src/components/Form.jsx
y añade el siguiente código:
import { Plus } from 'lucide-react';
export default function Form({ newTodo, setNewTodo, addTodo }) {
return (
<form onSubmit={addTodo} className="flex gap-2 mb-6">
<input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="¿Qué necesitas hacer?"
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
type="submit"
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2"
>
<Plus size={20} />
Agregar
</button>
</form>
);
}
Vamos a explicar las funciones más destacadas:
Componente Form
:
Es una función que recibe tres propiedades: newTodo
, setNewTodo
, y addTodo
.
newTodo
: Representa el valor actual del texto de la nueva tarea.setNewTodo
: Función que se usa para actualizar el valor denewTodo
.addTodo
: Función que se ejecuta cuando el usuario envía el formulario, agregando una nueva tarea a la lista.
Formulario HTML con manejo de eventos:
<form onSubmit={addTodo} className="flex gap-2 mb-6">
- Se usa un elemento
<form>
que ejecuta la funciónaddTodo
cuando se envía (eventoonSubmit
). La funciónaddTodo
se encargará de agregar una nueva tarea a la lista de tareas.
Campo de entrada de texto:
<input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="¿Qué necesitas hacer?"
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
El valor de este campo (
value={newTodo}
) está ligado a la propiedadnewTodo
, permitiendo el control del estado.El evento
onChange
actualiza el estado (setNewTodo
) cada vez que el usuario escribe algo, capturando el valor ingresado.
Hemos finalizado el componente Form.jsx . Continuamos con el componente Todo.jsx.
Componente Todo
El componente Todo.jsx tendrá como función mostrar la información de cada una de las tareas de la lista. Crea un fichero src/components/Todo.jsx
y añade el siguiente código:
import { Trash2, Check } from 'lucide-react';
export default function Todo({ todo, toggleTodo, deleteTodo }) {
return (
<div className="flex items-center justify-between p-4 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors">
<div className="flex items-center gap-3">
<button
onClick={() => toggleTodo(todo.id)}
className={`w-6 h-6 rounded-full border-2 flex items-center justify-center
${todo.completed
? 'bg-green-500 border-green-500 text-white'
: 'border-gray-400'
}`}
>
{todo.completed && <Check size={16} />}
</button>
<span className={`text-gray-800 ${todo.completed ? 'line-through text-gray-500' : ''}`}>
{todo.text}
</span>
</div>
<button
onClick={() => deleteTodo(todo.id)}
className="text-red-500 hover:text-red-700 focus:outline-none"
>
<Trash2 size={20} />
</button>
</div>
);
}
A continuación se explica las funcionalidades más destacadas:
Propiedades recibidas:
todo
: Un objeto que representa una tarea específica.toggleTodo
: Función que se ejecuta cuando el usuario marca o desmarca la tarea como completada.deleteTodo
: Función que se ejecuta cuando el usuario elimina la tarea.
Botón para marcar como completada:
<button
onClick={() => toggleTodo(todo.id)}
className={`w-6 h-6 rounded-full border-2 flex items-center justify-center
${todo.completed
? 'bg-green-500 border-green-500 text-white'
: 'border-gray-400'
}`}
>
{todo.completed && <Check size={16} />}
</button>
Este botón permite al usuario marcar la tarea como completada o pendiente.
onClick={() => toggleTodo(
todo.id
)}
: Al hacer clic, se ejecuta la funcióntoggleTodo
pasando elid
de la tarea, lo que permite alternar el estado de completado.Estilización dinámica:
Si la tarea está completada (
todo.completed === true
):- Se aplica fondo y borde verde (
bg-green-500
,border-green-500
), y el íconoCheck
(una marca de verificación) es visible.
- Se aplica fondo y borde verde (
Si la tarea no está completada:
- Solo se muestra un borde gris (
border-gray-400
), sin ícono dentro del botón.
- Solo se muestra un borde gris (
Texto de la tarea:
<span className={`text-gray-800 ${todo.completed ? 'line-through text-gray-500' : ''}`}>
{todo.text}
</span>
Muestra el texto de la tarea (
todo.text
).Si la tarea está completada (
todo.completed
), se aplica el estilo de tachado (line-through
) y el color del texto se vuelve gris claro (text-gray-500
), indicando visualmente que la tarea ya ha sido realizada. Si no está completada, el texto se mantiene en gris oscuro (text-gray-800
).
Botón para eliminar la tarea:
<button
onClick={() => deleteTodo(todo.id)}
className="text-red-500 hover:text-red-700 focus:outline-none"
>
<Trash2 size={20} />
</button>
Este botón permite eliminar la tarea.
onClick={() => deleteTodo(
todo.id
)}
: Al hacer clic, se ejecuta la funcióndeleteTodo
con elid
de la tarea, eliminándola de la lista.
Con esto hemos finalizado con el componente Todo.jsx, ahora debemos actualizar el componente principal de la aplicación App.jsx.
Componente TodoApp
Este componente es la base de la aplicación de lista de tareas. Abre el fichero src/App.jsx
y actualízalo con el siguiente código:
import { useState } from 'react';
import Form from './components/Form';
import Todo from './components/Todo';
export default function TodoApp() {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState('');
const addTodo = (e) => {
e.preventDefault();
if (newTodo.trim()) {
setTodos([...todos, { id: Date.now(), text: newTodo, completed: false }]);
setNewTodo('');
}
};
const toggleTodo = (id) => {
setTodos(
todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<div className="min-h-screen bg-gray-100 py-8">
<div className="max-w-md mx-auto bg-white rounded-lg shadow-lg p-6">
<h1 className="text-2xl font-bold text-gray-800 mb-6">Lista de Tareas</h1>
<Form newTodo={newTodo} setNewTodo={setNewTodo} addTodo={addTodo} />
<div className="space-y-3">
{todos.map(todo => (
<Todo key={todo.id} todo={todo} toggleTodo={toggleTodo} deleteTodo={deleteTodo} />
))}
</div>
{todos.length === 0 && (
<div className="text-center text-gray-500 mt-6">
No hay tareas pendientes. ¡Añade una!
</div>
)}
</div>
</div>
);
}
A continuación explicamos sus funcionalidades más destacadas.
Importaciones de componentes y uso de useState
:
import { useState } from 'react';
import Form from './components/Form';
import Todo from './components/Todo';
Se importa el hook
useState
para manejar el estado dentro del componente.También se importan dos componentes hijos,
Form
yTodo
, que se encargan de manejar el formulario para agregar tareas y la representación de cada tarea respectivamente.
Estados del componente:
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState('');
todos
: Un arreglo que contiene todas las tareas creadas. Cada tarea es un objeto con las propiedadesid
,text
, ycompleted
.newTodo
: Almacena el texto de la nueva tarea que el usuario está escribiendo.
Función addTodo
para agregar nuevas tareas:
const addTodo = (e) => {
e.preventDefault();
if (newTodo.trim()) {
setTodos([...todos, { id: Date.now(), text: newTodo, completed: false }]);
setNewTodo('');
}
};
Esta función se ejecuta cuando se envía el formulario para agregar una nueva tarea.
e.preventDefault()
: Previene que la página se recargue al enviar el formulario.Validación: Se asegura de que el texto no esté vacío o compuesto solo de espacios en blanco con
newTodo.trim()
.Agregar la tarea: Si el texto es válido, se añade una nueva tarea al arreglo
todos
. La tarea tiene unid
único basado en la fecha actual (Date.now
()
), el texto (newTodo
), y un estadocompleted: false
(indica que la tarea no está completada).Después de agregar la tarea, se limpia el campo de texto con
setNewTodo('')
.
Función toggleTodo
para actualizar tareas completadas:
const toggleTodo = (id) => {
setTodos(
todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
La función toggleTodo
se utiliza para alternar el estado de completado de una tarea en una lista de tareas. Aquí te explico cómo funciona:
Parámetro
id
: La función recibe un parámetroid
, que representa el identificador único de la tarea que se desea actualizar.setTodos
: Esta es una función que se utiliza para actualizar el estado de las tareas.todos.map
: La funciónmap
se utiliza para crear un nuevo arreglo de tareas basado en el arreglo existentetodos
. Para cada tarea en el arreglo, se verifica si suid
coincide con elid
proporcionado a la funcióntoggleTodo
.Condición
todo.id
=== id
: Si elid
de la tarea actual coincide con elid
proporcionado, se crea un nuevo objeto de tarea con todas las propiedades de la tarea actual ({ ...todo }
), pero con el estadocompleted
invertido (!todo.completed
). Esto significa que si la tarea estaba marcada como completada, ahora estará pendiente, y viceversa.Retorno de
map
: Si elid
no coincide, la tarea se devuelve sin cambios.
Función deleteTodo
para eliminar tareas completadas:
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
La función deleteTodo
se utiliza para eliminar una tarea de la lista de tareas:
Parámetro
id
: La función recibe un parámetroid
, que representa el identificador único de la tarea que se desea eliminar.setTodos
: Esta es una función que se utiliza para actualizar el estado de las tareas.todos.filter
: La funciónfilter
se utiliza para crear un nuevo arreglo de tareas. Recorre cada tarea en el arreglotodos
y aplica una condición.Condición
todo.id
!== id
: La condición verifica si elid
de la tarea actual no coincide con elid
proporcionado. Si no coincide, la tarea se incluye en el nuevo arreglo, el nuevo arreglo contiene todas las tareas excepto aquella cuyoid
coincide con elid
proporcionado, efectivamente eliminando esa tarea de la lista.
Incorporación de los componentes Form
y Todo
:
Para incorporar el formulario lo hacemos con la siguiente línea:
<Form newTodo={newTodo} setNewTodo={setNewTodo} addTodo={addTodo} />
Este componente es responsable de renderizar el formulario que permite a los usuarios agregar nuevas tareas. Recibe tres propiedades para gestionar el estado y las acciones del formulario.
newTodo={newTodo}
: Representa el estado actual del texto de la nueva tarea que el usuario está escribiendo. Se utiliza para controlar el valor del campo de entrada de texto en el formulario.setNewTodo={setNewTodo}
: Se utiliza para actualizar el estado denewTodo
cada vez que el usuario escribe en el campo de entrada. Esto permite que el componente mantenga el control del valor del campo de texto.addTodo={addTodo}
: Se ejecuta cuando el usuario envía el formulario. Su propósito es agregar la nueva tarea a la lista de tareas, utilizando el texto almacenado ennewTodo
.
Para incorporar el componente Todo
lo hacemos con la siguiente línea:
<div className="space-y-3">
{todos.map(todo => (
<Todo key={todo.id} todo={todo} toggleTodo={toggleTodo} deleteTodo={deleteTodo} />
))}
</div>
Aquí se explica cada parte del código:
{
todos.map
(todo => ( ... ))}
: Itera sobre un arreglo llamadotodos
. Por cada elementotodo
en el arreglo, se ejecuta la función de flecha(todo => ( ... ))
, que devuelve un componenteTodo
.<Todo key={
todo.id
} todo={todo} toggleTodo={toggleTodo} deleteTodo={deleteTodo} />
: Este componenteTodo
se le pasan varias propiedades:key={
todo.id
}
: La propiedadkey
es un identificador único para cada elemento en la lista, lo cual ayuda a React a identificar qué elementos han cambiado, agregado o eliminado.todo={todo}
: Pasa el objetotodo
actual como una propiedad al componenteTodo
.toggleTodo={toggleTodo}
: Pasa la funcióntoggleTodo
como una propiedad, que se utiliza dentro del componenteTodo
para alternar el estado de completado de la tarea.deleteTodo={deleteTodo}
: Pasa la funcióndeleteTodo
como una propiedad, que se utiliza dentro del componenteTodo
para eliminar la tarea.
Con esto hemos finalizado la construcción del front-end de la aplicación Todo. Ahora vamos a realizar la conexión con la base de datos en FaunaDB.
FaunaDB - Creación de la base de datos
Ingresa a tu cuenta en FaunaDB, en el panel de control de tu cuenta, haz clic en el botón “Create Database” y ponle un nombre. Yo lo nombré “todoapp”.
Una vez creada la base de datos podemos crear la colección que contendrá las tareas. Para eso en el “shell” escribe la siguiente instrucción Collection.create({ name: "Todo" })
:
Continuamos con la creación del 'key secret’ que permitirá a nuestra aplicación realizar las operaciones CRUD.
FaunaDB - Creación del ‘key secret’
Para crear la clave secreta, hay que dirigirse a la pestaña ‘Keys’, hacer clic en el botón ‘Create Key’. Aparecerá una ventana, en ella elige un rol, ponle un nombre a la clave y haz clic en ‘Save’.
La clave aparecerá sólo una vez, una vez que cierres la ventana ya no podrás acceder a ella. Copia la clave, vuelve al editor y en la raíz del proyecto crea un fichero .env
para variables de entorno. Crea una nueva variable VITE_REACT_APP_FAUNA_SECRET
y pega la clave.
VITE_REACT_APP_FAUNA_SECRET=fnAFw2Cje_AAQn7vMLAlaUHnoXXlyfZROpb0zTO3
(Tanto la base de datos como la clave están eliminados al momento de la publicación del artículo).
Continuamos con la instalación y configuración de la biblioteca fauna
.
Instalación y configuración de fauna package
Este paquete permitirá conectar la aplicación con la base de datos y realizar las operaciones de lectura y escritura. Realiza la instalación con el siguiente comando:
npm install fauna
Ahora crea un fichero src/lib/fauna.js
que se encargará de realizar la conexión con la base de datos.
import { Client, fql } from "fauna"
// Asegúrate de crear un archivo .env para almacenar tu clave secreta
const client = new Client({
secret: import.meta.env.VITE_REACT_APP_FAUNA_SECRET,
})
export default client
Ahora en el mismo directorio crea un fichero src/lib/todoDb.js
que tendrá las funciones CRUD de la aplicación.
import client from './fauna'
import { fql } from 'fauna'
export const todoDb = {
// Obtener todas las tareas
getAllTodos: async () => {
try {
const result = await client.query(fql`
Todo.all()
`)
return result.data
} catch (error) {
console.error('Error al obtener las tareas:', error)
return []
}
},
// Crear una nueva tarea
createTodo: async (todoData) => {
try {
const result = await client.query(fql`
Todo.create({
text: ${todoData.text},
completed: false
})
`)
return result.data
} catch (error) {
console.error('Error al crear la tarea:', error)
throw error
}
},
// Actualizar una tarea
updateTodo: async (todoId, updates) => {
try {
const result = await client.query(fql`
Todo.byId(${todoId})!.update({
completed: ${updates.completed}
})
`)
return result.data
} catch (error) {
console.error('Error al actualizar la tarea:', error)
throw error
}
},
// Eliminar una tarea
deleteTodo: async (todoId) => {
try {
await client.query(fql`
Todo.byId(${todoId})!.delete()
`)
return true
} catch (error) {
console.error('Error al eliminar la tarea:', error)
throw error
}
}
}
Explicación de las funciones principales:
getAllTodos
(Obtener todas las tareas):Consulta la base de datos y obtiene todas las tareas almacenadas.
Si hay un error, lo captura y devuelve un array vacío.
createTodo
(Crear una nueva tarea):Recibe un objeto con los datos de una nueva tarea.
Inserta la tarea en la base de datos con el texto proporcionado y el estado
completed: false
por defecto.Si ocurre un error, lo captura y lo lanza.
updateTodo
(Actualizar una tarea):Recibe el ID de la tarea y los datos a actualizar.
Modifica el estado de la tarea (
completed
) en la base de datos.Captura y lanza errores si ocurren.
deleteTodo
(Eliminar una tarea):Recibe el ID de una tarea y la elimina de la base de datos.
Si la operación tiene éxito, devuelve
true
.Captura errores y los lanza.
A continuación debemos actualizar el componente src/App.jsx
como sigue:
import { useState, useEffect } from 'react';
import Form from './components/Form';
import Todo from './components/Todo';
import { todoDb } from './lib/todoDb';
export default function TodoApp() {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState('');
const [loading, setLoading] = useState(true);
// Cargar tareas al iniciar
useEffect(() => {
loadTodos();
}, []);
const loadTodos = async () => {
try {
const { data } = await todoDb.getAllTodos();
setTodos(data);
} catch (error) {
console.error('Error al cargar las tareas:', error);
} finally {
setLoading(false);
}
};
const addTodo = async (e) => {
e.preventDefault();
if (newTodo.trim()) {
try {
const newTodoData = await todoDb.createTodo({ text: newTodo });
setTodos([...todos, newTodoData]);
setNewTodo('');
} catch (error) {
console.error('Error al añadir la tarea:', error);
}
}
};
const toggleTodo = async (id) => {
try {
const todoToUpdate = todos.find(todo => todo.id === id);
const updatedTodo = await todoDb.updateTodo(id, {
completed: !todoToUpdate.completed
});
setTodos(todos.map(todo =>
todo.id === id ? updatedTodo : todo
));
} catch (error) {
console.error('Error al actualizar la tarea:', error);
}
};
const deleteTodo = async (id) => {
try {
await todoDb.deleteTodo(id);
setTodos(todos.filter(todo => todo.id !== id));
} catch (error) {
console.error('Error al eliminar la tarea:', error);
}
};
if (loading) {
return <div className="text-center mt-8">Cargando...</div>;
}
return (
<div className="min-h-screen bg-gray-100 py-8">
<div className="max-w-md mx-auto bg-white rounded-lg shadow-lg p-6">
<h1 className="text-2xl font-bold text-gray-800 mb-6">Lista de Tareas</h1>
<Form newTodo={newTodo} setNewTodo={setNewTodo} addTodo={addTodo} />
<div className="space-y-3">
{todos.map(todo => (
<Todo
key={todo.id}
todo={todo}
toggleTodo={toggleTodo}
deleteTodo={deleteTodo}
/>
))}
</div>
{todos.length === 0 && (
<div className="text-center text-gray-500 mt-6">
No hay tareas pendientes. ¡Añade una!
</div>
)}
</div>
</div>
);
}
De esta manera tenemos integrado las funciones CRUD del cliente de Fauna a nuestra aplicación.
Probar la aplicación
Para probar la aplicación corre el comando npm run dev
y prueba añadir, actualizar y eliminar una tarea, los datos deberían verse reflejados en la base de datos en fauna:
Conclusión
En este tutorial, hemos construido una aplicación TODO con ReactJS y la hemos integrado con FaunaDB para manejar el almacenamiento de tareas. Aprendimos a conectar nuestra aplicación con la base de datos, realizar operaciones CRUD (Crear, Leer, Actualizar y Eliminar tareas) y gestionar la comunicación con el backend de forma eficiente.
FaunaDB nos ofrece una base de datos serverless, escalable y fácil de manejar con consultas FQL, lo que hace que sea una excelente opción para aplicaciones modernas con React. Además, este enfoque nos permite trabajar con una arquitectura sin servidor, reduciendo la necesidad de infraestructura compleja.
Puedes encontrar el proyecto completo en mi repositorio de GitHub haciendo clic AQUÍ.
Espero que lo hayas encontrado entretenido, instructivo y claro. Si tienes alguna duda, puedes hacérmelo saber en los comentarios. Pronto estaré subiendo más tutoriales.
Nos vemos en la próxima. Saludos!👋😊
Subscribe to my newsletter
Read articles from Carlos Alberto Alegre directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Carlos Alberto Alegre
Carlos Alberto Alegre
Soy Carlos Alberto Alegre, analista programador y desarrollador web front-end y back-end. Me considero un autodidacta entusiasta y disfruto explorando y aprendiendo nuevas tecnologías para mantenerme al tanto de las últimas tendencias en diseño y desarrollo web.