A RESTful backend API for a travel/tourism booking platform built with Express.js and MongoDB. It handles travel destination management and user booking operations with JWT-based authentication and role-based access control.
- Runtime: Node.js
- Framework: Express.js v5
- Database: MongoDB (native driver v7)
- Authentication: JWT with JWKS validation (jose-cjs)
- Deployment: Vercel (serverless)
- Node.js (v18 or higher recommended)
- MongoDB Atlas account or local MongoDB instance
- A client application that exposes a JWKS endpoint (e.g., Clerk, NextAuth)
git clone <repository-url>
cd wanderlast-server
npm installCreate a .env file in the root directory:
PORT=5000
MONGO_DB_URI=mongodb+srv://<username>:<password>@<cluster>.mongodb.net/?retryWrites=true&w=majority
CLIENT_URL=http://localhost:3000| Variable | Description |
|---|---|
PORT |
Port number the server runs on |
MONGO_DB_URI |
MongoDB connection string |
CLIENT_URL |
Frontend client URL (used to fetch JWKS for token verification) |
Development (with hot-reload):
npm run serverProduction:
node index.jsDatabase name: wanderlast
| Collection | Description |
|---|---|
destinations |
Travel destinations managed by admins |
bookings |
User booking records |
The server validates JWTs using JWKS (JSON Web Key Set) fetched from the client application at CLIENT_URL/api/auth/jwks. Tokens must be sent in the Authorization header as a Bearer token.
Authorization: Bearer <token>
| Role | Permissions |
|---|---|
| User (authenticated) | Create bookings, view own bookings, delete own bookings |
| Admin | All user permissions + full CRUD on destinations |
verifyJWT— Validates the JWT and attaches the decoded payload toreq.user. Used for authenticated user routes.verifyAdminJWT— Validates the JWT and checks thatpayload.role === "admin". Returns 403 if the user is not an admin.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | / |
None | Returns server status |
Response:
Server is cooking!
GET /destinations
Auth: None (public)
Response:
[
{
"_id": "664f1a2b...",
"name": "Bali, Indonesia",
"description": "Tropical paradise...",
"image": "https://...",
"price": 1200
}
]GET /destinations/:id
Auth: None (public)
Parameters:
| Param | Type | Description |
|---|---|---|
id |
string | MongoDB ObjectId of the destination |
Response:
{
"_id": "664f1a2b...",
"name": "Bali, Indonesia",
"description": "Tropical paradise...",
"image": "https://...",
"price": 1200
}POST /destinations
Auth: Admin JWT required
Request Body:
{
"name": "Bali, Indonesia",
"description": "Tropical paradise with stunning beaches",
"image": "https://example.com/bali.jpg",
"price": 1200
}Response:
{
"success": true,
"message": "Destination added successfully",
"id": "664f1a2b..."
}PATCH /destinations/:id
Auth: Admin JWT required
Parameters:
| Param | Type | Description |
|---|---|---|
id |
string | MongoDB ObjectId of the destination |
Request Body: (partial update — only include fields to change)
{
"price": 1500
}Response:
{
"success": true,
"message": "Destination updated successfully",
"modifiedCount": 1
}DELETE /destinations/:id
Auth: Admin JWT required
Parameters:
| Param | Type | Description |
|---|---|---|
id |
string | MongoDB ObjectId of the destination |
Response:
{
"success": true,
"message": "Destination deleted successfully",
"deletedCount": 1
}POST /bookings
Auth: JWT required (any authenticated user)
Request Body:
{
"userId": "user_abc123",
"destinationId": "664f1a2b...",
"destinationName": "Bali, Indonesia",
"date": "2026-07-15",
"guests": 2
}Response:
{
"success": true,
"message": "Booking added successfully",
"id": "664f2c3d..."
}GET /bookings/:userId
Auth: JWT required (owner only — userId must match the token's sub claim)
Parameters:
| Param | Type | Description |
|---|---|---|
userId |
string | The authenticated user's ID |
Response:
[
{
"_id": "664f2c3d...",
"userId": "user_abc123",
"destinationId": "664f1a2b...",
"destinationName": "Bali, Indonesia",
"date": "2026-07-15",
"guests": 2
}
]Error (403): Returned if the authenticated user tries to access another user's bookings.
DELETE /bookings/:id
Auth: JWT required (owner only — booking must belong to the authenticated user)
Parameters:
| Param | Type | Description |
|---|---|---|
id |
string | MongoDB ObjectId of the booking |
Response:
{
"success": true,
"message": "Booking deleted successfully",
"deletedCount": 1
}Error (404): Booking not found. Error (403): User attempting to delete another user's booking.
All error responses follow this format:
{
"success": false,
"message": "Error description"
}| Status Code | Meaning |
|---|---|
| 401 | Unauthorized — missing or invalid token |
| 403 | Forbidden — insufficient permissions or accessing another user's resource |
| 404 | Not Found — resource does not exist |
The server is configured for deployment on Vercel as a serverless function.
The vercel.json routes all requests to index.js and sets CORS headers at the platform level:
- All HTTP methods supported (GET, POST, PUT, DELETE, PATCH, OPTIONS)
Access-Control-Allow-Origin: *- Authorization header allowed
vercel --prodwanderlast-server/
├── index.js # Main server file (routes, middleware, DB connection)
├── package.json # Dependencies and scripts
├── vercel.json # Vercel deployment configuration
├── .env # Environment variables (not committed)
└── .gitignore # Ignored files (node_modules, .env)
| Package | Version | Purpose |
|---|---|---|
| express | ^5.2.1 | Web framework |
| mongodb | ^7.2.0 | MongoDB native driver |
| jose-cjs | ^6.2.3 | JWT/JWKS verification |
| cors | ^2.8.6 | Cross-Origin Resource Sharing |
| dotenv | ^17.4.2 | Environment variable management |