Build a REST API with Node.js
Build a local Book Collection API with full CRUD operations using Express.
Introduction
30 Second Summary
Every app you use daily, from social media feeds to online shopping carts, relies on a server that receives requests and sends back data. The system that makes this communication possible is called a REST API.
In this project, you will build a REST API from scratch using Node.js and Express. You'll create a Book Collection API that can list, add, update, and delete books through HTTP requests.
What You'll Build
A local API server that manages a personal book library and responds to standard HTTP methods (GET, POST, PUT, DELETE).
By the end of this project, you'll have:
- A Node.js project with Express installed and configured.
- A running server on http://localhost:3000 with full CRUD routes (Create, Read, Update, Delete) for managing books.
- Hands-on experience testing your API using a web browser and curl commands from the terminal.
- Secret Mission: Add search and filtering to your API so users can find books by author or title.
Are there any prerequisites?
None. This project includes full setup instructions for every tool you need, including Node.js and Visual Studio Code. You don't need any prior programming experience with JavaScript or backend development.
Set Up Your Development Environment
Before you can build your API, you need a JavaScript runtime and a code editor. Node.js lets you run JavaScript outside the browser, which is what makes server-side code possible.
In this step, you'll install Node.js, set up Visual Studio Code as your editor, and create your project folder with the Express framework ready to go.
In this step, get ready to:
- Install Node.js on your machine.
- Set up Visual Studio Code and create your project folder.
- Install the Express framework.
Install Node.js
Node.js is the runtime that lets your computer execute JavaScript code directly, without needing a browser. You need version 24 or higher for this project.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
- Check if Node.js is already installed by running this command:
node --version
✔️ I see version 24 or higher
Nice, Node.js is ready to go. Confirm that npm (the Node package manager) is also available by running:
npm --version
You should see a version number printed. You're all set to move on to the next substep.
ⓧ I see an older version
You have Node.js installed, but you need version 24 or higher for this project. An older version can cause compatibility issues with the packages you'll install later.
- Go to https://nodejs.org/en/download/ in your browser.
- Download the LTS installer for your operating system (macOS or Windows).
- Run the installer and accept all the default settings.
- Close your current terminal and open a new one.
- Run node --version again to confirm you now see a version starting with v24.
Why close and reopen the terminal?
Your terminal loads the system path when it first opens. If you install Node.js while a terminal is already open, that terminal still points to the old version. A fresh terminal picks up the new installation.
ⓧ Command not found
Node.js is not installed on your machine yet. Let's install it now.
- Go to https://nodejs.org/en/download/ in your browser.
- Download the LTS installer for your operating system (macOS or Windows).
- Run the installer and accept all the default settings.
- Close your current terminal and open a new one.
- Verify the installation by running:
node --version
You should see a version number starting with v24.
- Also confirm npm is available by running:
npm --version
You should see a version number printed.
Still not seeing a version number?
Make sure you closed and reopened your terminal after installation. On Windows, try searching for PowerShell instead of Command Prompt, as some older Command Prompt sessions don't refresh the system path.
help me troubleshoot my Node.js installation
Open Visual Studio Code and create your project
Visual Studio Code is a free code editor that includes a built-in terminal. You'll use it to write and run all the code in this project.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Visual Studio Code and press Enter to open it.
Don't have Visual Studio Code?
Download it for free from https://code.visualstudio.com. Run the installer for your operating system and accept the defaults. Then search for it again using the steps above.
- Open the integrated terminal by pressing Ctrl+` (the backtick key, next to the number 1 on your keyboard).
Where is the backtick key?
The backtick (`) is the key in the top-left area of your keyboard, usually sharing a key with the tilde (~). On macOS, it's directly below the Escape key.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir book-api
cd book-api
- Initialize your project with npm by running:
npm init -y
What does this command do?
The npm init -y command creates a package.json file in your folder. This file tracks your project's name, version, and the packages it depends on.
The -y flag accepts all the default values so you don't have to answer prompts one by one.
- Confirm the file was created by running:
ls
You should see package.json listed in the output.
Don't see package.json?
Make sure you ran cd book-api before running npm init -y. The command creates the file in whatever folder your terminal is currently in.
help me figure out why package.json wasn't created
Install Express
Express is a framework that gives you tools for handling HTTP requests and building APIs. Instead of writing everything from scratch, Express handles the repetitive plumbing so you can focus on your application logic.
- Install Express into your project by running:
npm install express
What does this command do?
This command downloads the Express package from the npm registry and saves it into a node_modules folder in your project. It also adds Express as a dependency in your package.json so anyone else working on this project knows it's required.
- Verify the installation by running:
ls
You should now see node_modules and package-lock.json alongside your package.json.
Seeing an error during install?
Check that you're inside the book-api folder by running pwd (macOS) or cd (Windows). The path should end with book-api.
Also make sure your Node.js installation completed successfully by running node --version again.
help me fix my npm install error
Your development environment is locked in. Next up, you'll create your first API endpoint and see your server respond to a real HTTP request.
Create Your First API Endpoint
Your development environment is ready with Node.js and Express installed. Now it's time to write the code that actually responds to requests.
In this step, you'll create a server file that listens for incoming HTTP requests and responds with JSON data. This is the core of every backend application.
In this step, get ready to:
- Create an Express server file with a welcome route.
- Add sample book data to your server.
- Build a GET endpoint that returns your books as JSON.
Create the server file
Express needs a few lines of setup code to create a working server. You'll define the app, tell it which port to listen on, and add your first route that visitors can access.
- In VS Code, click File in the menu bar.
- Click Open Folder.
- Select the book-api folder on your Desktop, then click Open.
Why open the folder?
Opening the folder as a workspace lets VS Code's Explorer sidebar show your project files. This makes it easy to create, find, and edit files without switching to the terminal.
- In the Explorer sidebar (left panel), hover over the BOOK-API heading.
- Click the New File icon (a page with a plus sign).
- Type index.js and press Enter.
The new file opens in the editor. Add the following code to set up your server:
// Import the Express framework
const express = require('express');
// Create an Express application instance
const app = express();
// Define the port number the server will listen on
const port = 3000;
// Enable JSON parsing for incoming request bodies
app.use(express.json());
// Define a root route that returns a welcome message
app.get('/', (req, res) => {
res.json({ message: 'Welcome to the Book Collection API' });
});
// Start the server and listen for incoming requests
app.listen(port, () => {
console.log(`Book API server running at http://localhost:${port}`);
});
What does this code do?
- const express = require('express') loads the Express library into your file so you can use it.
- const app = express() creates your application. All routes and settings attach to this object.
- app.use(express.json()) is middleware that tells Express to automatically parse JSON data from incoming requests.
- app.get('/', ...) defines what happens when someone visits the root URL of your server.
- app.listen(port, ...) starts the server and keeps it running, waiting for requests on port 3000.
- Save index.js by pressing Ctrl+S (Windows) or Cmd+S (macOS).
- Open a terminal in VS Code by clicking Terminal in the menu bar, then New Terminal.
- Start your server by running this command:
node index.js
- Open your web browser and navigate to http://localhost:3000.
- You should see {"message":"Welcome to the Book Collection API"} displayed in the browser.
Don't see the welcome message?
- Make sure you saved index.js before running the command. Unsaved changes won't be included.
- Check that your terminal is inside the book-api folder. Run cd ~/Desktop/book-api if you're unsure.
- If you see Cannot find module 'express', run npm install express again in the book-api folder.
Still stuck? Help me debug why my Express server isn't starting.
Add sample book data
Your API needs data to serve. You'll store a collection of books in a JavaScript array that lives in your server's memory.
- Stop your running server by pressing Ctrl+C in the terminal.
- In index.js, find the line that says app.use(express.json());.
- Add the following book data below that line and above your routes:
// Sample book collection stored in memory
let books = [
{ id: 1, title: 'The Hobbit', author: 'J.R.R. Tolkien', year: 1937 },
{ id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 },
{ id: 3, title: '1984', author: 'George Orwell', year: 1949 }
];
What does this code do?
- let books uses let instead of const because you'll add, update, and delete books later.
- Each book object has an id for identification, a title, an author, and a year of publication.
- This data lives in memory only. If you restart the server, it resets to these three books.
- Save index.js.
- You won't see a visible change yet. The next chunk creates a route that serves this data.
Add the GET /books route
With book data in place, you need a route that clients can hit to retrieve it. A GET route responds to browser requests and returns data without changing anything on the server.
- In index.js, find the root route (the app.get('/', ...) block).
- Add the following route below it, before the app.listen block:
// Return the full list of books as JSON
app.get('/books', (req, res) => {
res.json(books);
});
What does this code do?
- app.get('/books', ...) tells Express to respond when someone sends a GET request to /books.
- res.json(books) converts the books array into JSON and sends it back to the client. The browser can read and display this format.
- Save index.js.
- Restart your server by running this command in the terminal:
node index.js
- Open your browser and navigate to http://localhost:3000/books.
- You should see your three books displayed as a JSON array with their id, title, author, and year fields.
Seeing an empty page or an error?
- Make sure you stopped the old server with Ctrl+C before running node index.js again. Two servers can't use the same port.
- Check that the books array is placed above the routes (between app.use(express.json()) and the first app.get). If it's below, the routes can't find it.
- Verify the URL is exactly http://localhost:3000/books with no trailing characters.
Need more help? Help me figure out why my /books route isn't returning data.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ 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;
app.use(express.json());
let books = [
{ id: 1, title: 'The Hobbit', author: 'J.R.R. Tolkien', year: 1937 },
{ id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 },
{ id: 3, title: '1984', author: 'George Orwell', year: 1949 }
];
app.get('/', (req, res) => {
res.json({ message: 'Welcome to the Book Collection API' });
});
app.get('/books', (req, res) => {
res.json(books);
});
app.listen(port, () => {
console.log(`Book API server running at http://localhost:${port}`);
});
Your server is alive and serving data. Next up, you'll add routes that let clients create new books and fetch individual ones by ID.
Add POST and GET-by-ID Routes
In the last step, you got your server running and returning the full list of books. That's great for browsing, but real APIs let clients request specific resources and create new ones.
In this step, you'll add two new routes: one that fetches a single book by its ID, and one that adds brand-new books to your collection via a POST request.
In this step, get ready to:
- Add a GET route that fetches a single book by its ID.
- Add a POST route that creates new books in the collection.
Add the GET /books/:id route
Route parameters let you capture values from the URL itself. When a user visits /books/2, Express stores 2 in req.params.id so your code can look up that specific book.
- In index.js, find the GET /books route (the one that returns the full books array). Add the following code below it:
app.get('/books/:id', (req, res) => {
// Find the book whose id matches the URL parameter
const book = books.find(b => b.id === parseInt(req.params.id));
if (!book) {
// Return a 404 error if no book matches
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. Express captures whatever value appears in that position of the URL.
- parseInt(req.params.id) converts the URL parameter from a string to a number so it can match the numeric id field in your books array.
- If no book is found, the route returns a 404 status code with a JSON error message. This tells the client that the resource doesn't exist.
- Save index.js.
- If your server is still running from the last step, press Ctrl+C in your terminal to stop it.
- Restart the server by running:
node index.js
- Open your browser and navigate to http://localhost:3000/books/1. You should see just The Hobbit's data returned as JSON.
- Try http://localhost:3000/books/99 to confirm you get the error message {"error":"Book not found"}.
Seeing "Cannot GET /books/1" instead of JSON?
- Make sure you saved the file and restarted the server. Changes only take effect after a restart.
- Check that the new route is placed after your GET /books route and before app.listen.
- Verify you used :id with a colon in the route path. Without the colon, Express treats it as a literal string.
Help me debug why my GET /books/:id route isn't returning a book.
Add the POST /books route
GET routes read data. A POST route lets clients send new data to the server. Your POST route will read the incoming book details from the request body, assign a unique ID, and add it to the collection.
Why does req.body work?
The express.json() middleware you added earlier is what makes req.body work. It automatically parses incoming JSON into a JavaScript object that your route can read.
- In index.js, add the following code below the GET /books/:id route you just created:
app.post('/books', (req, res) => {
// Build a new book object from the request body
const newBook = {
id: books.length > 0 ? Math.max(...books.map(b => b.id)) + 1 : 1,
title: req.body.title,
author: req.body.author,
year: req.body.year
};
// Add the new book to the array
books.push(newBook);
// Return the created book with a 201 Created status
res.status(201).json(newBook);
});
What does this code do?
- req.body.title, req.body.author, and req.body.year pull values from the JSON the client sends in their request.
- The ID calculation Math.max(...books.map(b => b.id)) + 1 finds the highest existing ID and adds 1. This ensures each new book gets a unique number.
- Status code 201 means "Created". It tells the client that a new resource was successfully made, which is the standard response for successful POST requests.
- Save index.js.
- Press Ctrl+C in your terminal to stop the server, then restart it by running:
node index.js
- Test your new POST route by running this curl command in a second terminal:
curl -X POST http://localhost:3000/books -H "Content-Type: application/json" -d "{\"title\": \"Dune\", \"author\": \"Frank Herbert\", \"year\": 1965}"
How do I open a second terminal?
Your server is using the first terminal, so you need a second one to run curl. In VS Code, click the + icon in the top-right corner of the terminal panel to open a new terminal tab.
- You should see the new book returned as JSON with an id of 4:
{"id":4,"title":"Dune","author":"Frank Herbert","year":1965}
- Confirm the book was added by visiting http://localhost:3000/books in your browser. You should now see 4 books in the array.
Getting an empty or undefined response from POST?
- Make sure app.use(express.json()) appears before your routes in the file. Without it, req.body will be undefined.
- Check your curl command includes -H "Content-Type: application/json". Without that header, Express won't parse the body as JSON.
- On Windows, make sure you use double quotes around the JSON data with escaped inner quotes, exactly as shown in the command above.
Help me figure out why my POST route returns undefined fields.
✔️ Awesome, I've got everything!
Great. Double check you've saved index.js and your server is running.
ⓧ I'd like to double check the full code
Here's the full index.js file at this point:
const express = require('express');
const app = express();
const port = 3000;
app.use(express.json());
let books = [
{ id: 1, title: 'The Hobbit', author: 'J.R.R. Tolkien', year: 1937 },
{ id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 },
{ id: 3, title: '1984', author: 'George Orwell', year: 1949 }
];
app.get('/', (req, res) => {
res.json({ message: 'Welcome to the Book Collection API' });
});
app.get('/books', (req, res) => {
res.json(books);
});
app.get('/books/:id', (req, res) => {
const book = books.find(b => b.id === parseInt(req.params.id));
if (!book) {
return res.status(404).json({ error: 'Book not found' });
}
res.json(book);
});
app.post('/books', (req, res) => {
const newBook = {
id: books.length > 0 ? Math.max(...books.map(b => b.id)) + 1 : 1,
title: req.body.title,
author: req.body.author,
year: req.body.year
};
books.push(newBook);
res.status(201).json(newBook);
});
app.listen(port, () => {
console.log(`Book API server running at http://localhost:${port}`);
});
Your API can now read individual books and create new ones. Next up, you'll add the ability to update and delete books to complete all four CRUD operations.
Add PUT and DELETE Routes
Your API can already list books, find a specific book, and add new ones. That covers the "Create" and "Read" parts of CRUD operations.
Now you'll complete the picture by adding the ability to update and delete books. These are the "Update" and "Delete" operations that make your API fully functional.
In this step, get ready to:
- Add a PUT route to update existing books.
- Add a DELETE route to remove books from the collection.
- Test the full CRUD cycle with curl commands.
Add the PUT route to update a book
The PUT method lets clients send updated data for an existing resource. Your route will find the book by its ID, overwrite its fields with the new values, and return the updated book.
- In index.js, find your app.post('/books', ...) route.
- Add the following PUT route directly below the closing }); of the POST route:
app.put('/books/:id', (req, res) => {
// Find the book with the matching ID
const book = books.find(b => b.id === parseInt(req.params.id));
if (!book) {
return res.status(404).json({ error: 'Book not found' });
}
// Update the book's properties with the new values from the request body
book.title = req.body.title;
book.author = req.body.author;
book.year = req.body.year;
res.json(book);
});
What does this code do?
This route handles PUT requests to /books/:id. The :id is a route parameter that captures whatever value the client puts in the URL.
- books.find() searches the array for a book whose id matches the URL parameter (converted from a string to a number with parseInt()).
- If no match is found, the server responds with a 404 status and an error message.
- If the book exists, its properties are overwritten with the new values from req.body, and the updated book is returned as JSON.
- Save index.js.
Seeing a syntax error?
Make sure your new app.put(...) block starts after the closing }); of the POST route. Each route must be its own separate app.method(...) call. Nesting one route inside another causes unexpected behavior.
Help me find where to place my PUT route in index.js
Add the DELETE route to remove a book
The DELETE method removes a resource permanently. Your route will locate the book's position in the array and splice it out.
- In index.js, add the following DELETE route directly below the closing }); of the PUT route you just added:
app.delete('/books/:id', (req, res) => {
// Find the index of the book in the array
const index = books.findIndex(b => b.id === parseInt(req.params.id));
if (index === -1) {
return res.status(404).json({ error: 'Book not found' });
}
// Remove the book from the array
books.splice(index, 1);
res.json({ message: 'Book deleted successfully' });
});
What does this code do?
This route uses a slightly different approach from the PUT route.
- books.findIndex() returns the position (index number) of the matching book, or -1 if no match exists.
- books.splice(index, 1) removes exactly one element at that position from the array.
- The response confirms deletion with a success message instead of returning the deleted book.
- Save index.js.
Why findIndex instead of find?
The PUT route uses find() because it needs the book object itself to update its properties. The DELETE route uses findIndex() because splice() needs to know the position of the item to remove it from the array.
✔️ Awesome, I've got everything!
Great. Make sure you've saved index.js before moving on to testing.
ⓧ I'd like to double check the full code
const express = require('express');
const app = express();
const port = 3000;
app.use(express.json());
let books = [
{ id: 1, title: 'The Hobbit', author: 'J.R.R. Tolkien', year: 1937 },
{ id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 },
{ id: 3, title: '1984', author: 'George Orwell', year: 1949 }
];
app.get('/', (req, res) => {
res.json({ message: 'Welcome to the Book Collection API' });
});
app.get('/books', (req, res) => {
res.json(books);
});
app.get('/books/:id', (req, res) => {
const book = books.find(b => b.id === parseInt(req.params.id));
if (!book) {
return res.status(404).json({ error: 'Book not found' });
}
res.json(book);
});
app.post('/books', (req, res) => {
const newBook = {
id: books.length > 0 ? Math.max(...books.map(b => b.id)) + 1 : 1,
title: req.body.title,
author: req.body.author,
year: req.body.year
};
books.push(newBook);
res.status(201).json(newBook);
});
app.put('/books/:id', (req, res) => {
const book = books.find(b => b.id === parseInt(req.params.id));
if (!book) {
return res.status(404).json({ error: 'Book not found' });
}
book.title = req.body.title;
book.author = req.body.author;
book.year = req.body.year;
res.json(book);
});
app.delete('/books/:id', (req, res) => {
const index = books.findIndex(b => b.id === parseInt(req.params.id));
if (index === -1) {
return res.status(404).json({ error: 'Book not found' });
}
books.splice(index, 1);
res.json({ message: 'Book deleted successfully' });
});
app.listen(port, () => {
console.log(`Book API server running at http://localhost:${port}`);
});
Test the full CRUD cycle
With all four HTTP methods in place, you can now test the complete lifecycle of a book. You'll update a book, delete it, then confirm it's gone.
- If your server is still running from the previous step, stop it by pressing Ctrl+C in your terminal.
- Restart the server by running:
node index.js
You should see Book API server running at http://localhost:3000 in your terminal.
- Test updating book 1 by running this curl command in a second terminal (open a new terminal with Terminal > New Terminal in VS Code):
curl -X PUT http://localhost:3000/books/1 -H "Content-Type: application/json" -d "{\"title\": \"Updated Title\", \"author\": \"Updated Author\", \"year\": 2024}"
What should I see?
You should see the updated book returned as JSON:
{"id":1,"title":"Updated Title","author":"Updated Author","year":2024}
- Test deleting book 1 by running:
curl -X DELETE http://localhost:3000/books/1
You should see {"message":"Book deleted successfully"} in the response.
- Confirm the deletion by fetching all books:
curl http://localhost:3000/books
You should see only two books remaining in the array. Book 1 ("The Hobbit") is gone.
Getting 'cannot be parsed' or connection errors?
Make sure your server is running in one terminal and you're running curl in a separate terminal. If you see Failed to connect, your server isn't running. Go back to the first terminal and run node index.js again.
If you see a JSON parse error, check that the escaped quotes in your curl command match exactly. Each quote inside the JSON data needs a backslash before it (\").
Help me troubleshoot my curl command for testing my Express API
Your API now supports all four CRUD operations. You can create, read, update, and delete books through standard HTTP methods. Next up, you'll take things further with a secret mission to add search and filtering capabilities.
Secret mission
Add Search and Filtering
Your API returns all books every time. What if a user only wants books by a specific author or with a certain word in the title? Add query parameter support so users can search and filter your book collection.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- The running Node.js server process on port 3000.
- The book-api project folder (including node_modules, index.js, and package.json).
- Node.js installed on your system.
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 on http://localhost:3000 until you close the terminal or press Ctrl+C.
- Node.js stays installed for future projects.
- Your book-api folder remains on your Desktop.
Pause - I'll come back to this later
Stop the running server to free up port 3000, but keep all your project files for later.
- In the VS Code terminal where your server is running, press Ctrl+C to stop the server.
- Your book-api folder and all files remain intact.
- To resume later, open the book-api folder in VS Code, open a terminal, and run node index.js.
Delete - I don't want to use this again
Remove all project files and start fresh. This cannot be undone.
- In the VS Code terminal where your server is running, press Ctrl+C to stop the server.
- Delete the project folder by running this command in your terminal:
macOS
cd ~/Desktop
rm -rf book-api
Windows
cd ~\Desktop
rmdir /s /q book-api
- To uninstall Node.js on Windows, open Settings, select Apps, then Installed apps. Search for Node.js and click Uninstall.
- To uninstall Node.js on macOS, remove it by running:
sudo rm -rf /usr/local/bin/node /usr/local/lib/node_modules/npm
- Confirm the project folder is removed by running ls ~/Desktop (macOS) or dir ~\Desktop (Windows). You should no longer see book-api in the output.
Nice Work!
Nice Work!
You just built a fully functional REST API from scratch using Node.js and Express. You now understand how backend servers receive requests and send responses, which is the foundation of every web application.
Here's what you learned:
- Built a working Express.js REST API server handling GET, POST, PUT, and DELETE requests that map to full CRUD operations.
- Used Express middleware and routing to parse JSON request bodies, handle route parameters, and return appropriate status codes.
- Tested your API end-to-end using both a browser and curl commands from the terminal.
- Added search and filtering with query parameters so users can find books by author or title.
Ready to quiz yourself?