GitHub avatar

Fox's Blog

← Back to projects

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>

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

  1. Password Hashing: All passwords are hashed using bcrypt with 10 salt rounds
  2. JWT Authentication: Stateless authentication with 24-hour token expiration
  3. Role-Based Access Control: Admin and user roles with different permissions
  4. Resource Authorization: Users can only access/modify their own resources
  5. Password Exclusion: Passwords are never returned in API responses
  6. Input Validation: Comprehensive validation prevents malicious input and data corruption
  7. 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
  • 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

  1. Install dependencies:

    npm install
    
  2. Set up environment variables in .env:

    JWT_SECRET=supersecretjwtkey
    
  3. Start development server:

    npm run dev OR npm run dev:tsx
    
  4. Build for production:

    npm run build
    
  5. Run 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 middlewares
  • routes/: Tests for API route handlers
  • database.test.ts: Database connection tests
  • setup.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