Build an app with React and Supabase

Nirmal Kumar
StackAnatomy
Published in
11 min readJul 13, 2023
Cover Image

This tutorial will explore building a basic CRUD (Address Book) application using React and Supabase, setting up a Supabase database, connecting a React application to it, and implementing CRUD operations in our application. By the end of this tutorial, you will have a solid understanding of how to use Supabase with React and how to implement basic CRUD operations in your web application.

React has been a popular choice for building web applications in the recent past. And technologies like Supabase (an open source alternative to Firebase) have eased the web development process by providing many features like authentication, databases, etc., that a typical backend would provide for the frontend applications. This eliminates the need for having a dedicated backend written with Node.js or any other backend framework; you get a full-stack app without all the work! And, almost all web applications operate on the basic principle of CRUD. CRUD stands for Create, Read, Update, and Delete. It refers to the four basic operations that can be performed on data stored in a database. These operations are fundamental to most database-driven applications and are essential for managing data.

You will need:

  • Node JS (LTS Version)
  • npm 7 or greater
  • VS Code or any Code editor of your choice.
  • Basic understanding of Supabase auth as we will build this project on top of an auth layer. Check the article here and come back.
  • Basic knowledge of Bootstrap
  • Starter code (Import from GitHub by running the command below)
git clone --branch start --single-branch git@github.com:nirmal1064/react-supabase-crud.git

Supabase Database Overview and Database Setup

For this CRUD project, let’s create a new project in Supabase and name it Address Book or whatever name you like. Supabase provides a Postgres relational database that comes with every project and is included in their free plan as well.

Supabase provides features like a Table view, SQL editor, Relationships manager, etc. PostgreSQL’s Row Level Security (RLS) is the most important feature, which helps developers define fine-grained access controls for each database table. RLS policies enable developers to control which rows in a table users can access, update, or delete based on their role or other criteria.

Let’s create a database for our project. Open the Supabase project, and click’ Table Editor’ on the left navigation menu. Then click on the Create a new Table button.

You will get a screen like the one below,

Enter the Name and ensure Enable Row Level Security (RLS) is checked.

Under the columns, there will be id and created_at columns by default. Let's add four more columns, as shown in the image below,

Once all is done, Save it.

Connecting React App to the Supabase Database

We have all the necessary code to connect to Supabase in the starter code. So, connecting to the database is pretty simple. The syntax is as follows.

The data contains the valid response data, and error contains the error response, if any.

We can easily chain methods to the above code and perform various operations. For example,

We will see each of these operations in detail in the upcoming sections.

At this point, you are presumed to have the starter code and understand Supabase user authentication. Also, you have created a user for this app by following the steps mentioned in the previous article. If not done, kindly revisit the article here and come back.

Implementation of CRUD operations

In the upcoming sections, we will implement each of the CRUD operations with the help of RLS.

Why RLS is needed here? RLS is needed because in our database we store users’ contact information. We want to restrict access to these data so that only the users who created those contacts can see it.

So, Now let’s start the coding part.

Let’s create a separate context to maintain the contacts state. Create ContactProvider.jsx inside the context folder.

To make the article concise, we will explain only the Supabase functionalities in detail, whereas the UI and styling part won’t be explained in detail. It’s up to the reader to style the components as they like.

Implementing Create Operation

The Create operation in React is an equivalent of a database insert operation. First, we must set up RLS for the insert operation, using the Supabase API.

As the Policy name field indicates, we will only allow insert access for authenticated users. You can describe the policy in whatever way you find suitable. This field doesn’t need to be the same as that.

In the WITH CHECK expression field, we set the condition as auth.uid() = user_id, which means the user_id column in the table should match the authenticated user's id, thereby restricting access only to the authenticated users.

Let’s add the insert functionality to our app. Open ContactProvider.jsx and add the following function inside it.

We will use Bootstrap’s’ Modal’ component to implement the user interface (UI). First, create the ContactList.jsx inside the pages directory. Let's add a route to this page as well.

You can copy the code for the UI from the GitHub commit here.

Until this point, the page would look like this,

We need to add a functionality where we display the modal to add a new contact when the Add Contact button is clicked. So let's create the modal first. Create ContactModal.jsx inside the components directory and add the code as shown in the GitHub commit here.

The component takes in four props.

  • show - to show or hide the modal.
  • handleClose - a function to close the modal when the user clicks on close or outside of the component.
  • type - To indicate the type of operation, i.e., Add or Edit. We will use the same component for both adding and editing the contact
  • contact - The active contact object to be displayed when editing; otherwise an empty object in case of Add operation.

We use react-bootstrap's <Modal/> component to render the Modal. We are using the useState and useRef hooks from React to manage the state of the form inputs and the useContacts hook from the ContactProvider context to manage the contacts data. We use the react-bootstrap's <Modal/> and <Card> components to style and display the form. The handleSubmit function is called when the user submits the form. It handles the inputs' validation and the contact data's saving.

Also, let’s create a react-bootstrap's Toast component to display our application's success and error messages.

Create ToastMessage.jsx inside the components directory and add the code as shown in the commit here.

This component takes in four props.

  • show to indicate whether to show/hide the toast message.
  • type, the type of the toast, success or error.
  • message, the message to display.
  • handleClose, the function to execute when the toast is closed.

