Build a REST API with Express.js

Build a bookshelf API with full CRUD operations using Node.js and Express.

Introduction

30 Second Summary

Every time you check the weather, scroll social media, or add something to a shopping cart, your device is talking to a server. The language they speak is called a REST API.

In this project, you will build a "Bookshelf API" using Node.js and Express.js. Your API will manage a collection of books, responding to requests to list, add, update, and delete books from your shelf.

What You'll Build

A REST API server that handles HTTP requests and returns JSON responses for managing a book collection.

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

  • A Node.js project with Express.js installed and configured.
  • Five working REST endpoints that create, read, update, and delete books.
  • A running local server tested end-to-end using your browser and PowerShell commands.
  • Secret Mission: Add a search endpoint that filters books by author or title using query parameters.

Are there any prerequisites?

None. This project includes full setup instructions for every tool you need, including Node.js and Visual Studio Code. All you need is a Windows computer with an internet connection.

Set Up Your Development Environment

Before you can build your API, you need the right tools installed on your computer.

In this step, you'll install Node.js (the runtime that lets you run JavaScript outside a browser), set up Visual Studio Code as your code editor, and create your project folder.

In this step, get ready to:
  • Install Node.js and verify it works.
  • Install Visual Studio Code.
  • Create and initialize your project folder.
Install Node.js

Node.js is a JavaScript runtime that lets you run JavaScript code on your computer, outside of a web browser. It also comes bundled with npm (Node Package Manager), which you'll use to install libraries like Express.

  • Open the Node.js download page at https://nodejs.org/en/download.
  • Download the LTS version (v24.16.0 at time of writing). The page detects your operating system automatically.
  • Run the installer and accept the default options at each step.

Once installation finishes, you need to verify it worked. Open a new terminal window.

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

✔️ I see version 18 or higher

Node.js is ready to go. You should see something like v24.16.0 for Node and 11.x.x for npm. Any version of Node.js at v18 or above works for this project.

ⓧ I see a version lower than 18

You have an older version of Node.js that won't work with Express 5. You need to upgrade.

  • Go to https://nodejs.org/en/download and download the LTS installer.
  • Run the installer. It will replace your existing version with the latest LTS.
  • Close your terminal and open a new one (the old terminal still has the old version in memory).
  • Run node -v again to confirm it now shows v24 or higher.

ⓧ Command not found

Node.js isn't installed yet. Follow these steps to install it.

  • Go to https://nodejs.org/en/download and download the LTS version.
  • Run the installer and accept all the default settings.
  • Close your terminal and open a brand new one (the terminal needs to reload its list of available commands).
  • Run node -v and npm -v to confirm both return version numbers.

What is Node.js?

JavaScript was originally designed to run only inside web browsers. Node.js lets you run JavaScript directly on your computer, which means you can use it to build servers, APIs, and command-line tools.

It also includes npm, a package manager that lets you download and install open-source libraries (like Express) with a single command.

Install Visual Studio Code

Visual Studio Code is a free code editor that makes it easy to write and organize your project files.

✔️ I already have VS Code

You're all set. Move on to the next substep.

ⓧ I need to install it

  • Go to https://code.visualstudio.com/ and click the download button for your operating system.
  • Run the installer and accept the default settings.
  • Once installed, open VS Code by pressing Cmd+Space (macOS) or the Windows key (Windows), typing Visual Studio Code, and pressing Enter.
Create your project

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

What does this command do?

The npm init -y command creates a package.json file in your project folder. This file is like a settings card for your project. It stores the project name, version, and a list of any libraries you install later.

The -y flag means "accept all the defaults" so you don't have to answer a series of questions.

  • 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 bookshelf-api before running npm init -y. The command creates package.json in whatever folder your terminal is currently in.
  • If you see an error about npm not being recognized, close your terminal, open a new one, and try again. The terminal needs to reload after installing Node.js.

Help me figure out why npm init isn't working in my bookshelf-api folder.

✔️ Awesome, I've got everything!

