RoadHelp 24/7
An Uber-style roadside assistance marketplace — request a tow, jump-start, fuel drop or tyre change, get an instant quote, and watch the nearest provider arrive in real time.
- Role
- Freelance Full-Stack Developer
- Year
- 2025 — 2026
- Category
- On-Demand Services
- Platform
- Mobile App
- Stack
- FlutterNestJSPostgreSQLPostGISRedisSocket.IORazorpayNext.js

Why I built this
When a vehicle breaks down — engine failure, flat tyre, dead battery, empty tank — the owner needs a nearby, vetted provider fast, with transparent pricing. Independent garages and towing companies lack the marketplace layer: dispatch, pricing, escrow and ratings. RoadHelp 24/7 builds that layer: customers request roadside services, get an instant fare estimate, and track the provider in real time, while drivers earn through an in-app wallet with automated payouts.
How it works
Service request with instant quote
Pick a service type (TOW, JUMP_START, FUEL_DELIVERY, FLAT_TYRE), pin your GPS location or enter it manually, and get an instant fare estimate from the pricing engine before committing.
Geo-spatial dispatch
A PostGIS query finds the nearest online drivers by service code and radius; job offers are pushed over Socket.IO, and acceptance is an atomic conditional update so only one driver can win under concurrency.
Real-time tracking
WebSockets broadcast job state changes and stream driver location every 3–5 seconds during active jobs; the driver app renders road-following polylines through driver → pickup → drop phases.
Escrow payments and driver wallets
Customers pay via Razorpay with HMAC-verified webhooks; funds move through escrow into driver wallets with automated payouts and a reconciliation script. Wallet transactions are idempotent.
Admin panel with KYC
A Next.js dashboard manages users, drivers, KYC verification, pricing rules, trips, payments, escrow, refunds and disputes — with invite, bulk-import and CSV export for driver onboarding.
Key decisions
Multiple drivers race for the same job; a conditional updateMany (state=DISPATCHING, driverId=null → ASSIGNED) makes the accept an atomic transaction — no double-assignment.
Customer payments sit in escrow until job completion, then settle into driver wallets. Everything — payment, escrow, payout — has an audit trail and a reconciliation script.
Spatial queries run in PostGIS (nearest-driver within radius); the Socket.IO gateway scales across processes with a Redis pub/sub adapter.
Backend architecture
Four deployable units in a monorepo
apps/customer_app and apps/driver_app (Flutter), apps/admin_web (Next.js), apps/backend (NestJS) plus packages/shared — wired together with Docker Compose (PostGIS, Redis, backend, admin) and deployed to Railway and Vercel.
30+ modular NestJS modules
Auth (OTP + JWT rotation), jobs with a 10+ state lifecycle and JobStatusHistory audit, dispatch, pricing/quotes, payments, wallets, payouts, escrow, realtime, notifications (FCM + SMS), ratings, KYC, admin and analytics.
40+ Prisma models
User/Driver/Provider/Vehicle, Job + status history, PostGIS-backed DriverLocation, Payment/Wallet/Escrow ledgers, Disputes, Ratings, KycDocument, Subscriptions, AuditLog, FeatureFlags and PlatformSettings — with enums, indexes and soft deletes.
OTP-first auth
Phone OTP via MSG91 with admin roles (SUPER_ADMIN/ADMIN/OPS/FINANCE/SUPPORT) seeded through Prisma, and refresh-token rotation on the backend.
Challenges
Live payment keys exposed
A live Razorpay key was found in committed .env files during a deployment readiness audit. Mandated rotation before launch — the highest-severity finding in the review.
Heavy mobile code-quality debt
flutter analyze reported 947 issues in the customer app and 507 in the driver app — flagged as weak release hygiene that increases hidden runtime risk.
Near-zero automated test confidence
Only a single basic backend test covered the financial, realtime, escrow and dispatch-concurrency critical paths — a blocker for public launch pending idempotency and reconciliation tests.
What I learned
Money movement demands defense-in-depth: HMAC webhook validation, idempotent wallet transactions, escrow with audit trails and a reconciliation script.
Readiness is an iterative audit loop — the project went from 'integration pending' to 'ready to deploy' through successive audits, but code readiness alone isn't launch readiness.
Secrets and config drift are the biggest launch risks; treat configuration hygiene as core engineering.
Code snippets
// Atomically accept job: only one driver can win under concurrency.
const assignment = await this.prisma.job.updateMany({
where: { id: data.jobId, state: 'DISPATCHING', driverId: null },
data: { driverId: driver.id, state: 'ASSIGNED' },
});
if (assignment.count === 0) {
return { error: 'Job no longer available' };
}