Build your first server. It will answer requests for a list of books — adding one, changing one, and deleting one — all before any frontend exists.
So far, your code has run in the browser. The browser is the client — it asks for things. Today you build the server — the computer that answers. A server is a program that waits for requests and sends back responses. Express is a tool that makes building a server in Node much faster.
You will test your server with Postman, a tool for sending requests without a browser. There is no frontend yet — that comes later. Today is just about the mechanics of CRUD: Create, Read, Update, Delete.
- Express docs: https://expressjs.com/en/starter/hello-world.html
- Postman download: https://www.postman.com/downloads/
This is a Node project, so there is no Vite step.
mkdir books-api
cd books-api
npm init -y
npm install expressCreate one file: app.js. Everything for this assignment lives in this single file.
Paste this at the top of app.js. This is your starting data — an array, which is just a list:
let books = [
{ id: 1, title: "The Pragmatic Programmer", author: "David Thomas", genre: "Tech", available: true },
{ id: 2, title: "Educated", author: "Tara Westover", genre: "Memoir", available: true },
{ id: 3, title: "Dune", author: "Frank Herbert", genre: "Sci-Fi", available: false },
{ id: 4, title: "Sapiens", author: "Yuval Noah Harari", genre: "History", available: true },
{ id: 5, title: "The Alchemist", author: "Paulo Coelho", genre: "Fiction", available: true },
];
let nextId = 6; // use this for any new book you createWhy: Before writing any routes, confirm the server itself can turn on.
Steps:
- Require
express:const express = require("express"); - Create the app:
const app = express(); - Add this line:
app.use(express.json());— it lets your server read JSON that's sent to it. Without it,req.bodywill always be empty. - At the bottom, start the server:
app.listen(8080, () => console.log("Server running on port 8080")); - In your terminal, run
node app.js.
Check it: Your terminal shows Server running on port 8080.
Why: A route is a rule: "when a request comes to this address, run this code." Write the smallest possible route first, before touching your book data.
Steps:
- Above
app.listen, add:app.get("/", (req, res) => res.send("Books API is running")); - Stop your server (
Ctrl+C) and runnode app.jsagain. (Express does not restart itself — you must do this every time you changeapp.js.) - Open
http://localhost:8080in your browser.
Check it: The page shows "Books API is running".
Why: This is the simplest way to read data — send back the whole list.
Steps:
- Add:
app.get("/api/books", (req, res) => res.json(books)); - Restart your server.
- Open Postman. Send a
GETrequest tohttp://localhost:8080/api/books.
Check it: Postman shows all 5 books as JSON.
Why: Now you'll read a single item using a route parameter — a piece of the URL itself, like the 2 in /api/books/2.
Steps:
- Add a new route:
app.get("/api/books/:id", (req, res) => { ... }) - Inside, read the id from
req.params.id. Important: this value is always a string, so wrap it inNumber()before comparing it to the ids in your array. - Find the matching book in the
booksarray. - If no book matches, send back status
404and stop (usereturn). - If a book matches, send it back as JSON.
- Restart your server.
Check it: GET /api/books/2 returns "Educated". GET /api/books/99 returns a 404 status.
Why: Now you'll let the client create something new, instead of only reading data.
Steps:
- Add:
app.post("/api/books", (req, res) => { ... }) - Read
title,author, andgenrefromreq.body. - Build a new book object. Use
nextIdfor its id, then increasenextIdby 1. - Add the new book to the
booksarray. - Send back status
201and the new book. - Restart your server.
Check it: In Postman, set the body to raw → JSON, and send { "title": "Clean Code", "author": "Robert Martin", "genre": "Tech" } to POST /api/books. Then GET /api/books and confirm 6 books are now in the list.
Why: PATCH changes only the fields you send — not the whole object.
Steps:
- Add:
app.patch("/api/books/:id", (req, res) => { ... }) - Find the book by id (
Number()again). If it's not found, send404and stop. - Copy the fields from
req.bodyonto the existing book, without erasing the fields you didn't send. (Object.assign(book, req.body)does this for you.) - Send back status
200and the updated book. - Restart your server.
Check it: Send { "available": false } to PATCH /api/books/1. Only available should change.
Why: The last CRUD action — removing something completely.
Steps:
- Add:
app.delete("/api/books/:id", (req, res) => { ... }) - Find the book's position in the array by id. If it's not found, send
404and stop. - Remove it from the
booksarray. - Send back status
204with no body:res.sendStatus(204). - Restart your server.
Check it: Delete a book, then GET /api/books and confirm it's gone.
req.params.idis always a string."2" === 2isfalse— always wrap it inNumber()first.req.bodyisundefinedif you forgetapp.use(express.json()).- Every route must send back exactly one response. Forgetting
returnbeforeres.sendStatus(404)will cause a "Cannot set headers after they are sent" crash. PATCHshould only update the fields sent in the request, not replace the whole object.204 No Contentshould have no body. Useres.sendStatus(204), notres.status(204).json(...).- Forgot to restart your server after a change? Stop it (
Ctrl+C) and runnode app.jsagain. - In Postman, set the request body format to raw → JSON for POST and PATCH routes.
Steps:
- Open your terminal. Make sure you are inside your
books-apifolder. - Run
git init. - Run
git add . - Run
git commit -m "complete books api assignment" - Go to github.com. Click the + icon, then New repository.
- Name it
books-api. Leave every checkbox unchecked. Click Create repository. - Copy the three commands GitHub shows you under "...or push an existing repository from the command line." Paste them into your terminal and press enter.
- Refresh the GitHub page in your browser to confirm your files are there.
Submit: Copy your repo's URL and submit that link.
- Check for required fields before touching the data store — validate at the boundary.
- Send back the status code that matches what happened:
200read/updated,201created,204deleted,404not found,400bad input. - Real projects split routes into separate files by resource instead of one big file — you'll practice that pattern in the stretch challenges.
Only attempt these after Parts 1–7 work and are tested in Postman. Try them roughly in this order.
- Filter by genre: Add a query string to your GET-all route. If
req.query.genrehas a value, send back only books matching that genre. Example:GET /api/books?genre=Sci-Fi. - Validation: On
POST /api/books, return400iftitleorauthoris missing fromreq.body. - Error handling: Wrap your route logic in
try/catch. Log the error, and respond with500if something unexpected happens. - Organize your code: Right now everything is in one file. Split it up:
- Create an
api/folder withindex.jsandbooks.js. - Move your book routes into
api/books.js, usingexpress.Router()instead ofapp. - In
api/index.js, mount the books router. - In
app.js, mount yourapi/index.jsrouter under/api, and remove the routes you moved out.
- Create an
- Add dev middleware: Install
corsandmorgan. Addapp.use(cors())so a future frontend can talk to your server, andapp.use(morgan("dev"))to log every request in your terminal. - Reviews (a second, nested resource): Create
api/reviews.js. Reviews belong to a book through abookIdfield.let reviews = [ { id: 1, bookId: 1, reviewer: "Alice", rating: 5, comment: "Must-read for any developer." }, { id: 2, bookId: 1, reviewer: "Bob", rating: 4, comment: "Dense but rewarding." }, { id: 3, bookId: 3, reviewer: "Charlie", rating: 5, comment: "One of the best sci-fi novels ever written." }, ]; let nextReviewId = 4;
-
GET /api/books/:bookId/reviews— return all reviews wherereview.bookId === Number(req.params.bookId). -
POST /api/books/:bookId/reviews— readreviewer,rating,commentfromreq.body. Create a new review with the next id and the correctbookId. Respond201. -
DELETE /api/reviews/:id— delete a review by its own id. Respond204. - On the POST review route, return
400ifratingis missing or is not a number between 1 and 5. - Mount this router in
api/index.jsunder both/booksand/reviews, since it needs to answer to both URL shapes.
-
- Add
GET /api/books/:id/reviewsthat also includes the book's own data, not just the reviews. - Add pagination to
GET /api/booksusing?page=1&limit=3query params. - Add
PATCH /api/reviews/:idto update a review. - Add
GET /api/books/availablethat returns only books whereavailableistrue. (Hint: this route must be defined before/api/books/:id, or Express will treat"available"as an id.) - Add middleware that counts every request that hits your server and logs the running total.
Before submitting, verify:
-
node app.jsstarts without errors. -
GET /api/booksreturns all 5 starter books. -
GET /api/books/:idreturns one book, or404if not found. -
POST /api/bookscreates a new book;GET /api/booksconfirms it's there. -
PATCH /api/books/:idchanges only the fields you sent. -
DELETE /api/books/:idremoves the book;GET /api/booksconfirms it's gone. - Your work has been committed and pushed to GitHub.