Great. Make sure your terminal is still inside the bookshelf-api folder. You'll stay here for the rest of the project.

ⓧ I'd like to double check the full code

Your package.json should look like this (exact values for name and license may vary slightly depending on your npm version):

{
  "name": "bookshelf-api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Your development environment is ready. Next up, you'll install Express and write your first working server.

Install Express and Create Your Server

Your project folder is ready and your package.json is in place. Now it's time to add the framework that makes building APIs fast and simple.

Express is a lightweight web framework for Node.js that handles incoming requests and sends responses. In this step, you'll install Express, write your first server file, and see it respond in the browser.

In this step, get ready to:
  • Install Express as a project dependency.
  • Create a server file that responds with a welcome message.
  • Start your server and confirm it works in the browser.
Install Express

Express is a package hosted on the npm registry. Installing it downloads the framework and all its dependencies into your project.

  • Install Express by running this command in your terminal:
npm install express

What does this command do?

The npm install express command downloads the Express package and saves it as a dependency in your package.json.

It also creates a node_modules folder (where downloaded packages live) and a package-lock.json file (which locks exact version numbers for consistency).

  • Confirm the install succeeded by running:
ls node_modules
  • You should see an express folder listed in the output. Your terminal also shows a summary line like "added X packages" after the install command.

Seeing an error during install?

  • Make sure your terminal is inside the bookshelf-api folder. Run cd bookshelf-api if you're not sure.
  • Check your internet connection. npm needs to download packages from the registry.
  • If you see permission errors, try closing your terminal and opening a new one as administrator.

Help me fix my npm install error.

Create the server file

Your server lives in a single file called app.js. This file imports Express, configures the app, and tells it how to respond to incoming requests.

  • Open the bookshelf-api folder in Visual Studio Code. Click File in the top menu bar, then Open Folder, and select the bookshelf-api folder on your Desktop.
  • Create a new file by clicking the New File icon at the top of the Explorer panel on the left.
  • Name the file app.js and press Enter.
  • Add the server setup code to app.js by pasting the following:
const express = require('express');
const app = express();
const port = 3000;

// Parse incoming JSON request bodies
app.use(express.json());

What does this code do?

  • require('express') imports the Express framework you just installed.
  • express() creates your application instance. All routes and settings attach to this app object.
  • port = 3000 sets the port number your server will listen on.
  • express.json() is middleware that automatically parses JSON data sent in request bodies. You'll need this when creating and updating books later.

You won't see a change yet. The next chunk adds the route and starts the server.

  • Add the route and server start code below the setup by pasting this at the bottom of app.js:
// Respond with a welcome message at the root URL
app.get('/', (req, res) => {
  res.json({ message: 'Welcome to the Bookshelf API!' });
});

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

What does this code do?

  • app.get('/', ...) defines a route. When someone visits the root URL (/), Express runs the callback function.
  • res.json(...) sends back a JSON response. This is how REST APIs communicate. They send structured data instead of HTML pages.
  • app.listen(port, ...) starts the server. Once this line runs, your server is live and waiting for incoming requests on port 3000.
  • Save app.js by pressing Ctrl+S.

Seeing red squiggly lines in VS Code?

  • Make sure you copied all the code exactly, including the backtick characters (`) around the template string in console.log. These are backticks (next to the 1 key), not single quotes.
  • Check that the file is named app.js (not app.txt or App.js).

Help me fix my app.js code.

✔️ Awesome, I've got everything!

Great. Double check you've saved app.js and let's start it up.

ⓧ I'd like to double check the full code

Here's the complete app.js file so far:

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

app.use(express.json());

app.get('/', (req, res) => {
  res.json({ message: 'Welcome to the Bookshelf API!' });
});

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

Your code is saved. Time to bring the server to life and see it respond to a real request.

  • Start your server by running this command in the terminal:
node app.js

What should I see?

Your terminal prints Bookshelf API running at http://localhost:3000 and the cursor stops blinking. This means the server is running and waiting for requests. The terminal is now "occupied" by the server process.

  • Open your browser and go to http://localhost:3000.
  • You should see a JSON response: {"message":"Welcome to the Bookshelf API!"}.

What just happened?

Your browser sent an HTTP GET request to localhost:3000. Express matched the / route, ran your callback, and sent back the JSON object. This is the request/response cycle that powers every web application.

  • Return to your terminal and press Ctrl+C to stop the server.

Browser showing an error or blank page?

  • Check your terminal. If it shows an error instead of the "running" message, there's a typo in app.js. Read the error line number and compare against the full code above.
  • Make sure you typed http://localhost:3000 exactly (not https).
  • If the terminal says the port is already in use, another process is using port 3000. Close other terminal windows and try again.

Help me get my Express server running.

Your server is alive and responding. Next, you'll add endpoints that manage your bookshelf collection.

Build Your GET Endpoints

Your server is up and running, but it only returns a generic welcome message. An API needs real data and meaningful endpoints that clients can request.

In this step, you'll add a collection of books to your server and create two GET endpoints. One returns the full list of books, and the other finds a single book by its ID.

In this step, get ready to:
  • Add a books array as your in-memory data store.
  • Create GET endpoints to retrieve all books and a single book by ID.
  • Test your endpoints in the browser to confirm they return JSON.
Add book data and a GET all books endpoint

Your API needs data to serve. You'll store books in a JavaScript array that lives in memory for the lifetime of the server.

  • In VS Code, open app.js.
  • Find the line that says app.use(express.json()); and add the following code below it:
// Our in-memory book collection
let books = [
  { id: 1, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', year: 1925 },
  { id: 2, title: '1984', author: 'George Orwell', year: 1949 },
  { id: 3, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 }
];

// GET /books - return the full list of books
app.get('/books', (req, res) => {
  res.json(books);
});

What does this code do?

The books array holds three book objects, each with an id, title, author, and year. This is your in-memory data store.

The app.get('/books', ...) line creates a route that responds to GET requests at /books. When a client hits this URL, the server sends back the entire books array as JSON.

  • Save app.js.
  • You won't see a change yet because the server isn't running. You'll test this endpoint in the final substep.
Add a GET single book endpoint

Listing all books is useful, but clients also need to fetch a specific book by its ID. Route parameters let you capture a value from the URL itself.

  • In app.js, find the GET /books route you just added and paste the following code below it:
// GET /books/:id - find and return a single book by its ID
app.get('/books/:id', (req, res) => {
  // Parse the ID from the URL and find the matching book
  const book = books.find(b => b.id === parseInt(req.params.id));
  if (!book) {
    // Return a 404 status if no book matches the ID
    return res.status(404).json({ error: 'Book not found' });
  }
  res.json(book);
});

What does this code do?

The :id in the route path is a route parameter. When someone visits /books/2, Express captures 2 and stores it in req.params.id.

Since URL parameters arrive as strings, parseInt() converts it to a number so it can match against the book IDs. If no book matches, the server returns a 404 status code with an error message.

What are HTTP status codes?

Status codes tell the client what happened with their request. 200 means success (Express sends this by default). 404 means the resource wasn't found.

You'll use more status codes in the next step when you add POST, PUT, and DELETE endpoints.

  • Save app.js.
  • You won't see a change yet. The next substep starts the server so you can test both endpoints.
Test your GET endpoints

Both endpoints are in place. Time to start the server and confirm they return the right data.

  • Start your server by running this command in your terminal:
node app.js
  • You should see Bookshelf API running at http://localhost:3000 printed in the terminal.
  • Open your browser and visit http://localhost:3000/books to see all three books as JSON.
  • Visit http://localhost:3000/books/1 to see just The Great Gatsby.
  • Visit http://localhost:3000/books/99 to see the 404 error response: {"error":"Book not found"}.

Not seeing JSON in the browser?

  • Make sure you saved app.js before running node app.js.
  • Check your terminal for error messages. A missing comma or bracket in the books array is the most common cause.
  • Confirm the URL starts with http:// not https://.

Why aren't my GET endpoints returning data?

  • Once you've confirmed all three URLs work, press Ctrl+C in the terminal to stop the server.

✔️ 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 is the complete app.js file so far:

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

app.use(express.json());

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

app.get('/', (req, res) => {
  res.json({ message: 'Welcome to the Bookshelf 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.listen(port, () => {
  console.log(`Bookshelf API running at http://localhost:${port}`);
});

