Node.js & Express - Building Scalable Backend Applications
Node.js has revolutionized server-side JavaScript development, and Express.js makes it incredibly easy to build powerful web applications and APIs. Let's dive into building a production-ready backend application!
๐ Why Node.js & Express?โ
Node.js and Express dominate the backend JavaScript ecosystem:
- โก Non-blocking I/O: Handle thousands of concurrent connections
- ๐ JavaScript Everywhere: Use the same language for frontend and backend
- ๐ฆ NPM Ecosystem: Access to 2+ million packages
- ๐ Fast Development: Build and deploy quickly
- ๐ช Scalable: Perfect for microservices and real-time applications
- ๐ฅ Active Community: Massive community support and resources
When to Use Node.jsโ
โ Perfect for:
- Real-time applications (chat, gaming, collaboration)
- RESTful APIs and microservices
- Single-page applications
- Streaming applications
- IoT applications
โ Not ideal for:
- CPU-intensive operations
- Heavy computational tasks
๐ฆ Getting Startedโ
Prerequisitesโ
# Check Node.js version (should be 18+)
node --version
# Check npm version
npm --version
# If not installed, download from nodejs.org
Project Setupโ
# Create project directory
mkdir express-ecommerce-api
cd express-ecommerce-api
# Initialize npm project
npm init -y
# Install dependencies
npm install express mongoose dotenv cors helmet morgan
npm install --save-dev nodemon
# Install development tools
npm install --save-dev @types/node @types/express
๐๏ธ Project Structureโ
Create a professional, scalable structure:
express-ecommerce-api/
โโโ src/
โ โโโ config/
โ โ โโโ database.js
โ โโโ controllers/
โ โ โโโ productController.js
โ โ โโโ orderController.js
โ โโโ models/
โ โ โโโ Product.js
โ โ โโโ Order.js
โ โโโ routes/
โ โ โโโ productRoutes.js
โ โ โโโ orderRoutes.js
โ โโโ middleware/
โ โ โโโ errorHandler.js
โ โ โโโ validateRequest.js
โ โโโ utils/
โ โ โโโ logger.js
โ โโโ app.js
โโโ .env
โโโ .gitignore
โโโ package.json
โโโ server.js
๐ฏ Building an E-Commerce APIโ
Let's build a complete e-commerce API with products and orders.
Step 1: Environment Configurationโ
Create .env:
# Server Configuration
PORT=5000
NODE_ENV=development
# Database
MONGODB_URI=mongodb://localhost:27017/ecommerce
# Or use MongoDB Atlas:
# MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/ecommerce
# JWT Secret
JWT_SECRET=your_super_secret_key_change_in_production
# API Configuration
API_VERSION=v1
Create .gitignore:
node_modules/
.env
*.log
dist/
build/
Step 2: Database Configurationโ
Create src/config/database.js:
const mongoose = require('mongoose');
const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log(`โ
MongoDB Connected: ${conn.connection.host}`);
} catch (error) {
console.error(`โ Error: ${error.message}`);
process.exit(1);
}
};
module.exports = connectDB;
Step 3: Create Modelsโ
Create src/models/Product.js:
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema(
{
name: {
type: String,
required: [true, 'Product name is required'],
trim: true,
maxlength: [100, 'Product name cannot exceed 100 characters'],
},
description: {
type: String,
required: [true, 'Product description is required'],
maxlength: [2000, 'Description cannot exceed 2000 characters'],
},
price: {
type: Number,
required: [true, 'Product price is required'],
min: [0, 'Price cannot be negative'],
},
category: {
type: String,
required: [true, 'Product category is required'],
enum: ['Electronics', 'Clothing', 'Books', 'Home', 'Sports', 'Other'],
},
stock: {
type: Number,
required: true,
min: [0, 'Stock cannot be negative'],
default: 0,
},
images: [{
type: String,
}],
rating: {
type: Number,
default: 0,
min: 0,
max: 5,
},
numReviews: {
type: Number,
default: 0,
},
featured: {
type: Boolean,
default: false,
},
},
{
timestamps: true,
}
);
// Index for better search performance
productSchema.index({ name: 'text', description: 'text' });
module.exports = mongoose.model('Product', productSchema);
Create src/models/Order.js:
const mongoose = require('mongoose');
const orderSchema = new mongoose.Schema(
{
orderNumber: {
type: String,
required: true,
unique: true,
},
customer: {
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
phone: {
type: String,
required: true,
},
},
items: [{
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product',
required: true,
},
quantity: {
type: Number,
required: true,
min: 1,
},
price: {
type: Number,
required: true,
},
}],
shippingAddress: {
street: String,
city: String,
state: String,
zipCode: String,
country: String,
},
totalAmount: {
type: Number,
required: true,
},
status: {
type: String,
enum: ['pending', 'processing', 'shipped', 'delivered', 'cancelled'],
default: 'pending',
},
paymentStatus: {
type: String,
enum: ['pending', 'completed', 'failed'],
default: 'pending',
},
},
{
timestamps: true,
}
);
// Generate order number before saving
orderSchema.pre('save', async function(next) {
if (!this.orderNumber) {
this.orderNumber = `ORD-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
}
next();
});
module.exports = mongoose.model('Order', orderSchema);
Step 4: Create Controllersโ
Create src/controllers/productController.js:
const Product = require('../models/Product');
// @desc Get all products
// @route GET /api/v1/products
// @access Public
exports.getProducts = async (req, res, next) => {
try {
const { page = 1, limit = 10, category, search, sort } = req.query;
// Build query
const query = {};
if (category) query.category = category;
if (search) query.$text = { $search: search };
// Build sort
let sortOption = {};
if (sort === 'price_asc') sortOption.price = 1;
else if (sort === 'price_desc') sortOption.price = -1;
else if (sort === 'rating') sortOption.rating = -1;
else sortOption.createdAt = -1;
// Execute query with pagination
const products = await Product.find(query)
.sort(sortOption)
.limit(limit * 1)
.skip((page - 1) * limit)
.exec();
const count = await Product.countDocuments(query);
res.status(200).json({
success: true,
count: products.length,
total: count,
page: Number(page),
pages: Math.ceil(count / limit),
data: products,
});
} catch (error) {
next(error);
}
};
// @desc Get single product
// @route GET /api/v1/products/:id
// @access Public
exports.getProduct = async (req, res, next) => {
try {
const product = await Product.findById(req.params.id);
if (!product) {
return res.status(404).json({
success: false,
message: 'Product not found',
});
}
res.status(200).json({
success: true,
data: product,
});
} catch (error) {
next(error);
}
};
// @desc Create product
// @route POST /api/v1/products
// @access Private/Admin
exports.createProduct = async (req, res, next) => {
try {
const product = await Product.create(req.body);
res.status(201).json({
success: true,
data: product,
});
} catch (error) {
next(error);
}
};
// @desc Update product
// @route PUT /api/v1/products/:id
// @access Private/Admin
exports.updateProduct = async (req, res, next) => {
try {
const product = await Product.findByIdAndUpdate(
req.params.id,
req.body,
{
new: true,
runValidators: true,
}
);
if (!product) {
return res.status(404).json({
success: false,
message: 'Product not found',
});
}
res.status(200).json({
success: true,
data: product,
});
} catch (error) {
next(error);
}
};
// @desc Delete product
// @route DELETE /api/v1/products/:id
// @access Private/Admin
exports.deleteProduct = async (req, res, next) => {
try {
const product = await Product.findByIdAndDelete(req.params.id);
if (!product) {
return res.status(404).json({
success: false,
message: 'Product not found',
});
}
res.status(200).json({
success: true,
message: 'Product deleted successfully',
});
} catch (error) {
next(error);
}
};
// @desc Get featured products
// @route GET /api/v1/products/featured
// @access Public
exports.getFeaturedProducts = async (req, res, next) => {
try {
const products = await Product.find({ featured: true })
.limit(10)
.sort('-rating');
res.status(200).json({
success: true,
count: products.length,
data: products,
});
} catch (error) {
next(error);
}
};
Step 5: Create Routesโ
Create src/routes/productRoutes.js:
const express = require('express');
const {
getProducts,
getProduct,
createProduct,
updateProduct,
deleteProduct,
getFeaturedProducts,
} = require('../controllers/productController');
const router = express.Router();
router.get('/featured', getFeaturedProducts);
router.route('/')
.get(getProducts)
.post(createProduct);
router.route('/:id')
.get(getProduct)
.put(updateProduct)
.delete(deleteProduct);
module.exports = router;
Step 6: Middlewareโ
Create src/middleware/errorHandler.js:
const errorHandler = (err, req, res, next) => {
let error = { ...err };
error.message = err.message;
// Log to console for dev
console.error(err);
// Mongoose bad ObjectId
if (err.name === 'CastError') {
const message = 'Resource not found';
error = { message, statusCode: 404 };
}
// Mongoose duplicate key
if (err.code === 11000) {
const message = 'Duplicate field value entered';
error = { message, statusCode: 400 };
}
// Mongoose validation error
if (err.name === 'ValidationError') {
const message = Object.values(err.errors).map(val => val.message);
error = { message, statusCode: 400 };
}
res.status(error.statusCode || 500).json({
success: false,
error: error.message || 'Server Error',
});
};
module.exports = errorHandler;
Step 7: Main Applicationโ
Create src/app.js:
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const errorHandler = require('./middleware/errorHandler');
// Import routes
const productRoutes = require('./routes/productRoutes');
const app = express();
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
app.use(helmet());
// Logging
if (process.env.NODE_ENV === 'development') {
app.use(morgan('dev'));
}
// Health check
app.get('/health', (req, res) => {
res.status(200).json({
success: true,
message: 'Server is healthy',
timestamp: new Date().toISOString(),
});
});
// API routes
app.use('/api/v1/products', productRoutes);
// Root route
app.get('/', (req, res) => {
res.json({
message: 'Welcome to E-Commerce API',
version: '1.0.0',
endpoints: {
products: '/api/v1/products',
health: '/health',
},
});
});
// Error handler (must be last)
app.use(errorHandler);
// 404 handler
app.use((req, res) => {
res.status(404).json({
success: false,
message: 'Route not found',
});
});
module.exports = app;
Create server.js:
require('dotenv').config();
const app = require('./src/app');
const connectDB = require('./src/config/database');
const PORT = process.env.PORT || 5000;
// Connect to database
connectDB();
// Start server
const server = app.listen(PORT, () => {
console.log(`๐ Server running in ${process.env.NODE_ENV} mode on port ${PORT}`);
});
// Handle unhandled promise rejections
process.on('unhandledRejection', (err) => {
console.log(`โ Error: ${err.message}`);
server.close(() => process.exit(1));
});
Update package.json:
{
"name": "express-ecommerce-api",
"version": "1.0.0",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
}
๐ Running the Applicationโ
# Development mode with auto-reload
npm run dev
# Production mode
npm start
Your API will be available at http://localhost:5000
๐งช Testing the APIโ
Create a Productโ
curl -X POST http://localhost:5000/api/v1/products \
-H "Content-Type: application/json" \
-d '{
"name": "MacBook Pro 16-inch",
"description": "Powerful laptop for developers",
"price": 2499,
"category": "Electronics",
"stock": 50,
"featured": true
}'
Get All Productsโ
# Basic request
curl http://localhost:5000/api/v1/products
# With pagination
curl "http://localhost:5000/api/v1/products?page=1&limit=10"
# Filter by category
curl "http://localhost:5000/api/v1/products?category=Electronics"
# Search products
curl "http://localhost:5000/api/v1/products?search=laptop"
# Sort by price
curl "http://localhost:5000/api/v1/products?sort=price_asc"
โก Advanced Featuresโ
1. Request Validationโ
Install validator:
npm install express-validator
Create validation middleware:
const { body, validationResult } = require('express-validator');
exports.validateProduct = [
body('name').trim().notEmpty().withMessage('Name is required'),
body('price').isNumeric().withMessage('Price must be a number'),
body('stock').isInt({ min: 0 }).withMessage('Stock must be non-negative'),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
},
];
2. Rate Limitingโ
npm install express-rate-limit
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests, please try again later',
});
app.use('/api/', limiter);
3. Async Handler Wrapperโ
const asyncHandler = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// Use it
exports.getProducts = asyncHandler(async (req, res) => {
const products = await Product.find();
res.json({ data: products });
});
4. File Uploadโ
npm install multer
const multer = require('multer');
const storage = multer.diskStorage({
destination: './uploads/',
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`);
},
});
const upload = multer({ storage });
app.post('/upload', upload.single('image'), (req, res) => {
res.json({ filename: req.file.filename });
});
๐ Security Best Practicesโ
1. Environment Variablesโ
Never commit .env files. Use different configs for different environments.
2. Input Sanitizationโ
npm install express-mongo-sanitize
const mongoSanitize = require('express-mongo-sanitize');
app.use(mongoSanitize());
3. XSS Protectionโ
npm install xss-clean
const xss = require('xss-clean');
app.use(xss());
4. HTTP Security Headersโ
Already using helmet:
app.use(helmet());
๐ Performance Optimizationโ
1. Caching with Redisโ
npm install redis
const redis = require('redis');
const client = redis.createClient();
// Cache middleware
const cache = (req, res, next) => {
const key = req.originalUrl;
client.get(key, (err, data) => {
if (data) {
return res.json(JSON.parse(data));
}
next();
});
};
2. Database Indexingโ
// In your model
productSchema.index({ name: 1, category: 1 });
3. Compressionโ
npm install compression
const compression = require('compression');
app.use(compression());
๐ Deploymentโ
Using PM2โ
npm install -g pm2
# Start app
pm2 start server.js --name "ecommerce-api"
# Monitor
pm2 monit
# Restart
pm2 restart ecommerce-api
Dockerโ
Create Dockerfile:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 5000
CMD ["node", "server.js"]
๐ Key Takeawaysโ
- Express is minimal: You have full control over your application structure
- Middleware pattern: Powerful and flexible request processing
- Async/await: Modern JavaScript for handling asynchronous operations
- Mongoose: Elegant MongoDB object modeling
- Security: Always implement security best practices
- Scalability: Node.js excels at handling concurrent connections
๐ What's Next?โ
- Add JWT authentication
- Implement WebSocket for real-time features
- Add GraphQL support
- Integrate payment gateways (Stripe, PayPal)
- Set up testing with Jest
- Deploy to AWS, Heroku, or DigitalOcean
Node.js and Express provide a solid foundation for building modern, scalable backend applications. Start building your APIs today!
Want to learn more? Check out our backend development curriculum or connect with us on Twitter!
