Setup Guide
This walks you from a fresh clone to a running stack. Tested on macOS and Linux. Windows users should use WSL2.
Prerequisites
| Tool | Version | Needed for |
|---|---|---|
| PHP | ≥ 8.2 (with pdo_mysql, mbstring, bcmath, openssl, curl, xml, tokenizer) | Backend |
| Composer | ≥ 2.5 | Backend |
| Node.js | ≥ 18 | Frontend + docs |
| npm | ≥ 9 | Frontend + docs |
| MySQL | ≥ 8 (or SQLite for quick local runs) | Backend |
| Flutter SDK | ≥ 3.19 (stable channel) | Mobile apps (optional) |
| Android Studio / Xcode | latest | iOS/Android simulators for Flutter (optional) |
1. Clone and install backend
cd backend
cp .env.example .env
composer install
php artisan key:generate
Edit .env to point at your database:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=vetty_saas
DB_USERNAME=root
DB_PASSWORD=
(For a zero-setup run, leave the SQLite fallback and point DB_CONNECTION=sqlite at database/database.sqlite.)
Create schema and sample data:
php artisan migrate --seed
The seeder (database/seeders/DatabaseSeeder.php) creates:
- Tenant: Paws & Claws Veterinary (premium plan, active)
- Users: Ava Thompson (clinic_admin), Dr. Sarah Lee (veterinarian), Emily Johnson (owner)
- Client: Emily Johnson (paired with the owner user)
- Pet: Luna (golden retriever)
- Appointment: vaccination booked two days from now
- Invoice: INV-2026-0001 — partial (balance due)
- Payment: $100 card payment
Generate the OpenAPI spec once so /docs/openapi.json has something to serve:
php artisan openapi:generate
Run the server:
php artisan serve
By default Laravel binds to http://127.0.0.1:8000.
2. Install the frontend
cd ../ui
npm install
Optional — point the SPA at your API:
# ui/.env.local
REACT_APP_API_BASE_URL=http://127.0.0.1:8000/api/v1
REACT_APP_API_MODE=http
Or skip this and run against the built-in mock adapter:
# ui/.env.local
REACT_APP_API_MODE=mock
Start the dev server:
npm start
The SPA opens on http://localhost:3000. Log in as:
sarah.lee@pawsclaws.com/ passwordpassword(staff)emily@example.com/ passwordpassword(pet owner)
3. Filament admin panel
Visit http://127.0.0.1:8000/systemadmin. The seeder creates a system admin you can log in as (check database/seeders/DatabaseSeeder.php for the latest credentials).
4. Flutter apps (optional)
Both Flutter apps live alongside the backend and SPA at the repository root.
vetty — pet-owner app (vetyy/)
cd vetyy
flutter pub get
flutter run \
--dart-define=VETTY_API_BASE=http://10.0.2.2:8000/api/v1 \
--dart-define=VETTY_WEB_BASE=http://localhost:3000
vetdoctor — vet/clinic app (vetdoctor/)
cd vetdoctor
flutter pub get
flutter run \
--dart-define=VETDOCTOR_API_BASE=http://10.0.2.2:8000/api/v1 \
--dart-define=VETDOCTOR_WEB_BASE=http://localhost:3000
Notes on API_BASE
| Target | Host to use |
|---|---|
| Android emulator | http://10.0.2.2:8000/api/v1 (emulator loopback) |
| iOS simulator | http://127.0.0.1:8000/api/v1 |
| Physical device on same Wi-Fi | http://<host-LAN-IP>:8000/api/v1 |
| Staging / prod | e.g. https://api.vetty.example/api/v1 |
WEB_BASE is the URL the backend should put into password-reset emails — it should point at the running React SPA.
Default credentials
- vetty — register any new account via the app's Create account screen, or use the seeded owner
emily@example.com/password. - vetdoctor — clinic users are provisioned through Filament or the SPA. Use the seeded vet
sarah.lee@pawsclaws.com/password.
5. Running the docs site
cd ../docs
npm install
npm start
Opens http://localhost:3000 — if that collides with the SPA, run with a different port: npm start -- --port 3100.
6. Tests
Backend unit/feature tests use SQLite and live in backend/tests/:
cd backend
php artisan test
The test runner writes to database/verify.sqlite (already present in the repo) to keep production data untouched.
Frontend tests live in ui/src/setupTests.js and can be run with:
cd ui
npm test
Flutter tests (test/widget_test.dart in each app) are compile smoke tests that pump the app's theme and assert it builds:
cd vetyy && flutter test
cd ../vetdoctor && flutter test
7. Razorpay (sandbox payments)
The vetty pet-owner app can pay clinic invoices with Razorpay. Test-mode keys from your Razorpay dashboard are enough for local development — no real money moves.
-
Pull the SDK (only needed once, after a fresh clone or after pulling a branch that upgrades
composer.json):cd backendcomposer require razorpay/razorpay:^2.9 -
Add sandbox keys to
backend/.env:RAZORPAY_KEY=rzp_test_xxxxxxxxxxxxxxRAZORPAY_SECRET=your_test_secretRAZORPAY_WEBHOOK_SECRET=any_string_you_set_in_the_dashboardRAZORPAY_CURRENCY=INRThe same publishable key also lives on the Flutter side — the app fetches it from the
create-orderendpoint, so you don't need to duplicate it anywhere. -
Run the migration that adds gateway columns to the
paymentstable:php artisan migrate -
(Optional) Expose your local API to Razorpay's webhook poller with
ngrok/cloudflared, then register the tunnel URL in the Razorpay dashboard and point it at:POST /api/v1/webhooks/razorpayEvents to subscribe:
payment.captured,payment.failed. The handler is idempotent (unique index onprovider_payment_id) so replays are safe. -
In the vetty app, open any unpaid invoice and tap Pay. Use Razorpay's test cards (test card reference) — e.g.
4111 1111 1111 1111, CVV123, any future expiry.
If RAZORPAY_KEY or RAZORPAY_SECRET is missing, the create-order endpoint returns 503 Payments unavailable instead of a cryptic 500 — handy when you've forgotten to drop your keys in.
8. Useful artisan commands
php artisan tinker # REPL
php artisan openapi:generate # Regenerate API spec
php artisan filament:upgrade # After Filament updates
php artisan migrate:fresh --seed # Wipe + reseed local DB
9. Common pitfalls
| Symptom | Fix |
|---|---|
SQLSTATE[HY000] [2002] | MySQL isn't running, or wrong host/port in .env |
Target class [openapi:generate] does not exist | The console route lives in routes/console.php — don't remove it |
| SPA gets CORS blocked | Add your SPA origin to SANCTUM_STATEFUL_DOMAINS in backend .env |
| SPA sees old data | Clear localStorage (vetty.session, vetty.currentTenant, access_token) |
| Filament admin 404 | Make sure APP_URL is set; the panel provider reads it |
| Flutter emulator can't reach API | Use 10.0.2.2 (Android) or 127.0.0.1 (iOS) — not localhost |
| Flutter app stuck on login screen after correct creds | Inspect the API response; AuthStore clears itself on 401 — verify Sanctum is issuing tokens |
mobile_scanner black screen | Run on a physical device — most emulators don't expose the camera |