Build a REST API for a Book Collection

Build a Node.js REST API with full CRUD operations for a book collection.

Introduction

30 Second Summary

Every time you open an app on your phone, it talks to a server somewhere using an API. Understanding how these APIs work is the single most important backend skill you can learn.

In this project, you will build a REST API server that manages a collection of books. You will create endpoints that let you add, view, update, and delete books using simple terminal commands.

What You'll Build

A fully functional REST API server built with Node.js and Express that handles CRUD operations for a book collection resource.

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

  • A Node.js project with Express installed and configured.
  • Five working API endpoints that create, read, update, and delete books.
  • Tested every endpoint using curl commands from your terminal.
  • Secret Mission: Add a search endpoint that filters books by title or author using query parameters.

Are there any prerequisites?

You only need basic familiarity with JavaScript (variables, functions, objects, and arrays). No prior backend experience is needed.

Set Up Your Development Environment

Before you can build an API, you need the right tools on your machine. Node.js is a JavaScript runtime that lets you run JavaScript outside the browser. It powers your server.

In this step, you will install Node.js, create your project folder, and install Express (the framework that makes building APIs fast and simple).

In this step, get ready to:
  • Install Node.js and verify it is working.
  • Create and initialize your project folder.
  • Install Express as a dependency.
Install Node.js

Node.js is the engine that will run your API server. You need version 22 or higher installed on your machine.

  • Go to the Node.js download page in your browser.
  • Download the LTS (Long Term Support) version.
  • Run the installer and accept the default options.

Once the installation finishes, you need to verify it worked.

  • Open a new terminal by pressing the Windows key and typing Terminal, then pressing Enter.
  • Check your Node.js version by running this command:
node --version

✔️ I see version 22 or higher

Your terminal shows something like v22.x.x or v24.x.x. Node.js is ready to go.

  • Confirm npm (the package manager bundled with Node.js) is also available by running:
npm --version

You should see a version number printed. You are all set to continue.

ⓧ I see an older version

Your terminal shows a version number lower than v22. You need to upgrade.

  • Go to nodejs.org and download the LTS installer.
  • Run the installer. It will replace your older version automatically.
  • Close your terminal completely and open a new one.
  • Run node --version again to confirm you now see v22 or higher.

ⓧ Command not found

Your terminal does not recognize the node command. This means Node.js is not installed yet.

  • Go to nodejs.org and download the LTS installer.
  • Run the installer and accept all default options.
  • Close your terminal completely and open a new one (the PATH update only takes effect in new terminals).
  • Run node --version again. You should now see a version number.
Create your project folder

Every Node.js project lives in its own folder with a package.json file that tracks dependencies and metadata.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir bookshelf-api
cd bookshelf-api
  • Initialize a new Node.js project by running:
npm init -y

What does this command do?

The -y flag tells npm to skip all the setup questions and create a package.json with sensible defaults.

This file is like a recipe card for your project. It lists the project name, version, and (soon) all the packages your project depends on.

  • Confirm the file was created by running:
ls

You should see package.json in the output.

Don't see package.json?

Make sure you ran npm init -y from inside the bookshelf-api folder. Run pwd (macOS/Linux) or cd (Windows) to check your current location.

If you are in a different folder, run cd ~/Desktop/bookshelf-api and try again.

Help me figure out why package.json was not created.

Install Express

Express is the web framework that handles incoming HTTP requests and routes them to the right code. It saves you from writing hundreds of lines of boilerplate.

  • Install Express by running:
npm install express

What does this command do?

This downloads Express and all its sub-dependencies into a node_modules folder. It also adds Express to the dependencies section of your package.json so the project remembers it needs Express to run.

  • Confirm Express was installed by running:
ls

You should now see node_modules, package.json, and package-lock.json in the output.

Seeing permission errors or install failures?

Make sure you are connected to the internet. npm needs to download packages from its registry.

On macOS or Linux, if you see "EACCES" permission errors, try running the command with sudo in front, or follow the npm docs to fix folder permissions.

Help me fix an npm install error.

  • Open the project folder in VS Code by running:
code .

code command not recognized?

On macOS, you may need to install the shell command first. Open VS Code manually, press Cmd+Shift+P, type Shell Command: Install 'code' command in PATH, and select it.

On Windows, the VS Code installer usually adds the command to your PATH. If it does not work, try closing and reopening your terminal.

Help me open my project in VS Code.

