Meal API

A food menu API with a single-page frontend. A Meal is one item on the menu; the collection is /api/menu, validated with zod and documented at /openapi.

Running

npm install
npm start          # node server.js

Open http://localhost:3000. API docs: http://localhost:3000/openapi.

npm run dev        # node --watch server.js
npm run bun        # bun server.js   (also runs on Node)

Schema

const MEAL_CATEGORIES = ['appetizer', 'main', 'dessert', 'drink']

const mealDraftSchema = z.object({
  name: z.string().trim().min(1, 'Name is required.').max(80, 'Keep the name under 80 characters.'),
  category: z.enum(MEAL_CATEGORIES, { error: 'Pick a category: …' }),
  price: z.number({ error: 'Price is required.' })
    .nonnegative('Price cannot be negative.')
    .refine((v) => Math.round(v * 100) / 100 === v, 'Price can have at most 2 decimal places.'),
  description: z.string().trim().max(240, '…').optional(),
  available: z.boolean({ error: 'Available must be true or false.' }).optional(),
})

The same schema validates the request, types the handler and generates the OpenAPI spec.

Field Type Notes
id integer ≥ 1 assigned by the server
name string, 1–80 chars trimmed
category enum appetizer · main · dessert · drink
price number ≥ 0 at most 2 decimal places
description string, ≤ 240 chars defaults to ""
available boolean defaults to true
createdAt ISO datetime set on create
updatedAt ISO datetime bumped on every update

Endpoints

Method Path Result
GET / the frontend
GET /openapi interactive API docs
GET /openapi/json the OpenAPI spec
GET /api/menu list meals
GET /api/menu/:id one meal
POST /api/menu create a meal → 201
PUT /api/menu/:id replace a meal
PATCH /api/menu/:id update only the fields you send
DELETE /api/menu/:id remove a meal → 204

GET /api/menu also accepts category, available, search, sort (name, price, createdAt) and order (asc, desc).

Errors come back as a message plus the offending fields:

{ "error": "Name is required.", "fields": { "name": "Name is required." } }

Examples

curl http://localhost:3000/api/menu
curl "http://localhost:3000/api/menu?category=main&sort=price&order=desc"
curl "http://localhost:3000/api/menu?search=adobo"
curl http://localhost:3000/api/menu/2

curl -X POST http://localhost:3000/api/menu \
  -H 'content-type: application/json' \
  -d '{"name":"Halo-Halo","category":"dessert","price":65.5}'

curl -X PATCH http://localhost:3000/api/menu/2 \
  -H 'content-type: application/json' \
  -d '{"price":55}'

curl -X DELETE http://localhost:3000/api/menu/2

GET /api/menu → 200

[
  {
    "id": 1,
    "name": "Adobo",
    "category": "main",
    "price": 75,
    "description": "Pork belly simmered in soy sauce, vinegar and bay leaves.",
    "available": true,
    "createdAt": "2026-09-16T12:00:00.000Z",
    "updatedAt": "2026-09-16T12:00:00.000Z"
  }
]

GET /api/menu/abc → 400 · GET /api/menu/99 → 404

{ "error": "ID must be a whole number, like 1." }
{ "error": "No menu item with ID 99." }
S
Description
A simple food menu API with a frontend (Elysia)
Readme
54 KiB
Languages
HTML 70.9%
JavaScript 29.1%