Your API now serves real data. Next up, you'll add the ability to create, update, and delete books using POST, PUT, and DELETE endpoints.

Build POST, PUT, and DELETE Endpoints

Your API can read data, but a real API also needs to create, update, and delete resources.

In this step, you'll add three new endpoints that complete your API's functionality. These use the POST, PUT, and DELETE HTTP methods to let clients add books, edit books, and remove books from your bookshelf.

In this step, get ready to:
  • Add a POST endpoint that creates new books.
  • Add PUT and DELETE endpoints that update and remove books.
  • Test all endpoints using PowerShell commands.
Add the POST endpoint to create books

The GET endpoints you built can only read existing books. A POST endpoint lets clients send new data to the server, which adds it to the collection.

  • Stop your running server by pressing Ctrl+C in the terminal.
  • In app.js, find the closing }); of your GET /books/:id route.
  • Add this POST route below it (before the app.listen call):
app.post('/books', (req, res) => {
  // Build a new book object from the request body
  const book = {
    id: books.length + 1,
    title: req.body.title,
    author: req.body.author,
    year: req.body.year
  };
  // Add the new book to our array
  books.push(book);
  // Return the created book with a 201 status
  res.status(201).json(book);
});

What does this code do?

This endpoint listens for POST requests at /books. When a client sends JSON data in the request body, Express makes it available through req.body (thanks to the express.json() middleware you added earlier).

  • id: books.length + 1 generates a simple unique ID based on the array length.
  • books.push(book) adds the new book to the in-memory array.
  • res.status(201).json(book) returns the new book with a 201 Created status code, telling the client the resource was successfully created.
  • Save app.js.
  • You won't see a change yet. The next substep adds more routes, then you'll restart the server and test everything together.
