Build a Bookstore REST API

Build a full CRUD REST API with Express.js to manage a book collection.

Introduction

30 Second Summary

Every time you search for a product online, add something to a cart, or update your profile, your browser is talking to an API behind the scenes. APIs are how modern software communicates, and knowing how to build one is one of the most in-demand skills in tech.

In this project, you will build a Bookstore Inventory API from scratch using Express.js and JavaScript. You will create, read, update, and delete books in a collection, all by firing commands from your terminal and seeing JSON responses come back.

What You'll Build

You fire a command from your terminal, and your API instantly responds with a JSON list of every book in the store. You add a new book, look one up by ID, update its details, or remove it from the catalog. Each interaction teaches you a core JavaScript concept and a REST pattern that interviewers love to ask about.

By the end of this project, you'll have:

  • A locally-running REST API you can demo by firing curl commands and explaining the HTTP methods and status codes behind each response.
  • Hands-on experience with JavaScript fundamentals (variables, objects, arrays, functions, conditionals, array methods) learned by solving real problems, not reading textbooks.
  • Full CRUD operations (Create, Read, Update, Delete) with proper input validation that rejects bad data with meaningful error messages.
  • Secret Mission: Add search and filtering so users can find books by genre, author, or title keyword.

Are there any prerequisites?

None. This project includes setup for all required tools. You do not need prior JavaScript or API experience.

Before We Start

Before We Start...

Before we dive in, let's lock in what you're about to build and why it matters.

Set Up Your Project

Every Express API starts with a project folder and a few configuration files. Without this foundation, you have nowhere to write code or install packages.

In this step, you will create a project folder, initialize it with npm, install Express, and write a minimal server. By the end, you will have a running API that responds to requests from your terminal.

In this step, get ready to:
  • Create the project folder and initialize it with npm.
  • Install Express and write a minimal server.
  • Start the server and confirm it responds to requests.
Create the project folder and initialize it

You need a dedicated folder for your project so npm can track dependencies and Express knows where to find your files.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir bookstore-api
cd bookstore-api
  • Initialize the project by running this command:
npm init -y

You should see the contents of a newly generated package.json printed in your terminal, starting with Wrote to followed by the file path.

What does npm init -y do?

npm init -y creates a package.json file with default values, skipping all the setup questions. This file is your project's manifest. It tracks metadata (name, version) and lists every package your project depends on.

The -y flag means "yes to all defaults." You can always edit the file later.

Seeing a permission error?

Make sure you ran cd ~/Desktop first. If your terminal opened inside a protected system directory, mkdir can fail.

help me fix a permission error when creating my project folder

Install Express and create the server file

Express is the framework that handles incoming HTTP requests and sends responses. Installing it adds the package to your project's dependencies.

  • Install Express by running this command:
npm install express

You should see output ending with a line like added X packages confirming the install succeeded.

Install failed or timed out?

Check your internet connection. npm install downloads the package from the npm registry, so it needs network access.

If you see a permission error, try running the command again. Do not use sudo with npm.

help me fix npm install express failing

  • Switch to VS Code.
  • Click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the bookstore-api folder, and click Open.

You should see bookstore-api in the Explorer sidebar with node_modules, package.json, and package-lock.json listed.

  • Hover over BOOKSTORE-API in the Explorer sidebar and click the New File icon (a page with a plus sign).
  • Type server.js and press Enter.
  • Paste the following code into server.js:
// Import the Express framework
const express = require('express');
// Create a new Express application
const app = express();
// Set the port number the server will listen on
const port = 3000;

// Define a route that responds to GET requests at the root URL
app.get('/', (req, res) => {
  res.json({ message: 'Bookstore API is running' });
});

// Start the server and print a confirmation message
app.listen(port, () => {
  console.log(`Bookstore API running at http://localhost:${port}`);
});