VS Code opens with your bookshelf-api folder in the Explorer sidebar. You should see package.json and the node_modules folder listed.

✔️ Awesome, I've got everything!

Great. Double check that you have saved all your files and that your package.json includes Express in its dependencies.

ⓧ I'd like to double check the full code

Click on package.json in the VS Code Explorer sidebar. Its contents should look like this:

{
  "name": "bookshelf-api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.21.2"
  }
}

What about the version number?

Your Express version number (e.g. ^4.21.2) may differ slightly depending on when you ran the install. That is perfectly fine. As long as you see express listed under dependencies, you are good to go.

Your development environment is ready. Next up, you will create your first Express server and see it respond to a request.

Create Your First Express Server

Your project folder is ready and Express is installed. Now it's time to write actual server code.

Express is a web framework that listens for incoming HTTP requests and routes them to handler functions you define. In this step, you'll create a server file and confirm it responds when you send it a request.

In this step, get ready to:
  • Create an Express server file that handles a welcome route.
  • Run the server and test it with a curl request.
Create the server file

Every Express app starts with a file that imports Express, creates an app instance, defines routes, and starts listening for requests. You'll create this file now.

  • In VS Code's Explorer sidebar, right-click inside the bookshelf-api folder and select New File.
  • Name the file app.js and press Enter.
  • Add the following code to app.js:
const express = require('express');
const app = express();
const port = 3000;

// Parse incoming JSON request bodies
app.use(express.json());

// Welcome route
app.get('/', (req, res) => {
  res.json({ message: 'Welcome to the Bookshelf API!' });
});

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

What does this code do?

  • const express = require('express') imports the Express framework you installed in the previous step.
  • const app = express() creates a new Express application instance. This is the object you'll attach routes and middleware to.
  • app.use(express.json()) registers middleware that automatically parses JSON data from incoming request bodies into req.body.
  • app.get('/', ...) defines a route that responds to GET requests at the root URL with a JSON welcome message.
  • app.listen(port, ...) starts the server on port 3000 and prints a confirmation message.
  • Save app.js by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Confirm app.js appears in the Explorer sidebar on the left.

Don't see app.js in the sidebar?

Make sure you created the file inside the bookshelf-api folder, not a parent or nested folder. If you accidentally saved it elsewhere, delete it and create a new file directly in the bookshelf-api root.

help me find where my app.js file was saved

Run and test your server

Your server code is saved, but it's not doing anything yet. You need to start it with Node.js, then send it a request to confirm it responds.

  • Start your server by running this command in the VS Code terminal:
node app.js

You should see this message in your terminal:

Bookshelf API server running at http://localhost:3000

What does this mean?