Add PUT and DELETE endpoints

PUT lets clients update an existing resource. DELETE removes one entirely.

  • In app.js, find the closing }); of your new POST route.
  • Add the PUT route below it:
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 only the fields that were sent in the request
  book.title = req.body.title || book.title;
  book.author = req.body.author || book.author;
  book.year = req.body.year || book.year;
  res.json(book);
});

What does the PUT route do?

This route finds a book by its :id parameter, then updates whichever fields the client sent.

The || (OR) pattern means: use the new value if one was sent, otherwise keep the existing value. This lets clients update just one field without overwriting the others.

  • Now add the DELETE route directly below the PUT route:
app.delete('/books/:id', (req, res) => {
  // Find the index of the book to delete
  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);
  // Return 204 No Content (successful deletion, nothing to send back)
  res.status(204).send();
});

What does the DELETE route do?

Instead of .find(), DELETE uses .findIndex() to get the position of the book in the array. Then books.splice(index, 1) removes it.

The 204 status code means "No Content." It tells the client the deletion succeeded, but there's nothing to return in the response body.

  • Save app.js.
  • You won't see a change yet. The next substep restarts the server and tests all three new endpoints.

Seeing a syntax error when you save?

  • Make sure each route ends with }); on its own line. A missing closing brace or parenthesis is the most common issue.
  • Check that your new routes are placed between the GET /books/:id route and the app.listen call at the bottom.

Help me find a syntax error in my Express routes.

Test your endpoints with PowerShell

Your browser can only send GET requests through the address bar. To test POST, PUT, and DELETE, you'll use PowerShell's built-in Invoke-RestMethod command.

  • Restart your server by running:
node app.js
  • Open a second PowerShell window (keep the server running in the first one).
  • Create a new book by running this POST command:
Invoke-RestMethod -Uri "http://localhost:3000/books" -Method POST -ContentType "application/json" -Body '{"title":"Dune","author":"Frank Herbert","year":1965}'

