Skip to main content

Overview

Aria Healthcare uses Supabase as its backend platform, providing RESTful APIs, real-time subscriptions, authentication, and file storage. All APIs are accessed through the Supabase client with TypeScript type safety.

Getting Started

Supabase Client Setup

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

// Environment variables
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.VITE_SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.VITE_SUPABASE_ANON_KEY;

if (!supabaseUrl || !supabaseAnonKey) {
  console.error('Missing Supabase environment variables');
}

// Create singleton client instance
export const supabase = createClient(
  supabaseUrl || 'https://placeholder.supabase.co',
  supabaseAnonKey || 'placeholder',
);

Environment Variables

NEXT_PUBLIC_SUPABASE_URL
string
required
Your Supabase project URL (e.g., https://xxxxx.supabase.co)
NEXT_PUBLIC_SUPABASE_ANON_KEY
string
required
Your Supabase anonymous/public API key for client-side usage
VITE_SUPABASE_URL
string
Alternative environment variable for Vite compatibility
VITE_SUPABASE_ANON_KEY
string
Alternative environment variable for Vite compatibility

Authentication API

Sign Up

Create a new user account with email and password.
const { data, error } = await supabase.auth.signUp({
  email: '[email protected]',
  password: 'secure-password',
  options: {
    data: {
      name: 'Dr. John Doe',
      user_type: 'doctor',
    },
  },
});

if (error) {
  console.error('Sign up error:', error.message);
} else {
  console.log('User created:', data.user);
}
email
string
required
User’s email address
password
string
required
User’s password (minimum 6 characters)
options.data
object
Additional user metadata (e.g., name, user_type)
data.user
object
User object containing id, email, and metadata
data.session
object
Session object with access_token and refresh_token

Sign In

Authenticate an existing user.
const { data, error } = await supabase.auth.signInWithPassword({
  email: '[email protected]',
  password: 'secure-password',
});

if (error) {
  console.error('Sign in error:', error.message);
} else {
  console.log('Session:', data.session);
}

Sign Out

End the current user session.
const { error } = await supabase.auth.signOut();

if (error) {
  console.error('Sign out error:', error.message);
}

Get Current User

Retrieve the currently authenticated user.
const { data: { user } } = await supabase.auth.getUser();

if (user) {
  console.log('Current user:', user.email);
} else {
  console.log('No user signed in');
}

Session Management

Handle authentication state changes.
supabase.auth.onAuthStateChange((event, session) => {
  console.log('Auth event:', event);
  
  switch (event) {
    case 'SIGNED_IN':
      console.log('User signed in:', session?.user.email);
      break;
    case 'SIGNED_OUT':
      console.log('User signed out');
      break;
    case 'TOKEN_REFRESHED':
      console.log('Token refreshed');
      break;
  }
});

Database API

Waitlist Table

Manage waitlist entries for new users.

Insert Waitlist Entry

import { supabase } from '@/integrations/supabase/client';
import { customList } from 'country-codes-list';

async function submitWaitlist(values: WaitlistForm) {
  // Format phone number with country code
  const callingCodes = customList('countryCode', '+{countryCallingCode}');
  const callingCode = callingCodes[values.phoneNumberCountryCode];
  const fullPhoneNumber = `${callingCode} ${values.phoneNumber}`;
  
  const { data, error } = await supabase
    .from('waitlist')
    .insert({
      name: values.name,
      user_type: values.userType,
      user_type_other: values.userTypeOther,
      phone_number: fullPhoneNumber,
      email: values.email,
      features: values.features,
      features_other: values.featuresOther,
      source: values.source,
      source_other: values.sourceOther,
    })
    .select();

  if (error) {
    throw new Error(error.message);
  }

  return data;
}
name
string
required
User’s full name
user_type
enum
required
Type of user: doctor, patient, or other
user_type_other
string
Specification if user_type is other
phone_number
string
required
Phone number with country code (e.g., +91 9876543210)
email
string
required
User’s email address
features
array<string>
required
Array of interested features: ai_features, records, ABHA, appointment, messaging, telehealth, other
features_other
string
Specification if features includes other
source
enum
required
How they heard about Aria: search_engine, social_media, recommendation, word_of_mouth, other
source_other
string
Specification if source is other
data
array
Array containing the inserted waitlist entry with auto-generated id and created_at fields

Query Waitlist Entries

const { data, error } = await supabase
  .from('waitlist')
  .select('*');

Patient Records Table

Manage patient medical records (example schema).

Create Patient Record

const { data, error } = await supabase
  .from('patient_records')
  .insert({
    patient_id: 'uuid-here',
    abha_number: '12345678901234',
    abha_address: 'patient@abdm',
    doctor_id: 'doctor-uuid',
    diagnosis: 'Hypertension',
    prescription: [
      {
        medication: 'Amlodipine',
        dosage: '5mg',
        frequency: 'Once daily',
        duration: '30 days',
      },
    ],
    notes: 'Patient advised lifestyle modifications',
    visit_date: new Date().toISOString(),
  })
  .select();
patient_id
uuid
required
Unique identifier for the patient
abha_number
string
14-digit ABHA number
abha_address
string
ABHA address (e.g., patient@abdm)
doctor_id
uuid
required
Unique identifier for the treating doctor
diagnosis
string
required
Medical diagnosis
prescription
array<object>
Array of prescribed medications with dosage details
notes
string
Additional clinical notes
visit_date
timestamp
required
Date and time of the visit

Query Patient Records

const { data, error } = await supabase
  .from('patient_records')
  .select('*')
  .eq('patient_id', patientId)
  .order('visit_date', { ascending: false });

Real-time Subscriptions

Listen to database changes in real-time.
const channel = supabase
  .channel('patient_records_changes')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'patient_records',
      filter: `patient_id=eq.${patientId}`,
    },
    (payload) => {
      console.log('New record:', payload.new);
      // Update UI with new record
    }
  )
  .subscribe();

