Overview
🗺️What It Does ⚙️Tech Stack 👥User Roles
Structure
📁Folder Structure 🗄️Database Models 🛣️API Endpoints
Features
🔐Auth Flow 💬Real-time Chat 🗺️Map & Safety 📅Booking & Payment
Setup
🔑Environment Keys ▶️Running Locally 🌐Deployment Checklist
MERN Stack Socket.IO Real-time Leaflet Maps Cloudinary Media Brevo Email  Live on Render + Vercel

JourneyShield

A full-stack travel safety platform connecting Travelers and local Guides. Travelers discover places on a live interactive map, check real-time locality safety scores, hire verified guides, chat with rich media support, book tours, and pay — all in one app.

13
DB Models
40+
API Routes
15
Pages
14
Services
5
Ext. APIs

⚙️Tech Stack

CategoryTechnologyPurpose
Backend RuntimeNode.js 18+ (ESM modules)Server-side JS with import/export syntax
Web FrameworkExpress.jsREST API, middleware chain, route handling
DatabaseMongoDB Atlas + MongooseCloud-hosted NoSQL — users, bookings, messages, guides
Real-timeSocket.IO v4Live chat, typing indicators, read receipts
AuthJWT + bcryptjsStateless tokens, secure password hashing
Email (OTP)Brevo REST APIRegistration email verification — 6-digit OTP, 5 min TTL
Media StorageCloudinaryAvatar images + chat files/images/audio
MapsLeaflet + react-leafletInteractive map on the Discover page
Geocoding + RoutesTomTom APICity → coordinates, route polyline calculation
Places (POI)Foursquare APINearby attractions, restaurants, museums
WeatherOpenWeatherMap APILive weather used in safety score algorithm
FrontendReact 18 + ViteSPA — fast HMR dev server, optimized production build
StylingTailwind CSS v3Utility-first dark theme throughout
HTTP ClientAxiosAll frontend → backend API calls with JWT headers
Deploy BackendRenderAuto-deploy from GitHub on every push
Deploy FrontendVercelAuto-deploy + SPA routing via vercel.json

👥User Roles

🧳 Traveler

  • Register/login as Traveler
  • Search places on interactive map
  • View real-time safety scores
  • Browse and book verified guides
  • Chat with guides (approval required)
  • Join or browse group tours
  • Track bookings, pay, rate guides

🗺️ Guide

  • Register/login as Guide
  • Maintain a detailed public profile
  • Accept or reject booking requests
  • Manage chat — approve/reject requests
  • Create and manage group tours
  • Kick participants if needed
  • Confirm cash payments received
💡 Role: Both

A user can register with role Both — meaning they can log in as either Traveler or Guide at the login screen, switching which dashboard opens. The same account serves both functions.

📁Folder Structure

Two completely independent sub-projects under one parent folder. They communicate over HTTP and WebSockets.

