Backend-only ride-hailing API built with Node.js, Express, JavaScript, MongoDB, microservices, and AWS SQS. Uses OSRM for distance/duration and Strategy pattern for driver matching and fare calculation.
- Microservices: Auth, Users, Drivers, Rides, Notifications (+ API Gateway)
- Single MongoDB with one database per service
- JWT access tokens + refresh tokens; roles:
rider,driver,admin - Vehicle types with different fare (base + per-km + per-minute)
- Driver matching strategies: nearest by distance, nearest by ETA (OSRM)
- Fare calculation strategy: vehicle-type-based formula
- OSRM (public API) for route distance and duration
- AWS SQS for notifications (eventual consistency)
- Email notifications via AWS SES (production) or SMTP/Mailpit (local); rider and driver get different, role-specific emails for ride_matched, ride_accepted, ride_completed
- Transactions only for fare + payment in Rides service
- Aggregated Swagger at API Gateway
- Node.js >= 18
- MongoDB (run locally or via Docker)
- AWS SQS optional: use real AWS or LocalStack for local notifications
Option A – Docker Compose (recommended):
npm run docker:upThis starts MongoDB on port 27017. To also start Mailpit (local inbox for email testing, Web UI at http://localhost:8025):
docker compose --profile mail up -dSet EMAIL_PROVIDER=smtp, SMTP_HOST=localhost, SMTP_PORT=1025 in .env. Rider and driver emails will appear in Mailpit.
To also start LocalStack for SQS:
docker compose --profile sqs up -dThen create the SQS queue and get the URL:
npm run localstack:init-sqsCopy the printed URL into .env as SQS_NOTIFICATIONS_QUEUE_URL, and add AWS_ENDPOINT_URL=http://localhost:4566 when using LocalStack.
Option B – Local MongoDB: ensure mongod is running and MONGODB_URI=mongodb://localhost:27017 in .env.
npm install
cd shared && npm install && cd ..
cd services/auth-service && npm install && cd ../..
cd services/users-service && npm install && cd ../..
cd services/drivers-service && npm install && cd ../..
cd services/rides-service && npm install && cd ../..
cd services/notifications-service && npm install && cd ../..
cd services/api-gateway && npm install && cd ../..Or from repo root (if you use a single install script):
npm run install:allCopy .env.example to .env and set at least:
MONGODB_URI(defaultmongodb://localhost:27017; use same when using Docker MongoDB)JWT_ACCESS_SECRET,JWT_REFRESH_SECRET(use strong values in production)SQS_NOTIFICATIONS_QUEUE_URL(optional; leave empty to skip SQS, or set afternpm run localstack:init-sqs)AWS_ENDPOINT_URL=http://localhost:4566when using LocalStack for SQSINTERNAL_API_SECRET(shared secret for service-to-service calls; Notifications uses it to fetch user email from Users)- Email:
EMAIL_PROVIDER=smtp(local with Mailpit) orses(AWS SES); for SMTP setSMTP_HOST=localhost,SMTP_PORT=1025when using Mailpit
Start MongoDB, then run each service in a separate terminal (or use npm run dev:all if you have concurrently):
# Terminal 1 – Auth
npm run dev --prefix services/auth-service
# Terminal 2 – Users
npm run dev --prefix services/users-service
# Terminal 3 – Drivers
npm run dev --prefix services/drivers-service
# Terminal 4 – Rides
npm run dev --prefix services/rides-service
# Terminal 5 – Notifications (SQS consumer)
npm run dev --prefix services/notifications-service
# Terminal 6 – API Gateway
npm run dev --prefix services/api-gatewayDefault ports: Gateway 3000, Auth 3001, Users 3002, Drivers 3003, Rides 3004, Notifications 3005.
Monitoring (local – optional): To run Prometheus, Grafana, and Jaeger:
docker compose --profile monitoring up -d- Prometheus: http://localhost:9090 (metrics; scrapes each service at
host.docker.internal:3000–3005) - Grafana: http://localhost:3100 (login
admin/admin). Prometheus and Jaeger are pre-provisioned; open Dashboards → Ride Service → Services overview for request rate and p95 latency by service. - Jaeger UI: http://localhost:16686 (traces). Select a service (e.g. gateway) and Find Traces to see request flow across services.
Start the app with npm run dev:all so services expose /metrics and send traces; then you can query in Prometheus, build dashboards in Grafana, and view traces in Jaeger. Optional: set OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 in .env if Jaeger is not on the default.
- Swagger UI: http://localhost:3000/api-docs
- OpenAPI JSON: http://localhost:3000/openapi.json
Seed admin user (once):
npm run seed:adminCreates an admin in users_db and auth credentials in auth_db. Default login: admin@ride-service.local / Admin123!. Override with env: ADMIN_EMAIL, ADMIN_PASSWORD, ADMIN_NAME.
Seed vehicle types (recommended):
npm run seed:vehicle-typesCreates Economy, Premium, and XL vehicle types in rides_db. Run once (with MongoDB up).
Create more vehicle types (admin):
- Use the access token of an admin user.
POST /vehicle-typeswithname,baseFare,perKm,perMinute(e.g. Economy: 2, 1.5, 0.3).- Riders need a
vehicleTypeIdwhen requesting a ride.
RideService-Node/
├── docs/
│ ├── ARCHITECTURE.md # High-level architecture, Notifications structure
│ └── DESIGN_DECISIONS.md # Design choices, trade-offs, service-to-service auth (interview prep)
├── shared/ # Shared lib (errors, logger, JWT, OSRM, config)
├── services/
│ ├── auth-service/
│ ├── users-service/ # + internal route for Notifications (email lookup)
│ ├── drivers-service/
│ ├── rides-service/ # Strategies (matching, fare), OSRM, transactions
│ ├── notifications-service/ # SQS consumer; Strategy (SMTP/SES); handler → service → providers/templates
│ └── api-gateway/ # Proxy + aggregated Swagger
├── monitoring/ # Prometheus config, Grafana provisioning and dashboards
├── .env.example
└── README.md
- Strategy pattern: Driver matching and fare calculation (Rides) and email delivery (Notifications:
SmtpEmailProvider/SesEmailProvider) use the same pattern; seeservices/rides-service/src/strategies/andservices/notifications-service/src/providers/. - Layered Notifications: Thin handler →
notificationService(orchestrate) → userClient, templates, emailSender (delegates to EmailProvider). Keeps structure consistent with other services. - Service-to-service auth: Internal APIs (e.g. Notifications → Users) use a shared secret (
INTERNAL_API_SECRET) for v1; production alternatives (OAuth2 client credentials, mTLS) are documented in docs/DESIGN_DECISIONS.md. - Transactions: Used only when completing a ride (update ride + insert payment in one transaction).
- OSRM: Public API for distance/duration; production would use dedicated routing or self-hosted OSRM.
- SQS: Notifications are sent as messages; the Notifications service consumes and sends rider/driver–specific emails (idempotent handling recommended).
Full rationale and trade-offs: docs/DESIGN_DECISIONS.md.
| Script | Description |
|---|---|
npm run install:all |
Install root, shared, and all services |
npm run dev:all |
Start all services (needs concurrently) |
npm run seed:admin |
Create admin user (optional: ADMIN_EMAIL, ADMIN_PASSWORD, ADMIN_NAME) |
npm run seed:vehicle-types |
Seed Economy, Premium, XL vehicle types |
npm run docker:up |
Start MongoDB (and optionally --profile sqs or --profile monitoring) |
npm run docker:down |
Stop Docker Compose services |
npm run localstack:init-sqs |
Create SQS queue in LocalStack and print URL |
npm test |
Run all unit and API tests (Jest) |
npm run test:watch |
Run tests in watch mode |
npm run test:coverage |
Run tests with coverage report; enforces thresholds |
Tests use Jest (unit) and Supertest (API), with mongodb-memory-server for in-memory MongoDB in API tests. One command from repo root runs everything.
npm test– Run all tests (unit + API).npm run test:watch– Run tests in watch mode.npm run test:coverage– Run tests and generate coverage; fails if global thresholds are not met (statements/lines 70%, branches 60%, functions 65%).
- Unit tests: Next to source or in
__tests__:- shared:
shared/__tests__/*.test.js– errors, JWT, OSRM, metrics, auth middleware. - Rides strategies:
services/rides-service/src/strategies/*.test.js– fare, nearest-by-distance, nearest-by-ETA. - Services:
services/*/src/services/*.test.js– auth, users, drivers, rides, notifications (Mongoose and HTTP mocked).
- shared:
- API tests:
services/*/src/__tests__/*.api.test.js– Supertest against each service’s Express app; Auth, Users, Drivers, Rides use in-memory MongoDB (started once via Jest global setup).
- Unit tests mock external deps (Mongoose models, axios, bcrypt, SQS, OSRM) so no real DB or network is needed.
- API tests require the service’s app from
src/app.js, connect to mongodb-memory-server inbeforeAll, and assert HTTP status and body. Auth API test mocks the Users service HTTP call; Rides API test mocks OSRM and Drivers service. - Coverage is collected from
shared/**/*.jsandservices/*/src/**/*.js;index.js,db.js,app.js, andtracing.jsare excluded.
| Metric | Threshold |
|---|---|
| Statements | 70% |
| Branches | 60% |
| Functions | 65% |
| Lines | 70% |
Running npm run test:coverage generates:
- Console: A text summary and a per-file table (% Stmts, Branch, Funcs, Lines).
- HTML report: Written to
coverage/index.html. Open this file in a browser to see an interactive report with line-by-line coverage (green = covered, red = uncovered). Example:open coverage/index.html
- lcov: The
coverage/lcov-report/index.htmldirectory contains the same data in lcov-report form;coverage/is listed in.gitignoreand should not be committed.
| Variable | Description | Default |
|---|---|---|
| MONGODB_URI | MongoDB connection string | mongodb://localhost:27017 |
| JWT_ACCESS_SECRET | Secret for access JWT | (set in .env) |
| JWT_REFRESH_SECRET | Secret for refresh JWT | (set in .env) |
| JWT_ACCESS_EXPIRY | Access token TTL | 15m |
| JWT_REFRESH_EXPIRY | Refresh token TTL | 7d |
| OSRM_BASE_URL | OSRM server | https://router.project-osrm.org |
| SQS_NOTIFICATIONS_QUEUE_URL | SQS queue for notifications | (optional) |
| AWS_ENDPOINT_URL | Use LocalStack for SQS | (optional, e.g. http://localhost:4566) |
| AWS_REGION | AWS region for SQS/SES | us-east-1 |
| INTERNAL_API_SECRET | Secret for internal APIs (e.g. Notifications → Users) | (set in .env) |
| EMAIL_PROVIDER | smtp (Mailpit/local) or ses (AWS SES) |
smtp |
| EMAIL_FROM | From address for emails | (e.g. Ride Service <noreply@…>) |
| SMTP_HOST / SMTP_PORT | For local Mailpit | localhost / 1025 |
| DRIVER_MATCHING_STRATEGY | distance or eta |
distance |
| OTEL_EXPORTER_OTLP_ENDPOINT | Jaeger OTLP HTTP endpoint (when using monitoring profile) | http://localhost:4318 |
| PORT | Per-service port (set in each service) | 3001–3005, 3000 gateway |
MIT