Your server is now actively listening on port 3000. It will stay running in this terminal until you stop it. Any HTTP request sent to http://localhost:3000 will be handled by your Express app.

  • Open a second terminal by pressing Ctrl+Shift+` (backtick key, usually above Tab).
  • Test your server by running this command in the new terminal:
curl http://localhost:3000

You should see this response:

{"message":"Welcome to the Bookshelf API!"}

Keep the server terminal visible

Throughout this project, you'll use two terminals. One runs your server. The other is where you send curl requests. Always keep the server running in the first terminal.

  • Switch to your server terminal (the first one) and press Ctrl+C to stop the server.

Why stop the server?

Node.js loads app.js once when you run node app.js. If you edit the file while the server is running, it won't pick up your changes. You need to stop and restart the server each time you update the code.

Seeing 'address already in use' or no response?

  • If you see an EADDRINUSE error, another process is using port 3000. Press Ctrl+C in all terminals, close and reopen VS Code, then try node app.js again.
  • If curl returns an error, make sure you're running it in the second terminal while the server is still running in the first.

help me troubleshoot my Express server not responding

✔️ Awesome, I've got everything!

Great. Make sure you've saved app.js and that the server is stopped before moving on.

ⓧ I'd like to double check the full code

Here's the complete app.js file you should have at this point:

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

app.use(express.json());

app.get('/', (req, res) => {
  res.json({ message: 'Welcome to the Bookshelf API!' });
});

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

Your server is alive and responding to requests. Next up, you'll add book data and create the endpoints that let you retrieve it.

Add Book Data and GET Endpoints

Your server is running and responding to requests, but it has nothing useful to serve yet. A real API needs data and endpoints that let clients retrieve that data.

In this step, you will add a collection of books to your server and create two GET endpoints. One endpoint returns all books. The other returns a single book by its ID.

In this step, get ready to:
  • Add an in-memory array of books to act as your data store.
  • Create GET routes to retrieve books from the API.
  • Test your endpoints using curl commands.
Add in-memory book data

Every API needs data to serve. In a production app, this data lives in a database. For this project, you will use a simple JavaScript array that stores book objects in memory.

  • In VS Code, open app.js.
  • Find the line that says app.use(express.json());.
  • Add the following array below that line, leaving a blank line between them:
let books = [
  { id: 1, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', year: 1925 },
  { id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 },
  { id: 3, title: '1984', author: 'George Orwell', year: 1949 }
];

What does this code do?

This creates a let variable called books holding an array of three book objects. Each book has four properties: id, title, author, and year.

Using let instead of const allows the array to be modified later when you add, update, or delete books.

  • Save app.js.

You won't see a change yet. The next chunk creates routes that serve this data.

Create GET routes

A GET request is how clients ask a server for data. You will create two GET routes: one that returns the full book list, and one that returns a single book by its ID.

  • In app.js, find the closing }); of the welcome route (the app.get('/', ...) block).
  • Add the following two routes below it, leaving a blank line between the welcome route and your new code:
// GET all books
app.get('/books', (req, res) => {
  res.json(books);
});

// GET a single book by ID
app.get('/books/:id', (req, res) => {
  const book = books.find(b => b.id === parseInt(req.params.id));
  if (!book) {
    return res.status(404).json({ error: 'Book not found' });
  }
  res.json(book);
});

What does this code do?

  • The first route (/books) returns the entire books array as JSON.
  • The second route (/books/:id) uses a route parameter (:id) to capture the book's ID from the URL.
  • req.params.id gives you the value from the URL, but it arrives as a string. parseInt() converts it to a number so it matches the numeric id in your data.
  • If no book is found, the route returns a 404 status code with an error message. This tells the client that the requested resource does not exist.
  • Save app.js.

You won't see a change yet. The next substep starts the server so you can test these routes.

Seeing a syntax error when you start the server later?

Make sure you placed the new routes after the welcome route's closing }); and before the app.listen(...) call at the bottom of the file.

Check that every opening parenthesis and curly brace has a matching close. help me fix a syntax error in my Express routes

✔️ Awesome, I've got everything!

Great. Double check you've saved app.js and move on to testing.

ⓧ I'd like to double check the full code

Here is the full app.js file so far. Make sure yours matches:

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', year: 1925 },
  { id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 },
  { id: 3, title: '1984', author: 'George Orwell', year: 1949 }
];

app.get('/', (req, res) => {
  res.json({ message: 'Welcome to the Bookshelf API!' });
});

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

// GET a single book by ID
app.get('/books/:id', (req, res) => {
  const book = books.find(b => b.id === parseInt(req.params.id));
  if (!book) {
    return res.status(404).json({ error: 'Book not found' });
  }
  res.json(book);
});

app.listen(port, () => {
  console.log(`Bookshelf API server running at http://localhost:${port}`);
});
Test your GET endpoints

Your routes are in place. Time to start the server and prove they work by sending requests with curl.

  • Start your server by running this command in your terminal:
node app.js
  • You should see: Bookshelf API server running at http://localhost:3000.
  • Open a second terminal window. Keep the server running in the first one.
  • Test the endpoint that returns all books by running:
curl http://localhost:3000/books

You should see all three books returned as a JSON array.

  • Test the endpoint that returns a single book by running:
curl http://localhost:3000/books/1

You should see only "The Great Gatsby" returned as a JSON object.

  • Test what happens when a book does not exist by running:
curl http://localhost:3000/books/99

You should see {"error":"Book not found"} because no book has ID 99.

  • Press Ctrl+C in the server terminal to stop the server.

Not seeing the expected output?

  • If you see Cannot GET /books, your routes may not be saved. Stop the server, save app.js, then start it again.
  • If you see a connection error, make sure the server is still running in the other terminal window.
  • If the single-book route returns the full array, check that you used /books/:id (with the colon) in your route definition.

help me debug my GET endpoints

Your API now serves data. Next up, you will add the ability to create, update, and delete books using POST, PUT, and DELETE endpoints.

Add POST, PUT, and DELETE Endpoints

Your API can read books, but a real API also needs to create, update, and delete them. These three operations complete the CRUD pattern (Create, Read, Update, Delete) that every production API follows.

