Files
bonpin-adrian-meal-api/README.md
T

117 lines
3.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```sh
npm install
npm start # node server.js
```
Open <http://localhost:3000>. API docs: <http://localhost:3000/openapi>.
```sh
npm run dev # node --watch server.js
npm run bun # bun server.js (also runs on Node)
```
## Schema
```js
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:
```json
{ "error": "Name is required.", "fields": { "name": "Name is required." } }
```
## Examples
```sh
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`
```json
[
{
"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`
```json
{ "error": "ID must be a whole number, like 1." }
```
```json
{ "error": "No menu item with ID 99." }
```