Back to articles

From Zero to Production-Ready: Building a Full-Stack App with React, Tailwind CSS, and Supabase

AuthorMajd Muhtaseb05/02/202512 minutes
From Zero to Production-Ready: Building a Full-Stack App with React, Tailwind CSS, and Supabase

Introduction

This guide walks you through creating a basic full-stack application using React for the frontend, Tailwind CSS for styling, and Supabase as the backend. We'll cover setting up each technology and connecting them to build a simple to-do list app.

Setting Up the Project

First, initialize a React project using Create React App:

npx create-react-app todo-app
cd todo-app

Next, install Tailwind CSS:

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Configure tailwind.config.js:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Include Tailwind directives in src/index.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

Finally, install the Supabase client library:

npm install @supabase/supabase-js

Creating the Supabase Backend

  1. Create a new project on the Supabase website (supabase.com).
  2. Retrieve your Supabase URL and API key from the project settings.
  3. Create a table named todos with columns: id (UUID, primary key), task (text), and completed (boolean, default false).

Connecting React to Supabase

Create a supabaseClient.js file:

import { createClient } from '@supabase/supabase-js';

const supabaseUrl = process.env.REACT_APP_SUPABASE_URL;
const supabaseKey = process.env.REACT_APP_SUPABASE_ANON_KEY;
const supabase = createClient(supabaseUrl, supabaseKey);

export default supabase;

Remember to set your Supabase URL and API key in your .env file (create one if it doesn't exist):

REACT_APP_SUPABASE_URL=YOUR_SUPABASE_URL
REACT_APP_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY

Building the React Components

Create a TodoList.js component:

import React, { useState, useEffect } from 'react';
import supabase from './supabaseClient';

function TodoList() {
  const [todos, setTodos] = useState([]);
  const [newTask, setNewTask] = useState('');

  useEffect(() => {
    fetchTodos();
  }, []);

  const fetchTodos = async () => {
    const { data, error } = await supabase
      .from('todos')
      .select('*')
      .order('id', { ascending: false });

    if (error) {
      console.error('Error fetching todos:', error);
    } else {
      setTodos(data);
    }
  };

  const addTodo = async () => {
    if (newTask.trim() === '') return;

    const { data, error } = await supabase
      .from('todos')
      .insert([{ task: newTask, completed: false }])
      .select();

    if (error) {
      console.error('Error adding todo:', error);
    } else {
      setTodos([...todos, data[0]]);
      setNewTask('');
    }
  };

    const toggleComplete = async (id, completed) => {
    const { error } = await supabase
      .from('todos')
      .update({ completed: !completed })
      .eq('id', id)

    if(error){
      console.log("error updating todo", error);
    } else {
        fetchTodos();
    }


  }

  return (
    <div className="max-w-md mx-auto p-4">
      <h1 className="text-2xl font-bold mb-4">My To-Do List</h1>
      <div className="flex mb-4">
        <input
          type="text"
          className="shadow appearance-none border rounded w-full py-2 px-3 mr-4 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
          placeholder="Add New Task"
          value={newTask}
          onChange={(e) => setNewTask(e.target.value)}
        />
        <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" onClick={addTodo}>Add</button>
      </div>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id} className="flex items-center justify-between py-2 border-b border-gray-200">
            <span className={todo.completed ? 'line-through text-gray-500' : 'text-gray-800'}>{todo.task}</span>
              <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => toggleComplete(todo.id, todo.completed)}
                className="mr-2 leading-tight"
              />
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TodoList;

Update App.js to use the TodoList component:

import React from 'react';
import TodoList from './TodoList';

function App() {
  return (
    <div className="App">
      <TodoList />
    </div>
  );
}

export default App;

Running the Application

Start the React development server:

npm start

Visit http://localhost:3000 in your browser to see your to-do list application.

Conclusion

This guide provides a basic implementation of a full-stack application using React, Tailwind CSS, and Supabase. You can expand this further by adding features like user authentication, more complex data structures, and more sophisticated styling. Experiment and explore the capabilities of each technology to build even more powerful applications.