SafeJourney/
├── journeyshield-backend/
├── .envall secret keys — never committed to git
├── .gitignore
├── package.json
├── server.jsExpress app, Socket.IO setup, all routes mounted here
├── seeder.jsseeds test users, guides, bookings
├── config/
├── db.jsmongoose.connect() using MONGO_URI
└── cloudinary.jsCloudinary SDK configuration
├── controllers/all business logic lives here
├── alertController.js
├── bookingController.js
├── chatController.js
├── geocodeController.js
├── guideController.js
├── incidentController.js
├── paymentController.js
├── placesController.js
├── reviewController.js
├── safetyReportController.js
├── searchController.js
├── tourController.js
└── userController.js
├── data/
└── incidentSeeder.jsseeds incident data for the safety map
├── middleware/
├── authMiddleware.jsJWT protect() — guards all private routes
└── upload.jsmulter + multer-storage-cloudinary
├── models/13 Mongoose schemas
├── alertModel.js
├── bookingModel.js
├── chatLogModel.js
├── chatRequestModel.js
├── conversationModel.js
├── guideModel.js
├── incidentModel.js
├── messageModel.js
├── otpModel.js
├── paymentModel.js
├── reviewModel.js
├── tourModel.js
└── userModel.js
├── routes/thin — import controller, define method + path
├── alertRoutes.js
├── bookingRoutes.js
├── chatRoutes.js
├── geocodeRoutes.js
├── guideRoutes.js
├── incidentRoutes.js
├── paymentRoutes.js
├── placesRoutes.js
├── reviewRoutes.js
├── safetyReportRoutes.js
├── searchRoutes.js
├── tourRoutes.js
├── uploadRoutes.jsPOST /api/upload/avatar
├── userRoutes.js
└── weatherRoutes.js
└── socket/
└── socketHandlers.jsall Socket.IO event registration
└── journeyshield-frontend/
├── .env
├── .gitignore
├── index.html
├── package.json
├── tailwind.config.js
├── vercel.jsonSPA rewrite — all routes → index.html
├── vite.config.js
└── src/
├── App.jsx
├── index.css
├── main.jsx
├── components/
├── common/
├── ProtectedRoute.jsx
├── SafetyModal.jsx
├── SimpleMap.jsx
└── Toast.jsx
└── layout/
└── Navbar.jsx
├── pages/15 pages, role-protected where needed
├── Alerts.jsx
├── Chat.jsx
├── Dashboard.jsx
├── Discover.jsx
├── EditProfile.jsx
├── GroupTours.jsx
├── GuideDashboard.jsx
├── GuideReviews.jsx
├── Guides.jsx
├── Guideviewtravelerprofile.jsx
├── HomePage.jsx
├── Login.jsx
├── MyBookings.jsx
├── Myprofile.jsx
├── Registration.jsx
└── TravelerEditProfile.jsx
└── services/one file per backend API section
├── alertService.js
├── authService.js
├── bookingService.js
├── chatService.js
├── geocodeService.js
├── guidesService.js
├── incidentService.js
├── keepAlive.js
├── placesService.js
├── reviewService.js
├── routeService.js
├── safetyReportService.js
├── searchService.js
├── tourService.js
└── travelerService.js

🗄️Database Models (MongoDB)

All 13 Mongoose schemas. Every model includes timestamps (createdAt, updatedAt) unless noted.

User
name, username, emailString
passwordbcrypt hash
roleTraveler|Guide|Both
avatarCloudinary URL
bio, homeCity, phoneString
travelStyleenum String
interests, languages[String]
emergencyContact{ name, phone, relation }
socialLinks{ instagram, website }
Guide
user→ User (1:1)
avatarCloudinary URL
bio, locationString
experience, phoneString
languages, specialties[String]
availabilityString
pricePerHourNumber (INR)
certificationsString
socialLinks{ instagram, facebook, website }
rating, reviewsNumber (recomputed)
Booking
traveler→ User
guide→ User
statusPending|Accepted|Rejected|Completed
dateDate
isPrivateGroupBoolean
groupMembers[{ name, age, notes }]
isReviewedBoolean — prevents double review
Message
conversation→ Conversation
sender→ User
typetext|image|audio|file|location
textString (max 2000 chars)
fileUrl, fileName, fileSizeCloudinary + meta
durationNumber (seconds, audio)
location{ lat, lng, address }
reactions[{ userId, emoji }]
readBy[→ User]
expiresAtTTL — auto-delete 60 days
Conversation
participants[→ User] always 2
lastMessage{ text, sender, createdAt }
unreadCountMap<userId → count>
ChatRequest
from, to→ User
statuspending|approved|rejected
messageString (max 300)
index { from, to }unique — no duplicate requests
ChatLog
conversationId→ Conversation (unique)
initiator, receiver→ User
requestApprovedAtDate
lastActivityAtDate (no content stored)
Payment
booking→ Booking (unique)
traveler, guide→ User
totalAmount, guideReceivesNumber (INR)
methodCash | Online | Pending
statusinitiated → confirmed → completed
timeline[{ step, completedBy, note }]
expiresAtTTL — auto-delete 6 months
Review
user→ User (traveler)
guide→ Guide
ratingNumber 1–5
commentString
editCountNumber
Tour
guide→ User
title, description, locationString
fixedDateDate
maxParticipants, pricePerPersonNumber
travelers[→ User]
kickedTravelers[{ user, reason }]
statusOpen|Full|Completed|Cancelled
Incident
typeTheft|Harassment|Assault…
descriptionString
locationGeoJSON Point (2dsphere)
severityLow|Medium|High|Critical
reportedBy→ User
OTP
emailString
otpString (6-digit)
createdAtTTL — auto-deleted after 5 min
Alert
location, descriptionString
severitylow|medium|high
typeweather|crowd|traffic|general

