Build a Book Collection REST API
Build a REST API with Express to manage a book collection using CRUD operations.
Introduction
30 Second Summary
Every time you browse a library catalog, scroll through a bookstore app, or search for a title online, there's an API behind the scenes fetching that data for you.
In this project, you will build a REST API server that manages a book collection. You'll create endpoints that handle creating, reading, updating, and deleting books using Express and Node.js.
What You'll Build
A local REST API server built with Express that accepts HTTP requests and responds with JSON data, implementing full CRUD operations for a book collection.
By the end of this project, you'll have:
- A Node.js project with Express installed and configured as your API framework.
- A running API server with five endpoints implementing full CRUD (Create, Read, Update, Delete) operations.
- Tested endpoints that return proper HTTP status codes for success and error cases.
- Secret Mission: Add search functionality that lets users filter books by author or title using query parameters.
Are there any prerequisites?
You should be comfortable with basic JavaScript concepts like variables, functions, arrays, and objects. No backend or server experience is needed.
Set Up Your Development Environment
Every web application talks to a backend server. Your REST API will be the backend that receives HTTP requests and sends back structured data about books.
In this step, you'll confirm Node.js is ready, create your project folder, install Express, and write a minimal server that proves everything is connected.
In this step, get ready to:
- Confirm Node.js and npm are ready on your machine.
- Create your project folder and install Express.
- Write and start your first Express server.
Confirm your Node.js installation
Node.js lets you run JavaScript outside the browser. npm is its package manager, which you'll use to install libraries like Express.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal and press Enter to open it.
- Check your Node.js version by running this command:
node --version
- Check your npm version by running:
npm --version
You should see version numbers printed for both. Your Node.js version should start with v24.
✔️ I see version 24 or higher
Node.js is ready. You're all set to create your project.
ⓧ I see an older version
You need Node.js v24 or higher for this project.
- Visit nodejs.org and download the LTS version.
- Run the installer and follow the prompts.
- Close and reopen Terminal, then run node --version again to confirm.
ⓧ Command not found
Node.js is not installed on your machine yet.
- Visit nodejs.org and download the LTS version.
- Run the installer and follow the prompts.
- Close and reopen Terminal, then run node --version again to confirm.
Create your project and install Express
Express is a lightweight framework that gives Node.js the ability to handle HTTP requests and send responses. You'll install it into a dedicated project folder.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir book-collection-api
cd book-collection-api
- Initialize your project with npm by running:
npm init -y
What does this command do?
The -y flag tells npm to accept all defaults and skip the interactive prompts. This creates a package.json file that tracks your project's name, version, and dependencies.
- Install Express by running:
npm install express@5.2.1
What does this command do?
This downloads Express version 5.2.1 and saves it as a dependency in your package.json. The files are stored in a node_modules folder that npm manages for you.
- Confirm the install worked by running:
ls
You should see node_modules, package.json, and package-lock.json in the output.
Don't see node_modules?
Make sure you ran npm install express@5.2.1 from inside the book-collection-api folder. Run pwd to check your current directory.
Help me troubleshoot my npm install
- Now update your package.json to include a start script. Open package.json in any text editor and replace its entire contents with:
{
"name": "book-collection-api",
"version": "1.0.0",
"description": "A REST API for managing a book collection",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "5.2.1"
}
}
Why replace the file?
The npm init -y command generates extra fields you don't need. Replacing it keeps your project clean and adds a start script so you can later run your server with npm start.
- Save package.json.
- Run npm install once more to sync the lock file with your updated dependency version:
npm install
Write and run your first Express server
With Express installed, you can write a server in just a few lines. This server listens for incoming requests and responds with JSON data.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Cursor (or VS Code) and press Enter to open it.
- Click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop and select the book-collection-api folder, then click Open.
What is Cursor?
Cursor is a code editor built on VS Code with AI features added. If you don't have Cursor, VS Code works identically for this project. Both give you a file explorer, built-in terminal, and syntax highlighting.
- Create a new file by clicking the new file icon in the Explorer sidebar (or press Cmd+N on macOS / Ctrl+N on Windows).
- Name the file index.js and press Enter.
- Add your Express server code by pasting the following into index.js:
const express = require('express');
const app = express();
const port = 3000;
// GET / - Welcome message
app.get('/', (req, res) => {
res.json({ message: 'Welcome to the Book Collection API' });
});
app.listen(port, () => {
console.log(`Book Collection API running at http://localhost:${port}`);
});
What does this code do?
- require('express') imports the Express library you just installed.
- express() creates your application instance. Every route and setting attaches to this app object.
- app.get('/', ...) defines a route. When someone visits the root URL, the server responds with a JSON message.
- app.listen(port, ...) starts the server on port 3000 and prints a confirmation to your terminal.
- Save index.js.
- Open the built-in terminal by pressing Ctrl+` (backtick key, same shortcut on macOS and Windows).
- Start your server by running:
node index.js
You should see this message printed in your terminal:
Book Collection API running at http://localhost:3000
- Open your browser and navigate to http://localhost:3000.
You should see a JSON response: {"message":"Welcome to the Book Collection API"}.
Don't see the JSON response?
- Check that your terminal still shows the "running" message. If the server crashed, look for a syntax error in the terminal output.
- Make sure the URL is exactly http://localhost:3000 with no trailing path.
- If you see "port already in use", another process is using port 3000. Close other terminal windows and try again.
Help me debug my Express server
✔️ Awesome, I've got everything!
Great. Double check you've saved both files before moving on.
ⓧ I'd like to double check the full code
Here is what your index.js should look like:
const express = require('express');
const app = express();
const port = 3000;
// GET / - Welcome message
app.get('/', (req, res) => {
res.json({ message: 'Welcome to the Book Collection API' });
});
app.listen(port, () => {
console.log(`Book Collection API running at http://localhost:${port}`);
});
And here is what your package.json should look like:
{
"name": "book-collection-api",
"version": "1.0.0",
"description": "A REST API for managing a book collection",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "5.2.1"
}
}
Your server is alive and responding to requests. Next up, you'll add book data and build your first real API endpoints.
Build Your First GET Endpoints
Your server is running and responding to requests. Now it is time to give it something useful to serve.
Every REST API needs data and endpoints. An endpoint is a URL path that your server listens on. When a client sends a request to that path, the server responds with the appropriate data. In this step, you will create two GET endpoints that let clients retrieve books from your collection.
In this step, get ready to:
- Add an in-memory book collection to your server.
- Create endpoints that return all books or a single book by ID.
- Test your endpoints using curl in the terminal.
Add your book data and the GET /books endpoint
Your API needs data to serve. Instead of connecting to a database (which adds complexity), you will store books in a plain JavaScript array. This keeps the focus on learning how Express routes work.
- In index.js, find the line that says const port = 3000;. Add the following code below it, above the existing GET / route:
// In-memory book collection
let books = [
{ id: 1, title: 'The Hobbit', author: 'J.R.R. Tolkien', year: 1937 },
{ id: 2, title: 'Dune', author: 'Frank Herbert', year: 1965 },
{ id: 3, title: 'Neuromancer', author: 'William Gibson', year: 1984 }
];
- Now find the closing }); of the GET / route. Add this code below it:
// GET /books - Get all books
app.get('/books', (req, res) => {
res.json(books);
});
What does this code do?
The books array holds three book objects. Each book has an id, title, author, and year property. This is your in-memory data store.
The app.get('/books', ...) line registers a route handler. When a client sends a GET request to /books, Express calls your function, which responds with the full array as JSON.
- Save index.js.
- You will not see a change yet because the server needs to be restarted to pick up your edits. The next substep adds one more endpoint before you test everything together.
Syntax error when you save?
Make sure your books array is placed between const port = 3000; and the // GET / - Welcome message comment. Check that each object in the array ends with a comma except the last one, and that the array itself ends with ];.
help me fix a syntax error in my Express books array
Create the GET /books/:id endpoint
Returning all books is useful, but clients often need a single specific book. A route parameter lets you capture a value from the URL itself. Express uses the :id syntax to define a dynamic segment in the path.
- In index.js, find the GET /books route you just added. Add the following code below its closing });:
// GET /books/:id - Get a single book by ID
app.get('/books/:id', (req, res) => {
const id = parseInt(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?
- req.params.id grabs the value from the URL. If the client requests /books/2, then req.params.id is "2" (a string).
- parseInt() converts the string to a number so it matches the numeric id in your books array.
- books.find() searches the array for a book with a matching ID. If none exists, it returns undefined.
- The if (!book) check handles the missing-book case by returning a 404 status code with an error message. This tells the client the resource does not exist.
- Save index.js.
- You will test this endpoint in the next substep along with the GET /books endpoint.
Red underline on the route handler?
Check that the new route is placed after the GET /books route but before the app.listen(...) call at the bottom of the file. Express matches routes in the order they are defined, and app.listen must always be last.
help me fix my Express route placement
✔️ Awesome, I've got everything!
Great. Double check you have saved index.js before moving on to testing.
ⓧ I'd like to double check the full code
Here is what your full index.js should look like at this point:
const express = require('express');
const app = express();
const port = 3000;
// In-memory book collection
let books = [
{ id: 1, title: 'The Hobbit', author: 'J.R.R. Tolkien', year: 1937 },
{ id: 2, title: 'Dune', author: 'Frank Herbert', year: 1965 },
{ id: 3, title: 'Neuromancer', author: 'William Gibson', year: 1984 }
];
// GET / - Welcome message
app.get('/', (req, res) => {
res.json({ message: 'Welcome to the Book Collection API' });
});
// GET /books - Get all books
app.get('/books', (req, res) => {
res.json(books);
});
// GET /books/:id - Get a single book by ID
app.get('/books/:id', (req, res) => {
const id = parseInt(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);
});
app.listen(port, () => {
console.log(`Book Collection API running at http://localhost:${port}`);
});
Test your endpoints with curl
Your code is ready. Now you will send real HTTP requests to your server to confirm each endpoint works. You will use curl, a command-line tool for making HTTP requests that comes pre-installed on macOS.
- Switch back to your terminal. Stop the running server (if it is still running from the previous step) by pressing Ctrl+C.
- Restart the server with the updated code by running:
node index.js
- Open a second terminal so you can send requests while the server keeps running. Press Ctrl+Shift+` (backtick) to open a new terminal tab in Cursor or VS Code.
Why a second terminal?
Your server occupies the first terminal. It is actively listening for requests and will not accept new commands. The second terminal lets you send requests to it using curl.
- Test the GET /books endpoint by running this command in your second terminal:
curl http://localhost:3000/books
You should see the full array of three books printed as JSON in your terminal:
[{"id":1,"title":"The Hobbit","author":"J.R.R. Tolkien","year":1937},{"id":2,"title":"Dune","author":"Frank Herbert","year":1965},{"id":3,"title":"Neuromancer","author":"William Gibson","year":1984}]
- Test fetching a single book by running:
curl http://localhost:3000/books/1
You should see only The Hobbit returned:
{"id":1,"title":"The Hobbit","author":"J.R.R. Tolkien","year":1937}
- Test requesting a book that does not exist by running:
curl http://localhost:3000/books/99
You should see a 404 error message:
{"error":"Book not found"}
Seeing "Connection refused" or no output?
- Make sure your server is running in the first terminal. You should see the Book Collection API running at http://localhost:3000 message.
- If you see a syntax error when starting the server, switch back to index.js and compare your code against the full-file double check tab above.
- Make sure you are running the curl commands in the second terminal, not in the terminal where the server is running.
help me debug my Express server connection issue
Your API can now serve data to any client that asks for it. Next up, you will add the ability to create and update books using POST and PUT requests.
Add POST and PUT Endpoints
Your API can read data, but a read-only API is only half the story. Real-world APIs let clients create new resources and update existing ones too.
In this step, you'll add two new endpoints. A POST endpoint lets clients add new books to the collection. A PUT endpoint lets them update a book that already exists.
In this step, get ready to:
- Enable JSON body parsing with Express middleware.
- Create a POST endpoint that adds new books to your collection.
- Create a PUT endpoint that updates existing books.
Enable JSON body parsing and create the POST endpoint
When a client sends data to your API (like a new book's title and author), that data arrives in the request body as JSON. Express doesn't parse request bodies by default. You need to add middleware that reads incoming JSON and makes it available on req.body.
- In index.js, find the line const port = 3000; near the top of your file.
- Add the JSON parsing middleware directly below it by pasting this code:
// Middleware to parse JSON request bodies
app.use(express.json());
What does express.json() do?
express.json() is built-in Express middleware that reads the raw JSON text from incoming requests and converts it into a JavaScript object.
Without it, req.body would be undefined in Express 5. With it, you can access the parsed data directly (e.g. req.body.title).
- Now find your books array. Add a counter variable directly below the closing bracket ]; by pasting this line:
let nextId = 4;
- Add the POST route below your GET /books/:id route by pasting this code:
// POST /books - Add a new book
app.post('/books', (req, res) => {
const { title, author, year } = req.body;
if (!title || !author || !year) {
return res.status(400).json({ error: 'Title, author, and year are required' });
}
const newBook = { id: nextId++, title, author, year };
books.push(newBook);
res.status(201).json(newBook);
});
What does this code do?
- const { title, author, year } = req.body pulls the book details out of the incoming JSON body using destructuring.
- The if (!title || !author || !year) check returns a 400 (Bad Request) status if any required field is missing.
- nextId++ assigns the current counter value as the new book's ID, then increments it for next time.
- res.status(201).json(newBook) sends back the created book with a 201 (Created) status code, signalling that a new resource was successfully created.
- Save index.js.
Seeing a syntax error when you save?
- Make sure the app.use(express.json()) line is placed before any route definitions. Middleware must be registered before the routes that depend on it.
- Check that let nextId = 4; is outside the books array, not inside it.
- Verify you pasted the POST route after the closing }); of your GET /books/:id route, not inside it.
Help me fix a syntax error in my Express POST route
Create the PUT endpoint
The PUT method updates an existing resource. Your PUT endpoint will find a book by its ID, replace its properties with the new data, and return the updated version.
- Add the PUT route directly below your POST route by pasting this code:
// PUT /books/:id - Update an existing book
app.put('/books/:id', (req, res) => {
const id = parseInt(req.params.id);
const bookIndex = books.findIndex(b => b.id === id);
if (bookIndex === -1) {
return res.status(404).json({ error: 'Book not found' });
}
const { title, author, year } = req.body;
books[bookIndex] = { ...books[bookIndex], title, author, year };
res.json(books[bookIndex]);
});
What does this code do?
- findIndex returns the position of the matching book in the array, or -1 if no match exists.
- The spread operator ...books[bookIndex] copies the existing book's properties, then the new title, author, and year values overwrite the old ones.
- Unlike POST, a successful update returns 200 (the default) because no new resource was created.
- Save index.js.
Seeing 'Cannot find module' or server won't start?
- Make sure all your route handlers end with }); and that brackets are balanced.
- Check that the PUT route is placed after the POST route and before app.listen.
Help me debug why my Express server won't start after adding the PUT route
Test both endpoints with curl
Time to send some data to your API. You'll use curl with additional flags to send JSON in the request body.
- Stop your running server by pressing Ctrl+C in the terminal.
- Restart it with the updated code by running:
node index.js
- Test the POST endpoint by sending a new book. Open a second terminal window and run:
curl -X POST http://localhost:3000/books -H "Content-Type: application/json" -d '{"title":"1984","author":"George Orwell","year":1949}'
What do these curl flags mean?
- -X POST tells curl to use the POST HTTP method instead of the default GET.
- -H "Content-Type: application/json" sets a header telling the server that the body contains JSON data.
- -d '{...}' attaches the JSON data as the request body.
You should see the newly created book returned with an id of 4 and a 201 status.
- Verify the book was actually added by running:
curl http://localhost:3000/books
You should see all four books in the response, including the new "1984" entry.
- Test the PUT endpoint by updating the book you just added. Run:
curl -X PUT http://localhost:3000/books/4 -H "Content-Type: application/json" -d '{"title":"Nineteen Eighty-Four","author":"George Orwell","year":1949}'
You should see the updated book with its title changed to "Nineteen Eighty-Four".
- Test the 404 error case by trying to update a book that doesn't exist:
curl -X PUT http://localhost:3000/books/99 -H "Content-Type: application/json" -d '{"title":"Ghost Book","author":"Nobody","year":2000}'
You should see {"error":"Book not found"} returned with a 404 status.
Getting an empty response or 'undefined' errors?
- Make sure app.use(express.json()) is placed before all your route definitions. Without it, req.body will be undefined.
- Check that your curl command uses straight quotes around the JSON, not curly/smart quotes.
- Make sure you restarted the server after saving your changes.
Help me debug why my POST or PUT endpoint returns undefined or empty data
✔️ Awesome, I've got everything!
Great. Double check you've saved index.js and your server is running with the latest changes.
ⓧ I'd like to double check the full code
Here's what your complete index.js should look like at this point:
const express = require('express');
const app = express();
const port = 3000;
// Middleware to parse JSON request bodies
app.use(express.json());
// In-memory book collection
let books = [
{ id: 1, title: 'The Hobbit', author: 'J.R.R. Tolkien', year: 1937 },
{ id: 2, title: 'Dune', author: 'Frank Herbert', year: 1965 },
{ id: 3, title: 'Neuromancer', author: 'William Gibson', year: 1984 }
];
let nextId = 4;
// GET / - Welcome message
app.get('/', (req, res) => {
res.json({ message: 'Welcome to the Book Collection API' });
});
// GET /books - Get all books
app.get('/books', (req, res) => {
res.json(books);
});
// GET /books/:id - Get a single book by ID
app.get('/books/:id', (req, res) => {
const id = parseInt(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);
});
// POST /books - Add a new book
app.post('/books', (req, res) => {
const { title, author, year } = req.body;
if (!title || !author || !year) {
return res.status(400).json({ error: 'Title, author, and year are required' });
}
const newBook = { id: nextId++, title, author, year };
books.push(newBook);
res.status(201).json(newBook);
});
// PUT /books/:id - Update an existing book
app.put('/books/:id', (req, res) => {
const id = parseInt(req.params.id);
const bookIndex = books.findIndex(b => b.id === id);
if (bookIndex === -1) {
return res.status(404).json({ error: 'Book not found' });
}
const { title, author, year } = req.body;
books[bookIndex] = { ...books[bookIndex], title, author, year };
res.json(books[bookIndex]);
});
app.listen(port, () => {
console.log(`Book Collection API running at http://localhost:${port}`);
});
Your API now supports creating and updating books. Next up, you'll add the ability to delete books and handle errors for routes that don't exist.
Add the DELETE Endpoint and Error Handling
Your API can now create, read, and update books. The final piece of CRUD is the D: Delete.
In this step, you'll add a DELETE endpoint so users can remove books from the collection. You'll also add middleware that catches requests to routes that don't exist and handles unexpected server errors gracefully.
In this step, get ready to:
- Create a DELETE endpoint that removes a book by ID.
- Add catch-all middleware for unmatched routes and server errors.
- Test everything with curl to confirm your API handles all scenarios.
Create the DELETE endpoint
When a user wants to remove a book, your API needs to find it by ID, remove it from the array, and respond with a status code that means "done, nothing to send back." That status code is 204 No Content.
- In index.js, find the app.listen call at the bottom of your file.
- Add the following DELETE route above the app.listen call:
// DELETE /books/:id - Delete a book
app.delete('/books/:id', (req, res) => {
const id = parseInt(req.params.id);
const bookIndex = books.findIndex(b => b.id === id);
if (bookIndex === -1) {
return res.status(404).json({ error: 'Book not found' });
}
books.splice(bookIndex, 1);
res.status(204).send();
});
What does this code do?
- parseInt(req.params.id) converts the URL parameter from a string to a number so you can compare it against the book IDs in your array.
- findIndex returns the position of the matching book in the array, or -1 if no book has that ID.
- books.splice(bookIndex, 1) removes exactly one element at that position from the array.
- res.status(204).send() sends back a 204 response with no body. This tells the client "the deletion worked, and there's nothing to return."
- Save index.js.
You won't test this yet. The next substep adds error-handling middleware, then you'll restart and test everything together.
Add catch-all and error-handling middleware
Right now, if someone hits a URL your API doesn't recognize (like /pizza), Express sends a default HTML error page. You want to return a clean JSON error instead.
- In index.js, add the following two middleware functions below the DELETE route you just added and above the app.listen call:
// 404 handler for unmatched routes
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong' });
});
What does this code do?
The first app.use has no path, so it catches any request that didn't match a route above it. It returns a JSON object with a 404 status instead of the default HTML page.
The second app.use has four parameters: (err, req, res, next). Express recognizes this signature as an error handler. If any route throws an unexpected error, this middleware catches it and returns a clean 500 response instead of crashing the server.
Why does the order matter?
Express processes middleware in the order you define it. The catch-all 404 handler must come after all your routes. If you put it before a route, that route would never be reached.
The error handler must be last because it only catches errors thrown by middleware above it.
- Save index.js.
You'll restart the server and test all the new functionality in the next substep.
Test the DELETE endpoint and error handling
Time to restart your server and confirm that deletion, missing-book errors, and unknown-route errors all work correctly.
- Switch to your Terminal where the server is running.
- Press Ctrl+C to stop the server.
- Restart the server by running:
node index.js
You should see Book Collection API running at http://localhost:3000 in your Terminal.
- Delete the book with ID 2 by running:
curl -X DELETE http://localhost:3000/books/2
Why is there no output?
A 204 No Content response has no body. When curl receives a 204, it prints nothing. This is the expected behavior for a successful delete.
- Confirm the book is gone by running:
curl http://localhost:3000/books
You should see only two books in the response. The book with ID 2 (Dune) is no longer in the array.
- Try deleting a book that doesn't exist by running:
curl -X DELETE http://localhost:3000/books/99
You should see a 404 error response:
{"error":"Book not found"}
- Test your catch-all middleware by hitting a route that doesn't exist:
curl http://localhost:3000/unknown
You should see your custom 404 message:
{"error":"Route not found"}
What's the difference between the two 404s?
The Book not found error comes from the DELETE route when the ID doesn't match any book. The Route not found error comes from your catch-all middleware when the URL path itself doesn't match any route.
Not seeing the expected responses?
- Make sure you stopped and restarted the server after saving. Changes to index.js don't take effect until you restart.
- Check that the catch-all app.use middleware is placed after all your routes but before app.listen. If it's above a route, that route becomes unreachable.
- Verify you don't have a typo in bookIndex or splice. A misspelled variable name will cause a 500 error instead of a 204.
Still stuck? Help me debug why my DELETE endpoint or error middleware isn't working.
✔️ Awesome, I've got everything!
Great. Double check you've saved index.js and your server is running with the latest code.
ⓧ I'd like to double check the full code
Here's the complete index.js file at this point:
const express = require('express');
const app = express();
const port = 3000;
// Middleware to parse JSON request bodies
app.use(express.json());
// In-memory book collection
let books = [
{ id: 1, title: 'The Hobbit', author: 'J.R.R. Tolkien', year: 1937 },
{ id: 2, title: 'Dune', author: 'Frank Herbert', year: 1965 },
{ id: 3, title: 'Neuromancer', author: 'William Gibson', year: 1984 }
];
let nextId = 4;
// GET / - Welcome message
app.get('/', (req, res) => {
res.json({ message: 'Welcome to the Book Collection API' });
});
// GET /books - Get all books
app.get('/books', (req, res) => {
res.json(books);
});
// GET /books/:id - Get a single book by ID
app.get('/books/:id', (req, res) => {
const id = parseInt(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);
});
// POST /books - Add a new book
app.post('/books', (req, res) => {
const { title, author, year } = req.body;
if (!title || !author || !year) {
return res.status(400).json({ error: 'Title, author, and year are required' });
}
const newBook = { id: nextId++, title, author, year };
books.push(newBook);
res.status(201).json(newBook);
});
// PUT /books/:id - Update an existing book
app.put('/books/:id', (req, res) => {
const id = parseInt(req.params.id);
const bookIndex = books.findIndex(b => b.id === id);
if (bookIndex === -1) {
return res.status(404).json({ error: 'Book not found' });
}
const { title, author, year } = req.body;
books[bookIndex] = { ...books[bookIndex], title, author, year };
res.json(books[bookIndex]);
});
// DELETE /books/:id - Delete a book
app.delete('/books/:id', (req, res) => {
const id = parseInt(req.params.id);
const bookIndex = books.findIndex(b => b.id === id);
if (bookIndex === -1) {
return res.status(404).json({ error: 'Book not found' });
}
books.splice(bookIndex, 1);
res.status(204).send();
});
// 404 handler for unmatched routes
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong' });
});
app.listen(port, () => {
console.log(`Book Collection API running at http://localhost:${port}`);
});
Your REST API now handles all four CRUD operations and responds gracefully to errors. Next up, you'll take on the Secret Mission and add search functionality with query parameters.
Secret mission
Add Search with Query Parameters
Your API returns every book every time. Real-world APIs let users search and filter results without downloading the entire collection. Can you add query parameter support to narrow down results by author or title?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs or cloud resources to worry about.
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- The book-collection-api project folder on your Desktop (contains index.js, package.json, and node_modules).
- The running Node.js server process (if still active in your terminal).
Keep everything running
No action needed. Choose this if you're still actively building or want to keep experimenting with your API.
- Your server continues running as long as the terminal stays open.
- You can restart it anytime by running node index.js from inside the book-collection-api folder.
Pause - I'll come back to this later
Stop the running server to free up your terminal. Your project files stay intact for next time.
- Switch to the terminal where your server is running.
- Press Ctrl+C to stop the server process.
- Your book-collection-api folder remains on your Desktop. You can reopen it and run node index.js whenever you're ready to continue.
Delete - I don't want to use this again
Remove all project files and start fresh. This permanently deletes your code.
- Switch to the terminal where your server is running and press Ctrl+C to stop it.
- Delete the project folder by running this command (macOS/Linux):
cd ~/Desktop
rm -rf book-collection-api
On Windows, run this instead:
cd ~/Desktop
rmdir /s /q book-collection-api
- Confirm the folder is gone by running ls (macOS/Linux) or dir (Windows) on your Desktop. You should no longer see book-collection-api listed.
That's a Wrap!
That's a Wrap!
Nice work! You just built a fully functional REST API from scratch using Node.js and Express.
You've learned how to:
- Built an Express server with full CRUD endpoints (GET, POST, PUT, DELETE) that handle JSON requests and responses.
- Used route parameters and middleware to target specific resources and parse request bodies.
- Returned appropriate HTTP status codes (200, 201, 204, 404, 500) and added error handling for missing resources and unknown routes.
- Added search functionality using query parameters to filter books by author or title with case-insensitive partial matching.
Ready to quiz yourself?