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:
POST /api/auth/register
→ Create userPOST /api/auth/login
→ Get tokenPOST /api/tasks
→ Create task with token in headersGET /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
Contact us today for a free consultation and get one step closer to your next big innovation.