Build a REST API with Node.js

Build a REST API server that handles full CRUD operations for a book library.

Introduction

30 Second Summary

Every time you open an app on your phone, it talks to a server behind the scenes. That conversation happens through something called an API.

In this project, you will build a REST API server that manages a mini book library. You will create endpoints that let clients add, view, update, and delete books, then test everything from the command line.

What You'll Build

A REST API server built with Node.js and Express that handles full CRUD operations for a book collection stored in memory.

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

  • A Node.js project with Express installed and configured.
  • An app.js file containing API endpoints for creating, reading, updating, and deleting books.
  • Tested endpoints returning proper JSON responses with appropriate HTTP status codes.
  • Secret Mission: Add search functionality that lets users filter books by title or author using query parameters.

Are there any prerequisites?

None. This guide includes full setup instructions for Node.js, Cursor, and everything else you need to get started.

Set Up Your Development Environment

Before you can build your API, you need a few tools installed on your machine.

Node.js lets you run JavaScript outside the browser. Cursor is the code editor you will write your API in. In this step, you will install both tools and create the project folder.

In this step, get ready to:
  • Install Node.js (the JavaScript runtime).
  • Install Cursor (your code editor).
  • Create the book-api project folder with Express installed.
Install Node.js

Node.js is the runtime that executes your JavaScript code on your computer. It also comes with npm (Node Package Manager), which you will use to install libraries like Express.

  • Open https://nodejs.org/en/download in your browser.
  • Download the LTS installer for your operating system (macOS .pkg or Windows .msi).
  • Run the installer and accept the default settings at each prompt.

Once the installer finishes, verify that Node.js is working.

  • 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 your Node.js version by running this command:
node --version
  • Check your npm version by running:
npm --version

✔️ I see version 18 or higher

You should see something like v24.x.x for Node.js and v11.x.x for npm. Node.js is ready to go.

ⓧ I see a version lower than 18

You have an older version of Node.js installed. The Express framework you will use in this project requires Node.js 18 or higher.

  • Go to https://nodejs.org/en/download and download the latest LTS installer.
  • Run the installer. It will replace your existing Node.js with the latest LTS version.
  • Close and reopen your terminal, then run node --version again to confirm it now shows v24.x.x or higher.

Why do I need to close my terminal?

Your terminal caches the path to executables when it first opens. Closing and reopening it forces the terminal to pick up the newly installed version.

ⓧ Command not found

Node.js is not installed on your machine yet.

  • Go to https://nodejs.org/en/download and download the LTS installer for your operating system.
  • Run the installer and accept all default settings.
  • Close and reopen your terminal, then run node --version again.
  • You should now see a version number like v24.x.x.

Still seeing command not found?

Make sure you downloaded the installer (not the source code) and ran it to completion. If the issue persists, restart your computer and try the version check again.

Help me troubleshoot why node --version is not working after installing Node.js

Install Cursor

Cursor is a code editor that helps you write code faster with built-in AI assistance. You will use it to write and edit your API code throughout this project.

  • Open https://www.cursor.com in your browser.
  • Download the installer for your operating system and run it.
  • Follow the setup prompts to complete installation.
  • Once installation finishes, press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Cursor and press Enter to open it.

Cursor not opening?

On macOS, you may need to drag Cursor to your Applications folder before it appears in search. On Windows, try searching for it in the Start menu after restarting your computer.

Help me get Cursor to open on my machine

Create your project

Now you will create the project folder and install Express, the framework that makes building APIs in Node.js straightforward.

  • Switch back to your terminal from earlier.
  • 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
  • Confirm the folder was created by running:
pwd

You should see a path ending in book-api, confirming you are inside your new project folder.

  • Initialize a new Node.js project by running:
npm init -y

What does npm init -y do?

This command creates a package.json file, which is the configuration file for any Node.js project. It tracks your project's name, version, and which libraries (dependencies) it uses.

The -y flag means "yes to all defaults" so you skip the interactive prompts.

  • Install Express by running:
npm install express

What is Express?

Express is a web framework for Node.js that handles incoming HTTP requests and sends back responses. It gives you simple tools to define routes (like /api/books) and respond with data.

You should see npm download Express and its dependencies. When it finishes, your project folder now contains three things.

  • Confirm your project files exist by running:
ls

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

npm install failed?

Make sure you ran npm init -y first. Without a package.json file, npm does not know where to install packages.

If you see a permissions error, try closing and reopening your terminal, then running the commands again from the cd ~/Desktop/book-api step.

Help me fix my npm install error

Now open your project in Cursor so you can see the files.

  • In Cursor, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop and select the book-api folder, then click Open.
  • In the Explorer sidebar on the left, click package.json to open it.

