A full-stack real-time ambulance booking application that connects customers in need of emergency medical transport with ambulance riders.
This is a comprehensive full-stack ride-sharing application specifically designed for emergency ambulance services. Built by Sarthak Chakraborty, this project demonstrates the complete implementation of a production-ready mobile application with real-time tracking, dynamic fare calculation, and seamless rider-customer matching. The application leverages modern technologies including React Native for cross-platform mobile development, Node.js for the backend infrastructure, MongoDB for data persistence, and Socket.IO for real-time bidirectional communication. This project showcases end-to-end development capabilities, from user authentication and location services to WebSocket integration and map-based interfaces, making it a robust solution for on-demand emergency medical transport services.
AMVER is a comprehensive ambulance booking platform consisting of a React Native mobile application for customers and riders, and a Node.js backend server with real-time WebSocket communication. The application enables customers to request ambulances, track them in real-time, and riders to accept and fulfill ride requests efficiently.
- Dual User Roles: Separate interfaces for customers and ambulance riders
- Real-time Location Tracking: Live GPS tracking of ambulances during rides
- Phone-based Authentication: Quick and secure authentication using phone numbers
- Multiple Vehicle Types: Three ambulance tiers with different pricing
- Dynamic Fare Calculation: Automatic fare computation based on distance and vehicle type
- WebSocket Communication: Real-time updates for ride status and location
- OTP Verification: Secure ride verification system
- Nearby Rider Matching: Intelligent matching of customers with nearby available riders
- Location-based pickup and drop-off selection
- Real-time ambulance tracking
- Fare estimation before booking
- Ride status updates
- On-duty/off-duty toggle
- Receive nearby ride requests
- Accept/decline ride offers
- Navigation to pickup and drop locations
- Ride completion and OTP verification
- Framework: React Native with Expo
- Language: TypeScript
- Routing: Expo Router (file-based)
- State Management: Zustand
- Local Storage: React Native MMKV
- Maps: React Native Maps (Google Maps)
- Real-time: Socket.IO Client
- HTTP Client: Axios
- UI: Custom components with React Native Gesture Handler
- Runtime: Node.js
- Framework: Express.js
- Database: MongoDB with Mongoose
- Real-time: Socket.IO
- Authentication: JWT (jsonwebtoken)
- Location: Geolib
- Environment: dotenv
AMVER/
├── client/ # Mobile application
│ ├── src/
│ │ ├── app/ # Expo Router screens
│ │ ├── components/ # Reusable UI components
│ │ ├── db/ # State management (Zustand)
│ │ ├── service/ # API & WebSocket services
│ │ ├── styles/ # StyleSheets
│ │ ├── utils/ # Utility functions
│ │ └── assets/ # Static assets
│ ├── app.config.ts # Expo configuration (dynamic)
│ ├── eas.json # EAS Build configuration
│ ├── .env # Environment variables
│ ├── .env.example # Environment template
│ └── package.json # Dependencies
│
└── server/ # Backend server
├── config/ # Database connection
├── controllers/ # Business logic
├── errors/ # Custom error classes
├── middleware/ # Express middleware
├── models/ # Mongoose schemas
├── routes/ # API routes
├── utils/ # Utility functions
├── app.js # Main entry point
├── .env # Environment variables
└── package.json # Dependencies
┌─────────────────────────────────────────┐
│ Mobile Application │
├─────────────────────────────────────────┤
│ UI Layer (Screens & Components) │
│ ├─ Customer Screens │
│ └─ Rider Screens │
├─────────────────────────────────────────┤
│ State Management (Zustand) │
│ ├─ User Store │
│ └─ Rider Store │
├─────────────────────────────────────────┤
│ Services Layer │
│ ├─ Auth Service │
│ ├─ Ride Service │
│ ├─ WebSocket Provider │
│ └─ API Interceptors │
├─────────────────────────────────────────┤
│ Storage (MMKV) │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Express Server │
├─────────────────────────────────────────┤
│ WebSocket Layer (Socket.IO) │
│ ├─ Connection Management │
│ ├─ Event Handlers │
│ └─ Real-time Updates │
├─────────────────────────────────────────┤
│ REST API Layer │
│ ├─ Authentication Routes │
│ └─ Ride Management Routes │
├─────────────────────────────────────────┤
│ Business Logic (Controllers) │
│ ├─ Auth Controller │
│ ├─ Ride Controller │
│ └─ Socket Controller │
├─────────────────────────────────────────┤
│ Database Layer (MongoDB) │
│ ├─ User Model │
│ └─ Ride Model │
└─────────────────────────────────────────┘
- Node.js (v14 or higher)
- MongoDB (local or cloud instance)
- Expo CLI:
npm install -g expo-cli - Google Maps API Key
- iOS Simulator (Mac) or Android Studio
-
Clone the repository:
git clone <repository-url> cd AMVER
-
Setup Server:
cd server npm install cp .env.example .env # Edit .env with your configuration npm start
-
Setup Client:
cd client npm install cp .env.example .env # Edit .env with your server URL and Google Maps API key npm start
PORT=5000
MONGO_URI=mongodb://localhost:27017/amver
ACCESS_TOKEN_SECRET=your_access_token_secret
ACCESS_TOKEN_EXPIRY=15m
REFRESH_TOKEN_SECRET=your_refresh_token_secret
REFRESH_TOKEN_EXPIRY=7d
NODE_ENV=development# API Configuration
EXPO_PUBLIC_BASE_URL=your_public_base_url
EXPO_PUBLIC_SOCKET_URL=your_public_socket_url
# Google Maps API Keys
EXPO_PUBLIC_GOOGLE_MAPS_API_KEY=your_google_maps_api_key_here
# EAS Configuration
EXPO_PUBLIC_EAS_PROJECT_ID=your_eas_project_id
# App Package Identifiers (optional - defaults are set in app.config.ts)
EXPO_PUBLIC_IOS_BUNDLE_ID=com.vacantvectors.amver
EXPO_PUBLIC_ANDROID_PACKAGE=com.vacantvectors.amverFor Local Development:
# Uncomment and use your local IP address
# EXPO_PUBLIC_BASE_URL=http://192.168.0.137:5000
# EXPO_PUBLIC_SOCKET_URL=http://192.168.0.137:5000Authenticate user with phone number and role.
Request:
{
"phone": "1234567890",
"role": "customer" | "rider"
}Response:
{
"message": "User logged in successfully",
"user": { "_id": "...", "phone": "...", "role": "..." },
"access_token": "...",
"refresh_token": "..."
}Refresh access token.
Request:
{
"refresh_token": "..."
}Create a new ride request (requires authentication).
Request:
{
"vehicle": "ambulance" | "ambulancePlus" | "ambulanceProPlus",
"pickup": {
"address": "...",
"latitude": 0.0,
"longitude": 0.0
},
"drop": {
"address": "...",
"latitude": 0.0,
"longitude": 0.0
}
}Accept a ride request (rider only).
Update ride status.
Request:
{
"status": "START" | "ARRIVED" | "COMPLETED"
}Get all rides for the authenticated user.
Rider Events:
goOnDuty: Rider becomes availablegoOffDuty: Rider becomes unavailableupdateLocation: Update rider's location
Customer Events:
subscribeToZone: Get nearby riderssearchrider: Search for available riderscancelRide: Cancel a ride request
Shared Events:
subscribeRide: Subscribe to ride updatessubscribeToriderLocation: Subscribe to rider location
nearbyriders: List of nearby ridersrideOffer: Ride offer for ridersrideAccepted: Ride has been acceptedrideUpdate: Ride status updaterideCanceled: Ride has been canceledriderLocationUpdate: Rider location updaterideData: Complete ride dataerror: Error message
{
role: String, // "customer" | "rider"
phone: String, // Unique
createdAt: Date,
updatedAt: Date
}{
vehicle: String, // "ambulance" | "ambulancePlus" | "ambulanceProPlus"
distance: Number, // in kilometers
pickup: {
address: String,
latitude: Number,
longitude: Number
},
drop: {
address: String,
latitude: Number,
longitude: Number
},
fare: Number,
customer: ObjectId, // ref: User
rider: ObjectId, // ref: User
status: String, // "SEARCHING_FOR_RIDER" | "START" | "ARRIVED" | "COMPLETED"
otp: String, // 4-digit OTP
createdAt: Date,
updatedAt: Date
}| Vehicle Type | Base Fare | Per KM Rate | Minimum Fare |
|---|---|---|---|
| Ambulance | ₹500 | ₹25/km | ₹600 |
| Ambulance Plus | ₹1000 | ₹50/km | ₹1200 |
| Ambulance Pro Plus | ₹1500 | ₹75/km | ₹1800 |
- Launch app → Select "Customer" role
- Enter phone number → Authenticate
- Allow location permissions
- Select pickup location
- Select drop-off location
- Choose vehicle type → View fare estimate
- Confirm booking → Wait for rider acceptance
- Track rider in real-time
- Verify OTP when rider arrives
- Ride completes
- Launch app → Select "Rider" role
- Enter phone number → Authenticate
- Go "On Duty" from home screen
- Receive ride offers for nearby requests
- Accept ride offer
- Navigate to pickup location
- Update status to "ARRIVED"
- Share OTP with customer
- Update status to "START"
- Navigate to drop location
- Complete ride
- Riders update location every few seconds when on duty
- Customers see real-time ambulance movement on map
- 200km radius for nearby rider detection
- Automatic matching based on proximity
- Retry mechanism: searches every 10 seconds
- Maximum 20 retries (5 minutes)
- Ride cancellation if no riders found
- JWT-based authentication for API and WebSocket
- Automatic token refresh mechanism
- Phone-based user identification
- OTP verification for ride security
- Secure token storage with MMKV
- Input validation on all endpoints
Server:
cd server
npm startThe server runs with nodemon for auto-restart on file changes.
Client:
cd client
npm startChoose your platform (iOS/Android/Web) from the Expo dev tools.
Android:
cd client
eas build --platform androidiOS:
cd client
eas build --platform iosServer won't start:
- Verify MongoDB is running
- Check
.envfile exists and has valid values - Ensure port 5000 is not in use
Client can't connect to server:
- Update
EXPO_PUBLIC_BASE_URLwith correct IP address - Ensure phone/emulator is on same network as server
- Check firewall settings
Maps not displaying:
- Verify Google Maps API key is valid
- Enable Maps SDK for Android/iOS in Google Cloud Console
- Rebuild app after changing API keys
Location not updating:
- Check location permissions are granted
- Enable location services on device
- Verify GPS signal is available
Current Version: 1.0.1
For issues and questions, please open an issue in the GitHub repository.