What does this code do?

  • const express = require('express') pulls in the Express package you just installed. require() is how Node.js loads external packages.
  • const app = express() creates a new Express application. const declares a variable that will not be reassigned.
  • app.get('/', ...) registers a route. When someone sends a GET request to /, the arrow function (req, res) => {} runs. Arrow functions are a compact way to write functions in JavaScript.
  • res.json({ message: '...' }) sends back a JSON object. Object literals {} hold key-value pairs of structured data.
  • app.listen(port, ...) starts the server on port 3000 and prints a confirmation to the terminal.
  • Save server.js by pressing Cmd+S (macOS) or Ctrl+S (Windows).

File not showing in the sidebar?

Make sure you opened the bookstore-api folder itself, not the Desktop. The Explorer sidebar should show BOOKSTORE-API as the top-level heading with your files underneath.

help me find my server.js file in VS Code

✔️ Awesome, I've got everything!

Great. Double check you have saved server.js and you are ready to run it.

ⓧ I'd like to double check the full code

Here is the complete server.js file. Make sure yours matches:

const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
  res.json({ message: 'Bookstore API is running' });
});

app.listen(port, () => {
  console.log(`Bookstore API running at http://localhost:${port}`);
});
Start the server and confirm it works

Your code is written. Time to see if the server actually responds.

  • Switch back to your Terminal.
  • Start the server by running this command:
node server.js

You should see this message printed in your terminal:

Bookstore API running at http://localhost:3000

What is happening?

Your server is now running and listening for requests on port 3000. This terminal tab is "taken" by the server process. It will stay running until you stop it with Ctrl+C.

To send a request to the server, you need a second terminal tab.

  • Open a new Terminal tab by pressing Cmd+T (macOS) or Ctrl+Shift+T (Windows).

Before you run the next command, what do you think the server will send back?

  • Test the server by running this command:
curl http://localhost:3000

You should see this JSON response printed in your terminal:

{"message":"Bookstore API is running"}

That confirms your server is alive and responding to HTTP requests.

Seeing 'Connection refused'?

Make sure node server.js is still running in your first terminal tab. If you accidentally closed it or pressed Ctrl+C, the server stopped. Go back to that tab and run node server.js again.

Also check that you typed the URL exactly as shown. Port 3000 must match the port in your server.js file.

help me fix connection refused when curling my Express server

Your environment is ready and your server is alive. Next up, you will add real book data and build the first endpoint that serves your collection as JSON.

Serve Your Book Collection

Your server is alive and responding to requests. That's the foundation sorted.

A bookstore API needs books. In this step, you'll create a collection of book data and build the first real endpoint that serves it. By the end, you'll hit your API with curl and see actual book data come back as JSON.

In this step, get ready to:
  • Create a book data array with three sample books.
  • Add a GET /books route that returns the full collection.
  • Test the endpoint with curl and see JSON output.
Create the book data

