Advanced RESTful API Development with Node.js and Express.js: Real-World Example

Introduction

In our previous guide, Building a RESTful API using Node.js and Express.js, we explored the fundamentals of setting up a REST API. We covered basic routing, middleware, and CRUD operations.

But real-world applications go beyond the basics. They require secure authentication, data validation, error handling, and deployment readiness.

In this continuation, we’ll walk through building a Task Management API using Node.js, Express.js, MongoDB, and JWT authentication. By the end, you’ll have a production-ready API that can be extended into enterprise projects.

If you’re a business looking for Custom API Development, this tutorial will give you insights into how professional teams approach scalability and security.

Setting Up a Real-World Project

Let’s create a Task Management API where users can:

  • Register & log in
  • Create, read, update, and delete tasks
  • Protect endpoints with authentication

Install Dependencies

mkdir task-api && cd task-api
npm init -y
npm install express mongoose dotenv bcryptjs jsonwebtoken cors morgan
npm install --save-dev nodemon

Project Structure

task-api/
│── config/
│   └── db.js
│── models/
│   ├── User.js
│   └── Task.js
│── routes/
│   ├── authRoutes.js
│   └── taskRoutes.js
│── middleware/
│   ├── authMiddleware.js
│   └── errorMiddleware.js
│── server.js
│── .env

Connecting to MongoDB

Use Mongoose for defining schemas.

config/db.js

const mongoose = require("mongoose");

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGO_URI);
    console.log("MongoDB Connected...");
  } catch (err) {
    console.error(err.message);
    process.exit(1);
  }
};

module.exports = connectDB;

Add your MongoDB URI in .env:

MONGO_URI=mongodb+srv://username:password@cluster.mongodb.net/taskdb
JWT_SECRET=supersecretkey

User and Task Models

models/User.js

const mongoose = require("mongoose");

const userSchema = new mongoose.Schema({
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true }
});

module.exports = mongoose.model("User", userSchema);

models/Task.js

const mongoose = require("mongoose");

const taskSchema = new mongoose.Schema({
  title: { type: String, required: true },
  completed: { type: Boolean, default: false },
  user: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }
}, { timestamps: true });

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

Implementing Authentication (JWT)

We’ll secure our API with JWT tokens.

routes/authRoutes.js

const express = require("express");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const User = require("../models/User");

const router = express.Router();

// Register
router.post("/register", async (req, res) => {
  const { name, email, password } = req.body;
  const userExists = await User.findOne({ email });
  if (userExists) return res.status(400).json({ message: "User already exists" });

  const hashedPassword = await bcrypt.hash(password, 10);
  const user = await User.create({ name, email, password: hashedPassword });

  res.status(201).json({ message: "User registered successfully" });
});

// Login
router.post("/login", async (req, res) => {
  const { email, password } = req.body;
  const user = await User.findOne({ email });

  if (!user || !(await bcrypt.compare(password, user.password))) {
    return res.status(401).json({ message: "Invalid credentials" });
  }

  const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: "1d" });
  res.json({ token });
});

module.exports = router;

Middleware for Authentication & Errors

middleware/authMiddleware.js

const jwt = require("jsonwebtoken");
const User = require("../models/User");

const protect = async (req, res, next) => {
  let token = req.headers.authorization?.split(" ")[1];
  if (!token) return res.status(401).json({ message: "Not authorized" });

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = await User.findById(decoded.id).select("-password");
    next();
  } catch (err) {
    res.status(401).json({ message: "Invalid token" });
  }
};

module.exports = protect;

middleware/errorMiddleware.js

const errorHandler = (err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ message: err.message });
};

module.exports = errorHandler;

CRUD Operations for Tasks

routes/taskRoutes.js

const express = require("express");
const Task = require("../models/Task");
const protect = require("../middleware/authMiddleware");

const router = express.Router();

// Create Task
router.post("/", protect, async (req, res) => {
  const task = await Task.create({ ...req.body, user: req.user._id });
  res.status(201).json(task);
});

// Get Tasks
router.get("/", protect, async (req, res) => {
  const tasks = await Task.find({ user: req.user._id });
  res.json(tasks);
});

// Update Task
router.put("/:id", protect, async (req, res) => {
  const task = await Task.findOneAndUpdate(
    { _id: req.params.id, user: req.user._id },
    req.body,
    { new: true }
  );
  res.json(task);
});

// Delete Task
router.delete("/:id", protect, async (req, res) => {
  await Task.findOneAndDelete({ _id: req.params.id, user: req.user._id });
  res.json({ message: "Task deleted" });
});

module.exports = router;

Server Entry Point

server.js

const express = require("express");
const dotenv = require("dotenv");
const connectDB = require("./config/db");
const authRoutes = require("./routes/authRoutes");
const taskRoutes = require("./routes/taskRoutes");
const errorHandler = require("./middleware/errorMiddleware");
const morgan = require("morgan");

dotenv.config();
connectDB();

const app = express();
app.use(express.json());
app.use(morgan("dev"));

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

app.use(errorHandler);

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

Testing the API

  • Use Postman to send requests.
  • Example flow:
    1. POST /api/auth/register → Create user
    2. POST /api/auth/login → Get token
    3. POST /api/tasks → Create task with token in headers
    4. GET /api/tasks → Fetch tasks

Deploying the API

  • Push code to GitHub.
  • Deploy on Render or Heroku.
  • Set environment variables (MONGO_URI, JWT_SECRET).
  • Use Web Development Services from API Pilot if you want a scalable production setup.

Conclusion

We’ve extended the basics from Part 1 into a production-grade REST API with:

  • JWT authentication
  • Secure CRUD operations
  • Error handling and middleware
  • Database integration with MongoDB
  • Deployment readiness

This is the same foundation our team uses in Custom Software Development projects to build scalable, secure applications.

Get Started Today

Your business deserves software that works exactly the way you do. Partner with API Pilot for custom software application development that’s built to last.

Contact us today for a free consultation and get one step closer to your next big innovation.

Thank you for your message. It has been sent.
There was an error trying to send your message. Please try again later.