In this step, you will add three new routes to handle the remaining operations. Each route uses a different HTTP method to signal its intent to the server.

In this step, get ready to:
  • Create a POST route that adds new books to your collection.
  • Create PUT and DELETE routes that update and remove books.
  • Test all endpoints using curl commands.
Create the POST route

A POST request tells the server to create a new resource. Your POST route will read the book details from the request body, assign a new ID, and add the book to the array.

  • In app.js, add the following code below your GET /books/:id route:
// POST create a new book
app.post('/books', (req, res) => {
  const book = {
    id: books.length > 0 ? Math.max(...books.map(b => b.id)) + 1 : 1,
    title: req.body.title,
    author: req.body.author,
    year: req.body.year
  };
  books.push(book);
  res.status(201).json(book);
});

What does this code do?

This route listens for POST requests at /books. When a request arrives, it builds a new book object from the data sent in the request body (req.body).

  • The id line calculates the next available ID by finding the highest existing ID and adding 1.
  • res.status(201).json(book) sends back the newly created book with a 201 status code, which means "Created".
  • Save app.js.
  • You won't see a change yet. You will test this route alongside PUT and DELETE in the next substep.
Create the PUT and DELETE routes

PUT updates an existing resource. DELETE removes one. Both routes use a route parameter (:id) to identify which book to target.

  • In app.js, add the PUT route below your POST route:
// PUT update a book
app.put('/books/:id', (req, res) => {
  const book = books.find(b => b.id === parseInt(req.params.id));
  if (!book) {
    return res.status(404).json({ error: 'Book not found' });
  }
  book.title = req.body.title || book.title;
  book.author = req.body.author || book.author;
  book.year = req.body.year || book.year;
  res.json(book);
});

What does the PUT route do?

This route finds the book by ID, then updates only the fields that were sent in the request body. The || operator keeps the existing value if the request does not provide a replacement.

If no book matches the ID, it returns a 404 error. This is the same pattern you used in the GET route.

  • Add the DELETE route directly below the PUT route:
// DELETE a book
app.delete('/books/:id', (req, res) => {
  const index = books.findIndex(b => b.id === parseInt(req.params.id));
  if (index === -1) {
    return res.status(404).json({ error: 'Book not found' });
  }
  books.splice(index, 1);
  res.status(204).send();
});

What does the DELETE route do?

Instead of find(), this route uses findIndex() to get the position of the book in the array. It then uses splice() to remove the book at that position.

A 204 status code means "No Content". It tells the client the deletion succeeded, but there is no data to send back.

  • Save app.js.
  • You won't see a change yet. The next substep tests all three new routes together.

Seeing a syntax error when you save?

  • Make sure every opening { has a matching closing }. Each route handler has two levels of braces.
  • Check that the app.listen block is still at the very bottom of the file, below all routes.
  • Confirm you placed each new route between the last GET route and app.listen().

Help me fix my syntax error.

Test all endpoints

With all three routes in place, you can now test the complete CRUD cycle. You will create a book, update one, delete one, and then check the final state.

  • Start your server by running this command:
node app.js
  • Open a second terminal window (keep the server running in the first one).
  • Create a new book by running this command:
curl -X POST http://localhost:3000/books -H "Content-Type: application/json" -d "{\"title\":\"Dune\",\"author\":\"Frank Herbert\",\"year\":1965}"

What does this curl command do?

  • -X POST tells curl to send a POST request instead of the default GET.
  • -H "Content-Type: application/json" sets a header telling the server the body contains JSON data.
  • -d sends the JSON data as the request body. The backslash-quotes escape the double quotes inside the JSON string.

You should see the new book returned with "id":4 and a 201 status.

  • Update book 1's title by running this command:
curl -X PUT http://localhost:3000/books/1 -H "Content-Type: application/json" -d "{\"title\":\"The Great Gatsby (Updated)\"}"

You should see book 1 returned with the updated title, while the author and year remain unchanged.

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

You will not see any response body. A 204 status means the deletion succeeded with no content to return.

  • Confirm the deletion by fetching all books:
curl http://localhost:3000/books

You should see three books in the response: To Kill a Mockingbird, 1984, and your newly created Dune. The Great Gatsby is gone.

  • Press Ctrl+C in the server terminal to stop the server.