🛣️API Endpoints

All routes mounted in server.js. 🔒 means a valid JWT token is required in the Authorization: Bearer <token> header.

Users & Auth

MethodEndpointAuthDescription
POST/api/users/send-otpGenerate & email a 6-digit OTP via Brevo
POST/api/users/verify-registerVerify OTP → create account → return JWT
POST/api/users/loginLogin with email/username + password → return JWT
GET/api/users/me🔒Get own traveler profile
PUT/api/users/me🔒Update traveler profile
GET/api/users/guidesAll guides list (public)
GET/api/users/guide/:userId🔒Full guide profile (traveler viewing)
GET/api/users/traveler/:userId🔒Limited traveler profile (guide viewing)

Guides

MethodEndpointAuthDescription
GET/api/guidesAll guide profiles
GET/api/guides/me🔒Own guide profile
PUT/api/guides/profile🔒Update guide profile (bio, languages, price…)

Bookings

MethodEndpointAuthDescription
POST/api/bookings🔒Traveler creates a booking for a guide
GET/api/bookings/mybookings🔒Traveler's own bookings
GET/api/bookings/guidebookings🔒All requests received by the guide
PUT/api/bookings/:id/status🔒Guide accepts / rejects / completes a booking

Chat

MethodEndpointAuthDescription
POST/api/chat/request🔒Send a chat request to another user
GET/api/chat/request/status/:userId🔒Check connection status
GET/api/chat/requests/pending🔒All pending requests received by me
PUT/api/chat/request/:requestId🔒Approve or reject → creates Conversation on approve
GET/api/chat/conversations🔒My conversations list with unread counts
GET/api/chat/messages/:conversationId🔒Paginated message history
POST/api/chat/messages🔒Send any message type (text / image / audio / file / location)
POST/api/chat/messages/:id/react🔒Toggle emoji reaction on a message
GET/api/chat/unread-count🔒Total unread count across all conversations

Safety, Maps & Places

MethodEndpointAuthDescription
GET/api/safety?city=Safety report by city name
GET/api/safety/current?lat=&lon=Safety report by coordinates
GET/api/safety/incidentsNearby incidents (geospatial query)
GET/api/search/geocode?q=City name → lat/lon (TomTom)
GET/api/search/placesNearby POIs (Foursquare)
GET/api/search/routeRoute polyline between two points (TomTom)
GET/api/alerts?city=🔒Weather alert for a city (OpenWeatherMap)
GET/api/incidentsAll seeded incidents
GET/api/weather/localWeather-based safety report by coords
GET/api/weather/:cityWeather-based safety report by city

Reviews, Tours & Payments

MethodEndpointAuthDescription
POST/api/reviews🔒Submit or update review (requires completed booking)
GET/api/reviews/:guideIdAll public reviews for a guide
GET/api/reviews/:guideId/my🔒My own review for a guide
GET/api/toursAll open group tours
POST/api/tours🔒Guide creates a group tour
DEL/api/tours/:id🔒Guide cancels their tour
POST/api/tours/:id/join🔒Traveler joins a group tour
POST/api/tours/:id/kick🔒Guide removes a traveler (reason required)

🔐Auth Flow

1
Send OTP

