Local Development
Local Development
Section titled “Local Development”Get ClinicFlow running locally from scratch.
Prerequisites
Section titled “Prerequisites”| Requirement | Version | Notes |
|---|---|---|
| Node.js | 22+ | node --version to check |
| npm | 10+ | Comes with Node 22 |
| Redis | Any | Local Docker, Railway plugin, or hosted provider |
| Supabase | — | Create a free project at supabase.com |
Step 1: Clone and Install
Section titled “Step 1: Clone and Install”git clone git@github.com:ErnestNuma/clinic-saas-production.gitcd clinic-saas-production
# Install backend dependenciescd backend && npm install
# Install frontend dependenciescd ../frontend && npm installStep 2: Configure Environment
Section titled “Step 2: Configure Environment”Backend
Section titled “Backend”cd backendcp .env.example .envOpen .env and fill in the required values. The minimum set to get running locally:
PORT=3001NODE_ENV=development
# Supabase (get from supabase.com → project → Settings → API)SUPABASE_URL=https://your-project.supabase.coSUPABASE_ANON_KEY=eyJhbGc...SUPABASE_SERVICE_ROLE_KEY=eyJhbGc...# URL-encode password special characters, e.g. # as %23.SUPABASE_DB_URL=postgresql://postgres:[password]@db.[ref].supabase.co:5432/postgres
# Redis (Railway plugin or local instance)REDIS_URL=rediss://default:[password]@[host]:[port]
# AuthJWT_PRIVATE_KEY={"kty":"EC","crv":"P-256","x":"...","y":"...","d":"...","alg":"ES256","use":"sig","kid":"local-key"}JWT_PUBLIC_KEY={"kty":"EC","crv":"P-256","x":"...","y":"...","alg":"ES256","use":"sig","kid":"local-key","key_ops":["verify"]}JWT_KEY_ID=local-keyJWT_ISSUER=https://your-project.supabase.co/auth/v1JWT_AUDIENCE=authenticatedJWT_SECRET=your-qr-token-secret-at-least-32-chars
# Encryption (EXACTLY 32 characters)ENCRYPTION_KEY=exactly-32-characters-goes-here!
# App URLs (local dev)FRONTEND_URL=http://localhost:5173BACKEND_URL=http://localhost:3001BACKEND_PUBLIC_URL=http://localhost:3001
# SuperadminSUPER_ADMIN_KEY=any-long-random-string-for-local
# WhatsApp (optional for local — chatbot won't work without these)WHATSAPP_APP_SECRET=your-meta-app-secretWHATSAPP_WEBHOOK_VERIFY_TOKEN=your-verify-tokenSee Environment Variables for the complete reference.
Frontend
Section titled “Frontend”cd frontendcp .env.example .envEdit .env:
VITE_API_URL=http://localhost:3001/apiThe LemonSqueezy URLs can be left blank for local development (Billing page links won’t work).
Step 3: Set Up the Database
Section titled “Step 3: Set Up the Database”- Go to your Supabase project → SQL Editor
- Open
backend/src/db/schema.sql - Paste and run the entire file
This creates all tables, indexes, and RLS policies.
To run future migrations:
cd backendnpm run migrateStep 4: Create Your First Clinic (Invite Code)
Section titled “Step 4: Create Your First Clinic (Invite Code)”Registration requires an invite code. For local development, create one via the superadmin API:
curl -X POST http://localhost:3001/api/superadmin/invite-codes \ -H "X-Admin-Key: your-SUPER_ADMIN_KEY-value" \ -H "Content-Type: application/json" \ -d '{"plan": "clinica", "notes": "local dev"}'Copy the code from the response. Use it at http://localhost:5173/register.
Step 5: Start the Backend
Section titled “Step 5: Start the Backend”cd backendnpm run devServer starts on http://localhost:3001. Logs stream to the terminal.
Expected output:
info: ✅ Bull Board secured and integrated at /admin/queuesinfo: ✅ Redis connectedinfo: ✅ ClinicFlow API running on port 3001Step 6: Start the Frontend
Section titled “Step 6: Start the Frontend”Open a new terminal:
cd frontendnpm run devFrontend starts on http://localhost:5173.
Step 7: Run Tests
Section titled “Step 7: Run Tests”# Backend testscd backend && npm test# Expected: 458 tests, all pass
# Frontend testscd frontend && npm test
# Lintcd frontend && npm run lint# Expected: 0 errorsCommon Issues
Section titled “Common Issues”Redis ENOSPC (no space left on device)
Section titled “Redis ENOSPC (no space left on device)”The /tmp filesystem is full. BullMQ uses tmp space for job processing.
df -h /tmp # check usagedu -sh /tmp/* | sort -rh | head -10 # find large filesCommon culprit: accumulated log files. Remove safely:
rm /tmp/backend.log # if no process holds it openSupabase RLS Errors
Section titled “Supabase RLS Errors”If you see insufficient privilege or empty query results, RLS is blocking your queries. Make sure you’re using the service role key in .env (SUPABASE_SERVICE_ROLE_KEY), not the anon key, for supabaseAdmin.
The anon key is only for public-facing operations (unauthenticated reads).
WhatsApp Webhook Local Testing
Section titled “WhatsApp Webhook Local Testing”To test the chatbot locally, you need Meta to send webhooks to your machine. Use ngrok:
ngrok http 3001Then set your Meta App webhook URL to https://{ngrok-id}.ngrok.io/webhooks/whatsapp.
JWT_SECRET Length
Section titled “JWT_SECRET Length”If you see JsonWebTokenError: secretOrPrivateKey must have a value, your JWT_SECRET is empty or too short. It must be at least 32 characters.
ENCRYPTION_KEY Length
Section titled “ENCRYPTION_KEY Length”ENCRYPTION_KEY must be exactly 32 characters for AES-256. Add or remove characters until echo -n "$ENCRYPTION_KEY" | wc -c returns 32.