Build a Bookstore REST API with Express
Build a full CRUD REST API with Express 5 and file-based persistence.
Introduction
30 Second Summary
Every time you browse products, add items to a cart, or update your profile on a website, a REST API is handling that request behind the scenes. As a frontend developer, you've consumed these APIs for years, but now it's time to build one yourself.
In this project, you will build a bookstore inventory API from scratch using Express 5 and Node.js. You'll create endpoints that let clients browse, add, update, and remove books from a catalog, with data that persists between server restarts.
What You'll Build
You'll fire up your terminal, hit your API with real HTTP requests, and watch it respond with properly formatted JSON data for every book in your catalog.
By the end of this project, you'll have:
- A running REST API that responds to GET, POST, PUT, and DELETE requests for a bookstore catalog.
- Firsthand experience with the data persistence problem by watching in-memory data vanish on server restart, then solving it with file-based storage.
- A reusable project structure and pattern you can adapt for any CRUD API you build in the future.
- Secret Mission: Add search and filtering so you can query books by genre, author, or year.
Are there any prerequisites?
You should be comfortable writing JavaScript and have a basic understanding of what GET and POST requests are. You also need Node.js installed on your machine.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters. You've consumed dozens of REST APIs as a frontend developer, and now you're building one from scratch to understand every design decision on the server side.
Set Up the Project
Every API starts the same way: a server that listens for requests and sends back responses. Before you can build endpoints for your bookstore catalog, you need a running Express server that proves the foundation works.
In this step, you will create a new project folder, initialize it with npm, install Express, and write a minimal server that responds to requests. By the end, you will see "Hello World!" in your browser.
In this step, get ready to:
- Create the project directory and initialize it with npm.
- Install Express as a dependency.
- Create a minimal Express server and verify it runs in your browser.
Create the project directory and initialize it
Every Node.js project lives in its own folder with a package.json file that tracks its name, version, and dependencies. You will create this structure now.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir bookstore-api
cd bookstore-api
- Confirm the folder exists by running:
ls
You should see an empty directory (no output) since you just created it.
- Initialize the project with npm by running:
npm init -y
You should see a JSON object printed in your terminal showing the generated package.json contents.
What does this command do?
The npm init -y command generates a package.json file with default values. This file is the manifest for your project. It tracks metadata, scripts, and which packages your project depends on.
The -y flag accepts all defaults so you skip the interactive prompts.
🙋♀️ Seeing 'mkdir: cannot create directory'?
A folder called bookstore-api already exists on your Desktop from a previous attempt. Delete it first, then re-run the mkdir command.
help me delete the existing bookstore-api folder and start fresh
Install Express
Express is the framework that handles incoming HTTP requests and lets you define routes. You need to install it as a project dependency before writing any server code.
- Install Express by running:
npm install express
You should see output showing packages added, including express and its dependencies.
What does this command do?
The npm install express command downloads the Express package and all its dependencies into a node_modules folder. It also adds Express to the dependencies section of your package.json so other developers can install the same packages.
🙋♀️ Install failed or timed out?
Check your internet connection and try running the command again. If you see permission errors, make sure you are running your terminal normally (not as a restricted user).
help me fix my npm install error
- Open package.json in your code editor.
- Verify that express appears under "dependencies".
Now update three fields so your package.json accurately describes your project:
- Change the "description" value from "" to "A bookstore inventory REST API".
- Change the "main" value from "index.js" to "app.js".
- Replace the "test" script with a "start" script by changing "scripts" to:
"scripts": {
"start": "node app.js"
}
- Save package.json.
Why update these fields?
The "main" field tells Node.js which file is the entry point. Since your server will live in app.js, you update it from the default index.js.
The "start" script lets anyone run your server with npm start without needing to know the exact filename.
Create app.js with a minimal Express server
With Express installed, you can write the simplest possible server: one that listens on a port and responds to a single request. This proves your setup works end to end.
- Create a new file called app.js in the bookstore-api folder.
- Add the following code to app.js:
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`Bookstore API running at http://localhost:${port}`);
});
What does this code do?
- const express = require('express') loads the Express library into your file.
- const app = express() creates your application instance. Every route and setting gets attached to this object.
- app.get('/', ...) registers a route that responds to GET requests at the root URL with "Hello World!".
- app.listen(port, ...) starts the server and prints a confirmation message to your terminal.
- Save app.js.
Before you run your server, predict what you think will happen when you visit http://localhost:3000 in your browser.
- Start your server by running:
node app.js
You should see this message in your terminal:
Bookstore API running at http://localhost:3000
- Open your browser and visit http://localhost:3000.
You should see "Hello World!" displayed on the page. Your Express server is running and responding to requests.
Not seeing Hello World in the browser?
If you see "This site can't be reached", make sure you ran node app.js and saw the "Bookstore API running" message in your terminal. The server must be running for the browser to connect.
If you see an error about port 3000 being in use, another process is already using that port. Close other terminal sessions or applications that might be running a server.
help me fix my Express server so it responds in the browser
✔️ Awesome, I've got everything!
Great. Double check you have saved both package.json and app.js.
ⓧ I'd like to double check the full code
Here is what package.json should look like:
{
"name": "bookstore-api",
"version": "1.0.0",
"description": "A bookstore inventory REST API",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^5.2.1"
}
}
Here is what app.js should look like:
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`Bookstore API running at http://localhost:${port}`);
});
Your Express server is alive. Next up, you will replace that "Hello World!" response with real book data and build your first REST API endpoint.
Serve Your Book Catalog
Your Express server is running and responding with a simple greeting. But a REST API needs to serve real data, not a static string.
In this step, you will give your API its first real payload. You will define a catalog of books and expose two endpoints that return JSON data any client can consume.
In this step, get ready to:
- Add seed data and a GET /books endpoint that returns all books.
- Add a GET /books/:id endpoint that returns a single book by ID.
Add seed data and the GET /books endpoint
Your API needs data to serve. You will define a small array of book objects directly in app.js, then create a route that returns the entire array as JSON.
- In app.js, find the existing Hello World route:
app.get('/', (req, res) => {
res.send('Hello World!');
});
- Delete those three lines and replace them with the following code:
// Seed data - our initial book catalog
let books = [
{ id: '1', title: 'The Hobbit', author: 'J.R.R. Tolkien', genre: 'fantasy', year: 1937 },
{ id: '2', title: '1984', author: 'George Orwell', genre: 'dystopian', year: 1949 },
{ id: '3', title: 'To Kill a Mockingbird', author: 'Harper Lee', genre: 'fiction', year: 1960 }
];
// Return all books as JSON
app.get('/books', (req, res) => {
res.json(books);
});
What does this code do?
The books array holds three book objects, each with an id, title, author, genre, and year. This is your bookstore's starting inventory.
The app.get('/books', ...) route responds to GET requests at /books. res.json(books) converts the array to JSON and sends it back with the correct Content-Type header automatically.
- Save app.js.
- If your server is still running from Step 1, stop it by pressing Ctrl+C in your terminal.
- Start the server again by running:
node app.js
- Open http://localhost:3000/books in your browser.
- You should see a JSON array of 3 book objects displayed in the page.
Seeing an empty page or an error?
- Make sure you deleted the old app.get('/', ...) route entirely before pasting the new code.
- Check that you saved app.js before restarting the server.
- Confirm you are visiting /books (not just /). The old / route no longer exists.
Still stuck? Help me debug why my /books endpoint is not returning data.
Add the GET /books/:id endpoint
Returning the full catalog is useful, but clients often need a single book. Express route parameters let you capture a value from the URL and use it to look up a specific item.
- In app.js, find this line:
app.listen(port, () => {
- Add the following route directly above that line:
// Return a single book by its ID
app.get('/books/:id', (req, res) => {
const book = books.find(b => b.id === req.params.id);
if (!book) {
return res.status(404).json({ error: 'Book not found' });
}
res.json(book);
});
What does this code do?
The :id in the route path is a route parameter. When a client visits /books/2, Express captures 2 and stores it in req.params.id.
The books.find() method searches the array for a book whose id matches the URL parameter. If no match exists, the route sends back a 404 status with an error message so the client knows the resource was not found.
- Save app.js.
- Stop the server with Ctrl+C and restart it by running:
node app.js
Before you test, think about what you expect to see when you visit /books/999. Will you get a book back, or something else?
- Open http://localhost:3000/books/1 in your browser. You should see a single book object for The Hobbit.
- Now visit http://localhost:3000/books/999 in your browser. You should see {"error":"Book not found"} because no book has that ID.
Getting a blank page at /books/1?
- Make sure your route uses '/books/:id' (with the colon before id). Without the colon, Express treats it as a literal path segment.
- Check that your book IDs in the array are strings ('1'), not numbers (1). req.params.id is always a string, so the comparison with === fails if your IDs are numbers.
- Confirm you restarted the server after saving.
Need a hand? Help me figure out why /books/1 is not returning the correct book.
✔️ Awesome, I've got everything!
Great. Double check you have saved app.js and your server is running.
ⓧ I'd like to double check the full code
Here is the complete app.js at this point:
const express = require('express');
const app = express();
const port = 3000;
let books = [
{ id: '1', title: 'The Hobbit', author: 'J.R.R. Tolkien', genre: 'fantasy', year: 1937 },
{ id: '2', title: '1984', author: 'George Orwell', genre: 'dystopian', year: 1949 },
{ id: '3', title: 'To Kill a Mockingbird', author: 'Harper Lee', genre: 'fiction', year: 1960 }
];
app.get('/books', (req, res) => {
res.json(books);
});
app.get('/books/:id', (req, res) => {
const book = books.find(b => b.id === req.params.id);
if (!book) {
return res.status(404).json({ error: 'Book not found' });
}
res.json(book);
});
app.listen(port, () => {
console.log(`Bookstore API running at http://localhost:${port}`);
});
Your API now serves a full catalog and handles lookups for individual books. Next up, you will make it interactive by adding the ability to create, update, and delete books.
Make It Interactive with POST, PUT, and DELETE
Your bookstore API can serve data, but it cannot accept any. Every request so far is a GET, which makes your API a read-only catalog.
Real APIs let clients create, modify, and remove resources. In this step, you will add POST, PUT, and DELETE endpoints so your bookstore becomes fully interactive.
In this step, get ready to:
- Enable JSON body parsing so Express can read incoming request data.
- Add a POST /books endpoint to create new books.
- Add PUT and DELETE endpoints to update and remove books.
Enable JSON body parsing and add POST /books
When a client sends a POST request, the book data arrives as a JSON string in the request body. Express does not parse that body automatically.
- If your server is still running from the previous step, stop it by pressing Ctrl+C in your terminal.
- In app.js, find the closing bracket of your books array. Add this line below it, before your GET routes:
app.use(express.json());
What does this line do?
express.json() is built-in middleware that intercepts incoming requests and parses any JSON body into a JavaScript object. Without it, req.body would be undefined and your POST route would have nothing to read.
- Now add the POST route below your existing GET routes by pasting this code above the app.listen line:
app.post('/books', (req, res) => {
// Build a new book object from the request body
const newBook = {
id: String(Date.now()),
title: req.body.title,
author: req.body.author,
genre: req.body.genre,
year: req.body.year
};
// Add the new book to the array
books.push(newBook);
// Respond with 201 Created and the new book
res.status(201).json(newBook);
});
What does this code do?
- String(Date.now()) generates a unique ID using the current timestamp in milliseconds.
- req.body contains the parsed JSON the client sent, so req.body.title grabs the title field from that payload.
- res.status(201) sets the HTTP status code to 201 Created, which is the correct response for a successful resource creation.
- Save app.js.
- Start the server by running:
node app.js
Before you test, what do you think will happen when you POST a new book to your server? Will it return the book you sent, or something different?
- Test your POST endpoint by running this command in a new terminal window:
curl.exe -X POST http://localhost:3000/books -H "Content-Type: application/json" -d "{\"title\":\"Dune\",\"author\":\"Frank Herbert\",\"genre\":\"sci-fi\",\"year\":1965}"
You should see a JSON response with the new book, including a generated id and a 201 status. The id will be a long number like 1721482938271 because it comes from Date.now().
Seeing an empty response or undefined fields?
- Make sure app.use(express.json()) appears before your routes. If it comes after, the body will not be parsed yet when the route runs.
- Check that you used curl.exe (not curl). In PowerShell, curl without the .exe is aliased to a different command that will not work.
- Verify the -H "Content-Type: application/json" header is included. Without it, Express does not know the body is JSON.
Still stuck? Help me debug my POST /books endpoint that returns undefined fields.
Add PUT /books/:id and DELETE /books/:id
Your API can now create books. Next, you will add the ability to update existing books and remove ones you no longer want.
- Stop the server with Ctrl+C.
- In app.js, add the PUT route below your POST route (and above app.listen):
app.put('/books/:id', (req, res) => {
// Find the book's position in the array
const index = books.findIndex(b => b.id === req.params.id);
if (index === -1) {
return res.status(404).json({ error: 'Book not found' });
}
// Merge the update into the existing book, keeping the original id
books[index] = { ...books[index], ...req.body, id: books[index].id };
res.json(books[index]);
});
What does the spread merge do?
{ ...books[index], ...req.body, id: books[index].id } copies all existing book fields, overwrites any that the client sent in the request body, then forces id back to the original value. This lets the client update just one field (like year) without needing to resend the entire book.
- Add the DELETE route directly below the PUT route:
app.delete('/books/:id', (req, res) => {
// Find the book's position in the array
const index = books.findIndex(b => b.id === req.params.id);
if (index === -1) {
return res.status(404).json({ error: 'Book not found' });
}
// Remove the book and capture it for the response
const deleted = books.splice(index, 1);
res.json(deleted[0]);
});
What does splice do here?
books.splice(index, 1) removes one element at the given index and returns it in an array. You grab deleted[0] and send it back so the client can confirm which book was removed.
- Save app.js and restart the server:
node app.js
- Test the PUT endpoint by updating The Hobbit's publication year:
curl.exe -X PUT http://localhost:3000/books/1 -H "Content-Type: application/json" -d "{\"year\":1938}"
You should see the full book object returned with year updated to 1938 and all other fields unchanged.
- Test the DELETE endpoint by removing the book with ID 2:
curl.exe -X DELETE http://localhost:3000/books/2
You should see the deleted book (1984 by George Orwell) returned as JSON.
- Confirm it is gone by running:
curl.exe http://localhost:3000/books
The response should show only two books remaining. Book with ID 2 is no longer in the array.
Getting a 404 on PUT or DELETE?
- The ID in the URL must match the string IDs in your array ("1", "2", "3"). If you use a number like /books/01, it will not match.
- Make sure you restarted the server after adding the new routes. Express only loads routes at startup.
Still stuck? Help me debug my PUT or DELETE endpoint returning 404.
✔️ Awesome, I've got everything!
Great. Double check you have saved app.js and your server is running.
ⓧ I'd like to double check the full code
Your app.js should now contain these sections in order. Scroll up and confirm each one is present:
- The require statement, app and port declarations.
- The books array with 3 seed books.
- app.use(express.json()) middleware.
- GET /books route.
- GET /books/:id route with 404 handling.
- POST /books route with Date.now() ID and 201 status.
- PUT /books/:id route with spread merge and 404 handling.
- DELETE /books/:id route with splice and 404 handling.
- app.listen on port 3000.
Your API now handles all four CRUD operations. You can create, read, update, and delete books. But there is a catch. In the next step, you will discover what happens to all those new books when you restart the server.
Survive the Restart
You now have a fully interactive REST API that handles all four CRUD operations. But there is a hidden problem lurking beneath the surface.
Every book you add, update, or delete lives in a JavaScript variable. The moment you stop the server, that variable is wiped from memory and your changes vanish. In this step, you will experience that problem firsthand, then solve it with file-based persistence.
In this step, get ready to:
- Witness data loss by restarting the server after adding a book.
- Add file-based persistence so data survives restarts.
- Verify that a newly added book is still there after a full restart.
Experience the problem
Right now your books array is a plain JavaScript variable. Variables live in your computer's RAM and disappear the instant the process stops.
- Make sure your server is running. If it is not, start it by running:
node app.js
- Add a new book to your catalog by running this command:
curl.exe -X POST http://localhost:3000/books -H "Content-Type: application/json" -d "{\"title\":\"Dune\",\"author\":\"Frank Herbert\",\"genre\":\"sci-fi\",\"year\":1965}"
You should see the new book returned with a generated ID and the fields you sent.
- Now stop the server by pressing Ctrl+C in the terminal.
- Restart the server by running:
node app.js
Before you check the book list, what do you think you will see? Will Dune still be in the catalog, or will it be gone?
- Open http://localhost:3000/books in your browser.
Dune is gone. Only the three hardcoded books remain. Your POST worked perfectly, but the data lived in RAM and vanished when the process stopped.
Why did the data disappear?
Node.js allocates memory for your books array when the server starts. Every POST, PUT, and DELETE modifies that memory.
When you press Ctrl+C, the process exits and the operating system reclaims that memory. On the next node app.js, your code re-declares the array with only the original three books.
Add file-based persistence
The fix is simple: store the books in a file on disk instead of only in memory. Every time a route changes the data, you write it back to the file.
- Stop the server if it is still running (Ctrl+C).
- Create a data folder inside your bookstore-api directory by running:
mkdir data
- Create the seed data file inside that folder by running:
echo [{"id":"1","title":"The Hobbit","author":"J.R.R. Tolkien","genre":"fantasy","year":1937},{"id":"2","title":"1984","author":"George Orwell","genre":"dystopian","year":1949},{"id":"3","title":"To Kill a Mockingbird","author":"Harper Lee","genre":"fiction","year":1960}] > data/books.json
Easier with your editor
That one-liner creates the file, but the JSON is hard to read. You can instead create data/books.json manually in your code editor and paste in the formatted seed data below.
- Open data/books.json in your code editor and replace its contents with this formatted version:
[
{
"id": "1",
"title": "The Hobbit",
"author": "J.R.R. Tolkien",
"genre": "fantasy",
"year": 1937
},
{
"id": "2",
"title": "1984",
"author": "George Orwell",
"genre": "dystopian",
"year": 1949
},
{
"id": "3",
"title": "To Kill a Mockingbird",
"author": "Harper Lee",
"genre": "fiction",
"year": 1960
}
]
What is this file for?
This is the same seed data that was hardcoded in your books array, now stored as a JSON file on disk. Your API will read from and write to this file so data persists between restarts.
- Save data/books.json.
- Confirm the file exists by running:
ls data
You should see books.json in the output.
Don't see books.json?
Make sure you created the data folder inside bookstore-api, not somewhere else. Run cd and check your current directory is bookstore-api.
help me create the data/books.json file in the right place
Now you need to rewrite app.js to read from this file instead of a hardcoded array.
- Open app.js in your code editor.
- Select all the code and delete it. You will paste the new version in sections.
- Paste this first section, which sets up the imports, configuration, and helper functions:
const express = require('express');
const fs = require('node:fs');
const path = require('node:path');
const app = express();
const port = 3000;
// Path to the JSON file that stores all book data
const DATA_FILE = path.join(__dirname, 'data', 'books.json');
app.use(express.json());
// Read all books from the JSON file
function readBooks() {
if (!fs.existsSync(DATA_FILE)) {
return [];
}
const data = fs.readFileSync(DATA_FILE, 'utf-8');
return JSON.parse(data);
}
// Write the updated books array back to the JSON file
function writeBooks(books) {
const dir = path.dirname(DATA_FILE);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(DATA_FILE, JSON.stringify(books, null, 2));
}
What does this code do?
The fs and path modules are built into Node.js. They let you read and write files without installing anything extra.
DATA_FILE points to data/books.json using an absolute path so it works regardless of where you run the command from.
readBooks() opens the file and parses the JSON into a JavaScript array. writeBooks() takes an array and saves it back as formatted JSON.
- Below the code you just pasted, add the GET and POST route handlers:
// Return all books from the file
app.get('/books', (req, res) => {
const books = readBooks();
res.json(books);
});
// Return a single book by ID
app.get('/books/:id', (req, res) => {
const books = readBooks();
const book = books.find(b => b.id === req.params.id);
if (!book) {
return res.status(404).json({ error: 'Book not found' });
}
res.json(book);
});
// Add a new book and persist it to the file
app.post('/books', (req, res) => {
const books = readBooks();
const newBook = {
id: String(Date.now()),
title: req.body.title,
author: req.body.author,
genre: req.body.genre,
year: req.body.year
};
books.push(newBook);
writeBooks(books);
res.status(201).json(newBook);
});
What changed in these routes?
Each handler now calls readBooks() to load fresh data from the file instead of referencing an in-memory variable.
The POST handler also calls writeBooks(books) after pushing the new book. This writes the updated array back to disk so the new book survives a restart.
- Below the POST route, add the PUT and DELETE handlers plus the listen call:
// Update an existing book and persist changes
app.put('/books/:id', (req, res) => {
const books = readBooks();
const index = books.findIndex(b => b.id === req.params.id);
if (index === -1) {
return res.status(404).json({ error: 'Book not found' });
}
books[index] = { ...books[index], ...req.body, id: books[index].id };
writeBooks(books);
res.json(books[index]);
});
// Delete a book and persist the removal
app.delete('/books/:id', (req, res) => {
const books = readBooks();
const index = books.findIndex(b => b.id === req.params.id);
if (index === -1) {
return res.status(404).json({ error: 'Book not found' });
}
const deleted = books.splice(index, 1);
writeBooks(books);
res.json(deleted[0]);
});
app.listen(port, () => {
console.log(`Bookstore API running at http://localhost:${port}`);
});
What does writeBooks do here?
Both PUT and DELETE modify the array, then call writeBooks(books) to save those changes. Without that call, updates and deletions would only last until the next restart.
- Save app.js.
- Start the server by running:
node app.js
- Open http://localhost:3000/books in your browser.
You should see the same three seed books, now served from data/books.json instead of a hardcoded array.
Seeing an error when you start the server?
If you see SyntaxError: Unexpected token, double-check that you pasted all three sections in order with no gaps or duplicated lines between them.
If you see Cannot find module, confirm that your require statements at the top use 'node:fs' and 'node:path' with the node: prefix.
help me fix the error I'm seeing when starting my Express server after adding file persistence
Verify data survives a restart
The real test: add a book, kill the server, bring it back, and check if the book is still there.
- POST a new book by running:
curl.exe -X POST http://localhost:3000/books -H "Content-Type: application/json" -d "{\"title\":\"Dune\",\"author\":\"Frank Herbert\",\"genre\":\"sci-fi\",\"year\":1965}"
You should see Dune returned with a generated ID.
- Stop the server with Ctrl+C.
- Restart it by running:
node app.js
Before you check, predict: will Dune still be in the catalog this time?
- Open http://localhost:3000/books in your browser.
Dune is still there. Your data survived the restart because writeBooks() saved it to data/books.json on disk.
What is happening under the hood?
When you POSTed Dune, the handler called writeBooks() which wrote the updated four-book array to data/books.json. You can open that file in your editor right now and see Dune sitting there as JSON.
On restart, readBooks() loads whatever is in that file. The file does not care whether the server is running or not.
✔️ Awesome, I've got everything!
Great. Double-check you have saved both app.js and data/books.json.
ⓧ I'd like to double check the full code
Here is the complete app.js at this point. Scroll up and confirm each section is present in order: imports, constants, helpers, GET routes, POST route, PUT route, DELETE route, listen.
And here is data/books.json (your file may also contain Dune if you ran the POST test):
[
{
"id": "1",
"title": "The Hobbit",
"author": "J.R.R. Tolkien",
"genre": "fantasy",
"year": 1937
},
{
"id": "2",
"title": "1984",
"author": "George Orwell",
"genre": "dystopian",
"year": 1949
},
{
"id": "3",
"title": "To Kill a Mockingbird",
"author": "Harper Lee",
"genre": "fiction",
"year": 1960
}
]
For app.js, confirm these sections appear in this order:
- Three require statements (express, fs, path).
- app, port, and DATA_FILE constants.
- express.json() middleware.
- readBooks() function.
- writeBooks() function.
- Five route handlers: GET /books, GET /books/:id, POST /books, PUT /books/:id, DELETE /books/:id.
- app.listen() at the very end.
Your API now persists data to disk. You have experienced the most common backend gotcha (in-memory data loss) and solved it yourself. Next up, you will tackle a secret mission to add search and filtering to your catalog.
Secret mission
Add Search and Filtering
Real-world APIs rarely return every record without filtering. Can you add query parameter support so clients can search by genre, author, or year? You will need case-insensitive matching and the ability to combine multiple filters in a single request.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.
Resources you used:
- The bookstore-api project folder on your Desktop (includes app.js, data/books.json, package.json, and node_modules).
- The Node.js server process (if still running on port 3000).
Keep everything running
No action needed. Choose this if you are still actively building or plan to extend your API further.
- Your project folder stays on your Desktop and your server can be restarted any time with node app.js.
- Node.js remains installed on your machine (it was there before this project).
Pause - I'll come back to this later
Stop the running server process to free up port 3000. Your files stay on disk so you can pick up where you left off.
- If your server is still running, press Ctrl+C in the terminal to stop it.
- Your bookstore-api folder stays on your Desktop. To resume, open a terminal in that 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.
- If your server is still running, press Ctrl+C in the terminal to stop it.
- Navigate out of the project folder and delete it. On macOS/Linux, run:
cd ~/Desktop
rm -rf bookstore-api
On Windows (PowerShell), run:
cd ~\Desktop
Remove-Item -Recurse -Force bookstore-api
- Confirm the folder is gone by running ls (macOS/Linux) or dir (Windows) on your Desktop. You should no longer see bookstore-api listed.
That's a Wrap!
That's a Wrap!
Nice work! You just built a complete REST API from scratch using Express 5 and Node.js. You now understand the server side of every REST API you have ever consumed as a frontend developer.
You've learned how to:
- Build a REST API from scratch that handles all four CRUD operations (Create, Read, Update, Delete) with proper HTTP methods and status codes.
- Experience the data persistence problem firsthand by watching in-memory data vanish on server restart, then solve it with file-based JSON storage.
- Design RESTful endpoints with route parameters, JSON request/response handling, and meaningful error responses.
- Add query parameter filtering so clients can search the catalog by genre, author, or year (Secret Mission).
Ready to quiz yourself?