User enters their email and desired username. Backend checks for duplicates, generates a 6-digit code, stores it in the OTP collection with a 5-minute MongoDB TTL expiry, and sends it via Brevo's REST API. The code is gone automatically after 5 minutes — no cron job needed.

2
Verify & Create Account

User submits the OTP along with their full details. Backend looks up the OTP document — if it expired MongoDB already deleted it. On success: password is hashed with bcrypt (10 salt rounds), a User document is created, and if role is Guide or Both, an empty Guide document is auto-created. A JWT token is returned and stored in sessionStorage.

3
Login

User submits identifier (email or username) + password + selected role. Backend finds the user, calls bcrypt.compare(). If the user's role is Both, the selected role determines which dashboard they land on. Returns the same JWT format — stored in sessionStorage (clears on tab close).

4
Protected Requests

Every protected API call includes Authorization: Bearer <token>. The protect middleware calls jwt.verify(), fetches the user from DB (without the password field), and attaches it to req.user. All route handlers downstream can use req.user._id and req.user.role.

💬Real-time Chat

Chat uses a dual-channel approach — REST API for loading history and sending media, Socket.IO for real-time delivery.

1
Chat Request System

Either user can initiate. A ChatRequest is created with status pending. The recipient sees it in their Chat sidebar. On approval, a Conversation document is created linking both user IDs. A ChatLog entry is also created as a permanent audit record.

2
Sending Text Messages

Text goes via Socket.IO for zero-latency delivery. The server saves the message to MongoDB, updates the conversation's lastMessage and unreadCount for the other user, then emits newMessage to both the conversation room and the other user's personal socket room.

3
Rich Media Messages

Images, audio recordings, and files are uploaded directly from the browser to Cloudinary using an unsigned upload preset. The Cloudinary URL is then sent to the backend via REST (POST /api/chat/messages) and emitted to the other user via Socket.IO.

4
Typing Indicators & Read Receipts

While typing, the client emits a typing socket event (debounced — auto-cancels after 1.5s). When a user opens a conversation, a markRead event fires — all unread messages get their readBy array updated, and the sender receives a messagesRead event showing ✓✓.

5
Emoji Reactions

Any message can be reacted to with one emoji per user. The server toggles the reaction, broadcasts the updated reactions array to the whole conversation room so all participants see it live.

🗺️Map & Safety

1
Discover Page (Leaflet Map)

User types a city name — the backend geocodes it via TomTom API to get coordinates, then queries Foursquare for nearby places of interest. Results appear as category-colored markers. Clicking a marker shows its name and a "Get Directions" button that calls the TomTom route API and draws a polyline on the map.

2
Safety Score Algorithm

The backend fetches current weather from OpenWeatherMap. Starting score is 100 — it deducts points for extreme temperature (above 40°C or below 5°C), rain or storm conditions, and nearby incident count. Returns a structured report: safety score, level label (Safe / Low Risk / Moderate / High / Danger), weather summary, and list of recent incidents.

3
Incident Markers

Incidents are seeded with GeoJSON Point coordinates and a 2dsphere index in MongoDB. The safety API queries them using $nearSphere to find incidents within a given radius. They appear as red CircleMarkers on the Leaflet map with severity-based colour coding.

📅Booking & Payment

1
Traveler Books a Guide

From the Guide's profile page, traveler picks a date and optionally adds group members. A Booking is created with status Pending. The guide sees it in their dashboard and can Accept or Reject it.

2
Guide Manages the Booking

Guide accepts → status becomes Accepted. After the actual tour happens, guide marks it Completed. This unlocks two things for the traveler: the "Pay Now" button and the "Rate Guide" button.

3
Payment (Cash Mode)

Currently only Cash payment is implemented. Traveler initiates → a Payment document is created with a timeline. Traveler confirms they paid in-person → guide confirms they received it → status reaches completed. The payment record auto-deletes after 6 months via MongoDB TTL.

4
Review

