Skip to main content
CampusBite’s menu management system allows store employees to maintain their restaurant’s menu with full control over items, pricing, categories, and availability status. Each menu item in the system contains the following information:
backend/src/models/MenuItem.js
const menuItemSchema = new mongoose.Schema({
  store_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Store', required: true, index: true },
  name: { type: String, required: true, trim: true },
  description: { type: String, default: null },
  price: { type: Number, required: true, min: 0 },
  image_url: { type: String, default: null },
  category: { type: String, default: null },
  is_available: { type: Boolean, default: true },
});
  • store_id: Links the item to the store owner
  • name: The display name of the menu item (required)
  • description: Optional details about the item
  • price: Price in rupees (must be non-negative)
  • image_url: Path to the uploaded item image
  • category: Optional category for grouping items (e.g., “Beverages”, “Main Course”)
  • is_available: Controls whether customers can order this item

Adding menu items

Store employees can add new items to their menu with optional image uploads.
1

Verify store ownership

The system checks that the authenticated user owns a store before allowing them to add items.
2

Process uploaded image

If an image is uploaded, the file is processed and the URL is generated using the upload configuration.
3

Create menu item

The new menu item is created in the database with all provided details.
4

Return formatted item

The newly created item is formatted and returned to the client.
backend/src/controllers/menuController.js
export const addMenuItem = async (req, res, next) => {
  try {
    const userId = req.user.id;

    // Verify store ownership
    const store = await Store.findOne({ owner_id: userId });
    if (!store) {
      return res.status(404).json({
        success: false,
        message: 'You do not own a store.',
      });
    }

    const { name, description, price, category } = req.body;
    let imageUrl = null;
    
    // Process uploaded image
    if (req.file) {
      imageUrl = resolveUploadedFilePath(req.file.filename);
    }

    // Create menu item
    const menuItem = await MenuItem.create({
      store_id: store._id,
      name,
      description: description || null,
      price: Number(price),
      image_url: imageUrl,
      category: category || null,
    });

    res.status(201).json({
      success: true,
      message: 'Menu item added successfully.',
      data: { menuItem: formatMenuItem(menuItem) },
    });
  } catch (error) {
    next(error);
  }
};
Images are optional when creating menu items. If you provide an image, it’s uploaded via multipart form data and stored using the configured upload middleware.

Updating menu items

You can update any aspect of a menu item, including its availability status, price, or image.
backend/src/controllers/menuController.js
export const updateMenuItem = async (req, res, next) => {
  try {
    const { id } = req.params;
    const userId = req.user.id;

    // Find and populate store info
    const item = await MenuItem.findById(id).populate('store_id', 'owner_id');

    if (!item) {
      return res.status(404).json({
        success: false,
        message: 'Menu item not found.',
      });
    }

    // Verify authorization
    if (item.store_id.owner_id.toString() !== userId) {
      return res.status(403).json({
        success: false,
        message: 'You are not authorized to update this menu item.',
      });
    }

    const { name, description, price, category, is_available } = req.body;

    // Update fields
    if (name) item.name = name;
    if (description !== undefined) item.description = description;
    if (price !== undefined) item.price = Number(price);
    if (category !== undefined) item.category = category;
    if (is_available !== undefined) {
      item.is_available = is_available === true || is_available === 'true';
    }

    // Update image if provided
    if (req.file) {
      item.image_url = resolveUploadedFilePath(req.file.filename);
    }

    await item.save();

    res.json({
      success: true,
      message: 'Menu item updated successfully.',
      data: { menuItem: formatMenuItem(item) },
    });
  } catch (error) {
    next(error);
  }
};
Only the store owner can update menu items. The system validates that the authenticated user owns the store associated with the menu item before allowing any modifications.

Toggling availability

Store employees can quickly toggle whether a menu item is available for ordering without deleting it.
backend/src/controllers/menuController.js
export const toggleAvailability = async (req, res, next) => {
  try {
    const { id } = req.params;
    const userId = req.user.id;

    const item = await MenuItem.findById(id).populate('store_id', 'owner_id');

    if (!item) {
      return res.status(404).json({
        success: false,
        message: 'Menu item not found.',
      });
    }

    if (item.store_id.owner_id.toString() !== userId) {
      return res.status(403).json({
        success: false,
        message: 'You are not authorized to update this menu item.',
      });
    }

    // Toggle availability
    item.is_available = !item.is_available;
    await item.save();

    res.json({
      success: true,
      message: `Menu item is now ${item.is_available ? 'available' : 'unavailable'}.`,
      data: { menuItem: formatMenuItem(item) },
    });
  } catch (error) {
    next(error);
  }
};
Use availability toggle when:
  • An item is temporarily out of stock
  • You want to hide seasonal items without losing the data
  • You may want to re-enable the item later
