Skip to main content
Node.js APIs Tutorial
CHAPTER 17 Beginner

Building a Complete REST API Project

Updated: May 14, 2026
45 min read

# CHAPTER 17

Building a Complete REST API Project

1. Introduction

Theoretical knowledge is useless without practical application. In this chapter, we will synthesize everything we have learned—Express Routing, Mongoose Models, Controllers, JWT Authentication, and Security Middleware—to architect a real-world project: a multi-user Task Management API. This project mimics the exact technical requirements you will face in a professional backend engineering role.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Architect a complete Node.js directory structure (MVC).
  • Design related Mongoose schemas (User and Task).
  • Implement an end-to-end authentication flow.
  • Restrict data access so users can only view their own tasks.

3. Project Overview: The Task Manager API

Requirements:
  • Users must be able to register and log in to receive a JWT.
  • Logged-in users can Create, Read, Update, and Delete (CRUD) tasks.
  • A task has a title, a description, and a status (completed or pending).
  • Crucial Security Rule: User A cannot view, edit, or delete User B's tasks.

4. Step 1: The Project Architecture

A professional Node.js API separates concerns. Create this folder structure:
text
12345678
/my-api
  /config      (db.js)
  /controllers (authController.js, taskController.js)
  /middleware  (auth.js)
  /models      (User.js, Task.js)
  /routes      (authRoutes.js, taskRoutes.js)
  app.js
  .env

5. Step 2: The Mongoose Models

models/Task.js Notice how we link the Task to a specific User using ObjectId.
javascript
1234567891011
const mongoose = require('mongoose');

const taskSchema = new mongoose.Schema({
    // Establish the relationship: This task belongs to a specific User
    user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
    title: { type: String, required: true },
    description: { type: String },
    completed: { type: Boolean, default: false }
}, { timestamps: true });

module.exports = mongoose.model('Task', taskSchema);

*(Assume models/User.js exists as created in Chapter 11).*

6. Step 3: The Task Controller (Business Logic)

Here we enforce the security rules.

controllers/taskController.js

javascript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
const Task = require('../models/Task');

// CREATE TASK
exports.createTask = async (req, res) => {
    try {
        // We do NOT trust the user to send their ID in req.body. 
        // We pull it securely from the JWT attached by the Auth middleware!
        const newTask = new Task({
            user: req.user.id, 
            title: req.body.title,
            description: req.body.description
        });

        await newTask.save();
        res.status(201).json({ status: "success", data: newTask });
    } catch (err) {
        res.status(400).json({ error: err.message });
    }
};

// GET ALL TASKS FOR THE LOGGED IN USER
exports.getTasks = async (req, res) => {
    try {
        // Query MongoDB to find ONLY tasks matching the token's User ID
        const tasks = await Task.find({ user: req.user.id });
        res.status(200).json({ status: "success", data: tasks });
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
};

// DELETE TASK
exports.deleteTask = async (req, res) => {
    try {
        const task = await Task.findById(req.params.id);
        if (!task) return res.status(404).json({ error: "Task not found" });

        // SECURITY CHECK: Does the logged-in user own this specific task?
        if (task.user.toString() !== req.user.id) {
            return res.status(401).json({ error: "Unauthorized access" });
        }

        await task.deleteOne();
        res.status(200).json({ message: "Task deleted successfully" });
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
};

7. Step 4: The Routes

We map the URLs to the controller functions, and inject the verifyToken middleware.

routes/taskRoutes.js

javascript
12345678910111213
const express = require('express');
const router = express.Router();
const taskController = require('../controllers/taskController');
const verifyToken = require('../middleware/auth'); // From Chapter 12

// Apply the JWT middleware to all routes in this file
router.use(verifyToken); 

router.post('/', taskController.createTask);
router.get('/', taskController.getTasks);
router.delete('/:id', taskController.deleteTask);

module.exports = router;

8. Step 5: Wiring the Server

Bring it all together securely.

app.js

javascript
1234567891011121314151617181920212223242526
require('dotenv').config();
const express = require('express');
const connectDB = require('./config/db');
const helmet = require('helmet');
const taskRoutes = require('./routes/taskRoutes');
const authRoutes = require('./routes/authRoutes');

const app = express();

// Security and Parsers
app.use(helmet());
app.use(express.json());

// Routes
app.use('/api/auth', authRoutes);
app.use('/api/tasks', taskRoutes);

// Global Error Handler
app.use((err, req, res, next) => {
    res.status(500).json({ error: "Server Error" });
});

// Boot the server
connectDB().then(() => {
    app.listen(process.env.PORT || 3000, () => console.log("API Running"));
});

9. Reviewing the Architecture

Look closely at the createTask controller method. We did not write user: req.body.userId. If we did, a hacker could change the JSON payload to userId: 5 and create tasks on someone else's account! Instead, we ignored the JSON body entirely for the ID. We forcefully grabbed the ID securely from req.user.id (which was decrypted from the unforgeable JWT token). This is professional backend development.

10. Summary

You just built a multi-user Software-as-a-Service (SaaS) backend!
  1. 1. Models established the relational constraints between Users and Tasks.
  1. 2. Auth Middleware protected the routes and identified the user.
  1. 3. Controllers handled the database queries and enforced strict ownership authorization.
  1. 4. Express Routers orchestrated the traffic elegantly.
You now have a portfolio-ready API that can power a React website or an iOS app.

11. Next Chapter Recommendation

Your application is brilliant, but it only exists on your laptop. It's time to show it to the world. Proceed to Chapter 18: Deploying Node.js APIs.

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: ·