Food Express API
A REST API for food delivery service with JWT authentication, role-based access control, and comprehensive input validation using Joi.
Features
- ✅ Authentication & Authorization: JWT-based stateless authentication with role-based access control
- ✅ Input Validation: Comprehensive validation using Joi for all endpoints
- ✅ Security: Password hashing with bcrypt, secure token handling
- ✅ Error Handling: Detailed validation error messages and proper HTTP status codes
- ✅ Data Integrity: MongoDB ObjectId validation, data type validation, and business rule enforcement
Authentication & Authorization
Overview
- Public Access: User registration, reading restaurants and menus
- Authenticated Access: Required for all write operations and user account management
- Admin Access: Full control over all resources
- User Access: Can only manage their own account
JWT Token Format
Include the JWT token in requests as:
Authorization: Bearer <your-jwt-token>
Input Validation
All API endpoints include comprehensive input validation using Joi:
Validation Features
- Email Validation: Proper email format checking
- Password Requirements: Minimum 6 characters, maximum 50 characters
- Username Rules: 3-30 characters, alphanumeric and underscores only
- MongoDB ObjectId: Proper 24-character hexadecimal format validation
- Price Validation: Positive numbers with maximum 2 decimal places
- Phone Number: International phone number format validation
- Pagination: Page and limit validation with reasonable bounds
- Enum Values: Predefined category and role validation
Validation Error Response
{
"error": "Validation failed",
"details": [
"Username must be at least 3 characters long",
"Please provide a valid email address"
]
}
API Endpoints
User Management
Register User (Public)
POST /users
Content-Type: application/json
{
"email": "[email protected]", // Required: Valid email format
"username": "username", // Required: 3-30 chars, alphanumeric + underscore
"password": "password123", // Required: 6-50 characters
"role": "user" // Optional: "user" or "admin", defaults to "user"
}
Login User (Public)
POST /users/login
Content-Type: application/json
{
"email": "[email protected]", // Required: Valid email format
"password": "password123" // Required
}
Response:
{
"user": {
"_id": "...",
"email": "[email protected]",
"username": "username",
"role": "user"
},
"token": "jwt-token-here"
}
Get All Users (Admin Only)
GET /users
Authorization: Bearer <admin-jwt-token>
Get User by ID (User can only access own data, Admin can access any)
GET /users/:id
Authorization: Bearer <jwt-token>
Update User (User can only update own data, Admin can update any)
PUT /users/:id // ID must be valid MongoDB ObjectId
Authorization: Bearer <jwt-token>
Content-Type: application/json
{
"email": "[email protected]", // Optional: Valid email format
"username": "newusername", // Optional: 3-30 chars, alphanumeric + underscore
"password": "newpassword123", // Optional: 6-50 characters
"role": "admin" // Optional: Only admin can change role
}
Delete User (User can only delete own account, Admin can delete any)
DELETE /users/:id // ID must be valid MongoDB ObjectId
Authorization: Bearer <jwt-token>
Restaurant Management
Get All Restaurants (Public)
GET /restaurants
Get Restaurant by ID (Public)
GET /restaurants/:id
Create Restaurant (Admin Only)
POST /restaurants
Authorization: Bearer <admin-jwt-token>
Content-Type: application/json
{
"name": "Restaurant Name", // Required: 2-100 characters
"address": "123 Main St", // Required: 10-200 characters
"phone": "555-1234", // Required: Valid phone number format
"opening_hours": "9AM-10PM" // Required: 5-50 characters
}
Update Restaurant (Admin Only)
PUT /restaurants/:id // ID must be valid MongoDB ObjectId
Authorization: Bearer <admin-jwt-token>
Content-Type: application/json
{
"name": "Updated Name", // Optional: 2-100 characters
"address": "456 New St", // Optional: 10-200 characters
"phone": "555-5678", // Optional: Valid phone number format
"opening_hours": "8AM-11PM" // Optional: 5-50 characters
}
Delete Restaurant (Admin Only)
DELETE /restaurants/:id
Authorization: Bearer <admin-jwt-token>
Menu Management
Get All Menu Items (Public)
GET /menus
Get Menu Item by ID (Public)
GET /menus/:id
Get Menu Items by Restaurant (Public)
GET /menus/restaurant/:restaurantId
Create Menu Item (Admin Only)
POST /menus
Authorization: Bearer <admin-jwt-token>
Content-Type: application/json
{
"restaurant_id": "restaurant-object-id", // Required: Valid MongoDB ObjectId
"name": "Menu Item Name", // Required: 2-100 characters
"description": "Item description", // Required: 10-500 characters
"price": 12.99, // Required: Positive number, max 2 decimals
"category": "appetizer" // Required: Valid category enum
}
Valid categories: appetizer, main course, dessert, beverage, snack, salad, soup, pasta, pizza, burger, sandwich, seafood, vegetarian, vegan, other
Update Menu Item (Admin Only)
PUT /menus/:id // ID must be valid MongoDB ObjectId
Authorization: Bearer <admin-jwt-token>
Content-Type: application/json
{
"restaurant_id": "restaurant-object-id", // Optional: Valid MongoDB ObjectId
"name": "Updated Name", // Optional: 2-100 characters
"description": "Updated description", // Optional: 10-500 characters
"price": 15.99, // Optional: Positive number, max 2 decimals
"category": "main course" // Optional: Valid category enum
}
Delete Menu Item (Admin Only)
DELETE /menus/:id
Authorization: Bearer <admin-jwt-token>
Security Features
- Password Hashing: All passwords are hashed using bcrypt with 10 salt rounds
- JWT Authentication: Stateless authentication with 24-hour token expiration
- Role-Based Access Control: Admin and user roles with different permissions
- Resource Authorization: Users can only access/modify their own resources
- Password Exclusion: Passwords are never returned in API responses
- Input Validation: Comprehensive validation prevents malicious input and data corruption
- MongoDB ObjectId Validation: Prevents invalid database queries and injection attempts
Validation Rules
User Validation
- Email: Must be valid email format
- Username: 3-30 characters, alphanumeric and underscores only
- Password: 6-50 characters
- Role: Must be "user" or "admin"
Restaurant Validation
- Name: 2-100 characters
- Address: 10-200 characters
- Phone: Valid international phone number format
- Opening Hours: 5-50 characters
Menu Item Validation
- Name: 2-100 characters
- Description: 10-500 characters
- Price: Positive number with maximum 2 decimal places
- Category: Must be one of the predefined categories
- Restaurant ID: Valid MongoDB ObjectId
Common Validation
- MongoDB ObjectIds: 24-character hexadecimal format
- Pagination: Page ≥ 1, Limit 1-100
- Sorting: Valid sort fields and order (asc/desc)
Error Responses
400 Bad Request (Validation Error)
{
"error": "Validation failed",
"details": [
"Username must be at least 3 characters long",
"Price must be a positive number"
]
}
401 Unauthorized
{
"error": "Token required"
}
403 Forbidden
{
"error": "Access denied. You can only access your own resources."
}
404 Not Found
{
"error": "User not found"
}
Development
Install dependencies:
npm installSet up environment variables in
.env:JWT_SECRET=supersecretjwtkeyStart development server:
npm run dev OR npm run dev:tsxBuild for production:
npm run buildRun sever from './dist'
node index.js
Testing
The project includes comprehensive tests using Jest and Supertest for API testing.
Running Tests
- Run all tests:
npm test - Run tests in watch mode (automatically re-run on file changes):
npm run test:watch - Run tests with coverage report:
npm run test:coverage
Test Structure
Tests are organized in the tests/ directory with the following structure:
models/: Tests for data models (User, Restaurant, MenuItem)middlewares/: Tests for authentication, authorization, and validation middlewaresroutes/: Tests for API route handlersdatabase.test.ts: Database connection testssetup.ts: Test configuration and setup
Database
- MongoDB connection:
mongodb://127.0.0.1:27017/foodexpress - Collections:
user,restaurants,menus
Docker
Run the application with Docker and Docker Compose.
- Build and run with Compose (production image):
docker-compose up --build
- Run in background:
docker-compose up -d --build
- Stop and remove containers:
docker-compose down
The compose setup includes a mongo service and the app service. The app is configured with MONGO_URI=mongodb://mongo:27017/foodexpress and exposes port 3000.
If you prefer to build the image manually:
docker build -t foodexpress:latest .
docker run -e MONGO_URI="mongodb://host.docker.internal:27017/foodexpress" -p 3000:3000 foodexpress:latest