Ridify is a real-time, peer-to-peer ride-sharing app. Drivers who are already making a trip offer their empty seats. Riders going the same way book those seats and pay only for the part of the route they ride.
Every ride is live. Drivers stream GPS over WebSockets, riders watch the car approach on the map, and everyone in the car shares a chat. Each step of the trip (request, accept, arrive, board, drop off, pay) is checked by the server and pushed to every phone instantly.
One Flutter codebase runs on Android, iOS and the web. It talks to a Node.js + Express 5 + Socket.IO backend on MongoDB, with an admin panel for moderation and KYC.
|
Drivers stream their position over Socket.IO. Riders see the car move on an OpenStreetMap view with the route drawn in, from booking to drop-off. |
Rides are matched by how close pickup and drop-off are to the driver's route, using turf.js. A sweep-line check makes sure no part of the route is ever over capacity. |
|
Each rider pays for their own slice of the route, not an equal share. The server recalculates every fare, so the client can't change the price. |
Each ride gets its own chat room for the driver and all co-passengers, with swipe-to-reply threads and messages delivered in real time. |
|
Sign-up and login are verified by an email OTP (stored as a SHA-256 hash, 5 tries max). Passwords use bcrypt. The app keeps you signed in with access and refresh tokens and refreshes them silently. |
|
|
A dashboard with live stats, user management, bans, force-cancel, and review of driver ID documents (uploaded to Google Drive through Apps Script). |
A monochrome Material theme in both modes. The same |
| Layer | What lives there |
|---|---|
| Clients | Flutter app (frontend/) with Provider state, ApiClient (auto-retries after a silent token refresh), SocketService, flutter_secure_storage and geolocator. The web build (web-app/) compiles the same lib/. |
| Edge | helmet, a locked-down CORS origin, three rate limiters (global 200 req/min, plus stricter auth and ride limiters), a 1 MB JSON cap, sanitize-html, the JWT authenticate middleware (with a ban check) and an adminOnly guard. |
| API | Express 5 routers for /api/auth, /api/rides and /api/admin, plus /health. Socket.IO authenticates the JWT during the handshake and auto-joins user, ride and global-search rooms. |
| Domain | Controllers for auth, rides and admin. Geo maths uses @turf/turf, seat capacity uses the sweep-line helpers in utils/rideHelpers.js, and winston handles logging. |
| Data & services | MongoDB (users, rides, otpverifications) with a 2dsphere index and a 30-day TTL on finished rides. EmailJS sends OTPs and Google Apps Script stores KYC documents. OSM tiles, OSRM and Nominatim are called directly from the app. |
Every step is a guarded PATCH on /api/rides/.... The controller checks the current state and who is asking, writes the change with an optimistic lock, and then emits to the ride's room:
| Step | Endpoint | Socket.IO event |
|---|---|---|
| Driver offers a ride | POST /api/rides |
— |
| Rider requests seats | PATCH /request/:id |
new_ride_request |
| Driver accepts / declines / removes | PATCH /accept · /decline · /kick |
ride_updated · passenger_kicked |
| Driver starts the trip | PATCH /start/:id |
ride_started |
| Live position | socket driver_location_update |
driver_location_update |
| Arrive → board → drop off → pay | PATCH /arrive · /board · /dropoff · /pay |
driver_arrived · passenger_boarded · passenger_dropped · passenger_paid |
| Trip ends | PATCH /end/:id |
ride_ended |
| Chat | POST /:id/chat |
receive_message |
A car with 4 seats can carry far more than 4 riders on one trip if they ride different stretches of it. Ridify turns every accepted booking into two events on the route, +seats at pickup and −seats at drop-off. It sorts them by route index (with drop-offs before pick-ups at the same index) and sweeps across them, tracking the running total. The request is accepted only if the peak stays within totalSeats.
changes.sort((a, b) => a.index - b.index || a.change - b.change); // drop-offs first
let current = 0, peak = 0;
for (const c of changes) { current += c.change; peak = Math.max(peak, current); }
return peak <= ride.totalSeats;Each rider's fare is worked out on the server from the part of the driver's route they actually ride:
rider fare = ride fare × (rider's segment distance ÷ total route distance)
So a rider covering 40% of a ₹200 trip pays ₹80. Anyone riding 99% or more pays the full fare, which avoids rounding losses.
| 📱 Frontend | ⚙️ Backend | 🌐 Services |
|---|---|---|
|
|
|
Prerequisites: Node.js 20+, Flutter 3.x (Dart ≥ 3.11), a MongoDB database (local or Atlas), and an Android/iOS device or emulator.
1. Clone
git clone https://github.com/Hackotber-26/T06-HopSync.git
cd ridify2. Backend
cd backend
npm install
cp .env.example .env # fill in the values below
npm run dev # validates env, then starts on http://localhost:50013. Frontend
cd frontend
cp .env.example .env # BACKEND_URL=http://10.0.2.2:5001 for the Android emulator
flutter pub get
flutter run💡 On a physical phone, point
BACKEND_URLat your computer's LAN IP. For release builds, pass it in with--dart-define=BACKEND_URL=https://your-api.example.com.
🔧 Backend environment variables
| Variable | Required | Description |
|---|---|---|
PORT |
HTTP port (default 5001) |
|
MONGO_URI |
✅ | MongoDB connection string |
JWT_SECRET |
✅ | Access-token secret (64-byte hex) |
JWT_REFRESH_SECRET |
✅ | Refresh-token secret, different from the above |
JWT_EXPIRES_IN / JWT_REFRESH_EXPIRES_IN |
Token lifetimes (default 24h / 7d) |
|
ADMIN_EMAILS |
✅ | Comma-separated admin accounts |
ADMIN_SECRET |
✅ | Secret for admin operations |
ALLOWED_ORIGIN |
✅ | CORS + Socket.IO origin (* for mobile-only) |
EMAILJS_SERVICE_ID · EMAILJS_TEMPLATE_ID · EMAILJS_PUBLIC_KEY · EMAILJS_PRIVATE_KEY |
✅ | OTP email delivery |
GOOGLE_APPS_SCRIPT_URL |
✅ | Apps Script endpoint for KYC uploads |
ALLOWED_EMAIL_DOMAIN |
Limit sign-ups to one email domain | |
BCRYPT_ROUNDS · MAX_FARE · SEARCH_RADIUS_DEFAULT_M · MIN_RIDE_DISTANCE_KM … |
Tuning knobs, see backend/.env.example |
Generate a secret with:
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"☁️ Deploy to Render (one blueprint)
The root render.yaml sets up both services on Render's free tier:
| Service | Type | What it does |
|---|---|---|
ridify-backend |
Node web service | npm ci → npm start, health-checked on /health |
ridify |
Static site | bash web-app/build.sh compiles the Flutter web app against BACKEND_URL |
In Render, choose New → Blueprint, connect this repo, fill in the secret env vars, and set ALLOWED_ORIGIN on the backend to the static site's URL. See web-app/README.md for details.
📡 REST API reference
| Area | Method & path | Purpose |
|---|---|---|
| Auth | POST /api/auth/signup-otp-request · /register |
Email OTP sign-up |
POST /api/auth/login · /login-otp-request |
Password or OTP login | |
POST /api/auth/refresh · /logout |
Token refresh / sign out | |
PATCH /api/auth/change-password |
Change password | |
GET /api/auth/user/:email/profile · POST /user/:email/upload-id |
Profile · KYC upload | |
| Rides | GET /api/rides/search |
Route-aware search |
POST /api/rides · GET /api/rides/:id |
Offer / fetch a ride | |
PATCH /request /accept /decline /kick /arrive /board /dropoff /pay /start /end /cancel |
Lifecycle | |
POST /api/rides/:id/chat |
In-ride message | |
GET /api/rides/stats/driver |
Driver earnings | |
| Admin | GET /api/admin/stats · /users · /rides · /verifications/pending |
Dashboard data |
POST /users/:id/ban · /unban · PATCH /users/:id/verify · /reject-verification |
Moderation & KYC | |
PATCH /rides/:id/cancel · DELETE /rides/:id |
Ride control |
🗂️ Project structure
ridify/
├── backend/ Node.js + Express 5 + Socket.IO API
│ ├── config/ db.js · socket.js (JWT handshake, rooms)
│ ├── controllers/ auth · ride · admin
│ ├── middleware/ authenticate · adminOnly
│ ├── models/ user · ride · OtpVerification
│ ├── routes/ /api/auth · /api/rides · /api/admin
│ └── utils/ rideHelpers (sweep-line) · jwt · otp · logger · validators
├── frontend/ Flutter app (Android · iOS · desktop)
│ └── lib/
│ ├── core/ theme · providers · socket_service · constants
│ ├── screens/ home · find/offer · live tracking · chat · admin …
│ ├── services/ api_client · auth · ride · location · nominatim …
│ └── widgets/ swipe_to_reply · address_search · verified_badge …
├── web-app/ isolated Flutter-web build for Render
├── render.yaml one-click Render blueprint
| 🧂 Hashed secrets bcrypt passwords SHA-256 OTPs, 5 tries |
🎟️ Short-lived JWTs separate access & refresh secrets verified on every socket |
🚧 Rate limits global + auth + ride limiters 1 MB body cap |
🧼 Clean input sanitize-html, validators server-side fare maths |
| 🪖 Helmet secure HTTP headers |
🔐 CORS lock single allowed origin |
🔁 Optimistic locking no double-booked seats |
🤖 CI checks npm audit · secret scan flutter analyze |
Released under the MIT License. See LICENSE.