Node.js: RESTful API Project

Last updated: 2026-08-26

1. Project Background

Alice and Bob were assigned a task: to develop a book management API for an online bookstore. Alice was responsible for designing the routing and middleware architecture, while Bob was responsible for writing the business logic. The two agreed on a standardized response format and error-handling conventions, and within two hours, they completed the entire project—from setting up the project to implementing full CRUD functionality.

(1) You will learn



2. Designing the Project Directory Structure

A well-structured directory hierarchy is the foundation of a project’s maintainability. Following community best practices, Alice organizes the project into layers based on responsibilities:

TEXT 📖 Display only
book-api/
├── app.js
├── routes/
│   └── books.js
├── controllers/
│   └── bookController.js
├── middleware/
│   ├── validate.js
│   └── errorHandler.js
└── models/
    └── bookModel.js
Directory/File Responsibilities Description
app.js Entry File Create an Express instance, attach routes and middleware
routes/ Route Definition Defines HTTP methods and paths that point to the corresponding controller
controllers/ Business Logic Process the request, call the model, and return a response
middleware/ Middleware Cross-cutting concerns such as request validation and error handling
models/ Data Models Data Storage and Data Operation Encapsulation


3. API Endpoint Design

Based on business requirements, Bob compiled a list of all API endpoints:

HTTP Method Path Function Success Status Code Failure Status Code
GET /api/books Get all books 200
GET /api/books/:id Retrieve a single book 200 404
POST /api/books New Books 201 400
PUT /api/books/:id Update Book 200 404 / 400
DELETE /api/books/:id Delete Book 200 404


4. Standardized Response Format

Alice insists on a consistent response format so that the front-end team doesn't have to guess field names:

▶ Example: Successful Response

JAVASCRIPT
{
  "success": true,
  "data": { "id": 1, "title": "Node.js Guide", "author": "Alice" }
}
▶ Try it Yourself

▶ Example: Error Response

JAVASCRIPT
{
  "success": false,
  "error": { "code": 404, "message": "Book not found" }
}
▶ Try it Yourself
Field Type Description
success boolean Whether the request was successful
data any Data returned upon success
error.code number Error Status Code
error.message string Error description


5. Data Model Layer

Bob uses arrays in the models directory to simulate a database and encapsulate all data operations:

▶ Example: bookModel.js

JAVASCRIPT
const books = [
  { id: 1, title: "Node.js Guide", author: "Alice", year: 2024 }
];

let nextId = 2;

function findAll() {
  return books;
}

function findById(id) {
  return books.find(b => b.id === id);
}

function create(data) {
  const book = { id: nextId++, ...data };
  books.push(book);
  return book;
}

function update(id, data) {
  const index = books.findIndex(b => b.id === id);
  if (index === -1) return null;
  books[index] = { ...books[index], ...data };
  return books[index];
}

function remove(id) {
  const index = books.findIndex(b => b.id === id);
  if (index === -1) return false;
  books.splice(index, 1);
  return true;
}

module.exports = { findAll, findById, create, update, remove };
▶ Try it Yourself

6. Request Validation Middleware

Alice implements data validation in the middleware layer to ensure that invalid requests do not reach the controller:

▶ Example: validate.js

JAVASCRIPT
function validateBook(req, res, next) {
  const { title, author, year } = req.body;
  const errors = [];
  if (!title || typeof title !== "string") {
    errors.push("title is required and must be a string");
  }
  if (!author || typeof author !== "string") {
    errors.push("author is required and must be a string");
  }
  if (year !== undefined && (typeof year !== "number" || year < 0)) {
    errors.push("year must be a non-negative number");
  }
  if (errors.length > 0) {
    return res.status(400).json({
      success: false,
      error: { code: 400, message: errors.join("; ") }
    });
  }
  next();
}

module.exports = { validateBook };
▶ Try it Yourself

7. Error-Handling Middleware

▶ Example: errorHandler.js

JAVASCRIPT
function errorHandler(err, req, res, next) {
  console.error(err.stack);
  const status = err.status || 500;
  res.status(status).json({
    success: false,
    error: { code: status, message: err.message || "Internal Server Error" }
  });
}

function createError(status, message) {
  const err = new Error(message);
  err.status = status;
  return err;
}

module.exports = { errorHandler, createError };
▶ Try it Yourself

8. Controller Layer

Bob handles the business logic in the controller, calls the model, and returns a response in a standardized format:

▶ Example: bookController.js

