To use Supabase with Angular, install @supabase/supabase-js, create an Angular service that initializes the Supabase client, and inject it into your components. Use environment files for the Supabase URL and anon key. The service wraps auth, database, and storage operations as observables, giving you reactive data flows that integrate naturally with Angular's change detection.
Building an Angular App with Supabase as the Backend
This tutorial shows you how to integrate Supabase into an Angular application. You will create a dedicated Angular service that initializes the Supabase client and exposes methods for authentication and database operations. By wrapping Supabase in a service, you get dependency injection, testability, and a clean separation between your UI components and backend logic.
Prerequisites
- Angular CLI installed (ng version 16+)
- A Supabase project with your URL and anon key
- Node.js 18+ installed
- Basic familiarity with Angular services and dependency injection
Step-by-step guide
Install the Supabase JavaScript client
Install the Supabase JavaScript client
Add the @supabase/supabase-js package to your Angular project. This is the official Supabase client library that works in any JavaScript environment, including Angular. It provides methods for auth, database queries, storage, and realtime subscriptions.
1npm install @supabase/supabase-jsExpected result: The @supabase/supabase-js package is added to your package.json dependencies.
Add Supabase credentials to Angular environment files
Add Supabase credentials to Angular environment files
Angular uses environment files to store configuration that varies between development and production. Add your Supabase URL and anon key to src/environments/environment.ts. The anon key is safe for client-side use because it respects Row Level Security. Never put the service role key in Angular code — it bypasses all security.
1// src/environments/environment.ts2export const environment = {3 production: false,4 supabaseUrl: 'https://your-project-ref.supabase.co',5 supabaseAnonKey: 'your-anon-key-here'6};78// src/environments/environment.prod.ts9export const environment = {10 production: true,11 supabaseUrl: 'https://your-project-ref.supabase.co',12 supabaseAnonKey: 'your-anon-key-here'13};Expected result: Your Supabase credentials are stored in the environment files and accessible throughout the app.
Create an Angular Supabase service
Create an Angular Supabase service
Generate a service using the Angular CLI and initialize the Supabase client inside it. The service uses providedIn: 'root' so it is a singleton available throughout the app. Expose the client directly for simple use cases, or wrap common operations in typed methods for better developer experience. The auth state listener converts Supabase's callback-based onAuthStateChange into an observable.
1// src/app/services/supabase.service.ts2import { Injectable } from '@angular/core';3import { createClient, SupabaseClient, User } from '@supabase/supabase-js';4import { BehaviorSubject } from 'rxjs';5import { environment } from '../../environments/environment';67@Injectable({ providedIn: 'root' })8export class SupabaseService {9 private supabase: SupabaseClient;10 private currentUser = new BehaviorSubject<User | null>(null);11 currentUser$ = this.currentUser.asObservable();1213 constructor() {14 this.supabase = createClient(15 environment.supabaseUrl,16 environment.supabaseAnonKey17 );1819 this.supabase.auth.onAuthStateChange((event, session) => {20 this.currentUser.next(session?.user ?? null);21 });22 }2324 get client(): SupabaseClient {25 return this.supabase;26 }27}Expected result: An injectable SupabaseService that initializes the client and emits auth state changes as an observable.
Add authentication methods to the service
Add authentication methods to the service
Extend the service with methods for sign up, sign in, and sign out. These wrap the Supabase auth methods and return promises that your components can await. The signUp method passes email and password to Supabase and returns the user data or an error. The signOut method clears the session and the BehaviorSubject automatically emits null via the auth state listener.
1// Add to SupabaseService class2async signUp(email: string, password: string) {3 const { data, error } = await this.supabase.auth.signUp({4 email,5 password6 });7 if (error) throw error;8 return data;9}1011async signIn(email: string, password: string) {12 const { data, error } = await this.supabase.auth.signInWithPassword({13 email,14 password15 });16 if (error) throw error;17 return data;18}1920async signOut() {21 const { error } = await this.supabase.auth.signOut();22 if (error) throw error;23}Expected result: The service exposes signUp, signIn, and signOut methods that components can call directly.
Add CRUD methods for database operations
Add CRUD methods for database operations
Add typed methods that perform common database operations. These methods call the Supabase client internally and return the data or throw on error. You can define TypeScript interfaces for your table rows to get full type safety. Remember that all operations go through RLS — if your table has RLS enabled, only rows permitted by your policies will be returned or modified.
1// Add interface and methods to SupabaseService2interface Todo {3 id: number;4 title: string;5 is_complete: boolean;6 user_id: string;7}89async getTodos(): Promise<Todo[]> {10 const { data, error } = await this.supabase11 .from('todos')12 .select('*')13 .order('id', { ascending: true });14 if (error) throw error;15 return data as Todo[];16}1718async addTodo(title: string): Promise<Todo> {19 const { data, error } = await this.supabase20 .from('todos')21 .insert({ title, is_complete: false })22 .select()23 .single();24 if (error) throw error;25 return data as Todo;26}2728async updateTodo(id: number, updates: Partial<Todo>): Promise<Todo> {29 const { data, error } = await this.supabase30 .from('todos')31 .update(updates)32 .eq('id', id)33 .select()34 .single();35 if (error) throw error;36 return data as Todo;37}Expected result: The service has typed methods for reading, creating, and updating todos that components can call.
Use the service in an Angular component
Use the service in an Angular component
Inject the SupabaseService into a component and use its methods to load and display data. Subscribe to currentUser$ to reactively show or hide content based on auth state. Call the CRUD methods from event handlers. Angular's async pipe works well with the observable-based auth state, while the database methods return promises you can await in component methods.
1// src/app/components/todo-list/todo-list.component.ts2import { Component, OnInit } from '@angular/core';3import { SupabaseService } from '../../services/supabase.service';45@Component({6 selector: 'app-todo-list',7 template: `8 <div *ngIf="(supabaseService.currentUser$ | async) as user; else login">9 <h2>Todos for {{ user.email }}</h2>10 <ul>11 <li *ngFor="let todo of todos">12 {{ todo.title }} - {{ todo.is_complete ? 'Done' : 'Pending' }}13 </li>14 </ul>15 </div>16 <ng-template #login>17 <p>Please sign in to see your todos.</p>18 </ng-template>19 `20})21export class TodoListComponent implements OnInit {22 todos: any[] = [];2324 constructor(public supabaseService: SupabaseService) {}2526 async ngOnInit() {27 this.todos = await this.supabaseService.getTodos();28 }29}Expected result: The component displays the authenticated user's todos, fetched from Supabase through the injected service.
Complete working example
1import { Injectable } from '@angular/core';2import { createClient, SupabaseClient, User } from '@supabase/supabase-js';3import { BehaviorSubject } from 'rxjs';4import { environment } from '../../environments/environment';56export interface Todo {7 id: number;8 title: string;9 is_complete: boolean;10 user_id: string;11}1213@Injectable({ providedIn: 'root' })14export class SupabaseService {15 private supabase: SupabaseClient;16 private currentUser = new BehaviorSubject<User | null>(null);17 currentUser$ = this.currentUser.asObservable();1819 constructor() {20 this.supabase = createClient(21 environment.supabaseUrl,22 environment.supabaseAnonKey23 );2425 this.supabase.auth.onAuthStateChange((event, session) => {26 this.currentUser.next(session?.user ?? null);27 });28 }2930 get client(): SupabaseClient {31 return this.supabase;32 }3334 async signUp(email: string, password: string) {35 const { data, error } = await this.supabase.auth.signUp({ email, password });36 if (error) throw error;37 return data;38 }3940 async signIn(email: string, password: string) {41 const { data, error } = await this.supabase.auth.signInWithPassword({ email, password });42 if (error) throw error;43 return data;44 }4546 async signOut() {47 const { error } = await this.supabase.auth.signOut();48 if (error) throw error;49 }5051 async getTodos(): Promise<Todo[]> {52 const { data, error } = await this.supabase53 .from('todos')54 .select('*')55 .order('id', { ascending: true });56 if (error) throw error;57 return data as Todo[];58 }5960 async addTodo(title: string): Promise<Todo> {61 const { data, error } = await this.supabase62 .from('todos')63 .insert({ title, is_complete: false })64 .select()65 .single();66 if (error) throw error;67 return data as Todo;68 }6970 async updateTodo(id: number, updates: Partial<Todo>): Promise<Todo> {71 const { data, error } = await this.supabase72 .from('todos')73 .update(updates)74 .eq('id', id)75 .select()76 .single();77 if (error) throw error;78 return data as Todo;79 }8081 async deleteTodo(id: number) {82 const { error } = await this.supabase83 .from('todos')84 .delete()85 .eq('id', id);86 if (error) throw error;87 }88}Common mistakes when using Supabase with Angular
Why it's a problem: Creating multiple Supabase client instances by calling createClient in each component instead of using a shared service
How to avoid: Always create the Supabase client once in a singleton service (providedIn: 'root') and inject it into components. Multiple instances can cause auth state mismatches.
Why it's a problem: Putting the Supabase service role key in Angular environment files, exposing it in the browser bundle
How to avoid: Only use the anon key in client-side Angular code. The service role key bypasses RLS and must only be used in server-side environments like Edge Functions.
Why it's a problem: Not subscribing to auth state changes, causing the UI to not react when users sign in or out
How to avoid: Use the BehaviorSubject pattern and subscribe to currentUser$ in your components using the async pipe or manual subscriptions.
Why it's a problem: Forgetting to enable RLS on tables, leaving data publicly accessible through the API
How to avoid: Always enable RLS on every table and write explicit policies. Without RLS, any user with the anon key can read and modify all data.
Best practices
- Create a single SupabaseService with providedIn: 'root' to ensure one client instance across the app
- Store Supabase credentials in Angular environment files and swap them at build time for production
- Use BehaviorSubject to expose auth state as an observable that works with Angular's async pipe
- Generate TypeScript types from your Supabase schema using supabase gen types for full type safety
- Always enable RLS on tables and write policies before exposing data through the client
- Handle errors in service methods by throwing — let components decide how to display errors to users
- Use Angular route guards with the auth state observable to protect routes from unauthenticated access
- Unsubscribe from observables in ngOnDestroy to prevent memory leaks
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to build an Angular 17 app with Supabase as the backend. Show me how to create a SupabaseService that handles auth and CRUD operations on a 'todos' table, with proper TypeScript types and observable auth state.
Help me set up Supabase in my Angular project. I need a service that initializes the client, provides sign in and sign out methods, and lets me query a todos table. Show me the environment configuration and a component that uses the service.
Frequently asked questions
Can I use Supabase with Angular standalone components?
Yes. The SupabaseService with providedIn: 'root' works with both NgModule-based and standalone component architectures. Inject it directly into standalone components the same way.
How do I handle Supabase auth redirects in Angular?
For OAuth flows, configure the redirectTo parameter to point to a route in your Angular app. In that route's component, call supabase.auth.getSession() to retrieve the session from the URL hash fragment.
Should I use Supabase realtime subscriptions in Angular?
Yes. You can subscribe to realtime changes in the SupabaseService and emit updates through a Subject or BehaviorSubject that components observe. Remember to unsubscribe from the Supabase channel in the service's ngOnDestroy.
How do I protect Angular routes based on Supabase auth?
Create an Angular route guard that checks the currentUser$ observable. If the user is null, redirect to the login page. Use canActivate in your route configuration.
Does Supabase support Angular Universal (SSR)?
Yes, but you need to handle the client differently on the server. Use @supabase/ssr for server-side rendering and ensure you do not access browser APIs like localStorage during server-side execution.
Can RapidDev help build a full Angular app with Supabase?
Yes. RapidDev can help you architect and build production Angular applications with Supabase, including auth flows, database design, RLS policies, and deployment configuration.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation