Build a Bookshelf REST API with Express
Build a full CRUD REST API for a bookshelf using Node.js and Express.
Introduction
30 Second Summary
Every time you open a weather app, scroll social media, or search for a product online, your device is sending requests to a server and getting data back. This invisible conversation between apps and servers is what makes the modern internet work.
In this project, you will build a REST API server that manages a personal bookshelf. You will create endpoints that handle adding, reading, updating, and deleting books, then test them using terminal commands.
What You'll Build
A locally-running REST API built with Node.js and Express that supports full CRUD operations for a bookshelf resource.
By the end of this project, you'll have:
- A Node.js project with Express installed and configured as your API server.
- A single server.js file containing working endpoints for creating, reading, updating, and deleting books.
- Verified API responses tested with curl commands from the terminal.
- Secret Mission: Add input validation middleware and centralized error handling to make your API production-ready.
Are there any prerequisites?
You should have basic JavaScript knowledge, including variables, functions, objects, and arrays. No prior experience with servers or APIs is required.
Before We Start
Before you can build a REST API, you need a few tools installed on your computer. The two essentials are Node.js (the JavaScript runtime that runs your server code) and npm (the package manager that installs libraries).
In this step, you will install Node.js, create your project folder, install Express (the web framework for building your API), and open everything in your code editor.
In this step, get ready to:
- Install and verify Node.js.
- Create your project folder and install Express.
- Open the project in Cursor (or VS Code).
Install and verify Node.js
Node.js lets you run JavaScript outside of a web browser. Your API server will run on Node.js, and npm (which comes bundled with it) will handle installing packages like Express.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
- Check if Node.js is already installed by running this command:
node -v
Also check that npm is available:
npm -v
✔️ I see version 18 or higher
Your output looks something like v24.16.0 (or any number starting with 18, 20, 22, or 24). Node.js is ready to go.
Skip ahead to the next substep.
ⓧ I see a version lower than 18
You have an older version of Node.js that won't work with Express 5. You need to upgrade to version 18 or higher.
- Go to https://nodejs.org in your browser.
- Download the LTS version (currently v24.16.0).
- Run the installer and follow the prompts. The new version will replace your old one.
- Close your terminal and open a new one.
- Run node -v again to confirm the update.
ⓧ Command not found
Node.js is not installed yet. Let's install it now.
- Go to https://nodejs.org in your browser.
- Download the LTS version (currently v24.16.0).
- Run the installer and accept the default settings.
- Close your terminal and open a new one.
- Run node -v and npm -v to confirm both are installed.
Still seeing command not found after installing?
Close ALL open terminal windows and open a fresh one. The terminal needs to restart to pick up the new installation path.
On macOS, if the issue persists, try opening a new terminal tab with Cmd+T. On Windows, try restarting your computer.
Why can't my terminal find Node.js after I installed it?
Create your project folder and install Express
Now you will create a dedicated folder for your project and install Express, the framework that makes building APIs in Node.js straightforward.
- 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
- Confirm the folder exists by running:
ls
The folder will be empty for now. That is expected.
- Initialize a new npm project by running this command:
npm init -y
What does this command do?
The -y flag tells npm to skip the interactive questionnaire and create a package.json file with default values. This file tracks your project's name, version, and installed dependencies.
You should see the contents of your new package.json printed in the terminal.
- Install Express by running this command:
npm install express@5.2.1
What does this command do?
This downloads Express version 5.2.1 and all its dependencies into a node_modules folder. It also adds Express to the dependencies section of your package.json so anyone who clones your project can install the same packages.
- Verify the installation worked by running:
ls
You should see node_modules, package.json, and package-lock.json in the output.
Seeing permission errors or EACCES?
On macOS, if you see a permissions error, try running the install command with sudo in front. On Windows, try opening Command Prompt as Administrator (right-click and select Run as administrator).
If npm install hangs or times out, check your internet connection and try again.
Help me fix npm permission errors on my machine.
Open the project in your code editor
Now that your project is set up, open it in your code editor so you can start writing code in the next step.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Cursor (or VS Code if you are using Visual Studio Code) and press Enter to open it.
- Click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the bookshelf-api folder, and click Open.
You should see node_modules, package.json, and package-lock.json listed in the Explorer sidebar on the left.
- Click on package.json in the sidebar to open it.
- Confirm that the dependencies section contains "express": "^5.2.1".
✔️ Awesome, I've got everything!
Great. Double check you have all three items in your sidebar: node_modules, package.json, and package-lock.json.
ⓧ I'd like to double check the full code
Here is what your package.json should look like. The exact values for name and description may differ slightly depending on your folder name.
{
"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": "^5.2.1"
}
}
Don't see express in your dependencies?
Make sure you ran npm install express@5.2.1 from inside the bookshelf-api folder (not from the Desktop). If you ran it in the wrong directory, cd into bookshelf-api and run the install command again.
Why isn't Express showing up in my package.json?
Your environment is ready. Next up, you will create the server file and build your first API endpoint.
Create the Server and Your First Route
Your project folder is ready and Express is installed. Now it's time to create the actual server file that will listen for incoming HTTP requests and respond with data.
In this step, you'll write your first Express server from scratch. You'll set it up to parse JSON request bodies, store some book data in memory, and respond to your first API endpoint.
In this step, get ready to:
- Create your server file and configure Express.
- Define your first GET route that returns book data.
- Test the endpoint using curl.
Update your project configuration
Before creating the server file, your package.json needs to know which file is the entry point for your app.
- Switch back to Cursor (or VS Code) and open package.json.
- Replace the entire contents of package.json with the following:
{
"name": "bookshelf-api",
"version": "1.0.0",
"description": "A simple REST API for managing a bookshelf",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^5.2.1"
}
}
What changed here?
- main now points to server.js instead of index.js. This tells Node.js which file to load when your project runs.
- The start script lets you run your server with npm start later.
- The description field gives your project a human-readable summary.
- Save package.json.
- Confirm that the "main" field reads "server.js" and the "scripts" section contains "start": "node server.js".
Create the server file
Every Express app needs an entry point file where you import the framework, create an application instance, and tell it to start listening for requests.
- Create a new file called server.js in your bookshelf-api folder.
- Add the following code to server.js:
const express = require('express');
const app = express();
const PORT = 3000;
// Middleware to parse JSON request bodies
app.use(express.json());
What does this code do?
- require('express') imports the Express library into your file.
- express() creates your application instance. You'll attach routes and settings to this app object.
- PORT stores the port number your server will listen on. Port 3000 is a common choice for local development.
- express.json() is middleware that automatically parses incoming JSON data from request bodies. You'll need this when creating and updating books later.
You won't see any output yet. The next chunk adds the book data and route you'll test.
Add your books data and first route
A REST API serves data in response to HTTP requests. Your first route will handle GET requests and return a list of books stored in memory.
- In server.js, add the following code below the app.use(express.json()) line:
// In-memory data store
let books = [
{ id: 1, title: '1984', author: 'George Orwell' },
{ id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee' },
{ id: 3, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' }
];
let nextId = 4;
// GET all books
app.get('/api/books', (req, res) => {
res.json(books);
});
What does this code do?
- The books array stores your data in memory. Each book has an id, title, and author.
- nextId tracks the next available ID for when you add new books. Since you already have books with IDs 1-3, it starts at 4.
- app.get('/api/books', ...) defines a route that responds to GET requests at the path /api/books.
- res.json(books) sends the entire books array back to the client as a JSON response with a 200 status code.
Now add the final piece that starts your server. Add this code at the bottom of server.js, below the GET route:
// Start the server
app.listen(PORT, () => {
console.log(`Bookshelf API server running at http://localhost:${PORT}`);
});
What does app.listen do?
app.listen(PORT, callback) tells Express to start accepting connections on port 3000. The callback function runs once the server is ready, printing a confirmation message to your terminal.
- Save server.js.
- Confirm that server.js appears in the Explorer sidebar alongside package.json and the node_modules folder.
Don't see server.js in the sidebar?
- Make sure you created the file inside the bookshelf-api folder, not in a subfolder or parent directory.
- Check that the file is named exactly server.js (lowercase, no extra spaces or extensions).
Help me figure out why my server.js file isn't showing up in my project.
✔️ Awesome, I've got everything!
Great. Make sure you've saved both files before moving on.
ⓧ I'd like to double check the full code
Here is the complete server.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 data store
let books = [
{ id: 1, title: '1984', author: 'George Orwell' },
{ id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee' },
{ id: 3, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' }
];
let nextId = 4;
// GET all books
app.get('/api/books', (req, res) => {
res.json(books);
});
// Start the server
app.listen(PORT, () => {
console.log(`Bookshelf API server running at http://localhost:${PORT}`);
});
And here is the complete package.json:
{
"name": "bookshelf-api",
"version": "1.0.0",
"description": "A simple REST API for managing a bookshelf",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^5.2.1"
}
}
Start the server and test your route
Your server code is written. Time to start it up and send your first HTTP request using curl.
- Start your server by running this command in your terminal:
node server.js
You should see this output in your terminal:
Bookshelf API server running at http://localhost:3000
What does this mean?
Your server is now running and listening for requests on port 3000. The terminal will stay open because the server process keeps running until you stop it.
- Open a second terminal window. Press Cmd+Shift+` (macOS) or Ctrl+Shift+` (Windows) to open a new terminal panel in Cursor.
- Test your GET route by running this command in the new terminal:
curl http://localhost:3000/api/books
You should see a JSON array of your three books printed in the terminal:
[{"id":1,"title":"1984","author":"George Orwell"},{"id":2,"title":"To Kill a Mockingbird","author":"Harper Lee"},{"id":3,"title":"The Great Gatsby","author":"F. Scott Fitzgerald"}]
What just happened?
curl sent an HTTP GET request to your server at http://localhost:3000/api/books. Your Express route handler matched the request path, ran the callback function, and sent back the books array as JSON.
The response came back with a 200 status code, which means "OK, here's your data." This is the default status code Express uses with res.json().
Seeing 'Connection refused' or no output?
- Make sure your server is still running in the first terminal. If you see a command prompt instead of the running message, run node server.js again.
- Check that you're running the curl command in a different terminal than the one running the server.
- Verify the URL is exactly http://localhost:3000/api/books with no typos.
Help me troubleshoot why my Express server isn't responding to curl requests.
- Leave the server running for now. You'll keep using it in the next step.
Your first endpoint is live and responding with data. Next up, you'll add routes to get a single book by its ID and create brand new books.
Add Create and Read-by-ID Routes
Your API can list all books, but a real bookshelf app needs more. Users need to look up a specific book by its ID and add new books to the collection.
In this step, you will add two new routes to your server. A GET route retrieves a single book, and a POST route creates a new one.
In this step, get ready to:
- Add a GET route that returns a single book by its ID.
- Add a POST route that creates a new book.
- Test both endpoints using curl commands.
Add a route to get a single book by ID
Right now, the only way to see your books is to request the entire list. You need a way to fetch one specific book using its unique ID.
- In server.js, find the closing }); of the GET /api/books route.
- Add the following code below it, before the // Start the server comment:
// GET a single book by ID
app.get('/api/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?
This route handles requests to a URL like /api/books/2. The :id part is a route parameter that Express captures from the URL.
- parseInt(req.params.id) converts the URL parameter from a string to a number so you can match it against the book IDs in your array.
- books.find(b => b.id === id) searches the array for a book whose id matches the requested one.
- If no book matches, the route sends back a 404 status code with an error message. This tells the client that the resource does not exist.
- Save server.js.
Seeing a syntax error when you save?
Make sure the new route is placed after the existing GET /api/books route's closing }); and before the app.listen block. A missing closing bracket or parenthesis from the previous route will break the file.
help me find the syntax error in my server.js file
Add a route to create a new book
Now you need a way to add books to your shelf. A POST request is the standard HTTP method for creating new resources.
- In server.js, add the following code below the GET /api/books/:id route you just added, and before the // Start the server comment:
// POST a new book
app.post('/api/books', (req, res) => {
const { title, author } = req.body;
const newBook = {
id: nextId++,
title,
author
};
books.push(newBook);
res.status(201).json(newBook);
});
What does this code do?
This route listens for POST requests at /api/books. When a client sends a new book's data in the request body, this code processes it.
- const { title, author } = req.body extracts the title and author fields from the incoming JSON body. The express.json() middleware you added earlier makes this possible.
- nextId++ assigns the current value of nextId as the new book's ID, then increments it for the next book.
- res.status(201).json(newBook) responds with status 201 (Created) and the full new book object, including its assigned ID.
- Save server.js.
Route not working later?
Double-check that this route uses app.post, not app.get. A GET route at the same path already exists for listing books. Using the wrong method means your new route will never fire.
help me figure out why my POST route isn't creating books
Test both new endpoints with curl
Time to verify that both routes work. You will use curl to send requests from the terminal.
- If your server is still running from the previous step, press Ctrl+C in that terminal window to stop it.
- Start the server again by running:
node server.js
- Open a second terminal window. Press Cmd+Shift+` (macOS) or Ctrl+Shift+` (Windows) in Cursor/VS Code to open a new terminal panel.
- Test the GET-by-ID route by running:
curl http://localhost:3000/api/books/1
You should see a single book object returned:
{"id":1,"title":"1984","author":"George Orwell"}
- Test the POST route by creating a new book:
curl -X POST -H "Content-Type: application/json" -d '{"title":"Dune","author":"Frank Herbert"}' http://localhost:3000/api/books
You should see the newly created book with an assigned ID of 4 and a 201 status.
{"id":4,"title":"Dune","author":"Frank Herbert"}
- Confirm the new book appears in the full list by running:
curl http://localhost:3000/api/books
The response should now include all four books, with Dune at the end.
What about the 404 case?
Try requesting a book ID that does not exist to confirm your error handling works:
curl http://localhost:3000/api/books/99
You should see {"error":"Book not found"} in the response. This confirms your 404 logic is working.
Seeing "Cannot GET" or an empty response?
Make sure you restarted the server after saving your changes. Node.js does not automatically reload files. Press Ctrl+C in the server terminal and run node server.js again.
If the POST command returns an empty object or null values, check that you included the -H "Content-Type: application/json" header. Without it, Express cannot parse the request body.
help me debug my curl commands for testing my Express API
✔️ Awesome, I've got everything!
Great. Double check you have saved server.js and that both curl commands returned the expected results.
ⓧ I'd like to double check the full code
Here is the full server.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 data store
let books = [
{ id: 1, title: '1984', author: 'George Orwell' },
{ id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee' },
{ id: 3, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' }
];
let nextId = 4;
// GET all books
app.get('/api/books', (req, res) => {
res.json(books);
});
// GET a single book by ID
app.get('/api/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 a new book
app.post('/api/books', (req, res) => {
const { title, author } = req.body;
const newBook = {
id: nextId++,
title,
author
};
books.push(newBook);
res.status(201).json(newBook);
});
// Start the server
app.listen(PORT, () => {
console.log(`Bookshelf API server running at http://localhost:${PORT}`);
});
Your API now supports reading individual books and creating new ones. Next up, you will add the ability to update and delete books to complete the full set of CRUD operations.
Add Update and Delete Routes
You can now list all books, fetch a single book by ID, and add new books to your shelf. But what happens when you need to fix a typo in a title or remove a book entirely?
In this step, you'll complete the CRUD cycle by adding PUT (update) and DELETE routes. These two operations give your API the power to modify and remove resources.
In this step, get ready to:
- Add a PUT route to update existing books.
- Add a DELETE route to remove books.
- Test both endpoints with curl commands.
Add the PUT route for updating books
The PUT method lets clients send updated data for an existing resource. Your route will find the book by ID, apply any changed fields, and return the updated book.
- In server.js, find the comment // Start the server near the bottom of your file.
- Add the following PUT route above that line:
// PUT (update) a book by ID
app.put('/api/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' });
}
const { title, author } = req.body;
if (title) book.title = title;
if (author) book.author = author;
res.json(book);
});
What does this code do?
This route handles PUT requests to /api/books/:id. It parses the ID from the URL and searches the books array for a match.
- If no book is found, it returns a 404 status with an error message.
- It destructures title and author from the request body, then only updates the fields that were actually sent.
- Finally, it returns the updated book object as JSON with a 200 status.
- Save server.js.
Seeing a syntax error when you save?
Make sure the closing }); of your PUT route is on its own line, and that it sits above the // Start the server comment. A missing bracket or misplaced route will break the file.
help me find the syntax error in my server.js PUT route
Add the DELETE route for removing books
The DELETE method removes a resource from the server. Your route will locate the book's position in the array, remove it, and return an empty 204 response to confirm the deletion.
- In server.js, add the following DELETE route directly below the PUT route you just wrote (still above // Start the server):
// DELETE a book by ID
app.delete('/api/books/:id', (req, res) => {
const id = parseInt(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?
This route handles DELETE requests to /api/books/:id.
- It uses findIndex instead of find because it needs the array position to remove the element.
- If findIndex returns -1, the book doesn't exist, so it returns a 404 error.
- It uses splice(index, 1) to remove exactly one element at that position.
- A 204 status means "success, but no content to return." The .send() ends the response without a body.
- Save server.js.
Accidentally placed the route after app.listen?
Routes defined after app.listen() will never be reached. Make sure both your PUT and DELETE routes appear above the // Start the server comment.
help me fix my route placement in Express server.js
✔️ Awesome, I've got everything!
Great. Make sure you've saved server.js before moving on to testing.
ⓧ I'd like to double check the full code
Here is the complete server.js file with all routes so far:
const express = require('express');
const app = express();
const PORT = 3000;
// Middleware to parse JSON request bodies
app.use(express.json());
// In-memory data store
let books = [
{ id: 1, title: '1984', author: 'George Orwell' },
{ id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee' },
{ id: 3, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' }
];
let nextId = 4;
// GET all books
app.get('/api/books', (req, res) => {
res.json(books);
});
// GET a single book by ID
app.get('/api/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 a new book
app.post('/api/books', (req, res) => {
const { title, author } = req.body;
const newBook = {
id: nextId++,
title,
author
};
books.push(newBook);
res.status(201).json(newBook);
});
// PUT (update) a book by ID
app.put('/api/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' });
}
const { title, author } = req.body;
if (title) book.title = title;
if (author) book.author = author;
res.json(book);
});
// DELETE a book by ID
app.delete('/api/books/:id', (req, res) => {
const id = parseInt(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();
});
// Start the server
app.listen(PORT, () => {
console.log(`Bookshelf API server running at http://localhost:${PORT}`);
});
Test the update and delete endpoints
Time to verify that both new routes work correctly. You'll restart the server and use curl to send PUT and DELETE requests.
- If your server is still running, stop it by pressing Ctrl+C in the terminal.
- Start the server again by running:
node server.js
- Open a second terminal window.
- Update book 1's title by running this curl command:
curl -X PUT -H "Content-Type: application/json" -d '{"title":"Updated Title"}' http://localhost:3000/api/books/1
- You should see the updated book returned with the new title:
{"id":1,"title":"Updated Title","author":"George Orwell"}
Why did the author stay the same?
Your PUT route only updates fields that are present in the request body. Since you only sent a title field, the author remained unchanged. This is a partial-update pattern that makes your API flexible.
- Now delete book 1 by running:
curl -X DELETE http://localhost:3000/api/books/1
You should see no output at all. A 204 status means the server successfully processed the deletion but has nothing to send back.
- Confirm the book is gone by running:
curl http://localhost:3000/api/books
- You should see only books 2 and 3 in the response. Book 1 is gone.
Still seeing all three books?
Make sure you restarted the server after saving your changes. The server needs to be stopped with Ctrl+C and started again with node server.js to pick up new code.
Also confirm that the DELETE curl command uses -X DELETE (uppercase). Without the method flag, curl defaults to GET.
help me debug why my DELETE route isn't working
Your API now supports all four CRUD operations. Next up, you'll run through the complete lifecycle from start to finish to confirm everything works together.
Test the Full API Workflow
Your API has all four CRUD operations wired up. Now it is time to prove the whole thing works end-to-end.
In this step you will restart the server with a clean slate and walk through an entire book's lifecycle: creating it, reading it, updating it, and deleting it. You will also poke at the edges by requesting things that do not exist.
In this step, get ready to:
- Run through the complete CRUD lifecycle using curl.
- Test error cases to confirm your API handles bad requests gracefully.
- Stop the server cleanly.
Restart the server and run the CRUD lifecycle
A fresh server start resets the in-memory book array back to the three default books. This gives you a predictable starting point for testing.
- If your server is still running from earlier, press Ctrl+C in that terminal to stop it.
- Start the server fresh by running:
node server.js
- You should see Bookshelf API server running at http://localhost:3000 in the terminal.
- Open a second terminal window. You will run all the curl commands in this second window while the server keeps running in the first.
Keep both terminals visible
Place your server terminal and your curl terminal side by side. This way you can see incoming requests logged in real time.
Now walk through an entire book's lifecycle. Start by creating a new book.
- Create a new book by running this command in your second terminal:
curl -X POST -H "Content-Type: application/json" -d '{"title":"Dune","author":"Frank Herbert"}' http://localhost:3000/api/books
- You should see a JSON response with id set to 4, the title Dune, and the author Frank Herbert. The 201 status confirms the book was created.
- Read all books to confirm the new book appears in the full list:
curl http://localhost:3000/api/books
- You should see an array of four books. The original three plus your new Dune entry.
- Read the new book by its ID:
curl http://localhost:3000/api/books/4
- You should see only the Dune book object returned.
- Update the book's title by running:
curl -X PUT -H "Content-Type: application/json" -d '{"title":"Dune: Part One","author":"Frank Herbert"}' http://localhost:3000/api/books/4
- You should see the book object with the title changed to Dune: Part One.
- Verify the update stuck by fetching the book again:
curl http://localhost:3000/api/books/4
- The title should still read Dune: Part One. This confirms the update persisted in memory.
- Delete the book:
curl -X DELETE http://localhost:3000/api/books/4
- You should see no response body. The server returned a 204 status which means the deletion succeeded with no content to return.
- Confirm the deletion by fetching all books one more time:
curl http://localhost:3000/api/books
- You should see only the original three books. The Dune entry is gone.
What just happened?
You walked a single resource through its entire lifecycle: birth (POST), existence (GET), change (PUT), and removal (DELETE). This is the core pattern behind every REST API in production.
Each operation returned a different HTTP status code: 201 for created, 200 for success, and 204 for deleted with no body. These codes tell the client exactly what happened without parsing the response.
Test error cases
A well-built API handles bad requests gracefully. Let's see what happens when you ask for something that does not exist.
- Request a book ID that does not exist:
curl http://localhost:3000/api/books/99
- You should see {"error":"Book not found"}. The server returned a 404 status to signal that this resource does not exist.
- Send a POST request with an empty body to observe what happens:
curl -X POST -H "Content-Type: application/json" -d '{}' http://localhost:3000/api/books
- You should see a response with an id but no title or author values. The server accepted the request without complaint.
Why did the empty POST succeed?
Your current API does not validate incoming data. It blindly accepts whatever is in the request body. This is a real problem in production because your data store fills up with incomplete records.
The Secret Mission at the end of this project adds validation middleware to catch this exact issue.
Stop the server
- Switch to the terminal where your server is running.
- Press Ctrl+C to stop the server process.
The terminal returns to a normal command prompt. Your API is no longer accepting requests.
Seeing 'connection refused' on your curl commands?
This means the server is not running in your other terminal. Switch to that terminal and run node server.js again before retrying your curl commands.
help me figure out why my server is not responding to curl requests
Secret mission
Add Input Validation and Error Handling
Your API currently accepts anything you throw at it. Try sending a POST request with an empty body and you will get a book with undefined fields. In a real-world API, that is a bug waiting to happen. Your mission: add a reusable validation middleware and a centralized error handler that catches the unexpected.
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 or delete them entirely.
Resources you used:
- The bookshelf-api project folder (contains server.js, package.json, and node_modules/).
- Node.js and npm (installed on your system).
Keep everything running
No action needed. Choose this if you plan to keep building on this API or want to reference the code later.
- Your project folder stays on your Desktop ready for you to reopen anytime.
- Node.js stays installed on your system. This is useful for any future JavaScript or Node.js projects.
Pause - I'll come back to this later
Stop the server to free up the terminal. Your project files stay on disk ready for you to pick back up.
- If the server is still running, press Ctrl+C in the terminal to stop it.
- Your project folder and Node.js installation remain untouched.
Delete - I don't want to use this again
Remove all project files and start fresh. Follow the steps below for your operating system.
- If the server is still running, press Ctrl+C in the terminal to stop it.
- Delete the project folder by running this command (macOS/Linux):
rm -rf ~/Desktop/bookshelf-api
- On Windows, run this instead:
Remove-Item -Recurse -Force ~\Desktop\bookshelf-api
- Confirm the folder is gone by running:
ls ~/Desktop/bookshelf-api
You should see an error like "No such file or directory" confirming the folder has been removed.
What about Node.js?
Node.js is a general-purpose tool useful for many future projects. Most learners choose to keep it installed.
If you want to remove it: on macOS, run brew uninstall node if you installed via Homebrew. On Windows, uninstall through Control Panel > Programs and Features > Node.js.
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 a REST API server with Express that handles all four CRUD operations (Create, Read, Update, Delete) using proper HTTP methods and status codes.
- Parsed JSON request bodies with Express built-in middleware and used route parameters to target individual resources by ID.
- Tested every endpoint using curl commands directly from the terminal, verifying correct responses and status codes for both success and error cases.
- Secret Mission: Added reusable input validation middleware and a centralized error handler to make the API production-ready.
Ready to quiz yourself?