JAVASCRIPT
const Book = require("../models/bookModel");
const { createError } = require("../middleware/errorHandler");

function getAllBooks(req, res) {
  res.json({ success: true, data: Book.findAll() });
}

function getBookById(req, res, next) {
  const book = Book.findById(Number(req.params.id));
  if (!book) return next(createError(404, "Book not found"));
  res.json({ success: true, data: book });
}

function createBook(req, res) {
  const book = Book.create(req.body);
  res.status(201).json({ success: true, data: book });
}

function updateBook(req, res, next) {
  const book = Book.update(Number(req.params.id), req.body);
  if (!book) return next(createError(404, "Book not found"));
  res.json({ success: true, data: book });
}

function deleteBook(req, res, next) {
  const removed = Book.remove(Number(req.params.id));
  if (!removed) return next(createError(404, "Book not found"));
  res.json({ success: true, data: { message: "Book deleted" } });
}

module.exports = { getAllBooks, getBookById, createBook, updateBook, deleteBook };
▶ Try it Yourself

9. Route Definitions

Alice decouples routing from the controller; routing is responsible only for mapping:

▶ Example: routes/books.js

JAVASCRIPT
const express = require("express");
const router = express.Router();
const controller = require("../controllers/bookController");
const { validateBook } = require("../middleware/validate");

router.get("/", controller.getAllBooks);
router.get("/:id", controller.getBookById);
router.post("/", validateBook, controller.createBook);
router.put("/:id", validateBook, controller.updateBook);
router.delete("/:id", controller.deleteBook);

module.exports = router;
▶ Try it Yourself

10. Entry File app.js

▶ Example: app.js

JAVASCRIPT
const express = require("express");
const booksRoute = require("./routes/books");
const { errorHandler } = require("./middleware/errorHandler");

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

app.use("/api/books", booksRoute);
app.use(errorHandler);

const PORT = 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
▶ Try it Yourself

11. Project Architecture Overview

Alice drew an architecture diagram that clearly illustrates the flow of requests:

100%
graph TD
    A[app.js Entrance] --> B[routes/books.js]
    B --> C{Needs verification?}
    C -->|POST/PUT| D[middleware/validate.js]
    C -->|GET/DELETE| E[controllers/bookController.js]
    D --> E
    E --> F[models/bookModel.js]
    F --> E
    E --> G[Standardized Response Format]
    G --> H[Client]
    E -->|Exception| I[middleware/errorHandler.js]
    I --> H


12. API Testing

Bob uses curl to verify each endpoint one by one:

curl command Description
curl localhost:3000/api/books Get All Books
curl localhost:3000/api/books/1 Get a Specific Book
curl -X POST -H "Content-Type: application/json" -d '{"title":"New Book","author":"Bob","year":2025}' localhost:3000/api/books New Books
curl -X PUT -H "Content-Type: application/json" -d '{"year":2026}' localhost:3000/api/books/1 Update Books
curl -X DELETE localhost:3000/api/books/1 Delete Book

▶ Example: Testing Insert and Query Operations

BASH
# Add a book
curl -X POST -H "Content-Type: application/json" \
  -d '{"title":"Express in Action","author":"Evan","year":2024}' \
  localhost:3000/api/books
TEXT 📖 Display only
{"success":true,"data":{"id":2,"title":"Express in Action","author":"Evan","year":2024}}
BASH
# Search All Books
curl localhost:3000/api/books
TEXT 📖 Display only
{"success":true,"data":[{"id":1,"title":"Node.js Guide","author":"Alice","year":2024},{"id":2,"title":"Express in Action","author":"Evan","year":2024}]}


13. Comprehensive Example: Complete Book Management API

Combining all the previous modules, here is the core code flow for the complete project:

JAVASCRIPT
// models/bookModel.js
const books = [{ id: 1, title: "Node.js Guide", author: "Alice", year: 2024 }];
let nextId = 2;
function findAll() { return books; }
function findById(id) { return books.find(b => b.id === id); }
function create(data) {
  const book = { id: nextId++, ...data };
  books.push(book);
  return book;
}
function update(id, data) {
  const index = books.findIndex(b => b.id === id);
  if (index === -1) return null;
  books[index] = { ...books[index], ...data };
  return books[index];
}
function remove(id) {
  const index = books.findIndex(b => b.id === id);
  if (index === -1) return false;
  books.splice(index, 1);
  return true;
}
module.exports = { findAll, findById, create, update, remove };
JAVASCRIPT
// middleware/validate.js
function validateBook(req, res, next) {
  const { title, author } = req.body;
  const errors = [];
  if (!title || typeof title !== "string") errors.push("title is required");
  if (!author || typeof author !== "string") errors.push("author is required");
  if (errors.length > 0) {
    return res.status(400).json({
      success: false,
      error: { code: 400, message: errors.join("; ") }
    });
  }
  next();
}
module.exports = { validateBook };
JAVASCRIPT
// middleware/errorHandler.js
function errorHandler(err, req, res, next) {
  const status = err.status || 500;
  res.status(status).json({
    success: false,
    error: { code: status, message: err.message || "Internal Server Error" }
  });
}
function createError(status, message) {
  const err = new Error(message);
  err.status = status;
  return err;
}
module.exports = { errorHandler, createError };
JAVASCRIPT
// controllers/bookController.js
const Book = require("../models/bookModel");
const { createError } = require("../middleware/errorHandler");
function getAllBooks(req, res) {
  res.json({ success: true, data: Book.findAll() });
}
function getBookById(req, res, next) {
  const book = Book.findById(Number(req.params.id));
  if (!book) return next(createError(404, "Book not found"));
  res.json({ success: true, data: book });
}
function createBook(req, res) {
  const book = Book.create(req.body);
  res.status(201).json({ success: true, data: book });
}
function updateBook(req, res, next) {
  const book = Book.update(Number(req.params.id), req.body);
  if (!book) return next(createError(404, "Book not found"));
  res.json({ success: true, data: book });
}
function deleteBook(req, res, next) {
  const removed = Book.remove(Number(req.params.id));
  if (!removed) return next(createError(404, "Book not found"));
  res.json({ success: true, data: { message: "Book deleted" } });
}
module.exports = { getAllBooks, getBookById, createBook, updateBook, deleteBook };
JAVASCRIPT
// routes/books.js
const express = require("express");
const router = express.Router();
const ctrl = require("../controllers/bookController");
const { validateBook } = require("../middleware/validate");
router.get("/", ctrl.getAllBooks);
router.get("/:id", ctrl.getBookById);
router.post("/", validateBook, ctrl.createBook);
router.put("/:id", validateBook, ctrl.updateBook);
router.delete("/:id", ctrl.deleteBook);
module.exports = router;
JAVASCRIPT
// app.js
const express = require("express");
const booksRoute = require("./routes/books");
const { errorHandler } = require("./middleware/errorHandler");
const app = express();
app.use(express.json());
app.use("/api/books", booksRoute);
app.use(errorHandler);
app.listen(3000, () => console.log("Server running on port 3000"));
BASH
# Start and Test
node app.js
curl -X POST -H "Content-Type: application/json" \
  -d '{"title":"Clean Code","author":"Robert","year":2008}' \
  localhost:3000/api/books
curl localhost:3000/api/books/1
curl -X DELETE localhost:3000/api/books/1

❓ FAQ

Q Why are controllers and routes separated?
A Separation of concerns—routes define path mappings, while controllers focus on business logic. By decoupling the two, they can be modified and tested independently.
Q How do I test an API?
A Use curl on the command line, Postman or the Thunder Client plugin for VS Code for a graphical interface, and the supertest library for automated testing.
Q At which layer should data validation take place?
A It should be placed in the middleware layer to intercept invalid data before the request reaches the controller, ensuring that the controller's logic remains clean.
Q How should resources that cannot be found be handled?
A Return a 404 status code along with the standardized error format { success: false, error: { code: 404, message: "Book not found" } }.
Q Should I use TypeScript for my project?
A JavaScript is sufficient for small practice projects; for large production projects, TypeScript is recommended, as it provides type safety and better IDE support.
Q Will data stored in memory be lost after a restart?
A Yes, arrays are stored in process memory, so the data is cleared when the service restarts. In a production environment, you should use a database (MongoDB, PostgreSQL, etc.).
Q What is the difference between PUT and PATCH?
A PUT requires providing the entire resource for a full replacement, while PATCH only sends the fields that need to be modified for a partial update. In this lesson, we’ll use PUT for simplicity.

📖 Summary


📝 Exercises

  1. Complete all the code examples in this lesson and make sure each one runs correctly.
  2. Modify the comprehensive example and add your own extensions
  3. Review the official documentation, identify 1–2 APIs not covered in this lesson, and write test code for them.
  4. Reflection: How would you apply what you’ve learned in this lesson to a real-world project?
  5. Try to combine what you’ve learned in this lesson with material from previous lessons to build a small project.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