Building a scalable REST API is crucial for modern web applications, whether you're developing a SaaS product, an e-commerce platform, or a mobile backend. Node.js with Express.js provides a lightweight and efficient way to create APIs that handle authentication, error management, and best practices.
In this guide, we'll cover:
✅ Project setup
✅ Routing and controllers
✅ Authentication with JWT
✅ Error handling
✅ Best practices for scalability
Let’s dive in! 🚀
- Setting Up Your Node.js Project
A. Install Node.js and Create a Project
First, initialize a new Node.js project:
mkdir scalable-api && cd scalable-api
npm init -y
Then, install Express.js and other essential packages:
npm install express dotenv cors helmet mongoose jsonwebtoken bcryptjs
📌 Package breakdown:
express → API framework
dotenv → Loads environment variables
cors → Enables Cross-Origin Resource Sharing
helmet → Adds security headers
mongoose → Connects to MongoDB
jsonwebtoken → Manages authentication
bcryptjs → Hashes passwords
- Creating the Express Server
A. Setting Up the server.js File
Create a server.js file in your project root and add the following:
require("dotenv").config();
const express = require("express");
const cors = require("cors");
const helmet = require("helmet");
const app = express();
// Middleware
app.use(express.json()); // Parse JSON requests
app.use(cors()); // Enable CORS
app.use(helmet()); // Security headers
// Routes
app.get("/", (req, res) => {
res.send("Welcome to the API");
});
// Start Server
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Run the server with:
node server.js
Your API is now running at http://localhost:5000 🎉
- Structuring Your API for Scalability
A well-structured API should follow the MVC (Model-View-Controller) pattern:
📂 Project Structure:
/scalable-api
│── /controllers
│ ├── authController.js
│ ├── userController.js
│── /models
│ ├── User.js
│── /routes
│ ├── authRoutes.js
│ ├── userRoutes.js
│── /middleware
│ ├── authMiddleware.js
│── server.js
│── .env
│── package.json
- Setting Up MongoDB with Mongoose
A. Connecting to MongoDB
Create a .env file for your database connection string:
MONGO_URI=mongodb+srv://yourUser:yourPassword@cluster.mongodb.net/yourDB?retryWrites=true&w=majority
JWT_SECRET=supersecretkey
Then, create a db.js file to establish a database connection:
const mongoose = require("mongoose");
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true });
console.log("MongoDB connected successfully");
} catch (error) {
console.error("Database connection failed", error);
process.exit(1);
}
};
module.exports = connectDB;
Now, import and call this function in server.js:
const connectDB = require("./db");
connectDB();
- Creating Authentication (JWT-Based)
A. Creating the User Model (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 },
}, { timestamps: true });
module.exports = mongoose.model("User", UserSchema);
B. Implementing Authentication (authController.js)
const User = require("../models/User");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
exports.register = async (req, res) => {
try {
const { name, email, password } = req.body;
let user = await User.findOne({ email });
if (user) return res.status(400).json({ message: "User already exists" });
const hashedPassword = await bcrypt.hash(password, 10);
user = new User({ name, email, password: hashedPassword });
await user.save();
res.status(201).json({ message: "User registered successfully" });
} catch (error) {
res.status(500).json({ message: "Server error" });
}
};
exports.login = async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user) return res.status(400).json({ message: "Invalid credentials" });
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) return res.status(400).json({ message: "Invalid credentials" });
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: "1h" });
res.json({ token });
} catch (error) {
res.status(500).json({ message: "Server error" });
}
};
C. Setting Up Routes (authRoutes.js)
const express = require("express");
const { register, login } = require("../controllers/authController");
const router = express.Router();
router.post("/register", register);
router.post("/login", login);
module.exports = router;
Now, import the routes in server.js:
const authRoutes = require("./routes/authRoutes");
app.use("/api/auth", authRoutes);
- Implementing Authentication Middleware
To protect routes, create authMiddleware.js:
const jwt = require("jsonwebtoken");
module.exports = (req, res, next) => {
const token = req.header("Authorization");
if (!token) return res.status(401).json({ message: "Access denied" });
try {
const verified = jwt.verify(token, process.env.JWT_SECRET);
req.user = verified;
next();
} catch (error) {
res.status(400).json({ message: "Invalid token" });
}
};
Apply this middleware to protected routes:
const authMiddleware = require("../middleware/authMiddleware");
router.get("/profile", authMiddleware, async (req, res) => {
const user = await User.findById(req.user.id).select("-password");
res.json(user);
});
- Error Handling & Best Practices
A. Centralized Error Handling
Create errorHandler.js:
module.exports = (err, req, res, next) => {
res.status(err.status || 500).json({ message: err.message || "Server error" });
};
Import and use it in server.js:
const errorHandler = require("./middleware/errorHandler");
app.use(errorHandler);
B. Best Practices for Scalability
✅ Use environment variables (dotenv)
✅ Modularize routes, controllers, and middleware
✅ Use caching (Redis) for performance
✅ Optimize database queries (Indexes, Pagination)
✅ Enable logging (Winston, Morgan)
Final Thoughts
By following this guide, you’ve built a scalable REST API with authentication, structured routes, and best practices using Node.js and Express. 🚀
I am open to collaboration on projects and work. Let's transform ideas into digital reality.
Top comments (0)