After a completed booking, traveler can submit one star rating (1–5) + comment. The isReviewed flag on the Booking prevents duplicate reviews. The guide's overall rating is recomputed on each new review submission.

🔑Environment Variables

🔒 Security Note

Both .env files are in .gitignore and must never be committed to version control. When deploying, add these as environment variables directly in the Render and Vercel dashboards — never in your code.

Backend — journeyshield-backend/.env

MONGO_URI
Your MongoDB Atlas connection stringrequired
JWT_SECRET
A long random string (32+ chars) for signing tokensrequired
FOURSQUARE_API_KEY
From developer.foursquare.comrequired
OPENWEATHER_API_KEY
From openweathermap.org/apirequired
TOMTOM_API_KEY
From developer.tomtom.comrequired
BREVO_API_KEY
From brevo.com → SMTP & API → API Keysrequired
BREVO_SENDER_EMAIL
A verified sender email in your Brevo accountrequired
CLOUDINARY_CLOUD_NAME
From your Cloudinary dashboardrequired
CLOUDINARY_API_KEY
From your Cloudinary dashboardrequired
CLOUDINARY_API_SECRET
Backend only — never expose this to the frontendrequired
PORT
5000 (default, or let Render assign it)optional

Frontend — journeyshield-frontend/.env

All keys must start with VITE_ — Vite only exposes variables with this prefix to browser code.

VITE_API_URL
http://localhost:5000 (dev) / your Render URL (prod)required
VITE_CLOUDINARY_CLOUD_NAME
Same cloud name as backendrequired
VITE_CLOUDINARY_UPLOAD_PRESET
An unsigned upload preset created in Cloudinary settingsrequired

▶️Running Locally

# 1. Clone the repo
git clone https://github.com/your-username/SafeJourney.git
cd SafeJourney

# 2. Backend setup
cd journeyshield-backend
npm install
# create .env with all required keys (see section above)
npm run dev       # starts on http://localhost:5000 with nodemon

# 3. Frontend setup (new terminal)
cd ../journeyshield-frontend
npm install
# create .env with VITE_API_URL=http://localhost:5000 + Cloudinary keys
npm run dev       # starts on http://localhost:5173

# Optional: seed the database
cd journeyshield-backend
npm run data:import   # creates test Traveler + 2 Guides + sample bookings
node data/incidentSeeder.js  # seeds safety incidents on the map

🌐Deployment

Backend → Render

  • New Web Service → connect GitHub repo
  • Root Directory: journeyshield-backend
  • Build Command: npm install
  • Start Command: node server.js
  • Add all .env keys in the Environment tab
  • Free tier spins down after 15 min — frontend pings /ping every 10 min to prevent this

Frontend → Vercel

  • New Project → import GitHub repo
  • Root Directory: journeyshield-frontend
  • Build Command: npm run build (auto-detected)
  • Add VITE_API_URL pointing to your Render URL
  • vercel.json already present — rewrites all routes to index.html so React Router works on direct URL access

Setup Checklist

Services & Accounts

MongoDB Atlas — cluster created, database user added, Network Access allows 0.0.0.0/0, connection string obtained

Cloudinary — account created, unsigned upload preset named journeyShield created, cloud name + API key + secret obtained

Brevo — account created, sender email domain verified, API key obtained

Foursquare, OpenWeatherMap, TomTom — API keys obtained and added to backend .env

Local Development

Both .env files created with all required keys filled in

npm install run in both backend and frontend folders

Backend starts: npm run dev shows MongoDB Connected and Server running on port 5000

Frontend starts: npm run dev opens in browser with no console errors

Registration flow works: OTP email arrives, account created, redirected to dashboard

Chat works: messages send and receive in real time, typing indicator visible

Map loads on Discover page: search finds places, incidents appear as markers

Safety score loads on Alerts page for both current location and searched city

Deployment

Backend deployed on Render with all env vars set — /ping endpoint responds

Frontend deployed on Vercel with VITE_API_URL pointing to the Render URL

Full registration → login → chat flow tested on the live deployed URLs