Before your API can serve books, it needs books to serve. You'll store them in a JavaScript array of objects, which is an in-memory data structure that lives only while the server is running.

  • In VS Code, open server.js.
  • Find the existing route that starts with app.get('/' (the one returning "Bookstore API is running").
  • Delete that entire route block (the app.get('/', ...) function and its closing bracket).
  • In its place, paste the following code directly below const port = 3000;:
app.use(express.json());

let books = [
  { id: 1, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', genre: 'Fiction', year: 1925 },
  { id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', genre: 'Fiction', year: 1960 },
  { id: 3, title: 'A Brief History of Time', author: 'Stephen Hawking', genre: 'Science', year: 1988 }
];

let nextId = 4;

What does this code do?

app.use(express.json()) enables your server to parse incoming JSON request bodies. You'll need this in a later step when clients send book data to the API.

let books = [...] creates an array (an ordered list) of three objects. Each object uses curly braces {} to hold key-value pairs like title and author. The keyword let declares a variable that can be reassigned later (you'll add and remove books from this array).

let nextId = 4 tracks the next available ID number. Since the three starter books use IDs 1, 2, and 3, the next book added will get ID 4.

  • Save server.js.
  • Switch to your terminal where the server is running and press Ctrl+C to stop it.
  • Restart the server by running:
node server.js
  • You should see Bookstore API running at http://localhost:3000 printed in your terminal. This confirms your data was added without any syntax errors.

Seeing a syntax error instead?

Check that every object in the books array has matching curly braces {} and that each object is separated by a comma. Also confirm the array itself ends with ]; (closing bracket and semicolon).

help me fix a syntax error in my books array in server.js

Add the GET /books route

Your data exists, but no one can access it yet. You need a route that listens for GET requests and responds with the books array.

  • In server.js, find the app.listen(port, ...) block at the bottom of the file.
  • Directly above it, add the following route:
// GET all books
app.get('/books', (req, res) => {
  res.json(books);
});

What does this route do?

app.get('/books', ...) tells Express to listen for GET requests at the /books path. When a request arrives, Express calls the callback function you provide.

The callback receives two arguments: req (what the client sent) and res (how you reply). res.json(books) converts the JavaScript array into JSON and sends it back to the client.

  • Save server.js.
Test the endpoint

Your route is wired up. Time to see if real book data comes back when you hit the API.

  • In your terminal, press Ctrl+C to stop the server.
  • Restart it by running:
node server.js

Before you test, what do you think you'll get back when you request /books? A single object, or something else?

  • Open a second terminal tab by pressing Cmd+T (macOS) or opening a new terminal window.
  • Hit the books endpoint by running:
curl http://localhost:3000/books

You should see a JSON array of 3 book objects, each with id, title, author, genre, and year fields printed in your terminal. That's your book catalog served as an API response.

Seeing 'Cannot GET /books' or an empty response?

Make sure you restarted the server after saving. Node.js does not auto-reload. Press Ctrl+C in the first terminal tab and run node server.js again.

Also confirm that your route uses '/books' (with the leading slash) and that it sits above the app.listen call, not below it.

help me debug why my GET /books route returns an error

✔️ Awesome, I've got everything!

Great. Double check you've saved server.js and move on to the next step.

ⓧ I'd like to double check the full code

Here is the complete server.js at this point. Compare your file against it:

const express = require('express');
const app = express();
const port = 3000;

app.use(express.json());

let books = [
  { id: 1, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', genre: 'Fiction', year: 1925 },
  { id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', genre: 'Fiction', year: 1960 },
  { id: 3, title: 'A Brief History of Time', author: 'Stephen Hawking', genre: 'Science', year: 1988 }
];

let nextId = 4;

// GET all books
app.get('/books', (req, res) => {
  res.json(books);
});

app.listen(port, () => {
  console.log(`Bookstore API running at http://localhost:${port}`);
});

Your API now serves real data. Next up, you'll add the ability to look up a single book by its ID and handle what happens when someone asks for a book that does not exist.

Look Up a Single Book

Your API can serve the entire book catalog. Now you need a way to fetch one specific book by its ID.

What happens when someone asks for a book that does not exist? A well-built API handles that gracefully with a 404 status code instead of crashing or returning empty data.

In this step, get ready to:
  • Add a route that returns a single book by its ID.
  • Handle requests for books that do not exist with a proper 404 response.
  • Test both cases with curl.
Add the GET /books/:id route

Your current GET /books route returns every book at once. You need a second route that accepts an ID in the URL and returns just that one book.

  • In server.js, find the closing }); of your GET /books route. Add the following new route below it:
// GET a single book by ID
app.get('/books/:id', (req, res) => {
  const id = Number(req.params.id);
  const book = books.find(b => b.id === id);

  res.json(book);
});

What does this code do?

This route introduces three new JavaScript concepts:

  • req.params.id captures the dynamic part of the URL. When someone hits /books/2, the value 2 lands in req.params.id as a string.
  • Number() converts that string into a number so you can compare it against the numeric id field in your book objects.
  • .find() searches an array and returns the first item that matches your condition. If nothing matches, it returns undefined.
  • Save server.js.
  • Switch to your Terminal and restart the server by pressing Ctrl+C to stop it, then running:
node server.js
  • In your second Terminal tab, test the route by running:
curl http://localhost:3000/books/1

You should see a single book object returned:

{
  "id": 1,
  "title": "The Great Gatsby",
  "author": "F. Scott Fitzgerald",
  "genre": "Fiction",
  "year": 1925
}

Not seeing a book object?

  • Make sure you restarted the server after saving. Changes to server.js only take effect after a restart.
  • Check that your new route is placed after the GET /books route and before app.listen().
  • Confirm the URL uses the number 1 at the end, not the word id.

Still stuck? Help me debug my GET /books/:id route that isn't returning a book object.

Handle the "not found" case

Your route works for valid IDs. But what happens when someone requests a book that does not exist? Right now .find() returns undefined, and your API sends back an empty response with no explanation. You need to return a 404 status with a clear error message instead.

  • In server.js, find the res.json(book) line inside your GET /books/:id route.
  • Add the following code above that line:
  if (!book) {
    return res.status(404).json({ error: 'Book not found' });
  }

Your complete route should now look like this:

// GET a single book by ID
app.get('/books/:id', (req, res) => {
  const id = Number(req.params.id);
  const book = books.find(b => b.id === id);

  if (!book) {
    return res.status(404).json({ error: 'Book not found' });
  }

  res.json(book);
});

What does this code do?

HTTP status 404 means "not found." The conditional if (!book) checks whether .find() came back empty.

The return keyword stops the function early. Without it, the code would keep going and try to send res.json(book) as well, causing an error from sending two responses.

  • Save server.js.
  • Restart the server (Ctrl+C, then node server.js).

Seeing a syntax error on restart?

  • Make sure the if block is inside the route handler function, not outside it.
  • Check that you have matching curly braces for the if block. You need an opening { and a closing }.
  • Confirm the if block sits between const book = books.find(...) and res.json(book).

Need help? Help me fix a syntax error in my Express route handler.

Test both cases

Before you run the next command, predict what you will see when you request a book with ID 99 (which does not exist in your collection).

  • First, confirm a valid book still works by running:
curl http://localhost:3000/books/1

You should see the full book object for The Great Gatsby.

  • Now request a book that does not exist. The -i flag includes response headers so you can see the status code:
curl -i http://localhost:3000/books/99

You should see HTTP/1.1 404 Not Found in the headers, followed by the JSON error body:

{"error":"Book not found"}

What does the -i flag do?

The -i flag tells curl to include the HTTP response headers in the output. This lets you see the status line (like 404 Not Found or 200 OK) alongside the response body.

✔️ Awesome, I've got everything!

Great. Double check you have saved server.js and both curl commands returned the expected results.

ⓧ I'd like to double check the full code

Your server.js should contain these sections in order:

  • The express require, app creation, and port variable.
  • The app.use(express.json()) middleware line.
  • The books array with 3 book objects and the nextId variable.
  • The GET /books route that returns all books.
  • The GET /books/:id route with the Number() conversion, .find() lookup, if (!book) check returning 404, and res.json(book) on success.
  • The app.listen() call at the bottom.

Your API can now serve individual books and reject invalid requests with proper error codes. Next up, you will add the ability to create new books and learn why input validation matters the hard way.

Add New Books

Your API can serve book data, but it cannot accept new books. A read-only API is only half the picture.

In this step, you will add a POST route that lets users submit new books to the collection. Along the way, you will discover what happens when someone sends incomplete data. Then you will fix it with input validation.

In this step, get ready to:
  • Add a POST route that creates new book entries.
  • Discover what happens when empty data slips through.
  • Add validation that rejects incomplete submissions.
Add the POST route without validation

The app.use(express.json()) middleware already in your file parses incoming JSON request bodies. This is what populates req.body so your POST route can read the data a client sends.

  • In server.js, find the app.listen block at the bottom of the file.
  • Add this POST route directly above app.listen:
// POST a new book
app.post('/books', (req, res) => {
  const { title, author, genre, year } = req.body;

  const newBook = {
    id: nextId,
    title,
    author,
    genre,
    year
  };
  nextId++;
  books.push(newBook);

  res.status(201).json(newBook);
});

What does this code do?

This route handles POST requests to /books. Here is what each piece does:

  • const { title, author, genre, year } = req.body uses destructuring to pull individual values out of the request body object into separate variables.
  • nextId gives each new book a unique ID, then increments so the next book gets a different one.
  • .push() adds the new book to the end of the books array.
  • res.status(201).json(newBook) sends back the created book with HTTP status 201, which means "created."
  • Save server.js.
  • Switch to your Terminal and stop the server with Ctrl+C.
  • Restart the server by running:
node server.js
  • In your second Terminal tab, add a new book by running:
curl -X POST http://localhost:3000/books -H "Content-Type: application/json" -d '{"title":"Dune","author":"Frank Herbert","genre":"Sci-Fi","year":1965}'

You should see the new book returned with an id of 4 and all the details you submitted.

Seeing an empty response or error?

  • Make sure you saved server.js before restarting the server.
  • Confirm the POST route is placed above app.listen and below the existing GET routes.
  • Check that your curl command includes -H "Content-Type: application/json". Without this header, Express cannot parse the body.

Why is my POST route not accepting data?

See the problem: missing data slips through

Your POST route works for valid books. But what happens when a client sends incomplete data?

  • Send an empty object to your API by running:
curl -X POST http://localhost:3000/books -H "Content-Type: application/json" -d '{}'

The server accepted it. Before you check the full collection, what do you think that book entry looks like?

  • Check your full book collection by running:
curl http://localhost:3000/books

You see a book entry with only an id field and nothing else. The title, author, genre, and year are all missing because JSON.stringify omits undefined values entirely.

This is broken data that should never have been accepted. Your collection now has garbage in it. This is the exact problem input validation solves.

Add validation to fix the problem

You need to check that required fields exist before creating the book. If they are missing, return a 400 status ("bad request") and reject the submission.

  • In server.js, find your POST route and replace it entirely with this updated version:
// POST a new book
app.post('/books', (req, res) => {
  const { title, author, genre, year } = req.body;

  if (!title || !author) {
    return res.status(400).json({ error: 'Title and author are required' });
  }

  const newBook = {
    id: nextId,
    title,
    author,
    genre: genre || 'Unknown',
    year: year || null
  };
  nextId++;
  books.push(newBook);

  res.status(201).json(newBook);
});

What does this code do?

Two changes protect your data now:

  • if (!title || !author) checks whether either required field is missing. The ! operator treats undefined, empty strings, and null as falsy. If the check fails, return stops the function early so no book gets created.
  • genre: genre || 'Unknown' provides a default value for optional fields. If the client does not send a genre, it falls back to 'Unknown' instead of undefined. The same pattern gives year a default of null.
  • Save server.js.
  • Stop the server with Ctrl+C and restart it by running:
node server.js

Now test the validation. Before you run the next command, do you think the server will accept or reject an empty submission?

  • Try sending an empty object again:
curl -X POST http://localhost:3000/books -H "Content-Type: application/json" -d '{}'

You see {"error":"Title and author are required"} returned. The server rejected the bad data with a 400 status.

  • Confirm valid submissions still work by running:
curl -X POST http://localhost:3000/books -H "Content-Type: application/json" -d '{"title":"Dune","author":"Frank Herbert","genre":"Sci-Fi","year":1965}'

You see the new book returned with a 201 status, an auto-assigned id, and all the details intact.

Still seeing empty books in the collection?

  • The garbage book from earlier is still in memory. Stop the server with Ctrl+C and restart it. Restarting clears the in-memory data back to the original 3 books.
  • Make sure the if (!title || !author) check is at the top of the route handler, before the newBook object is created.
  • Check that you have return before res.status(400). Without it, the function keeps running and creates the book anyway.

Help me fix my POST validation logic.

✔️ Awesome, I've got everything!

Great. Make sure you have saved server.js with the validated POST route.

ⓧ I'd like to double check the full code

Your server.js should have these sections in order. Scroll through your file and confirm each one is present:

  • The require, app, and port setup at the top.
  • app.use(express.json()) middleware.
  • The books array with 3 book objects and let nextId = 4.
  • GET /books route returning all books.
  • GET /books/:id route with 404 handling.
  • POST /books route with the if (!title || !author) validation, default values for genre and year, and res.status(201).json(newBook).
  • app.listen at the bottom.

Your API now accepts new books and rejects bad data. Next up, you will add PUT and DELETE routes to complete the full set of CRUD operations.

Update and Delete Books

Your API can list books, find a specific book, and add new ones. That covers Create and Read from CRUD.

CRUD is not complete without Update and Delete. These use PUT and DELETE HTTP methods with their own status codes. This rounds out the full set of operations interviewers expect you to know.

In this step, get ready to:
  • Add the PUT route to update existing books.
  • Add the DELETE route to remove books from the collection.
  • Test the full CRUD lifecycle end to end.
Add the PUT /books/:id route

PUT requests tell the server to update an existing resource. The client sends the new field values, and the server merges them into the existing book.

  • In server.js, find the closing }); of your POST route (the one that creates new books).
  • Add the PUT route below it by copying this code:
// PUT (update) a book
app.put('/books/:id', (req, res) => {
  const id = Number(req.params.id);
  const index = books.findIndex(b => b.id === id);

  if (index === -1) {
    return res.status(404).json({ error: 'Book not found' });
  }

  const { title, author, genre, year } = req.body;
  books[index] = {
    ...books[index],
    title: title || books[index].title,
    author: author || books[index].author,
    genre: genre || books[index].genre,
    year: year || books[index].year
  };

  res.json(books[index]);
});

What does this code do?

This route introduces two new JavaScript concepts.

  • .findIndex() searches an array and returns the position (index) of the matching item. If nothing matches, it returns -1.
  • The spread operator ... copies all properties from an existing object into a new one. Then you override specific fields with the values the client sent. Any field the client did not send stays unchanged.
  • Save server.js.

Seeing a syntax error when you save?

  • Make sure the PUT route is placed after the closing }); of the POST route, not inside it.
  • Check that you have matching curly braces for the books[index] = { ... } assignment.
  • Confirm the arrow function ends with }); (closing brace, parenthesis, semicolon).

