DEV Community

Cover image for Supabase + React Router: Signup and Auth Setup (Part 2)
Kevin Julián Martínez Escobar
Kevin Julián Martínez Escobar

Posted on • Edited on

Supabase + React Router: Signup and Auth Setup (Part 2)

Let’s continue our series by adding some React code to handle user registration with Supabase.

  • Creating a signup form using React Router's
  • Registering users in Supabase
  • Auto-creating profile rows with Supabase triggers
  • Keeping your TypeScript types in sync
  • Using .env for Supabase keys

If you’re following along from Part 1, you can continue as is. But if you want to reset your repo or make sure you're on the correct branch:

# Repo git@github.com:kevinccbsg/https-github.com-kevinccbsg-react git reset --hard git clean -d -f git checkout 02-react-router-integration 
Enter fullscreen mode Exit fullscreen mode

Signup Form

Let’s create src/pages/auth/Signup.tsx. We’ll use React Router’s <Form> to handle submission.

Want to learn more about React Router Forms? Check out this guide.

import { Form, Link } from "react-router"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; const Signup = () => { return ( <Card className="max-w-md mx-auto mt-10 p-6"> <CardHeader> <h1 className="text-2xl font-bold">Signup</h1> </CardHeader> <CardContent> <Form method="POST"> <div className="mb-4"> <Label className="mb-2" htmlFor="email">Email</Label> <Input type="email" id="email" name="email" required /> </div> <div className="mb-4"> <Label className="mb-2" htmlFor="password">Password</Label> <Input type="password" id="password" name="password" required /> </div> <div className="flex items-center gap-4"> <Button type="submit">Signup</Button> <Button type="button" asChild variant="link"> <Link to="/login">Login</Link> </Button> </div> </Form> </CardContent> </Card> ); }; export default Signup; 
Enter fullscreen mode Exit fullscreen mode

Update your route configuration in src/AppRoutes.tsx file:

import Signup from "./pages/auth/Signup"; const AppRoutes = createBrowserRouter([ ..., { path: "/signup", Component: Signup, }, ... ]); 
Enter fullscreen mode Exit fullscreen mode

To run the app:

npm run serve:dev 
Enter fullscreen mode Exit fullscreen mode

You can check the Sign up page in http://localhost:5173/signup.

Supabase Client Setup

Install the Supabase JS SDK:

npm install @supabase/supabase-js 
Enter fullscreen mode Exit fullscreen mode

Then create a client in src/services/supabase/client/supabase.ts:

import { createClient } from "@supabase/supabase-js"; const PROJECT_URL = import.meta.env.VITE_SUPABASE_PROJECT_URL as string; const ANON_KEY = import.meta.env.VITE_SUPABASE_ANON_KEY as string; const supabase = createClient(PROJECT_URL, ANON_KEY); export default supabase; 
Enter fullscreen mode Exit fullscreen mode

This code is okay, but we’re using TypeScript, and if we want help from autocompletion and type safety, we can export our Supabase types using this command:

npx supabase gen types --lang=typescript --local > src/services/supabase/client/database.types.ts 
Enter fullscreen mode Exit fullscreen mode

Sometimes this command gets stuck if the Supabase CLI isn’t fully installed. If that happens, just run npx supabase status, and it will ask to install anything missing. After that, run the gen types command again.

Once we have the types, update the Supabase client:

import { createClient } from "@supabase/supabase-js"; import type { Database } from "./database.types"; const PROJECT_URL = import.meta.env.VITE_SUPABASE_PROJECT_URL as string; const ANON_KEY = import.meta.env.VITE_SUPABASE_ANON_KEY as string; const supabase = createClient<Database>(PROJECT_URL, ANON_KEY); export default supabase; 
Enter fullscreen mode Exit fullscreen mode

Also, don’t forget to add the following environment variables in your .env file:

VITE_SUPABASE_PROJECT_URL=http://127.0.0.1:54321 VITE_SUPABASE_ANON_KEY=<YOUR_VITE_SUPABASE_ANON_KEY> VITE_SUPABASE_REDIRECT_URL=http://localhost:5173/ # we will use this later 
Enter fullscreen mode Exit fullscreen mode

Don’t forget to add .env to your .gitignore.

Signup logic

We’ll create a new file at src/services/supabase/auth/auth.ts to register a user using Supabase’s signUpUser method:

import supabase from "../client/supabase"; interface UserPayload { email: string; password: string; } const REDIRECT_URL = import.meta.env.VITE_SUPABASE_REDIRECT_URL as string; export const signUpUser = async (userPayload: UserPayload) => { const { data, error } = await supabase.auth.signUp({ email: userPayload.email, password: userPayload.password, options: { emailRedirectTo: REDIRECT_URL, data: { email: userPayload.email, }, }, }); if (error) { throw new Error(error.message); } return data; }; 
Enter fullscreen mode Exit fullscreen mode

Since we’re using React Router Data Mode, we’ll now create a file src/pages/auth/actions.ts to include this logic in an action.

import { signUpUser } from "@/services/supabase/auth/auth"; import { ActionFunctionArgs, redirect } from "react-router"; export const signup = async ({ request }: ActionFunctionArgs) => { const formData = await request.formData(); const userPayload = { email: formData.get('email') as string, password: formData.get('password') as string, }; await signUpUser(userPayload); return redirect('/'); }; 
Enter fullscreen mode Exit fullscreen mode

And finally, add the action to your route config:

const AppRoutes = createBrowserRouter([ ..., { path: "/signup", action: signup, Component: Signup, }, ... ]); 
Enter fullscreen mode Exit fullscreen mode

When we submit the form, a new user is created in Supabase Auth:

auth menu option

Linking Auth to Profiles (Trigger)

Creating a user via Supabase Auth doesn't automatically add them to the profiles table. To handle this, create a trigger and function in the SQL Editor:

-- inserts a row into public.profiles create function public.handle_new_user() returns trigger language plpgsql security definer set search_path = '' as $$ begin insert into public.profiles (id, email) values (new.id, new.raw_user_meta_data ->> 'email'); return new; end; $$; -- trigger the function every time a user is created create trigger on_auth_user_created after insert on auth.users for each row execute procedure public.handle_new_user(); 
Enter fullscreen mode Exit fullscreen mode

This need to be executed in the supabase studio SQL Editor

Now, new users will also be added to the profiles table automatically.

Deletion: Cascade from Auth to Profiles

Currently, deleting a user from Supabase Auth won’t remove their profile. To fix that, go to Supabase Studio → profiles table → and edit the foreign key for id to add ON DELETE CASCADE:

delete cascade

Now, profiles will be deleted when their corresponding Auth user is removed.

Remember to Create a Migration

Each time you make changes in Supabase Studio, don’t forget to create a migration:

npx supabase migration new create_auth_function_triggers npx supabase db diff --schema public > migration.sql # And copy migration.sql code to the new migration 
Enter fullscreen mode Exit fullscreen mode

Conclusion

This post was packed with practical steps! Here's a quick summary of what we did:

  • Built a signup form using React Router
  • Created a Supabase client with TypeScript support
  • Used the signUp method to register users
  • Created a trigger to sync new Auth users with the profiles table
  • Handled deletion cascades for cleaner data
  • Ensured changes are stored with migrations

In the next part, we’ll cover:

  • Login and logout
  • Route protection with loaders and redirects
  • Syncing the user session in your React app

Top comments (0)