DEV Community

Eugene Okogun
Eugene Okogun

Posted on

How to Build a REST API with Node.js and Express from Scratch

Have you ever wondered how mobile apps and websites get their data? The answer is usually an API. In this tutorial, you will build a working REST API from scratch using Node.js and Express. By the end, you will have an API that can create, read, update, and delete data — the four core operations of any real-world application.

No prior API experience needed. If you know basic JavaScript, you are ready.


What You Will Build

A simple Books API that lets you:

  • Get a list of all books
  • Get a single book by ID
  • Add a new book
  • Update a book
  • Delete a book

Prerequisites

Before starting, make sure you have:

  • Node.js installed (version 14 or higher)
  • A code editor (VS Code recommended)
  • A terminal / command prompt
  • Postman or Thunder Client to test your API

Step 1: Set Up Your Project

Open your terminal and run these commands one by one:


# Create a new folder for your project
mkdir books-api

# Move into the folder
cd books-api

# Create a package.json file (just press Enter for all questions)
npm init -y

# Install Express
npm install express
Enter fullscreen mode Exit fullscreen mode

Now create a file called index.js inside your books-api folder. This is where all your code will live.

Your folder structure should look like this:

books-api/
  index.js
  package.json
  node_modules/
Enter fullscreen mode Exit fullscreen mode

Step 2: Create Your First Server

Open index.js and add this code:

// Import Express
const express = require('express');

// Create an Express app
const app = express();

// This line lets our API understand JSON data
app.use(express.json());

// Choose a port number for our server
const PORT = 3000;

// Start the server
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

What each line does:

  • require('express') — loads the Express library we installed
  • app.use(express.json()) — tells Express to read JSON from incoming requests
  • app.listen(PORT) — starts the server and listens for requests on port 3000

Run your server:

node index.js
Enter fullscreen mode Exit fullscreen mode

You should see:

Server is running on http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

Your server is live. Now let's add data and routes.


Step 3: Add Some Sample Data

Since we are not using a database in this tutorial, we will store our books in an array. Add this to your index.js file, just below app.use(express.json()):

// Our "database" — a simple array of books
let books = [
  { id: 1, title: 'The Pragmatic Programmer', author: 'Andrew Hunt' },
  { id: 2, title: 'Clean Code', author: 'Robert C. Martin' },
  { id: 3, title: 'You Don\'t Know JS', author: 'Kyle Simpson' },
];
Enter fullscreen mode Exit fullscreen mode

Think of this array as a temporary database. Every time the server restarts, data resets — but it is perfect for learning.


Step 4: GET All Books

This route returns every book in our list. Add this below your books array:

// GET /books — returns all books
app.get('/books', (req, res) => {
  res.json(books);
});
Enter fullscreen mode Exit fullscreen mode

What is happening here:

  • app.get — we are setting up a GET route
  • '/books' — the URL path someone visits
  • req — the incoming request (what the user sends)
  • res — the response (what we send back)
  • res.json(books) — sends our books array as JSON

Test it: Open Postman, set the method to GET, and visit http://localhost:3000/books. You will see all three books.


Step 5: GET a Single Book

What if someone wants just one book? We use a URL parameter:

// GET /books/:id — returns one book by ID
app.get('/books/:id', (req, res) => {
  // Convert the id from text to a number
  const id = parseInt(req.params.id);

  // Find the book with that ID
  const book = books.find((b) => b.id === id);

  // If no book found, send a 404 error
  if (!book) {
    return res.status(404).json({ message: 'Book not found' });
  }

  // Send the book back
  res.json(book);
});
Enter fullscreen mode Exit fullscreen mode

What is happening here:

  • :id in the URL is a placeholder — it captures whatever number the user types
  • req.params.id reads that number from the URL
  • parseInt converts it from a string to a number
  • .find() searches the array for a matching book
  • If nothing is found, we return a 404 status with a helpful message

Test it: GET http://localhost:3000/books/1 returns the first book. GET http://localhost:3000/books/99 returns "Book not found".


Step 6: POST — Add a New Book

Now let's allow adding a new book:

// POST /books — adds a new book
app.post('/books', (req, res) => {
  // Get the title and author from the request body
  const { title, author } = req.body;

  // Simple validation — make sure both fields exist
  if (!title || !author) {
    return res.status(400).json({ message: 'Title and author are required' });
  }

  // Create the new book with a unique ID
  const newBook = {
    id: books.length + 1,
    title: title,
    author: author,
  };

  // Add it to our array
  books.push(newBook);

  // Send back the new book with a 201 status (means "created")
  res.status(201).json(newBook);
});
Enter fullscreen mode Exit fullscreen mode

