Embarking on a new web project often means tackling user authentication – a critical yet sometimes complex component. Fortunately, modern Backend-as-a-Service (BaaS) platforms like Supabase simplify this considerably. Supabase offers a robust suite of tools including a PostgreSQL database, real-time subscriptions, file storage, and, crucially for us, a comprehensive authentication system, all ready to go out of the box.
In this inaugural post of our series exploring Supabase integration with React, we’ll strip away the complexities and build a clean, functional authentication flow. We’ll guide you through setting up a React + TypeScript project, seamlessly connecting it to Supabase, and implementing signup, login, and logout functionalities, all beautifully styled with TailwindCSS.
Getting Started with Supabase
Your journey begins on the Supabase platform.
- Navigate to supabase.com and create an account if you haven’t already.
- Once logged in, create a new project. After the project is provisioned, make sure to save your unique Project URL and anon API key from your project dashboard – you’ll need these soon.
- For our authentication purposes, head over to the ‘Authentication’ section in your project settings and enable Email/Password login.
Setting Up Your React + TypeScript Project with Vite
We’ll kick off our frontend with Vite, a blazing-fast build tool, paired with React and TypeScript for a robust development experience. Open your terminal and run the following commands:
npx create-vite@latest supabase-auth-demo --template react-ts
cd supabase-auth-demo
npm install
npm install @supabase/supabase-js tailwindcss postcss autoprefixer
npx tailwindcss init -p
Next, configure TailwindCSS. Open tailwind.config.js
and update the content
array:
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
Finally, import Tailwind’s base styles into your src/index.css
file:
@tailwind base;
@tailwind components;
@tailwind utilities;
Initializing Supabase in Your React App
Now, let’s connect your React application to your Supabase project. Create a new file named src/supabaseClient.ts
:
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL as string;
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY as string;
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
To keep your API keys secure and manageable, add your Supabase credentials to a .env
file in the root of your project. Remember to replace the placeholders with your actual project details:
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
Crafting the Authentication UI
With Supabase initialized, we can now build the user interface for our authentication flow. This Auth
component will handle user input for email and password, and trigger the appropriate Supabase authentication methods.
import { useState, useEffect } from "react";
import { supabase } from "./supabaseClient";
export default function Auth() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [user, setUser] = useState(null); // State to hold authenticated user
const [error, setError] = useState(""); // State for displaying errors
// Check for existing session on component mount and listen for auth state changes
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user || null);
});
const { data: authListener } = supabase.auth.onAuthStateChange(
(event, session) => {
setUser(session?.user || null);
}
);
return () => {
authListener?.subscription.unsubscribe();
};
}, []); // Empty dependency array means this runs once on mount
const handleSignup = async () => {
setError(""); // Clear previous errors
const { data, error } = await supabase.auth.signUp({ email, password });
if (error) setError(error.message);
else if (data.user) alert("Check your email for the confirmation link!"); // Inform user about email verification
};
const handleLogin = async () => {
setError(""); // Clear previous errors
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) setError(error.message);
else setUser(data.user);
};
const handleLogout = async () => {
setError(""); // Clear previous errors
const { error } = await supabase.auth.signOut();
if (error) setError(error.message);
else setUser(null); // Clear user state on logout
};
return (
Supabase Auth
{error && {error}
}
{!user ? (
<>
setEmail(e.target.value)}
className="w-full mb-3 px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
setPassword(e.target.value)}
className="w-full mb-3 px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
>
) : (
Logged in as {user.email}
)}
);
}
Persisting the User Session
To ensure your application remembers logged-in users, you need to manage the user session. The useEffect
hook, combined with Supabase’s getSession
and onAuthStateChange
methods, allows you to fetch the current session when the component mounts and react to any changes in the authentication state.
The updated Auth
component above already incorporates this. The useEffect
hook runs once on component mount to check for an existing session. It also subscribes to onAuthStateChange
events, which automatically updates the user
state whenever a user signs in, signs out, or their session refreshes. This ensures your UI always reflects the current authentication status.
Wrapping Up
Congratulations! You’ve successfully built a fully functional authentication system in React using TypeScript and Supabase, complete with signup, login, and logout capabilities, all beautifully styled with TailwindCSS. This provides a robust foundation for user management in any web application.
Stay tuned for the next installment in this series, where we’ll delve into Supabase Edge Functions and learn how to create custom backend APIs to extend your application’s functionality. Don’t forget to share this guide with anyone who could benefit from learning about Supabase!