introduction
Serverless databases are all the rage right now because they allow you to build a fully functional app without building a server or writing any server code. A serverless database is a cloud computing system in which resources are distributed and managed dynamically.
Some examples of this type of database are Google's Firebase, DynamoDB, Supabase, etc.
In this tutorial we will look atSuperbasisand integration into a React CRUD app.
Prerequisite for this tutorial:
- Github-Konto
- Experience with React.js
- Context-API
- Basic understanding of asynchronous functions
What is Supabase?
React js developerYou're already familiar with what a CRUD app is, but if the concept of Supabase seems a little vague to you, here's an explanation.
Superbasisis an open-source, serverless database based on PostgreSQL. PostgreSQL is an object-relational database system with over 30 years of active development that has earned a solid reputation for reliability and robust performance. It was developed as an alternative to Google's Firebase and can serve as an excellent Firebase CRUD React platform.
Supabase comes with many out-of-the-box services/functionalities designed to make your life easier. These include:
- authentication: When a user logs in, Supabase creates a new user in the auth.users table. A JWT (JSON Web Token) is returned containing the user's UID (Unique Identification); subsequent requests to the database also send the JWT. Postgres examines the JWT to determine which user is making the request.
- guidelines: These are PostgreSQL rules engines. They are used to set constraints and rules on a table or row, allowing you to write SQL rules to suit your needs. These rules can be matched against users' UID to enforce read/write access to specific data in the table.
- RLS (Row Level Security): RLS is a system that allows you to restrict access to rows in your tables. Supabase makes it easy for you to enable PostgreSQL's RLS, literally with the click of a button.
- real-time database: Supabase adds real-time capabilities to Postgre so you can listen to changes in the database.
You get all these features/services ready to use when you create a project on Supabase.
Setting up a project
Create an account on Supabase
The first thing we need to do here is toCreate an account on Supabase. You need a Github account to continue from this point. If you don't already have one, you can create an account using the link in the Requirements section.

Once you've logged in, you should see the "New Project" button; Click on it to continue. Then select an organization or create a new organization. Once you're done with the process, you should see a form with the database name and password.

Fill this out and enter a secure password. Click the "Create New Project" button and wait for the database creation to finish. This process can take several minutes. After the process is complete, click on the table editor in the left pane to create a new table.

Then click the "Create New Table" button, fill in the table details and click Save to create your table. When a table finishes saving, you can preview it on the table editor page.
Now you can add columns to your table. By default, you create a table with an ID column.
To create an additional column for your table, click the "+" button:

In the next step you will see a form with the name of the column, a description, a dropdown menu with all the variable types to choose from and a default value. When you have filled out the form fields, click the "Save" button to continue.