Getting "Connection refused" or no response?

  • Make sure your server is still running in the first terminal window. If it crashed, check for error messages and fix any typos.
  • Confirm you are running the curl commands in a separate terminal window, not the one running the server.
  • If the POST or PUT command returns an empty response, double check that the backslash-escaped quotes in the -d flag are correct.

Help me troubleshoot my curl commands.

✔️ Awesome, I've got everything!

Great. Double check you have saved app.js and move on to the next step.

ⓧ I'd like to double check the full code

Here is the complete app.js at this point in the project:

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', year: 1925 },
  { id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 },
  { id: 3, title: '1984', author: 'George Orwell', year: 1949 }
];

app.get('/', (req, res) => {
  res.json({ message: 'Welcome to the Bookshelf API!' });
});

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

// GET a single book by ID
app.get('/books/:id', (req, res) => {
  const book = books.find(b => b.id === parseInt(req.params.id));
  if (!book) {
    return res.status(404).json({ error: 'Book not found' });
  }
  res.json(book);
});

// POST create a new book
app.post('/books', (req, res) => {
  const book = {
    id: books.length > 0 ? Math.max(...books.map(b => b.id)) + 1 : 1,
    title: req.body.title,
    author: req.body.author,
    year: req.body.year
  };
  books.push(book);
  res.status(201).json(book);
});

// PUT update a book
app.put('/books/:id', (req, res) => {
  const book = books.find(b => b.id === parseInt(req.params.id));
  if (!book) {
    return res.status(404).json({ error: 'Book not found' });
  }
  book.title = req.body.title || book.title;
  book.author = req.body.author || book.author;
  book.year = req.body.year || book.year;
  res.json(book);
});

// DELETE a book
app.delete('/books/:id', (req, res) => {
  const index = books.findIndex(b => b.id === parseInt(req.params.id));
  if (index === -1) {
    return res.status(404).json({ error: 'Book not found' });
  }
  books.splice(index, 1);
  res.status(204).send();
});

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

Your API now supports the full CRUD cycle. Next up, you will add custom middleware that logs every incoming request to your server terminal.

Add Request Logging Middleware

Your API now handles all four CRUD operations. Any client can create, read, update, and delete books from your collection.

In production, knowing which requests hit your server is critical for debugging and monitoring. Right now, your server processes requests silently. In this step, you will add custom middleware that logs every incoming request to the terminal.

In this step, get ready to:
  • Create a logging middleware function that runs before every route.
  • Test the logger by making requests and watching the server terminal.
Create a logger middleware

Express middleware is a function that runs between receiving a request and sending a response. It can inspect requests, modify responses, or simply pass control along to the next function in the chain.

  • In app.js, find the app.use(express.json()); line.
  • Add the following middleware directly below it:
// Request logging middleware
app.use((req, res, next) => {
  const timestamp = new Date().toISOString();
  console.log(`[${timestamp}] ${req.method} ${req.url}`);
  next();
});

What does this code do?

This middleware function receives three arguments: the request object, the response object, and a next function.

  • new Date().toISOString() creates a human-readable timestamp.
  • req.method is the HTTP verb (GET, POST, PUT, DELETE).
  • req.url is the path the client requested.
  • next() passes control to the next middleware or route handler. Without it, the request would hang forever.

Why does placement matter?

Express runs middleware in the order you define it. Because this logger is placed before all your routes, it intercepts every request. If you placed it after the routes, it would never run because the routes already send responses.

  • Save app.js.
  • You won't see a change yet. The next substep starts the server so you can confirm the logger is working.
Test the logger

Time to see your middleware in action. You will start the server, make a few requests, and watch the logs appear in real time.

  • Start your server by running this command:
node app.js
  • Open a second terminal window (keep the server running in the first one).
  • Make a couple of requests by running these commands:
curl http://localhost:3000/books
curl http://localhost:3000/books/2
  • Switch back to the terminal where your server is running.

You should see log entries like this:

[2026-06-04T10:30:00.000Z] GET /books
[2026-06-04T10:30:05.000Z] GET /books/2

What should I see?

Each log line shows the exact time the request arrived, which HTTP method was used, and which URL path was hit. Your timestamps will differ from the example above. The important thing is that every request you make appears as a new log line.

  • Press Ctrl+C in the server terminal to stop the server.

Not seeing any log lines?

  • Make sure you placed the middleware after app.use(express.json()) but before the let books array and all your routes.
  • Check that you called next() at the end of the middleware function. Without it, requests hang and routes never respond.
  • Confirm you restarted the server after saving your changes. The old server process does not pick up edits automatically.