Still stuck? Help me fix a syntax error in my PUT route

Add the DELETE /books/:id route

DELETE requests remove a resource entirely. The server finds the book, removes it from the array, and responds with an empty body to confirm deletion.

  • In server.js, add the DELETE route below your PUT route:
// DELETE a book
app.delete('/books/:id', (req, res) => {
  const id = Number(req.params.id);
  const index = books.findIndex(b => b.id === id);

  if (index === -1) {
    return res.status(404).json({ error: 'Book not found' });
  }

  books.splice(index, 1);
  res.status(204).send();
});

What does this code do?

  • .splice(index, 1) removes one item from the array at the given position. The book is gone from memory.
  • Status 204 means "success, no content to return." The deletion worked, but there is nothing to send back.
  • res.status(204).send() sends an empty response body. You use .send() instead of .json() because there is no data to serialize.
  • Save server.js.

Route not working after you save?

  • Make sure the DELETE route is placed between the PUT route's closing }); and the app.listen() call at the bottom.
  • Check that .splice(index, 1) has a comma between the two arguments, not a space.

Need help? Help me debug my DELETE route in Express

✔️ Awesome, I've got everything!

Great. Make sure you have saved server.js with both new routes before moving on.

ⓧ I'd like to double check the full code

Your server.js should now contain these sections in this order:

  • The require statement, app creation, and port variable.
  • The app.use(express.json()) middleware.
  • The books array with 3 book objects and the nextId variable.
  • GET /books route (returns all books).
  • GET /books/:id route (finds one book, returns 404 if missing).
  • POST /books route (validates input, creates a new book with status 201).
  • PUT /books/:id route (finds by index, returns 404 if missing, merges with spread operator).
  • DELETE /books/:id route (finds by index, returns 404 if missing, splices and sends 204).
  • The app.listen() call at the very bottom.