You should see the new book returned with id: 4, confirming it was created.

  • Update book 1's title by running this PUT command:
Invoke-RestMethod -Uri "http://localhost:3000/books/1" -Method PUT -ContentType "application/json" -Body '{"title":"The Great Gatsby (Updated)"}'

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

  • Delete book 2 by running this DELETE command:
Invoke-RestMethod -Uri "http://localhost:3000/books/2" -Method DELETE

This command produces no output. A 204 response means the deletion succeeded with nothing to send back.

  • Verify all your changes by fetching the full book list:
Invoke-RestMethod -Uri "http://localhost:3000/books"

You should see three books: The Great Gatsby (Updated), To Kill a Mockingbird, and Dune. Book 2 (1984) is gone because you deleted it.

Seeing an error or empty response?

  • Make sure your server is still running in the first terminal window. If it crashed, check for syntax errors in app.js and restart it with node app.js.
  • If you see a "connection refused" error, the server is not running. Switch to the first terminal and restart it.
  • Check that the single quotes around the -Body JSON are straight quotes, not curly quotes. PowerShell requires straight single quotes around the JSON string.

Help me troubleshoot my PowerShell API test commands.

✔️ Awesome, I've got everything!

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

ⓧ 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 Great Gatsby', author: 'F. Scott Fitzgerald', year: 1925 },
  { id: 2, title: '1984', author: 'George Orwell', year: 1949 },
  { id: 3, title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 }
];

app.get('/', (req, res) => {
  res.json({ message: 'Welcome to the Bookshelf 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 book = {
    id: books.length + 1,
    title: req.body.title,
    author: req.body.author,
    year: req.body.year
  };
  books.push(book);
  res.status(201).json(book);
});

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.title;
  book.author = req.body.author || book.author;
  book.year = req.body.year || book.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.status(204).send();
});

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

Secret mission

Add Search with Query Parameters

Your API can list books, add them, update them, and delete them. But what if someone wants to find all books by a specific author without scrolling through the entire collection? Add a search endpoint that filters books by author or title.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.

Resources you used:

  • The bookshelf-api project folder (including node_modules, app.js, and package.json).
  • A local Node.js server process (if still running).

Keep everything running

No action needed. Choose this if you're still actively building or plan to extend the API soon.

  • Your bookshelf-api folder stays on your Desktop.
  • Node.js and VS Code remain installed and ready for your next project.
  • If the server is still running in your terminal, you can stop it anytime with Ctrl+C.

Pause - I'll come back to this later

Stop the running server to free up your terminal. Your project files stay on disk so you can pick up where you left off.

  • In the terminal where your server is running, press Ctrl+C to stop the server.
  • Close any extra terminal windows you opened for testing.
  • To restart later, open a terminal in your bookshelf-api folder and run node app.js.

Delete - I don't want to use this again

Remove all project files and start fresh. This deletes everything you built in this project.

  • If the server is still running, press Ctrl+C in the terminal to stop it.
  • Delete the project folder by running the appropriate command for your operating system.

On macOS or Linux:

cd ~/Desktop
rm -rf bookshelf-api

On Windows (PowerShell):

cd ~\Desktop
Remove-Item -Recurse -Force bookshelf-api
  • Confirm deletion by running ls (macOS/Linux) or dir (Windows). The bookshelf-api folder should no longer appear.

Nice Work!

Nice Work!

You just built a fully functional REST API from scratch. Your Bookshelf API can create, read, update, and delete books, responding to HTTP requests with structured JSON data.

Here's what you learned:

  • Built an Express.js server that listens for HTTP requests and returns JSON responses on port 3000.
  • Implemented all four CRUD operations (Create, Read, Update, Delete) using GET, POST, PUT, and DELETE HTTP methods.
  • Used route parameters and status codes to target specific resources and communicate results back to the client.
  • Added search functionality with query parameters for case-insensitive partial matching across your book collection.

Ready to quiz yourself?