// Cleanup
return () => {
  supabase.removeChannel(channel);
};
Real-time subscriptions automatically handle reconnection and are optimized for low latency.

Storage API

Upload and manage medical records, images, and documents.

Upload File

const file = event.target.files[0];
const fileExt = file.name.split('.').pop();
const fileName = `${patientId}/${Date.now()}.${fileExt}`;

const { data, error } = await supabase.storage
  .from('medical-records')
  .upload(fileName, file, {
    cacheControl: '3600',
    upsert: false,
  });

if (error) {
  console.error('Upload error:', error);
} else {
  console.log('File uploaded:', data.path);
}

Get File URL

const { data } = supabase.storage
  .from('medical-records')
  .getPublicUrl(filePath);

console.log('File URL:', data.publicUrl);

Delete File

const { data, error } = await supabase.storage
  .from('medical-records')
  .remove([filePath]);

if (error) {
  console.error('Delete error:', error);
}

Error Handling

All Supabase operations return an error object when something goes wrong.
const { data, error } = await supabase
  .from('waitlist')
  .insert({ name: 'Test' });

if (error) {
  // Handle specific error codes
  switch (error.code) {
    case '23505': // Unique violation
      console.error('Duplicate entry');
      break;
    case '42501': // Insufficient privileges
      console.error('Permission denied');
      break;
    default:
      console.error('Error:', error.message);
  }
  
  throw error;
}

return data;

Type Safety

Supabase client can be configured with TypeScript types for your database schema.
// Generated from Supabase CLI
export type Database = {
  public: {
    Tables: {
      waitlist: {
        Row: {
          id: string;
          name: string;
          email: string;
          phone_number: string;
          user_type: 'doctor' | 'patient' | 'other';
          features: string[];
          created_at: string;
        };
        Insert: Omit<Database['public']['Tables']['waitlist']['Row'], 'id' | 'created_at'>;
        Update: Partial<Database['public']['Tables']['waitlist']['Insert']>;
      };
    };
  };
};

// Create typed client
import { createClient } from '@supabase/supabase-js';
import type { Database } from './database.types';

export const supabase = createClient<Database>(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

// Now you get full type safety
const { data } = await supabase.from('waitlist').select('*');
// data is typed as Database['public']['Tables']['waitlist']['Row'][]
Always validate and sanitize user input before inserting into the database, even with TypeScript types.

Rate Limits

Supabase implements rate limiting to prevent abuse:
  • Authentication: 30 requests per hour per IP
  • Database queries: 100 requests per second
  • Storage uploads: 100 requests per second
  • Real-time connections: 500 concurrent connections per project
Contact Supabase support for increased rate limits on production applications.

Best Practices

  1. Use React Query for Data Fetching
    • Automatic caching and background refetching
    • Optimistic updates for better UX
    • Error and loading state management
  2. Implement Row-Level Security
    • Always enable RLS on tables with sensitive data
    • Use policies to restrict access based on user roles
  3. Use Database Functions
    • Complex business logic in PostgreSQL functions
    • Better performance than client-side processing
  4. Handle Errors Gracefully
    • Show user-friendly error messages
    • Log detailed errors for debugging
    • Implement retry logic for transient failures
  5. Optimize Queries
    • Select only needed columns
    • Use indexes for frequently queried fields
    • Implement pagination for large datasets
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';

// Fetch data with React Query
function useWaitlist() {
  return useQuery({
    queryKey: ['waitlist'],
    queryFn: async () => {
      const { data, error } = await supabase
        .from('waitlist')
        .select('*')
        .order('created_at', { ascending: false });

      if (error) throw error;
      return data;
    },
    staleTime: 5 * 60 * 1000, // 5 minutes
  });
}

// Mutate data with optimistic updates
function useAddToWaitlist() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (values: WaitlistForm) => {
      const { data, error } = await supabase
        .from('waitlist')
        .insert(values)
        .select();

      if (error) throw error;
      return data[0];
    },
    onSuccess: () => {
      // Invalidate and refetch
      queryClient.invalidateQueries({ queryKey: ['waitlist'] });
    },
  });
}

Build docs developers (and LLMs) love