Use deletion when:
  • The item is permanently removed from the menu
  • You want to clean up old or unused items
  • The item was created by mistake

Deleting menu items

You can permanently remove menu items from your store’s menu.
backend/src/controllers/menuController.js
export const deleteMenuItem = async (req, res, next) => {
  try {
    const { id } = req.params;
    const userId = req.user.id;

    const item = await MenuItem.findById(id).populate('store_id', 'owner_id');

    if (!item) {
      return res.status(404).json({
        success: false,
        message: 'Menu item not found.',
      });
    }

    if (item.store_id.owner_id.toString() !== userId) {
      return res.status(403).json({
        success: false,
        message: 'You are not authorized to delete this menu item.',
      });
    }

    await MenuItem.deleteOne({ _id: id });

    res.json({
      success: true,
      message: 'Menu item deleted successfully.',
    });
  } catch (error) {
    next(error);
  }
};
Deleting a menu item is permanent and cannot be undone. Consider using the availability toggle instead if you might want to restore the item later.

Image uploads

Menu item images are handled through multipart form data uploads. The system uses middleware to process and store uploaded files.

Upload flow

1

Client sends multipart request

The client sends a POST or PUT request with Content-Type: multipart/form-data including the image file.
2

Multer middleware processes file

The upload middleware intercepts the request and handles the file upload, saving it to the configured directory.
3

Generate file path

The resolveUploadedFilePath() function generates the public URL for the uploaded image.
4

Store URL in database

The image URL is saved in the menu item’s image_url field.
Example upload usage
import { resolveUploadedFilePath } from '../config/uploads.js';

// In controller after file is uploaded
if (req.file) {
  imageUrl = resolveUploadedFilePath(req.file.filename);
}

Authorization checks

All menu management operations verify that the authenticated user owns the store associated with the menu item.
Authorization pattern
// Find item with store information
const item = await MenuItem.findById(id).populate('store_id', 'owner_id');

if (!item) {
  return res.status(404).json({
    success: false,
    message: 'Menu item not found.',
  });
}

// Verify ownership
if (item.store_id.owner_id.toString() !== userId) {
  return res.status(403).json({
    success: false,
    message: 'You are not authorized to update this menu item.',
  });
}
The authorization check uses .populate('store_id', 'owner_id') to fetch only the owner_id field from the related Store document, minimizing database overhead while ensuring secure access control.

API endpoints

Here’s a summary of the menu management endpoints:
EndpointMethodDescriptionAuth Required
/api/menu/itemsPOSTAdd a new menu itemStore employee
/api/menu/items/:idPUTUpdate a menu itemStore employee
/api/menu/items/:idDELETEDelete a menu itemStore employee
/api/menu/items/:id/togglePATCHToggle item availabilityStore employee
/api/stores/:storeId/menuGETGet all menu items for a storePublic
Add menu item (with image):
curl -X POST http://localhost:5000/api/menu/items \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -F "name=Masala Dosa" \
  -F "description=Crispy rice crepe with potato filling" \
  -F "price=60" \
  -F "category=South Indian" \
  -F "image=@/path/to/image.jpg"
Toggle availability:
curl -X PATCH http://localhost:5000/api/menu/items/ITEM_ID/toggle \
  -H "Authorization: Bearer YOUR_TOKEN"

Best practices

1

Use categories consistently

Define a consistent set of categories for your store (e.g., “Beverages”, “Main Course”, “Desserts”) to help customers browse your menu.
2

Optimize images

Compress images before uploading to reduce load times. Recommended size: 800x600 pixels or smaller.
3

Toggle instead of delete

Use the availability toggle for temporary unavailability rather than deleting and recreating items.
4

Write clear descriptions

Include allergen information and ingredients in the description field to help customers make informed choices.

Build docs developers (and LLMs) love