REST API powering AI-driven India travel planning
API Reference · Quick Start · Environment Variables · Architecture · Deployment
TripSage's backend is a Node.js / Express REST API backed by MongoDB and driven by two AI providers:
| Provider | Used for |
|---|---|
| Groq (LLaMA 3.3 70B) | Destination search, hotel generation, restaurant generation, Full day-by-day itinerary generation |
The server handles authentication, all CRUD operations across 8 resource types, AI generation with rate limiting, transactional email via Nodemailer, and real-time share-link generation.
- 🔐 JWT Authentication — HTTP-only cookie + Bearer token, bcrypt password hashing
- 🤖 Dual AI Integration — Groq for fast data generation, and for deep itinerary planning
- 📧 Transactional Email — Nodemailer welcome emails on registration (Gmail / Resend / Brevo)
- 🛡️ Security — Helmet headers, CORS, global + per-route rate limiting
- 🗄️ MongoDB / Mongoose — Full schema validation, indexes, and lean queries
- 🌱 Database Seeding — One-command seed for 40+ curated India destinations
- 🩺 Health endpoint —
/api/healthfor uptime monitors - 📝 Request logging — Morgan dev logger in development
| Tool | Version |
|---|---|
| Node.js | ≥ 18.x (22.x recommended) |
| npm | ≥ 9.x |
| MongoDB | Local 7.x or MongoDB Atlas (free tier works) |
git clone https://github.com/your-username/tripsage-backend.git
cd tripsage-backend
npm installcp .env.example .envEdit .env — at minimum you need MONGO_URI, JWT_SECRET, and one AI key. See the full Environment Variables table below.
npm run seedThis populates MongoDB with 40+ curated Indian destinations, sample hotels, restaurants and attractions so the app works out of the box.
npm run devThe API will be available at http://localhost:5000.
🚀 TripSage API running → http://localhost:5000
📊 Environment: development
🌐 Frontend: http://localhost:5173
🩺 Health: http://localhost:5000/api/health
Copy .env.example to .env and fill in each value.
cp .env.example .env| Variable | Required | Default | Description |
|---|---|---|---|
NODE_ENV |
✅ | development |
development or production |
PORT |
✅ | 5000 |
HTTP server port |
MONGO_URI |
✅ | — | MongoDB connection string |
JWT_SECRET |
✅ | — | Long random string for signing tokens |
JWT_EXPIRES_IN |
— | 7d |
Token expiry (e.g. 7d, 24h) |
GROQ_API_KEY |
✅ | — | Get free at console.groq.com |
ANTHROPIC_API_KEY |
✅ | — | Get at console.anthropic.com |
FRONTEND_URL |
✅ | http://localhost:5173 |
Allowed CORS origin |
COOKIE_SECRET |
✅ | — | Secret for signing cookies |
SMTP_HOST |
— | smtp.gmail.com |
SMTP server hostname |
SMTP_PORT |
— | 587 |
SMTP port (587 = STARTTLS) |
SMTP_SECURE |
— | false |
true for port 465 (SSL) |
SMTP_USER |
— | — | Your email address |
SMTP_PASS |
— | — | App Password (Gmail) or API key (Resend) |
Email is optional. If
SMTP_USER/SMTP_PASSare not set, emails are silently skipped — registration still works normally.
# JWT_SECRET — generate a strong random string
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
# COOKIE_SECRET — same method
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"src/
├── config/
│ └── db.js # MongoDB connection
├── controllers/
│ ├── authController.js # register / login / logout / me
│ ├── destinationController.js
│ ├── hotelController.js
│ ├── restaurantController.js
│ ├── itineraryController.js
│ ├── generateController.js # AI generation entrypoint
│ ├── reviewController.js
│ └── userController.js
├── middleware/
│ ├── auth.js # requireAuth / optionalAuth guards
│ └── errorHandler.js # global error handler
├── models/
│ ├── User.js
│ ├── Destination.js
│ ├── Hotel.js
│ ├── Restaurant.js
│ ├── Attraction.js
│ ├── Itinerary.js
│ ├── Review.js
│ ├── SavedPlan.js
│ └── Share.js
├── routes/
│ ├── auth.js
│ ├── destinations.js
│ ├── hotels.js
│ ├── restaurants.js
│ ├── itineraries.js
│ ├── generate.js
│ ├── users.js
│ └── reviews.js
├── services/
│ ├── groqService.js # Groq LLaMA — hotels, restaurants, destination search
│ ├── aiService.js # Anthropic Claude — itinerary generation
│ ├── emailService.js # Nodemailer transactional emails
│ └── promptService.js # Prompt templates
├── utils/
│ └── jwt.js # Token sign / verify helpers
├── seed/
│ └── seed.js # Database seeder
└── index.js # App entry point
All endpoints are prefixed with /api. Authenticated routes require either:
Authorization: Bearer <token>header, ortokenHTTP-only cookie (set automatically on login)
| Method | Endpoint | Auth | Body | Description |
|---|---|---|---|---|
POST |
/auth/register |
— | { name, email, password } |
Create account + sends welcome email |
POST |
/auth/login |
— | { email, password } |
Login, sets cookie + returns token |
POST |
/auth/logout |
— | — | Clears auth cookie |
GET |
/auth/me |
✅ Required | — | Get current user profile |
Register response:
{
"success": true,
"data": {
"_id": "...",
"name": "Arjun Sharma",
"email": "arjun@example.com",
"role": "user",
"createdAt": "2026-03-01T10:00:00Z"
},
"message": "User created successfully."
}| Method | Endpoint | Auth | Query / Body | Description |
|---|---|---|---|---|
GET |
/destinations |
— | ?category=&search=&limit=&offset= |
List all destinations |
POST |
/destinations/search |
— | { query: "beach trip in winter" } |
Groq AI natural-language search |
GET |
/destinations/:slug |
— | — | Single destination + hotels, restaurants, attractions |
GET |
/destinations/:slug/hotels |
— | ?tier=economy|standard|luxury |
Hotels for a destination (Groq-generated if empty) |
GET |
/destinations/:slug/restaurants |
— | ?isVeg=true&priceRange=budget |
Restaurants (Groq-generated if empty) |
GET |
/destinations/:slug/attractions |
— | ?category= |
Attractions list |
AI Search example:
curl -X POST http://localhost:5000/api/destinations/search \
-H "Content-Type: application/json" \
-d '{ "query": "hill station weekend trip from Delhi" }'{
"destinations": [
{
"_id": "ai_1709123456_0",
"name": "Shimla",
"state": "Himachal Pradesh",
"category": "Hills",
"description": "The colonial summer capital...",
"bestSeason": "Mar–Jun & Dec–Jan",
"avgDurationDays": 3,
"minPrice": 2500,
"highlights": ["The Mall", "Jakhu Temple", "Kufri", "Christ Church"],
"heroImageUrl": "https://images.unsplash.com/...",
"isAiGenerated": true
}
],
"query": "hill station weekend trip from Delhi",
"total": 6
}| Method | Endpoint | Auth | Query | Description |
|---|---|---|---|---|
GET |
/hotels |
— | ?destinationId=&tier=&minPrice=&maxPrice= |
List hotels |
GET |
/hotels/:hotelId |
— | — | Single hotel detail |
| Method | Endpoint | Auth | Query | Description |
|---|---|---|---|---|
GET |
/restaurants |
— | ?destinationId=&isVeg=&priceRange= |
List restaurants |
GET |
/restaurants/:restaurantId |
— | — | Single restaurant detail |
⚠️ Rate limited to 5 requests / minute per IP. Each AI call uses Claude.
| Method | Endpoint | Auth | Body | Description |
|---|---|---|---|---|
POST |
/generate |
Optional | See below | Generate full AI itinerary |
GET |
/generate/packages |
— | ?days=&adults=&children=&dailyBudget= |
Instant price packages (no AI) |
Generate body:
{
"destination": "Jaipur, Rajasthan",
"originCity": "Delhi",
"days": 4,
"adults": 2,
"children": 0,
"budget": "standard",
"tripStyle": ["Heritage", "Food"],
"accommodation": "hotel"
}| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/itineraries |
✅ Required | List user's own itineraries |
GET |
/itineraries/user/saved |
✅ Required | List saved (bookmarked) itineraries |
GET |
/itineraries/shared/:token |
— | Public share link lookup |
GET |
/itineraries/:id |
Optional | Get single itinerary |
DELETE |
/itineraries/:id |
✅ Required | Delete an itinerary |
POST |
/itineraries/:id/save |
✅ Required | Bookmark an itinerary |
DELETE |
/itineraries/:id/save |
✅ Required | Remove bookmark |
POST |
/itineraries/:id/share |
Optional | Generate shareable link |
All user routes require authentication.
| Method | Endpoint | Body | Description |
|---|---|---|---|
GET |
/users/me |
— | Get full profile |
PATCH |
/users/me |
{ name, username } |
Update profile details |
PATCH |
/users/me/password |
{ currentPassword, newPassword } |
Change password |
GET |
/users/me/saved |
— | Get saved plans |
DELETE |
/users/me/saved/:itineraryId |
— | Remove a saved plan |
| Method | Endpoint | Auth | Body / Query | Description |
|---|---|---|---|---|
GET |
/reviews |
— | ?destinationId= |
List reviews for a destination |
POST |
/reviews |
✅ Required | { destinationId, rating, comment, tripDate } |
Add review |
DELETE |
/reviews/:id |
✅ Required | — | Delete own review |
GET /api/health{
"status": "ok",
"timestamp": "2026-03-01T10:00:00.000Z",
"service": "tripsage-api",
"env": "production"
}Used for destination search, hotel generation, and restaurant generation. Falls back to generating and caching data in MongoDB when a collection is empty for a given destination.
Model: llama-3.3-70b-versatile
Temperature: 0.4 (low for factual structured data)
Max tokens: 1 400 (search) / 2 048 (hotels, restaurants)
Used for full itinerary planning. Each call generates a complete multi-day trip with day-by-day activities, hotel recommendations, food suggestions, budgets and travel tips.
Model: claude-sonnet-4-5-20251001 (or latest)
Max tokens: 4 096
| Endpoint | Limit | Window |
|---|---|---|
| All routes (global) | 300 req | 15 min |
POST /generate |
5 req | 1 min |
Transactional emails are sent via Nodemailer. The service is fire-and-forget — email failures are logged but never crash a request.
Currently implemented:
- ✅ Welcome email on registration
Supported providers:
| Provider | Free tier | Setup |
|---|---|---|
| Gmail | ~500/day | 2FA → App Password |
| Resend | 3 000/month | Swap SMTP_HOST=smtp.resend.com |
| Brevo | 300/day | Swap SMTP creds |
| SendGrid | 100/day | Swap SMTP creds |
To disable emails entirely, leave SMTP_USER and SMTP_PASS blank in .env.
| Model | Key fields |
|---|---|
User |
name, email, passwordHash, role, savedPlans |
Destination |
name, slug, state, category, heroImageUrl, pricing[], isActive |
Hotel |
destination (ref), name, tier, starRating, pricePerNight, amenities[] |
Restaurant |
destination (ref), name, cuisine[], priceRange, isVeg, rating |
Attraction |
destination (ref), name, category, entryFee, visitDurationMins |
Itinerary |
user (ref), destination (ref), days[], budget, generatedBy, shareToken |
Review |
user (ref), destination (ref), rating, comment, tripDate |
SavedPlan |
user (ref), itinerary (ref), note |
Share |
itinerary (ref), token (unique), viewCount |
npm run seedPopulates 40+ destinations with hotels, restaurants and attractions. Safe to re-run — uses upsert logic.
- Push your code to GitHub
- Connect repository in Railway or Render dashboard
- Set all environment variables in the dashboard
- Deploy — both platforms auto-detect Node.js
# On your server
git clone https://github.com/your-username/tripsage-backend.git
cd tripsage-backend
npm install --production
# Install PM2
npm install -g pm2
# Start with PM2
pm2 start src/index.js --name tripsage-api
pm2 save
pm2 startup
# Nginx reverse proxy (port 80 → 5000)
# Add to /etc/nginx/sites-available/tripsage:
# location /api { proxy_pass http://localhost:5000; }FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 5000
CMD ["node", "src/index.js"]docker build -t tripsage-api .
docker run -p 5000:5000 --env-file .env tripsage-api-
NODE_ENV=productionis set -
JWT_SECRETis a strong random string (64+ chars) -
COOKIE_SECRETis set -
FRONTEND_URLis your real domain - MongoDB Atlas cluster is used (not localhost)
- Rate limits are tuned for expected traffic
- HTTPS is enforced at the reverse proxy level
-
SMTP_USER/SMTP_PASSare configured - Health check endpoint is monitored (
/api/health)
| Command | Description |
|---|---|
npm run dev |
Start dev server with hot reload (nodemon) |
npm run start |
Start production server |
npm run seed |
Seed the database with destinations and sample data |
- Passwords are hashed with bcryptjs (10 salt rounds) — plaintext passwords are never stored
- JWT tokens are signed with
HS256— rotateJWT_SECRETto invalidate all existing sessions - HTTP-only cookies prevent XSS token theft
- Helmet sets secure response headers on all routes
- CORS is restricted to
FRONTEND_URLonly - The
SMTP_PASSin.envmust be a Gmail App Password, not your account password
MIT © 2026 TripSage