For the project, we create three additional columns:
- Element, with variable type varchar and Allow Nullable disabled.
- done, with variable type boolean and default value false.
- userId, with variable type UUID.
After creating the columns, the next step is to set up our React app with Supabase.
Build the React app
We can use create-react-app to initialize an app called react_supabase. In your terminal, run the following command to build the React app and install the required dependency.
npx create-react-appreakte_supabase --use-yarncdreakte_supabase
Yarn add @supabase/supabase-js bootstrap react-router-dom
After installing all dependencies, we clean up the file structure and create all the necessary files for the project. The file structure for the React app should now look similar to the example below:
📦react_supabase
┣ 📂node_modules
┣ 📂 public
┣ 📂src
┃ ┣ 📂Components
┃ ┃ ┣ 📜ActiveList.js
┃ ┃ ┣ 📜DoneList.js
┃ ┃ ┣ 📜Navbar.js
┃ ┃ ┣ 📜TodoItem.js
┃ ┃ ┗ 📜UpdateItem.js
┃ ┣ 📂pages
┃ ┃ ┣ 📜Home.js
┃ ┃ ┗ 📜Login.js
┃ ┣ 📜App.js
┃ ┣ 📜App.test.js
┃ ┣ 📜ItemsContext.js
┃ ┣ 📜index.css
┃ ┣ 📜index.js
┃ ┣ 📜reportWebVitals.js
┃ ┣ 📜setupTests.js
┃ ┗ 📜supabaseClient.js
┣ 📜.env.lokal
┣ 📜.gitignore
┣ 📜README.md
┣ 📜Paket.json
┗ 📜yarn.cap
Now we can start writing code.
In the App.js file, we instantiate the routing for the login page and home page as follows:
Import "bootstrap/dist/css/bootstrap.css"; import { useEffect } from "react"; import { Route, Switch, useHistory, withRouter } from react-router-dom; import navbar from "./components/Navbar";import home from "./pages/Home";import login from "./pages/Login";import { supabase } from "./supabaseClient";function App() { const history = useHistory(); useEffect(() => { supabase.auth.onAuthStateChange((_event, session) => { if (session === null) { history.replace("/login"); } else { history .replace("/" ); } });// eslint-disable-next-linereakt-hooks/exhaustive-deps}, []); const NavbarWithRouter = withRouter((props) => <Navbar {...props} />); return ( <> <NavbarWithRouter Exact /> <Switch> <Route Exact Path="/" Component={Home} /> <Route Exact Path="/Login" Component={Login} /> </Switch> </> );} Standard-App exportieren;
If you notice the useEffect hook, we have a supabase.auth.onAuthStateChange; this listens for changes in authentication/session status. The code block in useEffect checks if there is no active session; If so, the app will redirect you to the login route. otherwise it turns to the home route. The code block ensures that only authenticated users can access the home route.
Also worth mentioning is the withRouter, a higher-order function that lets you pass router props to a standalone component, the Navbar component.
After that, we need to wrap the app component imported into the index.js file with BrowserRouter.
...<React.StrictMode> <BrowserRouter> <App /> </BrowserRouter> </React.StrictMode>...
Set up Supabase
To set up Supabase, we need our API URL and Supabase Anon key; these are available on your Supabase dashboard. Click API on the left panel of your dashboard; From here, navigate to the Authentication page. You will find your database url and anonymous key.

Copy the database url and your anonymous key to the .env.local file.
REACT_APP_SUPABASE_URL='Your Supabase URL'REACT_APP_SUPABASE_ANON_KEY='Your anonymous key'
Inside the supabaseClient file import, create client from @supabase/supabase-js, initialize a variable supabase and call the createClient function with the supabase url and an anon key as parameters.
import { createClient } from „@supabase/supabase-js“; export const supabase = createClient(process.env.REACT_APP_SUPABASE_URL, process.env.REACT_APP_SUPABASE_ANON_KEY)
Set up context states
Now that we have the Supabase client setup, the procedure below is to set up our state management in the ItemsContext file.
import react, { createContext, useState } from react; import { supabase } from "./supabaseClient";// Context is initializedexport const ItemsContext = createContext(); export function ItemsContextProvider({ children }) { const [activeItems, setActiveItems] = useState([]); const [inactiveItems, setInactiveItems] = useState([]); const [laden, setLoading] = useState (false); const [hinzufügen, setAdding] = useState(false); ... return ( <ItemsContext.Provider value={{ activeItems, inactiveItems, loading, adding, ... }} > {children} </ItemsContext.Provider> );}
Now wrap your entire app with the ItemsContextProvider to complete the setup.
... <React.StrictMode> <ItemsContextProvider> <BrowserRouter> <App /> </BrowserRouter> </ItemsContextProvider> </React.StrictMode>...
Authentication handling
It's time to work on the core features of our app.
We'll start with authentication and the login page. For authentication, we use Supabase's Magic-Link authentication method.
Supabase also supports a number of other external authentication methods including:
- Apple
- Azurblau
- Bit Bucket
- discord
- GitHub
- GitLab
- Pull out
To start authenticating, copy and paste the following block of code into your context component:
// Authentication function to login new/old user with magic supabase linkconst logInAccount = async(email) => { setLoading(true); To attempt {// supabase method to send the magic link to the specified email addressconst { error } = await supabase.auth.signIn ({ email }); if (error) throw error;//Check if there was an error getting the data and move execution to the catch blockalert("Check your email for your magic login link!"); } catch (error) { alert(error.error_description || error.message); } finally { setLoading(false); } };
If you look at the code block above, the function accepts one parameter: the user's email address. Supabase signIn receives this email address as a parameter. If the request is successful, an email will be sent to the email address with an authentication link and you will be "magically" signed in when you click the link.
It might be worth mentioning that you can change the authentication redirect URL on your Supabase dashboard; the default URL is http://localhost:3000.
Let's create the login page and use the authentication feature. Copy and paste the following block of code into your Login.js file.
import React, { useContext, useEffect, useState } from "react"; import { useHistory } from react-router-dom; import { ItemsContext } from "../ItemsContext"; import { supabase } from "../supabaseClient";export default function Login() { const history = useHistory(); const [Email, setEMail] = useState(""); const { load, logInAccount } = useContext (ItemsContext); const handleSubmit = (e) => {e.preventDefault(); Login account (email); }; useEffect(() => { if (supabase.auth.user() !== null) { history.replace("/"); }// eslint-disable-next-linereakt-hooks/exhaustive-deps}, []); return ( <div className="container"> <div className="row justify-content-center mt-5"> <div className=" col-12 col-lg-6"> <div className="card"> < div className="card-header"> <h5 className="text-center text-uppercase">Sign in</h5> </div> <div className="card-body"> <form onSubmit={handleSubmit}> < div className="mb-4"> <label htmlFor="exampleInputEmail1" className="form-label"> Email address </label> <input type="email" value={email} onChange={(e ) = > setEmail(e.target.value)} name="email" required className="form-control form-control-lg w-100 mt-1" /> <div className="form-text"> Enter Your email to Get your magic link </div> </div> <button disabled={loading} type="submit" className="btn btn-primary btn-lg w-100 "> {loading ? "Loading..." : " Send"} </button> </form> </div> </div> </div> </div> </div> );}
add article
To add an element, we'll use another function with the Supabase insert method. You pass an object with keys that match the names of the columns created in your table.
...const user = supabase.auth.user();await supabase.from("todo").insert({ item, userId: user?.id }); //insert an object with the key-value pair, where the key is the column in the table...
First, you call the from method, passing in the name of the table to insert the data. You can use supabase.auth.user() to get the details of the authenticated user. The code above inserts a new row into the task table with the value of the item and the userid with the authenticated UUID.
Reading tasks from the database
After creating a row in the table, we can now read data from the table. To do this, we'll use another Supabase method, select with additional query parameters to filter data.
...// Get the currently logged in userconst user = supabase.auth.user(); const { error, data } = waiting for supabase .from("todo")//the table you want to work with.select("Element, erledigt, ID")//Columns to select from the database.eq("UserID", User?.ID) .// compare function to return only data with user id matching the currently logged in user.eq("done", wrong)//check if the Done column is equal to false.order("id", { ascending: false });// Sort the data so that the last element is on top;if (error) throw error;//Check if there was an error getting the data and move execution to the catch blockif (data) setActiveItems(data);...
The select() method accepts a string as a parameter with the columns to return. After that we have the eq() method that accepts two parameters; The first parameter is the name of the column to compare and the second parameter is the value to match. The code block above returns the item, done, and id columns from rows where the userId column matches the authenticated UUID.
Update a to-do entry
We will use the following function to update a row; We create an asynchronous function that receives two parameters; the new value to update and the row id to update. We use Supabase's Update method, which accepts an object with the column name as the key and the new value.
// Update column(s) in the databaseconst updateItem = async ({ item, id }) => { setLoading (true); try {const user = supabase.auth.user(); const { error } = warte auf supabase .from("todo") .update({ item }) .eq("userId", user?.id) .eq("id", id);//matching id of row to updateif (error) throw error; wait for getActiveItems(); } catch (error) { alert(error.error_description || error.message); } finally { setLoading(false); } };
Toggle an item to Done.
Toggling the item as done is very similar to the update function, except we already know the new value and the field to update. All we need to pass is the id of the row. We will use a function similar to the refresh button to toggle the done column in the table to either true or false.
// Change value of done to true.... const { error } = await supabase.from("todo") .update({ done: true }) .eq("userId", user?.id) .eq("id", id);// Match ID to toggle...
Delete an item
To delete an item from the database, copy and paste the following code block:
// Delete row from databaseconst deleteItem = async (id) => { try { const user = supabase.auth.user(); const { error } = warte auf supabase .from("todo") .delete()// Delete a line.eq("ID", ID)//the ID of the row to delete.eq("UserID", User?.ID) .//Check if the item to delete is owned by the userif (error) throw error; wait for getInactiveItems();// Get the list of new completed itemswait for getActiveItems();// Get the new active items list} catch (error) { alert(error.error_description || error.message); } };
The deleteItem function takes one parameter: the row ID to delete from; The eq query compares the ID to find the matching row. Once the request completes with a success status, Supabase deletes the matching row from our table.
Conclusion
Congratulations on making it this far! At this moment you have all the features to complete a React CRUD app operation in your React app. Inside you will find the complete project codeGitHub-Repositoryor test the live projecthere. For more information about Supabase, see the official documentationhere.