You should see "express" listed under "dependencies" near the bottom of the file. This confirms Express is installed in your project.

✔️ Awesome, I've got everything!

Make sure you have saved the file. Your project is set up and ready to build on.

ⓧ I'd like to double check the full code

Your package.json should look like this:

{
  "name": "book-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"
  }
}

The key thing to verify is that express appears under dependencies. The version number may differ slightly depending on when you run the install.

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

Create Your First Express Server

Your project folder is set up and Express is installed. Now it's time to write the actual server code that listens for requests and sends back data.

In this step, you'll create app.js, the main file for your API. This file will import Express, set up sample data, define your first route, and start the server so it can respond to incoming HTTP requests.

In this step, get ready to:
  • Create app.js with Express configuration and sample book data.
  • Define your first GET route that returns all books as JSON.
  • Run the server and confirm it responds to requests.
Create app.js and set up the Express application

Before writing the server, you need to update package.json so it points to app.js as the entry point and includes a start script.

  • In Cursor, open package.json from the Explorer sidebar on the left.
  • Replace the entire contents with the following:
{
  "name": "book-api",
  "version": "1.0.0",
  "description": "A REST API for managing a book collection",
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^5.2.1"
  }
}

What did you change?

  • description now explains what this project does.
  • main changed from index.js to app.js so Node.js knows your server file's name.
  • scripts now has a start command that runs node app.js, giving you a shortcut to launch the server.
  • Save package.json by pressing Cmd+S (macOS) or Ctrl+S (Windows).

Now create the main server file. This first chunk sets up Express and defines your sample data.

  • Create a new file called app.js in the root of your book-api folder by right-clicking in the Explorer sidebar and selecting New File.
  • Name the file app.js and press Enter.
  • Paste the following code into app.js:
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;

What does this code do?

  • require('express') imports the Express library you installed earlier.
  • express() creates your application instance. All routes and settings attach to this app object.
  • app.use(express.json()) adds middleware that automatically parses incoming JSON request bodies into JavaScript objects.
  • The books array is your in-memory data store. It holds three sample books, each with an id, title, and author.
  • nextId tracks what ID to assign to the next new book.

Now add the route and server startup code below the data store.

  • Add the following code at the bottom of app.js, directly below the let nextId = 4; line:
// GET all books
app.get('/api/books', (req, res) => {
  res.json(books);
});

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

What does this code do?

  • app.get('/api/books', ...) registers a route that responds to GET requests at the /api/books path.
  • res.json(books) sends the entire books array back to the client as a JSON response.
  • app.listen(port, ...) starts the server on port 3000 and prints a confirmation message to the terminal.
  • Save app.js.

Seeing red underlines in Cursor?

Make sure you saved the file with the .js extension. Cursor applies JavaScript syntax highlighting only when it recognizes the file type.

Help me fix red underlines in my app.js file

✔️ 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 the full app.js file. Make sure your code matches this exactly:

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(`Book API server running at http://localhost:${port}`);
});
Run and test the server

Your code is ready. Time to start the server and send it a request to confirm everything works.

  • Start the server by running this command in your terminal:
node app.js
  • You should see this message printed in the terminal:
Book API server running at http://localhost:3000

Why is the terminal stuck?

The terminal is not frozen. Your server is running and waiting for requests. It will stay active until you stop it with Ctrl+C. You cannot type other commands in this terminal while the server runs.

