Add movie collection API and frontend
Express app with an in-memory array as the store (no database) and three endpoints: - GET /api/movies -> the whole collection - GET /api/movies/:id -> one movie, 404 when the id is unknown - POST /api/movies -> add a movie, id assigned by the server zod schemas are the single source of truth for validation, so a missing title, genre or year comes back as a 400 with one message per field. index.html is served by the same app and drives all three endpoints with fetch(), refreshing the list after a movie is added.
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user