Skip to main content
This guide will help you set up Supabase as the backend database for Highway, storing verification data and call records.

Prerequisites

  • A Supabase account
  • Basic understanding of PostgreSQL
  • Access to create and manage database tables

Creating a Supabase Project

1

Sign up or sign in

Go to supabase.com and create an account or sign in.
2

Create a new project

Click New Project and provide:
  • Project name: Choose a descriptive name (e.g., “highway-production”)
  • Database password: Create a strong password (save this securely)
  • Region: Select the closest region to your users
3

Wait for provisioning

Supabase will set up your database. This typically takes 1-2 minutes.

Getting API Keys

Once your project is ready, you’ll need two API keys:
1

Navigate to Project Settings

Click on the Settings icon in the left sidebar, then select API.
2

Copy the Project URL

Find your Project URL (e.g., https://xxxxx.supabase.co). You’ll need this for the client configuration.
3

Get the anon key

Copy the anon (public) key. This key is safe to use in frontend applications with Row Level Security enabled.
4

Get the service_role key

Copy the service_role key. This key bypasses Row Level Security and should only be used server-side.
The service_role key has full database access. Never expose it in client-side code or commit it to version control.

Environment Configuration

Add your Supabase credentials to .env:
SUPABASE_URL=https://xxxxx.supabase.co
SUPABASE_ANON_KEY=your_anon_key_here
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key_here

Database Tables Setup

Highway requires two main tables: verifications and calls.

Verifications Table

Stores customer verification data:
1

Open SQL Editor

Navigate to SQL Editor in the Supabase dashboard.
2

Create verifications table

Run the following SQL:
CREATE TABLE verifications (
  id SERIAL PRIMARY KEY,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()),
  customer_name TEXT,
  phone_number TEXT,
  verification_data JSONB,
  status TEXT DEFAULT 'pending'
);
3

Add indexes

Optimize query performance:
CREATE INDEX idx_verifications_phone ON verifications(phone_number);
CREATE INDEX idx_verifications_status ON verifications(status);

Calls Table

Tracks call history and status:
1

Create calls table

Run this SQL in the SQL Editor:
CREATE TABLE calls (
  id SERIAL PRIMARY KEY,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()),
  verification INTEGER REFERENCES verifications(id),
  status TEXT DEFAULT 'in_progress',
  call_sid TEXT,
  duration INTEGER,
  recording_url TEXT,
  transcription TEXT,
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW())
);
2

Add indexes and constraints

CREATE INDEX idx_calls_verification ON calls(verification);
CREATE INDEX idx_calls_status ON calls(status);
CREATE INDEX idx_calls_created ON calls(created_at DESC);

Table Schemas and Relationships

Verifications Schema

ColumnTypeDescription
idSERIALPrimary key, auto-incrementing
created_atTIMESTAMPRecord creation timestamp
customer_nameTEXTCustomer’s name
phone_numberTEXTCustomer’s phone number
verification_dataJSONBFlexible data storage for verification info
statusTEXTVerification status (pending, completed, failed)

Calls Schema

ColumnTypeDescription
idSERIALPrimary key, auto-incrementing
created_atTIMESTAMPCall creation timestamp
verificationINTEGERForeign key to verifications table
statusTEXTCall status (in_progress, successful_call, etc.)
call_sidTEXTTwilio call SID
durationINTEGERCall duration in seconds
recording_urlTEXTURL to call recording
transcriptionTEXTCall transcription text
updated_atTIMESTAMPLast update timestamp

Relationship Diagram

verifications (1) ─── (many) calls
    ↑                      ↑
    └─ id            verification (FK)
The verification column in the calls table creates a one-to-many relationship, allowing multiple call attempts per verification.

Row Level Security (RLS) Policies

Enable RLS to secure your data:
1

Enable RLS on tables

ALTER TABLE verifications ENABLE ROW LEVEL SECURITY;
ALTER TABLE calls ENABLE ROW LEVEL SECURITY;
2

