Add movie collection API and frontend (#1)

This commit was merged in pull request #1.
This commit is contained in:
2026-09-21 06:52:11 +00:00
parent 2885727eb2
commit c45878bd86
6 changed files with 2152 additions and 0 deletions
+134
View File
@@ -0,0 +1,134 @@
# Movie Collection API
A simple Movie Collection REST API built with **Node.js + Express**, plus a single-page
frontend that talks to it with `fetch()`.
A **Movie** is one entry in the collection; the collection is `/api/movies`. Movies are
kept in a plain JavaScript array, so **there is no database — records added through the
API disappear when the server restarts.**
## Running
```sh
npm install
npm start # node server.js
```
Open <http://localhost:3000>.
```sh
npm run dev # node --watch server.js
PORT=4000 npm start
```
## Schema
Each movie has four fields:
| Field | Type | Notes |
| --- | --- | --- |
| `id` | integer ≥ 1 | **assigned by the server**, never sent by the client |
| `title` | string, 1–120 chars | required, trimmed |
| `genre` | string, 1–60 chars | required, trimmed |
| `year` | integer, 1888–2100 | required; `"2010"` and `2010` are both accepted |
The three request fields are required. Anything missing or blank comes back as a `400`
with one message per field.
## Endpoints
| Method | Path | Result |
| --- | --- | --- |
| `GET` | `/api/movies` | all movies → `200` |
| `GET` | `/api/movies/:id` | one movie → `200`, or `404` if there is no such id |
| `POST` | `/api/movies` | add a movie → `201` with a `Location` header |
| `GET` | `/` | the frontend |
Errors come back as a message plus the offending fields:
```json
{ "error": "Title is required.", "fields": { "title": "Title is required.", "year": "Year is required." } }
```
## Examples
```sh
curl http://localhost:3000/api/movies
curl http://localhost:3000/api/movies/2
curl -X POST http://localhost:3000/api/movies \
-H 'content-type: application/json' \
-d '{"title":"Inception","genre":"science fiction","year":2010}'
```
`GET /api/movies` → `200`
```json
[
{ "id": 1, "title": "Interstellar", "genre": "Science Fiction", "year": 2014 },
{ "id": 2, "title": "Avengers: Endgame", "genre": "Action", "year": 2019 },
{ "id": 3, "title": "Coco", "genre": "Animation", "year": 2017 }
]
```
`POST /api/movies` → `201` · `Location: /api/movies/4`
```json
{ "id": 4, "title": "Inception", "genre": "science fiction", "year": 2010 }
```
`POST /api/movies` with `{}` → `400`
```json
{
"error": "Title is required.",
"fields": {
"title": "Title is required.",
"genre": "Genre is required.",
"year": "Year is required."
}
}
```
`GET /api/movies/abc` → `400`
```json
{ "error": "ID must be a whole number, like 1." }
```
`GET /api/movies/99` → `404`
```json
{ "error": "No movie with ID 99." }
```
## Frontend
`index.html` is a single file (no build step, no framework) served by the same Express
app. It:
- displays the movie list in a table,
- has a form for adding a movie (title, genre, year),
- uses `fetch()` for every call to the API,
- refreshes the list after a movie is added.
Clicking a row fetches that one movie from `GET /api/movies/:id` and shows it in a detail
panel, so all three endpoints are exercised from the UI.
## Files
```
server.js Express app: schemas, in-memory store, routes, error handling
index.html the frontend (styles + fetch logic, no dependencies)
package.json express + zod
```
## Project structure notes
- **Storage** is `const movies = []` in `server.js`, with a `nextId` counter for the
auto-assigned `id`. It is seeded with three movies on startup.
- **Validation** uses [zod](https://zod.dev). One schema per shape (`movieDraftSchema`
for the request body, `movieSchema` for what is stored and returned), and a small
helper turns a failure into the `{ error, fields }` response the frontend renders next
to each input.
- `node_modules/` is git-ignored.