Now, Let’s add both of the above modals to the ContactList page and make the functionality to display the ContactModal when the Add Contact button is clicked. The code for this change can be found in the GitHub commit here.

We have declared two ToastMessage components, one with the type Success and the other with the type Error. For the ContactModal, we have a few state variables showContactModal to indicate whether to show or hide the modal, type indicates Add or Edit type, and activeContact contains the current contact to edit or an empty contact in case of Add.

When the Add Contact button is clicked, the handleAdd function is triggered, where we set the type to Add and showContactModal to true. We also have a closeContactModal function, triggered when the modal is closed. In this function, we set the activeContact to an empty object, showContactModal to false, and type to an empty string. We are passing these state variables and functions as props to the ContactModal.jsx.

When the Add Contact is clicked, the modal will open. It will be like the below,

Fill out the form and click submit. A contact will be added. And the toast will be displayed as shown in the below image.

As we haven’t yet implemented the Read operation, you can verify the created contact by visiting the supabase project -> Table editor -> <Table Name>.

Session Replay for Developers

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay suite for developers. It can be self-hosted in minutes, giving you complete control over your customer data.

Happy debugging! Try using OpenReplay today.

Implementing Read Operation

The Read operation in React is an equivalent of a database select operation. First, we must set up RLS for the select operation in Supabase.

The policy is similar to the insert policy but for the Select operation. Save the policy.

Now open the ContactProvider.jsx and add the fetchAll operation to select the contacts.

Also, we are calling the fetchAll function in the useEffect hook so that contacts are fetched from the DB whenever the component is mounted to the DOM or the page is refreshed.

Also, we have updated the addContact function to add the select operation at the end so that whenever a new contact is created, the same will be returned when the operation is successful. A point to note here is that we have used data[0] to get the contact because Supabase returns an array of data for the select operation. Since we are inserting only one contact, only one row will be returned. So we are getting the contact from the index 0.

Now, let’s implement the UI for displaying the contacts.

We will use bootstrap icons to indicate the edit and delete operations. To add bootstrap icons to our project, Open index.html and add the below line inside the <head> tag.

<link rel= "stylesheet" href= "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.3/font/bootstrap-icons.css" />

As usual, the code for this change can be found on GitHub here.

At this point, the UI would look like this,

Implementing Update Operation

The Update operation in React is an equivalent of a database update operation. First, we must set up RLS for the update operation in Supabase.

Let’s add update functionality to our app. Open ContactProvider.jsx and add the following function.

We have created an async editContact function which takes in two parameters, contact, the contact to be updated, and id, the id of the contact. In the function body, we call the update method by passing the contact object, and we add the condition where the id equals the id of the contact using the eq filter. The eq method takes in two parameters, the column name and the value to be checked against the column. There are other filters, like neq (not equals), gt (greater than), etc. You can check the official Supabase documentation for more such filters here.

Then we get the updated contact back by using the select() method at the end. So, when the update operation is successful, the data variable will contain the list of rows updated. If the operation fails, the error variable will contain the error information. Based on that, we are updating our contacts state and errorMsg state. For updating the contacts in an immutable way, we are creating an updated array by transforming the contacts array using the map operation and checking if the contact's id matches with the id passed to the editContact. If it matches, we are returning the updated contact, otherwise, we are returning the same contact. Then we update the contacts state using this updatedContacts variable.

For the UI changes related to the edit functionality, refer to the GitHub commit here and make the necessary changes.

We can edit the data and click on Save Changes. The update query will be run, and the data will be updated. We can close the modal and see the data changes.

Implementing Delete Operation

The Delete operation in React is an equivalent of a database delete operation. First, we need to set up RLS for the delete operation in Supabase.

Let’s add delete functionality to our app. Open ContactProvider.jsx and add the following function.

We have created an async deleteContact function which takes in one parameter, id, the id of the contact. In the function body, we call the delete with the condition where the id equals the id of the contact using the eq filter. We are getting an error object from Supabase. We will set the errorMessage state if there is an error. If there is no error, the delete operation is successful, so we will update the contacts state by using the filter method to create a new array that only includes the contacts that do not have the given ID. The updated array is then set as the new state for contacts. Using the functional form of setState with prevContacts ensures that the updated state is based on the previous state and prevents issues that could arise from asynchronous state updates.

Moving to the UI part, We will create another modal asking the user for confirmation before deleting the contact. It will be useful to prevent accidental deletions.
Then we will include the modal in the ContactList page.

As usual, the code changes can be found in the GitHub commit here.

Like the edit icon, we have added the icon class to the delete icon and updated its onClick function so that whenever the delete icon is clicked, the ConfirmModal will be shown.

That’s it. When you click the delete icon for any contact, the modal will be displayed.

Once we click Yes, the contact will be deleted, and the modal will close automatically.

Conclusion

In this article, we explored how to build a basic CRUD application using React and Supabase. We walked through setting up a Supabase database, connecting a React application to it, and implementing CRUD operations with RLS. We also discussed how RLS can help enforce data access policies and ensure that users only have access to the data they are authorized to view, update, or delete.

The complete source code of this article can be found on my GitHub. If you find the code useful, star the repo.

As a next step, you can implement more complex CRUD operations, such as pagination, filtering, and sorting.
Also, you can explore Supabase’s documentation to learn about more advanced features and integrations.

--

--