Build a REST API with Node.js

Build a Book Collection REST API with Express handling full CRUD operations.

Introduction

30 Second Summary

Every time you scroll social media, check the weather, or order food, there's a server somewhere receiving your request and sending back data. That communication follows a pattern called REST, and it powers nearly every app you use.

In this project, you will build a REST API server from scratch using Node.js and Express. You'll create endpoints that handle creating, reading, updating, and deleting books from a collection.

What You'll Build

A Book Collection REST API server that handles CRUD operations, responds with JSON data, and uses proper HTTP status codes.

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

  • A running Express.js server with 5 API endpoints that handle full CRUD operations.
  • Hands-on experience testing each endpoint with curl commands, just like professional developers do.
  • A clear understanding of the request/response cycle in action.
  • Secret Mission: Add filtering and search to your GET endpoint so users can find books by author or title.

Are there any prerequisites?

You'll need basic JavaScript knowledge (variables, functions, arrays, and objects). No prior experience with servers or APIs is required.

Set Up Your Development Environment

Before you can build your API, you need a working development environment. This means having Node.js installed, a project folder ready, and your code editor open.

In this step, you'll install Node.js (the JavaScript runtime that lets you run code outside the browser), set up your project folder, and install Express.js (the framework that makes building APIs straightforward).

In this step, get ready to:
  • Install Node.js (version 22 or later).
  • Create and initialize your project folder.
  • Install Express.js as a dependency.
Install Node.js

Node.js is what lets you run JavaScript on your computer (outside of a browser). Your API server will run on Node.js.

  • 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 --version

✔️ I see version 22 or higher

If you see v22.x.x or higher (like v24.x.x), you're all set.

  • Confirm npm is also available by running:
npm --version

You should see a version number printed. npm is the package manager that comes bundled with Node.js.

ⓧ I see an older version

If you see a version lower than v22 (like v18.x.x or v20.x.x), you need to upgrade.

  • Go to https://nodejs.org in your browser.
  • Download the LTS version (v22 or later).
  • Run the installer and follow the prompts. Accept the default options.
  • Close your terminal completely and reopen it.
  • Run node --version again to confirm it now shows v22 or higher.

Why close and reopen the terminal?

Your terminal loads the system PATH when it starts up. Installing Node.js updates the PATH, but your current terminal session still has the old version cached. Reopening gives it a fresh start.

ⓧ Command not found

If you see an error like "command not found" or "not recognized", Node.js isn't installed yet.

  • Go to https://nodejs.org in your browser.
  • Download the LTS version (v22 or later).
  • Run the installer and follow the prompts. Accept the default options.
  • Close your terminal completely and reopen it.
  • Run node --version to confirm it prints v22 or higher.
  • Run npm --version to confirm npm is available too.

The installer handles everything

The Node.js installer from nodejs.org automatically sets up both node and npm on your system. You don't need to install them separately.

Create your project

Now that Node.js is ready, you'll create a dedicated folder for your API project and initialize it as a Node.js project.

  • 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 a new Node.js project by running:
npm init -y

What does npm init -y do?

This creates a package.json file in your project. Think of package.json as your project's ID card. It stores the project name, version, and a list of packages (dependencies) your project uses.

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.

Now open VS Code so you can edit the project files.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the book-api folder, and click Open.

Don't see the book-api folder?

Make sure you look on your Desktop specifically. If you created the folder without running cd ~/Desktop first, it might be in your home directory instead.

help me find my book-api folder

The default package.json needs a few updates. You need to tell Node.js to use modern ES module syntax (import instead of the older require) and set up the project metadata.

  • In the VS Code Explorer sidebar on the left, click package.json to open it.
  • Select all the existing content and replace it with the following:
{
  "name": "book-api",
  "version": "1.0.0",
  "description": "A simple REST API for managing a book collection",
  "main": "server.js",
  "type": "module",
  "scripts": {
    "start": "node server.js"
  }
}

What did we change?

The key addition is "type": "module". This tells Node.js to use ES module syntax (import/export) instead of the older CommonJS syntax (require/module.exports). ES modules are the modern JavaScript standard.

The "main": "server.js" entry points to the file you'll create in the next step. The start script gives you a quick way to launch your server later.

  • Save the file with Cmd+S (macOS) or Ctrl+S (Windows).
Install Express

