Back to work
On-Demand Services·2025 — 2026

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
RoadHelp 24/7 roadside assistance app
01

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.

02

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.

03

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.

04

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.

05

Challenges

High

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.

High

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.

High

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.

06

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

apps/backend/src/realtime/realtime.gateway.tstypescript
// 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' };
}