Scroll through each section in your editor and confirm they match the code blocks you pasted above.

Test the full CRUD lifecycle
  • Switch to your terminal where the server is running.
  • Stop the server by pressing Ctrl+C.
  • Restart it by running:
node server.js

Now test the PUT route. Before you run this, what do you think the response will contain? Will it show the full book or just the changed field?

  • In your second terminal tab, update book 1's title by running:
curl -X PUT http://localhost:3000/books/1 -H "Content-Type: application/json" -d '{"title":"The Great Gatsby (Revised)"}'

You see the full book object returned with only the title changed. The author, genre, and year stayed the same because the spread operator preserved them.

  • Confirm the update persisted by fetching the book:
curl http://localhost:3000/books/1

The title now reads The Great Gatsby (Revised). Your PUT route works.

Now test DELETE. What HTTP status code do you expect back when a deletion succeeds?

  • Delete book 1 by running:
curl -X DELETE http://localhost:3000/books/1

You see no output. That is correct. Status 204 means success with no content. The empty response confirms the book was deleted.

  • Verify the book is gone by running:
curl http://localhost:3000/books

You see only books 2 and 3 in the array. Book 1 has been removed.

  • Try deleting a book that does not exist to confirm 404 handling:
curl -i -X DELETE http://localhost:3000/books/99