Express is the framework that handles incoming web requests and routes them to the right code. Instead of building all that plumbing yourself, Express gives you a clean interface for defining your API endpoints.

  • Open the integrated terminal in VS Code by pressing Ctrl+` (backtick key, same on both macOS and Windows).
  • Install Express by running this command:
npm install express@5.2.1

What does this command do?

This downloads the Express package (version 5.2.1) from the npm registry and saves it to a node_modules folder in your project. It also adds Express to the dependencies list in your package.json so anyone else can install the same packages later.

  • Verify the installation by opening package.json in the editor. You should see a new dependencies section that includes express.

Seeing an error during install?

Make sure your terminal is in the book-api folder. Run pwd (macOS) or cd (Windows) to check your current location. If you're not in book-api, navigate there first with cd ~/Desktop/book-api.

Also confirm you have an internet connection. npm needs to download the package from the online registry.

help me fix my npm install error

✔️ Awesome, I've got everything!

Great. Double check you've saved package.json.

ⓧ I'd like to double check the full code

Your package.json should look like this:

{
  "name": "book-api",
  "version": "1.0.0",
  "description": "A simple REST API for managing a book collection",
  "main": "server.js",
  "type": "module",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "5.2.1"
  }
}

Your environment is ready. Next up, you'll write your first Express server and get it running locally.

Create Your First Express Server

Your project folder is set up with Node.js and Express ready to go. Now it's time to write the actual server code that will handle incoming requests.

Express.js makes this straightforward. You'll create a single JavaScript file that imports Express, configures it to understand JSON data, and gets it listening for connections on a specific port.

In this step, get ready to:
  • Write the base server file with Express configuration.
  • Add seed data for your book collection.
  • Start the server and confirm it's running.
Write the server file

Every Express server starts with the same core pattern: import the framework, create an app instance, configure middleware, and tell it which port to listen on.

  • In the VS Code Explorer sidebar, click the New File icon (a page with a plus sign) at the top of your project folder.
  • Name the file server.js and press Enter.
  • Paste the following code into server.js:
import express from 'express';

const app = express();
const port = 3000;

// Middleware to parse JSON request bodies
app.use(express.json());

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

What does this code do?

This sets up the skeleton of your server in four parts:

  • import express from 'express' brings in the Express framework using ES module syntax.
  • const app = express() creates your application instance. Every route and piece of configuration attaches to this object.
  • app.use(express.json()) is middleware that automatically parses incoming JSON request bodies so you can read them in your route handlers.
  • app.listen(port, ...) starts the server and tells it to wait for connections on port 3000.
  • Save server.js.
  • Confirm the file 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 at the top level of your project folder, not inside node_modules. If the file ended up in the wrong location, drag it to the root of your project in the Explorer sidebar.

Also check the filename is exactly server.js (no capital letters, no extra spaces).

Help me figure out why my server.js file isn't in the right place.

Add seed data

Your API needs data to serve. Instead of connecting a database (which adds complexity), you'll store books in a simple JavaScript array. This keeps the focus on learning REST API patterns.

  • In server.js, find the line that says // Start the server.
  • Add the following code above that comment:
// In-memory data store with seed data
let books = [
  { id: 1, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', year: 1925 },
  { id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 },
  { id: 3, title: '1984', author: 'George Orwell', year: 1949 }
];

let nextId = 4;

What does this code do?

let books = [...] creates an array of three book objects, each with an id, title, author, and year. This is your data store. Every API endpoint you build next will read from and write to this array.

let nextId = 4 tracks the next available ID number. When a new book is created, it gets this ID, and nextId increments to stay unique.

  • Save server.js.
  • You won't see a visible change yet. The next substep starts the server so you can confirm everything works.

✔️ 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 the complete server.js file at this point:

import express from 'express';

const app = express();
const port = 3000;

// Middleware to parse JSON request bodies
app.use(express.json());

// In-memory data store with seed data
let books = [
  { id: 1, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', year: 1925 },
  { id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 },
  { id: 3, title: '1984', author: 'George Orwell', year: 1949 }
];

let nextId = 4;

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

Your server file is complete. Time to start it up and confirm Express is listening for requests.

  • Open the integrated terminal in VS Code by pressing Ctrl+` (macOS) or Ctrl+` (Windows).
  • Start your server by running this command:
node server.js
  • Check your terminal output. You should see:
Book API server running at http://localhost:3000

That message confirms Express is running and waiting for incoming requests on port 3000.

What is a port?

A port is like a numbered door on your computer. When your server listens on port 3000, it means any request sent to localhost:3000 will reach your Express app. Different apps use different ports so they don't interfere with each other.

Seeing an error instead?

If you see SyntaxError: Cannot use import statement outside a module, your package.json is missing "type": "module". Open package.json and confirm that line is present.

If you see a message about port 3000 already being in use, another process is occupying that port. Close any other terminal sessions that might be running a server, then try again.

Help me debug why my Express server won't start.

Your server is alive and listening. Next up, you'll add your first API endpoints so it can actually respond to requests with book data.

Build the READ Endpoints (GET)

Your server is running, but it doesn't know how to respond to any requests yet. Routes are what tell Express what to do when someone visits a specific URL with a specific HTTP method.

In REST APIs, the GET method is used to read data. You'll build two GET endpoints in this step: one to fetch all books and one to fetch a single book by its ID.

In this step, get ready to:
  • Add a GET endpoint that returns all books as JSON.
  • Add a GET endpoint that returns a single book by ID.
  • Test both endpoints with curl to verify they work.
Add the GET all books route

Your first endpoint will let anyone request the full list of books. When a client sends a GET request to /api/books, the server responds with the entire books array as JSON.

  • Stop your running server by pressing Ctrl+C in the terminal where it's running.
  • In server.js, find the line that says let nextId = 4; and add the following code below it:
// GET all books
app.get('/api/books', (req, res) => {
  res.json(books);
});

What does this code do?

This registers a route handler for GET requests to /api/books. When a request arrives at that path, Express calls your callback function with two objects: req (the incoming request) and res (the response you send back).

The res.json(books) call converts your JavaScript array into JSON format and sends it to the client with the correct Content-Type header automatically.

  • Save server.js.
  • Restart your server by running:
node server.js
  • You should see Book API server running at http://localhost:3000 printed in the terminal.
  • Open a new terminal tab or window to test your endpoint. Press Cmd+T (macOS) or Ctrl+Shift+T (Windows/Linux) to open a new tab.
  • Test the endpoint by running:
curl http://localhost:3000/api/books
  • You should see the full array of three books printed as JSON in your terminal.

Seeing "Connection refused"?

  • Make sure your server is still running in the other terminal tab. If you see the command prompt, it means the server has stopped. Run node server.js again.
  • Check that you're running the curl command in a separate terminal tab, not the one where the server is running.

Why can't I reach my server with curl?

Add the GET single book route

Fetching all books is useful, but clients often need a specific book. Your second endpoint uses a route parameter to identify which book to return.

  • Switch back to the terminal where your server is running and press Ctrl+C to stop it.
  • In server.js, find the GET all books route you just added. Copy the following code directly below it:
// GET a single book by ID
app.get('/api/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);
});

What does this code do?

  • The :id in the route path is a route parameter. Express captures whatever value appears in that URL position and stores it in req.params.id.
  • Since route parameters arrive as strings, parseInt(req.params.id) converts the string into a number so it matches the numeric id values in your books array.
  • If no book matches, the handler responds with HTTP status 404 and a JSON error message. The return keyword prevents the code from continuing to res.json(book) after sending the error.
  • Save server.js.
  • Restart your server by running:
node server.js
  • Confirm the server prints Book API server running at http://localhost:3000 again.

Seeing a syntax error on startup?

  • Check that the closing }); for your new route is on its own line. A missing parenthesis or brace is the most common cause.
  • Make sure the new route is placed between let nextId = 4; and app.listen(port, ...). Routes added after app.listen still work, but placing them before keeps your file organized.

Help me fix a syntax error in my Express server.

Test both endpoints with curl

Both routes are live. Time to verify they handle different scenarios correctly.

  • Switch to your testing terminal tab (the one not running the server).
  • Request a single book by running:
curl http://localhost:3000/api/books/1
  • You should see just the first book object returned as JSON:
{"id":1,"title":"The Great Gatsby","author":"F. Scott Fitzgerald","year":1925}
  • Now test with an ID that doesn't exist by running:
curl http://localhost:3000/api/books/99
  • You should see the 404 error response:
{"error":"Book not found"}

Why does error handling matter?

Without the 404 check, requesting a non-existent book would return undefined or crash your server. Proper HTTP status codes tell the client exactly what went wrong. A 404 means the resource doesn't exist. A 200 (the default) means everything is fine.

Getting an empty response or unexpected output?

  • Make sure you're using parseInt(req.params.id) in your route. Without parseInt, the string "1" won't match the number 1 in your data.
  • Double check that your route path is /api/books/:id with the colon before id. The colon tells Express this is a parameter, not a literal path segment.

Why is my GET by ID route not returning the right book?

✔️ Awesome, I've got everything!

Great. Double check you've saved server.js and your server is still running.

ⓧ I'd like to double check the full code

Here is what your server.js should look like at this point:

import express from 'express';

const app = express();
const port = 3000;

// Middleware to parse JSON request bodies
app.use(express.json());

// In-memory data store with seed data
let books = [
  { id: 1, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', year: 1925 },
  { id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 },
  { id: 3, title: '1984', author: 'George Orwell', year: 1949 }
];

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 book = books.find(b => b.id === parseInt(req.params.id));
  if (!book) {
    return res.status(404).json({ error: 'Book not found' });
  }
  res.json(book);
});

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

Your API can now read data. Next up, you'll build the POST endpoint so clients can add new books to the collection.

Build the CREATE Endpoint (POST)

Your API can read books, but a read-only API is not very useful. Users need a way to add new books to the collection.

In this step, you'll build the CREATE endpoint. This maps the POST HTTP method to an "add new item" operation, complete with input validation to reject incomplete data.

In this step, get ready to:
  • Add a POST route that creates new books.
  • Validate incoming request data and return helpful errors.
  • Test your endpoint with curl.
Add the POST route

When a client sends a POST request to your API, they include data in the request body. Your server needs to read that data, validate it, and create a new resource.

  • In server.js, find the comment // Start the server.
  • Add the following code above that line:
// POST a new book
app.post('/api/books', (req, res) => {
  const { title, author, year } = req.body;
  if (!title || !author) {
    return res.status(400).json({ error: 'Title and author are required' });
  }
  const newBook = { id: nextId++, title, author, year: year || null };
  books.push(newBook);
  res.status(201).json(newBook);
});

What does this code do?

  • app.post('/api/books', ...) registers a handler for POST requests to /api/books.
  • const { title, author, year } = req.body pulls the book data out of the request body. This works because express.json() middleware parses incoming JSON automatically.
  • The if (!title || !author) check is your input validation. If either required field is missing, the server responds with status 400 (Bad Request) and a descriptive error message.
  • nextId++ assigns a unique ID to the new book and increments the counter for the next one.
  • res.status(201).json(newBook) sends back the created book with status 201 (Created), confirming the operation succeeded.
  • Save server.js.
  • Switch to the terminal where your server is running. Press Ctrl+C to stop it.
  • Restart the server by running:
node server.js

You should see Book API server running at http://localhost:3000 printed in your terminal.

Server won't start?

  • Check for syntax errors in your code. A missing comma, brace, or parenthesis is the most common cause.
  • Make sure the new app.post(...) block is placed above app.listen(...) and below the existing app.get(...) routes.
  • If you see EADDRINUSE, the old server is still running. Press Ctrl+C again and retry.

Help me debug my Express server startup error.

Test your input validation

Before testing the happy path, let's make sure your validation catches bad requests. A solid API rejects incomplete data with a clear error message.

  • Open a second terminal window (keep your server running in the first one).
  • Send a POST request with a missing author field by running:

macOS

curl -X POST -H "Content-Type: application/json" -d '{"title":"Dune"}' http://localhost:3000/api/books

Windows

curl -X POST -H "Content-Type: application/json" -d "{\"title\":\"Dune\"}" http://localhost:3000/api/books

You should see this response:

{"error":"Title and author are required"}

What is status code 400?

Status 400 means "Bad Request." It tells the client that the server understood the request format but the data inside was invalid or incomplete.

By returning a descriptive error message, you help API consumers understand exactly what they need to fix.

Test the full creation flow

Now let's create a valid book and confirm it gets added to the collection.

  • Create a new book by running:

macOS

curl -X POST -H "Content-Type: application/json" -d '{"title":"Dune","author":"Frank Herbert","year":1965}' http://localhost:3000/api/books

Windows

curl -X POST -H "Content-Type: application/json" -d "{\"title\":\"Dune\",\"author\":\"Frank Herbert\",\"year\":1965}" http://localhost:3000/api/books

You should see the new book returned with an assigned ID:

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

What is status code 201?

Status 201 means "Created." It confirms that a new resource was successfully added. This is more specific than a generic 200 (OK) and helps API consumers distinguish between reads and writes.

  • Verify the book was added to the collection by running:
curl http://localhost:3000/api/books

You should see all four books in the response, including your new "Dune" entry at the end of the array.

New book not appearing?

  • Make sure you restarted your server after saving the code changes.
  • Check that your POST request included -H "Content-Type: application/json". Without this header, Express cannot parse the JSON body.
  • If you see an empty body in the response, the express.json() middleware might be missing. Confirm it's in your server.js above the route definitions.

My curl POST request is not creating a book in my Express API.

✔️ Awesome, I've got everything!

Great. Double check you've saved server.js and your server is running with the POST route active.

ⓧ I'd like to double check the full code

Here is the full server.js at this point:

import express from 'express';

const app = express();
const port = 3000;

// Middleware to parse JSON request bodies
app.use(express.json());

// In-memory data store with seed data
let books = [
  { id: 1, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', year: 1925 },
  { id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 },
  { id: 3, title: '1984', author: 'George Orwell', year: 1949 }
];

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 book = books.find(b => b.id === parseInt(req.params.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, year } = req.body;
  if (!title || !author) {
    return res.status(400).json({ error: 'Title and author are required' });
  }
  const newBook = { id: nextId++, title, author, year: year || null };
  books.push(newBook);
  res.status(201).json(newBook);
});

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

Your API can now create new books with proper validation. Next up, you'll complete the CRUD cycle by adding UPDATE and DELETE endpoints so users can modify and remove books from the collection.

Build the UPDATE and DELETE Endpoints (PUT & DELETE)

Your API can now create new books. But what if someone misspells an author's name, or a book needs to be removed from the collection entirely?

In this step, you'll add the final two pieces of the CRUD puzzle: PUT for updating existing books and DELETE for removing them. Once these are in place, your API handles the full lifecycle of a resource.

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.
Add the PUT route

The PUT method is used to update an existing resource. Your route will find a book by its ID and only change the fields the client sends in the request body.

  • In server.js, find the closing }); of your POST route (the one above the // Start the server comment).
  • Add the PUT route below it by copying this code:
// PUT (update) a book by ID
app.put('/api/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' });
  }
  const { title, author, year } = req.body;
  if (title) book.title = title;
  if (author) book.author = author;
  if (year) book.year = year;
  res.json(book);
});

What does this code do?

  • app.put('/api/books/:id', ...) listens for PUT requests with a book ID in the URL.
  • It finds the book first. If the book doesn't exist, it returns a 404 error immediately.
  • It only updates the fields that were sent in the request body. If a field isn't provided, it stays unchanged.
  • res.json(book) sends back the updated book so the client can confirm what changed.

You won't see a change yet. The next substep adds DELETE, then you'll test both routes together.

Add the DELETE route

DELETE removes a resource entirely. Unlike PUT, a successful deletion doesn't return the deleted item. It responds with status 204, which means "success, no content to send back."

  • In server.js, add the DELETE route directly below the PUT route's closing }); by copying this code:
// DELETE a book by ID
app.delete('/api/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.status(204).send();
});

What does this code do?

  • books.findIndex() returns the position of the book in the array, or -1 if not found.
  • books.splice(index, 1) removes exactly one element at that position.
  • res.status(204).send() sends a "No Content" response. This tells the client the deletion was successful without sending any body.

Why findIndex instead of find?

The PUT route uses find() because it needs to modify the book object directly. The DELETE route uses findIndex() because it needs the array position to remove the item with splice().

  • Save server.js.
  • Switch back to your terminal and stop the running server by pressing Ctrl+C.
  • Restart the server by running:
node server.js
  • You should see Book API server running at http://localhost:3000 printed in the terminal.

Server won't start?

  • Check for syntax errors. A missing comma, parenthesis, or curly brace in the new routes will prevent the server from starting.
  • Make sure the PUT and DELETE routes are placed between the POST route and the app.listen() call at the bottom.
  • If you see "address already in use", the previous server is still running. Press Ctrl+C again and retry.

Why won't my Express server start after adding the PUT and DELETE routes?

Test the full CRUD cycle

Your API now supports all four CRUD operations. Let's prove it by updating a book, confirming the change, deleting it, and confirming it's gone.

  • Open a second terminal window (keep the server running in the first one).
  • Update the title of book 3 ("1984") by running this curl command:
curl -X PUT -H "Content-Type: application/json" -d '{"title":"Nineteen Eighty-Four"}' http://localhost:3000/api/books/3

You should see the updated book returned with its new title, while author and year remain unchanged.

  • Verify the change stuck by fetching that book:
curl http://localhost:3000/api/books/3

The title should now say "Nineteen Eighty-Four" instead of "1984".

  • Now delete that book entirely by running:
curl -X DELETE http://localhost:3000/api/books/3

You won't see any response body. A 204 status means "success, nothing to return."

  • Confirm the book is gone by trying to fetch it again:
curl http://localhost:3000/api/books/3

You should see the 404 error message: {"error":"Book not found"}. The book is gone.

What just happened?

You just completed a full CRUD cycle. You created data (POST in Step 4), read it (GET in Step 3), updated it (PUT), and deleted it (DELETE). This is the same pattern every web application uses to manage data.

Seeing "Connection refused"?

  • Make sure your server is still running in the first terminal. If it crashed, restart it with node server.js.
  • Confirm you're using port 3000 in the URL. A typo in the URL will fail silently.

My curl commands are returning connection refused when testing PUT and DELETE routes.

✔️ Awesome, I've got everything!

Great. Double check you've saved server.js and your CRUD tests all passed.

ⓧ I'd like to double check the full code

Here's the complete server.js file with all routes. Compare your file against this:

import express from 'express';

const app = express();
const port = 3000;

// Middleware to parse JSON request bodies
app.use(express.json());

// In-memory data store with seed data
let books = [
  { id: 1, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', year: 1925 },
  { id: 2, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 },
  { id: 3, title: '1984', author: 'George Orwell', year: 1949 }
];

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 book = books.find(b => b.id === parseInt(req.params.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, year } = req.body;
  if (!title || !author) {
    return res.status(400).json({ error: 'Title and author are required' });
  }
  const newBook = { id: nextId++, title, author, year: year || null };
  books.push(newBook);
  res.status(201).json(newBook);
});

// PUT (update) a book by ID
app.put('/api/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' });
  }
  const { title, author, year } = req.body;
  if (title) book.title = title;
  if (author) book.author = author;
  if (year) book.year = year;
  res.json(book);
});

// DELETE a book by ID
app.delete('/api/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.status(204).send();
});

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

Secret mission

Add Filtering and Search

Your API works, but real APIs let users filter and search data. Can you upgrade your GET endpoint to support query parameters that filter books by author and search titles?

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 and server ready to go, or delete them entirely.

Resources you used:

  • The book-api project folder on your Desktop (contains server.js, package.json, and node_modules).
  • The Node.js server process running on port 3000.

Keep everything running

No action needed. Choose this if you're still actively building or want to keep experimenting with your API.

  • Your server keeps running as long as the terminal stays open. If you close the terminal, restart it with node server.js from inside the book-api folder.
  • Node.js remains installed on your system. This is useful for any future JavaScript or backend projects.

Pause - I'll come back to this later

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

  • Press Ctrl+C in the terminal where your server is running to stop it.
  • Your project files stay on your Desktop. When you're ready to resume, open a terminal, navigate to ~/Desktop/book-api, and run node server.js again.

Delete - I don't want to use this again

Remove all project files and start fresh.

  • Press Ctrl+C in the terminal where your server is running to stop it.
  • Delete the project folder by running the command for your operating system.

macOS

rm -rf ~/Desktop/book-api

Windows

rmdir /s /q $HOME\Desktop\book-api
  • Confirm the folder is gone by running ls ~/Desktop (macOS) or dir $HOME\Desktop (Windows). You should no longer see book-api in the output.
  • Node.js stays installed on your system. It is useful for future projects, so there is no need to remove it unless you are sure you will not use it again.

Nice Work!

Nice Work!

You just built a fully functional REST API server from scratch. Your Express.js server handles all four CRUD operations, responds with proper HTTP status codes, and parses JSON request bodies.

You've learned how to:

  • Built a complete REST API with Express.js, mapping HTTP methods to data operations: GET (read), POST (create), PUT (update), and DELETE (remove).
  • Used proper HTTP status codes (200, 201, 204, 400, 404) to communicate results to API consumers.
  • Tested your API endpoints with curl commands, the same tool developers use in the real world to debug and verify APIs.
  • Added query parameter filtering and search to your GET endpoint so users can find books by author or search titles.

Ready to quiz yourself?