Test it in Postman:

  • Method: POST
  • URL: http://localhost:3000/books
  • Body: raw → JSON
{
  "title": "Eloquent JavaScript",
  "author": "Marijn Haverbeke"
}
Enter fullscreen mode Exit fullscreen mode

You will get back the new book with an ID of 4.


Step 7: PUT — Update a Book

What if someone made a typo in a book title? This route fixes it:

// PUT /books/:id — updates an existing book
app.put('/books/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const { title, author } = req.body;

  // Find the position of the book in the array
  const index = books.findIndex((b) => b.id === id);

  // If book doesn't exist, return 404
  if (index === -1) {
    return res.status(404).json({ message: 'Book not found' });
  }

  // Update the book — keep the same ID, change title and author
  books[index] = { id, title, author };

  // Send back the updated book
  res.json(books[index]);
});
Enter fullscreen mode Exit fullscreen mode

Test it in Postman:

  • Method: PUT
  • URL: http://localhost:3000/books/1
  • Body:
{
  "title": "The Pragmatic Programmer (20th Anniversary Edition)",
  "author": "Andrew Hunt"
}
Enter fullscreen mode Exit fullscreen mode

Step 8: DELETE — Remove a Book

Finally, let's allow deleting a book:

// DELETE /books/:id — removes a book
app.delete('/books/:id', (req, res) => {
  const id = parseInt(req.params.id);

  // Find the book's position in the array
  const index = books.findIndex((b) => b.id === id);

  // If not found, return 404
  if (index === -1) {
    return res.status(404).json({ message: 'Book not found' });
  }

  // Remove the book from the array
  books.splice(index, 1);

  // Confirm deletion
  res.json({ message: 'Book deleted successfully' });
});
Enter fullscreen mode Exit fullscreen mode

Test it: DELETE http://localhost:3000/books/2 removes Clean Code from the list. Do a GET on /books and confirm it is gone.


Your Complete index.js File

Here is the full code in one place:

const express = require('express');
const app = express();
app.use(express.json());

const PORT = 3000;

let books = [
  { id: 1, title: 'The Pragmatic Programmer', author: 'Andrew Hunt' },
  { id: 2, title: 'Clean Code', author: 'Robert C. Martin' },
  { id: 3, title: "You Don't Know JS", author: 'Kyle Simpson' },
];

// GET all books
app.get('/books', (req, res) => {
  res.json(books);
});

// GET one book
app.get('/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({ message: 'Book not found' });
  res.json(book);
});

// POST a new book
app.post('/books', (req, res) => {
  const { title, author } = req.body;
  if (!title || !author) return res.status(400).json({ message: 'Title and author are required' });
  const newBook = { id: books.length + 1, title, author };
  books.push(newBook);
  res.status(201).json(newBook);
});

// PUT update a book
app.put('/books/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const { title, author } = req.body;
  const index = books.findIndex((b) => b.id === id);
  if (index === -1) return res.status(404).json({ message: 'Book not found' });
  books[index] = { id, title, author };
  res.json(books[index]);
});

// DELETE a book
app.delete('/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({ message: 'Book not found' });
  books.splice(index, 1);
  res.json({ message: 'Book deleted successfully' });
});

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

What You Just Built

You now have a fully working REST API with five routes:

Method Route What it does
GET /books Returns all books
GET /books/:id Returns one book
POST /books Adds a new book
PUT /books/:id Updates a book
DELETE /books/:id Deletes a book

What to Learn Next

Now that your API works, here are natural next steps:

  • Add a real database — connect MongoDB or PostgreSQL instead of an array
  • Add authentication — protect your routes with JWT tokens
  • Deploy your API — put it online using Railway or Render (both free)
  • Add input validation — use a library like express-validator for stricter checks

Conclusion

Building a REST API with Node.js and Express is simpler than most people think. You started from an empty folder and ended up with a working API that handles all four CRUD operations. Every API you use daily — Twitter, Instagram, your banking app — works on these same principles at its core.

The code in this tutorial is intentionally simple so you can focus on understanding the concepts. Once these feel comfortable, adding a database and authentication becomes a natural next step.

Happy building.


Enter fullscreen mode Exit fullscreen mode

Top comments (0)