You see HTTP/1.1 404 in the headers and {"error":"Book not found"} in the body. Your API rejects invalid operations with the correct status code.

Not seeing the expected responses?

  • If PUT returns an empty response or error, check that app.use(express.json()) is still near the top of your file. Without it, req.body is undefined.
  • If DELETE returns the book instead of an empty response, check that you used res.status(204).send() and not res.json().
  • If you get Cannot PUT or Cannot DELETE, the routes are not registered. Confirm you restarted the server after saving.

Need more help? Help me debug my PUT and DELETE routes

Your API now supports all four CRUD operations. You can Create, Read, Update, and Delete books with proper status codes for every scenario. Next up, you will extend this API with search and filtering in the Secret Mission.

Secret mission

Add Search and Filtering

Your API returns every book in the collection every single time. What if users want to browse only sci-fi titles, or search for a book by name? Add search and filtering to your GET /books endpoint using query parameters and JavaScript's .filter() method.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your project files for future reference, pause the running server, or delete everything entirely.

Resources you used:

  • The bookstore-api project folder on your Desktop (contains server.js, package.json, and node_modules/).
  • The Node.js server process (if still running in your Terminal).

Keep everything running

No action needed. Choose this if you want to keep experimenting with your API or plan to extend it further.

  • Your bookstore-api folder stays on your Desktop.
  • To restart the server later, open Terminal, run cd ~/Desktop/bookstore-api, then run node server.js.

