135 lines
3.6 KiB
Markdown
135 lines
3.6 KiB
Markdown
# 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.
|