Create policies for verifications

Allow authenticated users to read their own verifications:
CREATE POLICY "Enable read access for authenticated users"
ON verifications
FOR SELECT
TO authenticated
USING (true);

CREATE POLICY "Enable insert for authenticated users"
ON verifications
FOR INSERT
TO authenticated
WITH CHECK (true);
3

Create policies for calls

CREATE POLICY "Enable read access for authenticated users"
ON calls
FOR SELECT
TO authenticated
USING (true);

CREATE POLICY "Enable insert for authenticated users"
ON calls
FOR INSERT
TO authenticated
WITH CHECK (true);

CREATE POLICY "Enable update for authenticated users"
ON calls
FOR UPDATE
TO authenticated
USING (true)
WITH CHECK (true);
These are basic RLS policies. In production, you should implement more granular policies based on user roles and ownership.

Client Configuration

Backend Configuration

Highway’s backend uses the Supabase client for server-side operations:
routes.js
const { createClient } = require("@supabase/supabase-js");

const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_ANON_KEY
);

Using the Client

Creating a call record:
routes.js
const { data } = await supabase
  .from("calls")
  .insert([{ verification: verification, status: "in_progress" }])
  .select();
Fetching verification data:
websocket.js
const { data, error } = await supabase
  .from("verifications")
  .select("*")
  .eq("id", streamId);
Updating call status:
websocket.js
await supabase
  .from("calls")
  .update({ status: response.arguments.status })
  .eq("id", callId);

Frontend Configuration (Optional)

If you have a frontend dashboard, use the anon key:
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
)
Frontend environment variables should be prefixed with NEXT_PUBLIC_ (Next.js) or VITE_ (Vite) to expose them to the client.

Testing Database Operations

1

Install Supabase client

npm install @supabase/supabase-js
2

Test connection

Create a test script:
const { createClient } = require('@supabase/supabase-js');

const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_ANON_KEY
);

async function testConnection() {
  const { data, error } = await supabase
    .from('verifications')
    .select('count');
  
  if (error) {
    console.error('Error:', error);
  } else {
    console.log('Connection successful!', data);
  }
}

testConnection();
3

Run the test

node test-supabase.js

Database Monitoring

1

View table data

Navigate to Table Editor in Supabase to view and edit data directly.
2

Check logs

Go to Logs > Database to monitor queries and errors.
3

Monitor performance

Use Reports to track database performance and query patterns.

Best Practices

Security

  1. Use RLS policies: Always enable Row Level Security
  2. Separate keys: Use anon_key for frontend, service_role for backend only
  3. Validate inputs: Sanitize data before inserting into the database
  4. Rotate keys: Regularly rotate API keys for security

Performance

  1. Add indexes: Index frequently queried columns
  2. Use select(): Only fetch columns you need
  3. Batch operations: Use bulk inserts when possible
  4. Monitor queries: Check slow queries in Supabase logs

Data Management

  1. Backup regularly: Enable automated backups in project settings
  2. Clean old data: Archive or delete old call records
  3. Use JSONB wisely: Store flexible data in verification_data
  4. Add timestamps: Track created_at and updated_at for all records

Troubleshooting

Common Issues

Connection errors
  • Verify your Supabase URL and API keys are correct
  • Check that your project is not paused (free tier limitation)
  • Ensure network allows connections to Supabase
RLS blocking queries
  • Check RLS policies are configured correctly
  • Verify user authentication status
  • Use service_role key for server-side operations that need full access
Insert/Update failures
  • Validate data types match table schema
  • Check foreign key constraints
  • Review RLS policies for INSERT/UPDATE permissions
Slow queries
  • Add indexes on frequently queried columns
  • Limit result sets with .limit()
  • Use .select('column1, column2') instead of .select('*')

Next Steps

Build docs developers (and LLMs) love