Pause - I'll come back to this later

Stop the running server to free up your Terminal, but keep all files for later.

  • Switch to the Terminal tab where your server is running.
  • Press Ctrl+C to stop the server process.
  • Your project files remain on your Desktop. You can restart anytime with node server.js from inside the bookstore-api folder.

Delete - I don't want to use this again

Remove all project files and stop the server completely.

  • Switch to the Terminal tab where your server is running and press Ctrl+C to stop the server.
  • Delete the entire project folder by running:
rm -rf ~/Desktop/bookstore-api
  • Confirm the folder is gone by running:
ls ~/Desktop

You should no longer see bookstore-api in the output.

That's a Wrap!

That's a Wrap!

Nice work. You just built a fully functional REST API from scratch using Express.js and tested every endpoint from your terminal.

You've learned how to:

  • Built a REST API that handles all four CRUD operations (Create with POST, Read with GET, Update with PUT, Delete with DELETE) and responds with proper HTTP status codes (200, 201, 204, 400, 404).
  • Learned JavaScript fundamentals (variables, objects, arrays, functions, conditionals, destructuring, and array methods like .find(), .findIndex(), and .splice()) by using them to solve real problems.
  • Added input validation that rejects invalid data with meaningful error messages, after seeing firsthand what happens when bad data gets through.
  • Secret Mission: Added search and filtering with query parameters, using .filter() for case-insensitive matching on genre, author, and title.

Ready to quiz yourself?