Skip to main content
Express.js Tutorial
CHAPTER 10 Beginner

Building CRUD Applications with Express.js

Updated: May 14, 2026
35 min read

# CHAPTER 10

Building CRUD Applications with Express.js

1. Introduction

Up to this point, we have learned the individual puzzle pieces of Express: routing, handling JSON payloads, URL parameters, and middleware. In this chapter, we will synthesize everything. We will build a complete CRUD (Create, Read, Update, Delete) API. To adhere to professional standards, we will move our logic out of the main server file and introduce the MVC (Model-View-Controller) pattern to our Node.js backend.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Understand how to structure an Express app using Controllers and modular Routers.
  • Build the 5 standard REST endpoints for a resource.
  • Simulate a database using an in-memory JavaScript array.
  • Implement full CRUD operations handling JSON requests and responses.

3. Beginner-Friendly Explanation

If you put all your clothes, kitchen utensils, and power tools in one giant box in your living room, you would never find anything. This is what an index.js file looks like when you write 50 routes containing 500 lines of logic. To fix this, we build "Rooms."
  • Routes (routes/bookRoutes.js): The hallway signs. "Go to the kitchen."
  • Controllers (controllers/bookController.js): The actual rooms where the work happens.
We will strip all the heavy logic out of the routes and move it into a dedicated Controller file, making our codebase organized, modular, and easy to maintain.

4. Project Setup & Simulated Database

For this chapter, we will build an API to manage Books. Because we haven't connected a real database yet (that is the next chapter), we will use an array of objects to act as our temporary "database."

5. Step 1: The Controller (The Logic)

Create a new folder called controllers and a file named bookController.js. Here, we write functions for all 5 CRUD actions and export them.
javascript
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
// Simulated Database
let books = [
    { id: 1, title: '1984', author: 'George Orwell' },
    { id: 2, title: 'Dune', author: 'Frank Herbert' }
];

// READ ALL (GET /api/books)
const getBooks = (req, res) => {
    res.status(200).json({ status: 'success', data: books });
};

// READ ONE (GET /api/books/:id)
const getBookById = (req, res) => {
    const bookId = parseInt(req.params.id);
    const book = books.find(b => b.id === bookId);

    if (!book) return res.status(404).json({ error: 'Book not found' });
    res.status(200).json({ status: 'success', data: book });
};

// CREATE (POST /api/books)
const createBook = (req, res) => {
    const { title, author } = req.body;
    if (!title || !author) {
        return res.status(400).json({ error: 'Title and author required' });
    }

    const newBook = { id: books.length + 1, title, author };
    books.push(newBook);
    
    res.status(201).json({ status: 'success', data: newBook });
};

// UPDATE (PUT /api/books/:id)
const updateBook = (req, res) => {
    const bookId = parseInt(req.params.id);
    const book = books.find(b => b.id === bookId);

    if (!book) return res.status(404).json({ error: 'Book not found' });

    // Update fields if provided in req.body
    book.title = req.body.title || book.title;
    book.author = req.body.author || book.author;

    res.status(200).json({ status: 'success', data: book });
};

// DELETE (DELETE /api/books/:id)
const deleteBook = (req, res) => {
    const bookId = parseInt(req.params.id);
    books = books.filter(b => b.id !== bookId); // Remove the book

    // 204 means "No Content" (Successful deletion, nothing to return)
    res.status(204).send(); 
};

// Export all the functions
module.exports = { getBooks, getBookById, createBook, updateBook, deleteBook };

6. Step 2: The Router (The Hallway Signs)

Create a new folder called routes and a file named bookRoutes.js. Notice how incredibly clean this file is! It just maps a URL to a specific Controller function.
javascript
1234567891011121314
const express = require('express');
const router = express.Router(); // Create a modular router

// Import the Controller functions
const bookController = require('../controllers/bookController');

// Define the standard REST endpoints
router.get('/', bookController.getBooks);
router.post('/', bookController.createBook);
router.get('/:id', bookController.getBookById);
router.put('/:id', bookController.updateBook);
router.delete('/:id', bookController.deleteBook);

module.exports = router; // Export the router

7. Step 3: Wiring it to index.js

Finally, connect the modular router to your main server file.
javascript
123456789101112131415
const express = require('express');
const app = express();

// Import the Router file
const bookRoutes = require('./routes/bookRoutes');

// Essential Middleware!
app.use(express.json());

// Tell Express to use the bookRoutes for any URL starting with /api/books
app.use('/api/books', bookRoutes);

app.listen(3000, () => {
    console.log("Server running on port 3000");
});

8. Backend Workflow: Reviewing the Flow

Let's trace a complete request:
  1. 1. Mobile App sends POST /api/books with JSON data.
  1. 2. The Request hits index.js.
  1. 3. express.json() middleware parses the data into req.body.
  1. 4. index.js sees the URL starts with /api/books, so it hands traffic control to bookRoutes.
  1. 5. bookRoutes sees it is a POST request to /, so it triggers bookController.createBook.
  1. 6. The Controller function extracts the data, validates it, adds it to the array, and returns a 201 Status Code with the new JSON object.

9. Best Practices

  • Consistent File Structure: Always separate your concerns. index.js configures the server. routes/ defines the URLs. controllers/ contains the logic. This architecture is universally recognized by professional Node developers and makes scaling massive applications possible.

10. Common Mistakes

  • parseInt vs String: In the URL /api/books/1, the 1 is captured in req.params.id as a String ("1"). In our array, the ID is an Integer (1). If you try to find a match using strict equality (===) without converting the string to an integer using parseInt(), JavaScript will fail to find the book.

11. Exercises

  1. 1. Explain the architectural advantages of moving business logic out of the Router files and into dedicated Controller files.

12. Coding Challenges

  • Challenge: Using Postman, craft the JSON payload required to test the updateBook (PUT) route. What URL do you use? What body format do you select? What HTTP method do you select?

13. MCQs with Answers

Question 1

When building a RESTful CRUD API, which HTTP Status Code is the industry standard response for a successful DELETE operation where no JSON data needs to be returned?

Question 2

In a well-structured MVC Node.js API, what is the primary responsibility of a Controller file?

14. Interview Questions

  • Q: Explain the Separation of Concerns in an Express.js API regarding the Router layer and the Controller layer.
  • Q: Walk me through the 5 standard RESTful endpoints for a resource named "articles", including the HTTP verb and the URL path for each.

15. FAQs

Q: My array resets to 2 books every time I save my code! Why? A: Because Nodemon restarts the server every time you save a file. Variables in memory (our simulated database array) are completely erased and re-initialized on every restart. To save data permanently across server reboots, we need a real database!

16. Summary

In Chapter 10, we built a fully functional API. By adhering to REST principles, we implemented the 5 core CRUD operations (Read All, Read One, Create, Update, Delete). More importantly, we adopted professional architectural standards by extracting our business logic into dedicated Controller files and managing traffic via modular Routers, paving the way for massive, scalable applications.

17. Next Chapter Recommendation

Our application is perfectly structured, but our data disappears when the server restarts. It's time to persist our data permanently. Proceed to Chapter 11: Connecting Express.js with MongoDB.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·