To test the server, you need a second terminal.

  • Open a new terminal in Cursor by pressing Ctrl+Shift+` (backtick key, same on macOS and Windows).
  • Send a request to your server by running this command:
curl http://localhost:3000/api/books
  • You should see a JSON array of three books printed in the terminal output.

What is curl?

curl is a command-line tool for making HTTP requests. Think of it as a lightweight browser that prints the response directly in your terminal instead of rendering a web page.

Getting an error instead of JSON?

  • If you see a message about a missing module or syntax error, double check your app.js matches the full code in the tab above. Then stop the server with Ctrl+C and rerun node app.js.
  • Windows PowerShell users: if curl returns HTML instead of JSON, PowerShell aliases curl to a different command. Use curl.exe instead (with the .exe extension) or switch to Command Prompt.
  • If you see "connection refused", make sure the first terminal still shows the running server message.

Help me troubleshoot my Express server

Your server is alive and responding to requests. Next up, you'll add more endpoints so clients can fetch a single book and create new ones.

Add Read and Create Endpoints

Your server is up and running, and it can return the full list of books. That is great for getting an overview, but clients often need to work with individual resources.

In this step, you will add two new endpoints. One retrieves a single book by its ID. The other creates a brand new book and adds it to the collection.

In this step, get ready to:
  • Add a GET endpoint that retrieves a single book by its ID.
  • Add a POST endpoint that creates new books.
  • Test both endpoints with curl to confirm they work.
Add a GET endpoint for a single book

Right now your API can only return every book at once. Clients need a way to request one specific book by its ID. A route parameter (the :id in the URL path) lets the URL carry that identifier.

  • In app.js, find the comment // Start the server. Add the following code above that line:
// 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 GET requests to /api/books/:id. The :id portion is a route parameter. Express captures whatever value appears in that URL segment and makes it available at req.params.id.

  • parseInt(req.params.id) converts the URL string to a number so it can be compared against the numeric id field in each book object.
  • books.find() searches the array for a matching book. If none is found, the server responds with a 404 status and an error message.
  • If the book exists, it is returned as JSON with the default 200 status.

You won't see a change yet. The next chunk adds the POST endpoint, then you will test both together.

Add a POST endpoint to create a new book

GET routes read data. POST routes create data. This endpoint reads a title and author from the request body, assigns a unique ID, and adds the new book to the collection.

  • In app.js, add the following code directly below the GET single book route you just added (and above // Start the server):
// POST a new book
app.post('/api/books', (req, res) => {
  const { title, author } = req.body;

  if (!title || !author) {
    return res.status(400).json({ error: 'Title and author are required' });
  }

  const newBook = { id: nextId++, title, author };
  books.push(newBook);
  res.status(201).json(newBook);
});

What does this code do?

This route handles POST requests to /api/books. It expects the client to send a JSON body containing title and author fields.

  • The express.json() middleware you added earlier parses the incoming JSON and populates req.body.
  • If either field is missing, the server returns a 400 (Bad Request) error so the client knows what went wrong.
  • nextId++ assigns a unique ID and then increments the counter for the next book.
  • res.status(201).json(newBook) sends back the created book with a 201 (Created) status, confirming the resource was successfully added.
  • Save app.js.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before moving on.

ⓧ I'd like to double check the full code

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;

  if (!title || !author) {
    return res.status(400).json({ error: 'Title and author are required' });
  }

  const newBook = { id: nextId++, title, author };
  books.push(newBook);
  res.status(201).json(newBook);
});

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

Your server is still running the old version of app.js. You need to restart it to pick up the new routes.

  • Switch back to the terminal where your server is running.
  • Press Ctrl+C to stop the server.
  • Restart the server by running:
node app.js
  • Switch to your second terminal and test the GET single book endpoint 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 endpoint to create a new book by running:
curl -X POST http://localhost:3000/api/books -H "Content-Type: application/json" -d "{\"title\":\"Dune\",\"author\":\"Frank Herbert\"}"

You should see the newly created book with an assigned ID:

{"id":4,"title":"Dune","author":"Frank Herbert"}

What are these curl flags?

  • -X POST tells curl to send a POST request instead of the default GET.
  • -H "Content-Type: application/json" adds a header telling the server the body is JSON.
  • -d sends the JSON string as the request body.

Not seeing the expected output?

  • If you see a connection refused error, make sure you restarted the server with node app.js in the first terminal.
  • If GET /api/books/1 returns a 404 error, check that your route uses parseInt(req.params.id) to convert the string to a number.
  • If POST returns {"error":"Title and author are required"}, double check the escaped quotes in your curl command match the example above exactly.

Still stuck? Help me debug my GET or POST endpoint.

Two endpoints down, two to go. Next up, you will add PUT and DELETE endpoints to complete the full set of CRUD operations.

Add Update and Delete Endpoints

Your API can already list books, fetch a single book, and create new ones. That covers the "Read" and "Create" parts of CRUD.

Now you'll add the final two operations: updating an existing book and deleting one. Once these are in place, your API will handle the full lifecycle of a resource.

In this step, get ready to:
  • Add a PUT endpoint that updates a book's title or author.
  • Add a DELETE endpoint that removes a book from the collection.
  • Test both endpoints with curl to confirm they work.
Add a PUT endpoint to update a book

PUT requests let a client modify an existing resource. Your endpoint will find the book by ID, update whichever fields the client sends, and return the modified book.

  • In Cursor, open app.js and find the POST route's closing }); line.
  • Below it, add the PUT route by pasting this code:
// 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 array for a matching book.
  • If no book matches, it returns a 404 error.
  • It only updates title or author if the client actually sent those fields in the request body.
  • Finally, it returns the updated book object as JSON.
  • Save app.js.
  • You won't see a change yet. The next chunk adds the DELETE endpoint, and then you'll test both together.
Add a DELETE endpoint to remove a book

DELETE requests remove a resource entirely. Your endpoint will find the book, remove it from the array, and return an empty response with status 204 ("No Content").

  • In app.js, find the PUT route's closing }); line you just added.
  • Below it, add the DELETE route by pasting this code:
// 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 you need the array position to remove the item.
  • If index is -1, no book matched the ID, so it returns a 404 error.
  • The splice method removes the book from the array in place.
  • Status 204 means "No Content". The server successfully processed the request but has nothing to send back.
  • Save app.js.
  • You won't see a visible change yet. In the next substep, you'll restart the server and test both new endpoints.

Seeing a syntax error?

Make sure each route block ends with }); on its own line. A missing closing brace or parenthesis will cause Node.js to throw an error when you restart.

help me find the syntax error in my app.js

✔️ Awesome, I've got everything!

Great. Double check you've saved app.js before moving on.

ⓧ I'd like to double check the full code

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;

  if (!title || !author) {
    return res.status(400).json({ error: 'Title and author are required' });
  }

  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(`Book API server running at http://localhost:${port}`);
});
Test the update and delete endpoints