Help me debug my Express middleware logging

✔️ Awesome, I've got everything!

Great. Double check you have saved app.js with the logging middleware in place.

ⓧ I'd like to double check the full code

Here is the complete app.js file at this point in the project:

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

// Built-in middleware for parsing JSON request bodies
app.use(express.json());

// Request logging middleware
app.use((req, res, next) => {
  const timestamp = new Date().toISOString();
  console.log(`[${timestamp}] ${req.method} ${req.url}`);
  next();
});

// In-memory book data
let books = [
  { id: 1, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', year: 1925 },
  { id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 },
  { id: 3, title: '1984', author: 'George Orwell', year: 1949 }
];

// Welcome route
app.get('/', (req, res) => {
  res.json({ message: 'Welcome to the Bookshelf API!' });
});

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

// GET a single book by ID
app.get('/books/:id', (req, res) => {
  const book = books.find(b => b.id === parseInt(req.params.id));
  if (!book) {
    return res.status(404).json({ error: 'Book not found' });
  }
  res.json(book);
});

// POST create a new book
app.post('/books', (req, res) => {
  const book = {
    id: books.length > 0 ? Math.max(...books.map(b => b.id)) + 1 : 1,
    title: req.body.title,
    author: req.body.author,
    year: req.body.year
  };
  books.push(book);
  res.status(201).json(book);
});

// PUT update a book
app.put('/books/:id', (req, res) => {
  const book = books.find(b => b.id === parseInt(req.params.id));
  if (!book) {
    return res.status(404).json({ error: 'Book not found' });
  }
  book.title = req.body.title || book.title;
  book.author = req.body.author || book.author;
  book.year = req.body.year || book.year;
  res.json(book);
});

// DELETE a book
app.delete('/books/:id', (req, res) => {
  const index = books.findIndex(b => b.id === parseInt(req.params.id));
  if (index === -1) {
    return res.status(404).json({ error: 'Book not found' });
  }
  books.splice(index, 1);
  res.status(204).send();
});

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

Your API now has built-in observability. Every request that hits your server leaves a trace in the terminal. This is the foundation for the kind of monitoring you would see in production APIs.

Secret mission

Add a Search Endpoint

Your API lets you fetch all books or one by ID. But what if someone wants to find a book by part of its title or author name? Add a search endpoint that filters your collection using query parameters.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • The bookshelf-api project folder on your Desktop (contains app.js, package.json, and node_modules).
  • Node.js and npm (installed on your machine).

Keep everything running

No action needed. Choose this if you plan to keep building on your API or want to reference the code later.

  • Your project folder stays on your Desktop ready to use anytime.
  • To start your server again, open a terminal, navigate to the bookshelf-api folder, and run node app.js.
  • Node.js stays installed on your machine for future projects.

Pause - I'll come back to this later

Your server is already stopped, so there are no running processes to shut down. Everything is paused by default.

  • If you left the server running, press Ctrl+C in the terminal to stop it.
  • Your project files remain on your Desktop. No memory or CPU is used while the server is stopped.
  • When you are ready to resume, open a terminal in the bookshelf-api folder and run node app.js.

Delete - I don't want to use this again

Remove all project files from your machine. This cannot be undone.

  • Delete the project folder by running this command in your terminal:

macOS

cd ~/Desktop
rm -rf bookshelf-api

Windows

cd ~\Desktop
Remove-Item -Recurse -Force bookshelf-api
  • This removes app.js, package.json, node_modules, and everything inside the folder.
  • To uninstall Node.js on Windows, open Settings, select Apps, then Installed apps. Find Node.js in the list and click Uninstall.
  • To uninstall Node.js on macOS, if you installed it via the official installer, download and re-run the installer package which includes an uninstall option. If you used Homebrew, run brew uninstall node.
  • Removing Node.js is optional. Most developers keep it installed since many projects require it.

Nice Work!

Nice Work!

Nice work! You just built a fully functional REST API from scratch using Node.js and Express.

You've learned how to:

  • Built a Node.js server with Express that listens for HTTP requests and responds with JSON data.
  • Implemented all four CRUD operations (Create, Read, Update, Delete) using POST, GET, PUT, and DELETE endpoints with appropriate HTTP status codes.
  • Created custom middleware for request logging and used route parameters to target specific resources.
  • Added a search endpoint with query parameter filtering for case-insensitive matching.

Ready to quiz yourself?