Your server is still running the old version of app.js. You need to restart it to pick up the new routes.

  • Switch to the terminal where your server is running.
  • Press Ctrl+C to stop the server.
  • Restart the server by running:
node app.js
  • Switch to your second terminal to run the test commands.
  • Test the PUT endpoint by updating the title of book 1:
curl -X PUT http://localhost:3000/api/books/1 -H "Content-Type: application/json" -d "{\"title\":\"Updated Title\"}"
  • You should see the updated book returned as JSON:
{"id":1,"title":"Updated Title","author":"George Orwell"}
  • Test the DELETE endpoint by removing book 1:
curl -X DELETE http://localhost:3000/api/books/1

You should see no output. That's correct. Status 204 means the server processed the request but has no content to return.

  • Verify the book is gone by listing all books:
curl http://localhost:3000/api/books

You should see only two books remaining (IDs 2 and 3). Book 1 is gone.

Why did the title change and then the book disappear?

Your PUT command updated book 1's title to Updated Title. Then your DELETE command removed book 1 entirely from the array.

Since data lives in memory, it resets every time you restart the server. If you stop and restart now, all three original books will be back.

Seeing an empty response or error from PUT?

  • Make sure you restarted the server after saving your changes. The old process doesn't pick up new code.
  • On Windows PowerShell, curl is an alias for a different command. Use curl.exe instead, or run the commands in Command Prompt.
  • Check that the -H and -d flags are present in your PUT command. Without the Content-Type header, Express won't parse the JSON body.

help me debug my PUT and DELETE curl commands

Secret mission

Add Search with Query Parameters

Your API can list, create, update, and delete books. But what if someone has hundreds of books and only wants to find titles by a specific author? Add search functionality that lets users filter books by title or author using query parameters.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your project files to continue experimenting, or delete them to free up disk space.

Resources you used:

  • The book-api project folder on your Desktop (contains app.js, package.json, package-lock.json, and node_modules).
  • The running Node.js server process (if you haven't stopped it yet).

Keep everything running

No action needed. Choose this if you're still actively experimenting with your API or want to add more endpoints.

  • Your server will keep running until you close the terminal or press Ctrl+C.
  • To restart later, open a terminal in your book-api folder and run node app.js.
  • Node.js and Cursor remain installed on your machine for future projects.

Pause - I'll come back to this later

Stop the running server to free up the terminal, but keep your project files for later.

  • Switch to the terminal where your server is running.
  • Press Ctrl+C to stop the server.
  • Your book-api folder stays on your Desktop. When you come back, open a terminal there and run node app.js to start again.

Delete - I don't want to use this again

Remove all project files and start fresh. This permanently deletes your code.

  • Switch to the terminal where your server is running and press Ctrl+C to stop it.
  • Delete the project folder by running the following command.

macOS

cd ~/Desktop
rm -rf book-api

Windows

cd ~/Desktop
rmdir /s /q book-api
  • Confirm the folder is gone by running ls (macOS) or dir (Windows) on your Desktop. You should no longer see book-api listed.

Nice Work!

Nice Work!

You just built a fully functional REST API server from scratch using Node.js and Express. Your book library API handles every CRUD operation through proper HTTP endpoints, and you tested each one from the command line.

You've learned how to:

  • Build a REST API server from scratch with Express, including routing and JSON middleware.
  • Implement all four CRUD operations (Create, Read, Update, Delete) using proper HTTP methods (GET, POST, PUT, DELETE) and status codes (200, 201, 204, 404).
  • Test every API endpoint using curl commands and confirm correct JSON responses from the terminal.
  • Secret Mission: Added query parameter search and case-insensitive filtering to make the API more powerful.

Ready to quiz yourself?