Node.js & Express Backend Development
Building REST APIs for Full-Stack Applications
Course lectures and practices for JavaScript full‑stack web development with AI‑assisted workflows.
Node.js & Express Backend 🚀
2-Hour Focus: APIs → Database → Production Ready
🎯 Today’s Mission
Hour 1: Express Fundamentals (server, routing, middleware, REST APIs)
Hour 2: Database Integration (MySQL, data persistence, production patterns)
Using: fullstack-minimal-app
backend foundation
Goal: Build a complete API that serves the React frontend from last session! 🎉
Philosophy: Practical backend skills, database-driven applications, production-ready code
🗺️ The Backend Journey
Hour 1: EXPRESS FOUNDATIONS Hour 2: DATABASE & PRODUCTION
┌─────────────────┐ ┌─────────────────┐
│ Why Node/Express│ │ MySQL Setup │
│ REST API Design │ → │ Data Persistence│
│ Routes & Middle │ │ Error Handling │
│ JSON Responses │ │ Production Ready│
└─────────────────┘ └─────────────────┘
API Foundation Database Ready
Hour 1: Express Foundations
0:00 - 1:00
💡 Why Node.js + Express?
The Full-Stack JavaScript Advantage
Traditional Approach
Frontend: JavaScript (React)
Backend: PHP/Python/Java/C#
Database: MySQL/PostgreSQL
Problems:
❌ Different languages
❌ Context switching
❌ Different deployment
❌ Different tools/debugging
Node.js Approach
Frontend: JavaScript (React)
Backend: JavaScript (Node.js)
Database: MySQL/PostgreSQL
Benefits:
✅ Same language everywhere
✅ Shared code/utilities
✅ Single runtime
✅ Unified tooling
Real-World Success Stories: - Netflix: Migrated from Java to Node.js → 70% reduction in startup time - Uber: Real-time matching system built on Node.js - LinkedIn: Mobile API backend switched to Node.js → 10x performance - PayPal: 2x faster development, 33% fewer lines of code
Why Express? The minimal, unopinionated web framework that powers 60%+ of Node.js web applications.
🚀 fullstack-minimal-app Backend Tour
Let’s explore our starting point
# Navigate to backend
cd fullstack-minimal-app/backend
# Explore structure
ls -la
What’s included:
backend/
├── src/
│ ├── app.js ← Express app setup
│ ├── routes/ ← API route handlers
│ │ └── products.js ← Product endpoints
│ ├── middleware/ ← Custom middleware
│ ├── config/ ← Database config
│ └── models/ ← Database models (later)
├── package.json ← Dependencies
└── server.js ← Server entry point
Start the backend:
npm install
npm run dev # Starts on http://localhost:4000
Test it works:
curl http://localhost:4000/api/products
# Should return JSON array of products
🏗️ Express App Structure
Understanding the Express foundation
server.js (Entry Point)
const app = require('./src/app');
const PORT = process.env.PORT || 4000;
.listen(PORT, () => {
appconsole.log(`✅ Server running on http://localhost:${PORT}`);
console.log(`📝 API docs: http://localhost:${PORT}/api`);
; })
app.js (Express Setup)
const express = require('express');
const cors = require('cors');
const morgan = require('morgan');
const app = express();
// Middleware
.use(cors()); // Enable CORS for React frontend
app.use(morgan('dev')); // HTTP request logging
app.use(express.json()); // Parse JSON request bodies
app.use(express.urlencoded({ extended: true })); // Parse form data
app
// Routes
.use('/api/products', require('./routes/products'));
app
// Health check endpoint
.get('/health', (req, res) => {
app.json({ status: 'ok', timestamp: new Date().toISOString });
res;
})
// Catch-all error handler
.use((err, req, res, next) => {
appconsole.error(err.stack);
.status(500).json({ error: 'Something went wrong!' });
res;
})
.exports = app; module
Key Concepts: - Middleware: Functions that process requests before reaching routes - Routes: Define API endpoints and their handlers - Error Handling: Centralized error processing
🛣️ REST API Design Principles
RESTful APIs follow predictable patterns
HTTP Methods & Status Codes
Method | Purpose | Success Status | Example |
---|---|---|---|
GET | Retrieve data | 200 OK | Get all products |
POST | Create new resource | 201 Created | Create product |
PUT | Update entire resource | 200 OK | Update product |
PATCH | Update partial resource | 200 OK | Update product name |
DELETE | Remove resource | 200 OK or 204 No Content | Delete product |
URL Patterns
// Resource collections
/api/products // Get all products
GET /api/products // Create new product
POST
// Specific resources
/api/products/:id // Get one product
GET /api/products/:id // Update product
PUT /api/products/:id // Delete product
DELETE
// Nested resources
/api/products/:id/reviews // Get product reviews
GET /api/products/:id/reviews // Add review to product POST
Response Format Standards
// Success response
{"success": true,
"data": { /* actual data */ },
"message": "Optional success message"
}
// Error response
{"success": false,
"error": "Error message",
"details": { /* optional error details */ }
}
// Collection response
{"success": true,
"data": [ /* array of items */ ],
"meta": {
"total": 150,
"page": 1,
"limit": 20
} }
📝 Building Product Routes
Let’s build the review aggregator API endpoints
routes/products.js
const express = require('express');
const router = express.Router();
// Mock data (will replace with database)
let products = [
{id: 1,
name: "Wireless Bluetooth Headphones",
description: "Premium noise-canceling headphones",
price: 199.99,
category: "Electronics"
,
}
{id: 2,
name: "Smart Fitness Tracker",
description: "Track your daily activities and health metrics",
price: 149.99,
category: "Electronics"
};
]
let reviews = []; // Will store fetched reviews
let nextId = 3;
// GET /api/products - Get all products
.get('/', (req, res) => {
routertry {
.json({
ressuccess: true,
data: products,
meta: {
total: products.length
};
})catch (error) {
} .status(500).json({
ressuccess: false,
error: 'Failed to fetch products'
;
})
};
})
// GET /api/products/:id - Get specific product
.get('/:id', (req, res) => {
routertry {
const id = parseInt(req.params.id);
const product = products.find(p => p.id === id);
if (!product) {
return res.status(404).json({
success: false,
error: 'Product not found'
;
})
}
.json({
ressuccess: true,
data: product
;
})catch (error) {
} .status(500).json({
ressuccess: false,
error: 'Failed to fetch product'
;
})
};
})
// POST /api/products - Create new product
.post('/', (req, res) => {
routertry {
const { name, description, price, category } = req.body;
// Validation
if (!name || !price) {
return res.status(400).json({
success: false,
error: 'Name and price are required'
;
})
}
const newProduct = {
id: nextId++,
,
namedescription: description || '',
price: parseFloat(price),
category: category || 'Uncategorized'
;
}
.push(newProduct);
products
.status(201).json({
ressuccess: true,
data: newProduct,
message: 'Product created successfully'
;
})catch (error) {
} .status(500).json({
ressuccess: false,
error: 'Failed to create product'
;
})
};
})
.exports = router; module
🤖 AI Practice: Review Endpoints
Prompt for AI:
Context: Express.js REST API for product review aggregator
Task: Add review endpoints to existing products router
Endpoints needed:
1. POST /api/products/:id/fetch - Simulate fetching reviews from external scraper
2. GET /api/products/:id/reviews - Get all reviews for a product
3. GET /api/products/:id/aggregate - Get review statistics
Mock data structure for reviews:
{
id: number,
productId: number,
source: "Amazon" | "BestBuy" | "Walmart",
author: string,
rating: number (1-5),
title: string,
content: string,
createdAt: date
}
Requirements:
- Proper error handling and status codes
- JSON response format with success/error pattern
- Input validation for POST requests
- Mock external API delay (1-2 seconds)
- Calculate aggregate stats (average rating, count by source)
Output: Complete Express router code to add to products.js
After AI generates: Test endpoints with curl or Postman
🧪 Testing API Endpoints
Let’s test our API with different tools
Using curl
# Get all products
curl http://localhost:4000/api/products
# Get specific product
curl http://localhost:4000/api/products/1
# Create new product
curl -X POST http://localhost:4000/api/products \
-H "Content-Type: application/json" \
-d '{
"name": "Gaming Keyboard",
"description": "RGB mechanical keyboard",
"price": 89.99,
"category": "Electronics"
}'
# Fetch reviews for product
curl -X POST http://localhost:4000/api/products/1/fetch
# Get reviews
curl http://localhost:4000/api/products/1/reviews
# Get review stats
curl http://localhost:4000/api/products/1/aggregate
Using VS Code REST Client
Create test.http
file:
### Get all products
GET http://localhost:4000/api/products
### Get specific product
GET http://localhost:4000/api/products/1
### Create new product
POST http://localhost:4000/api/products
Content-Type: application/json
{
"name": "Smart Watch",
"description": "Fitness tracking smartwatch",
"price": 299.99,
"category": "Electronics"
}
### Fetch reviews
POST http://localhost:4000/api/products/1/fetch
You should now have: - ✅ Express server running on port 4000 - ✅ Product CRUD endpoints working - ✅ Review endpoints (fetch, get, aggregate) - ✅ Proper error handling and JSON responses - ✅ API testing experience with curl/REST client
Hour 2 Preview: Database integration, data persistence, production patterns!
Hour 2: Database & Production
1:00 - 2:00
🗄️ Why Databases Matter
Current Problem: In-Memory Data
// Current approach (loses data on restart!)
let products = [...];
let reviews = [...];
// Problems:
❌ Data lost when server restarts
❌ No data sharing between server instances
❌ No concurrent access protection
❌ No data relationships/recovery
❌ No backup ❌ Not scalable
Database Solution: Persistent Data
-- Data survives server restarts
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
VARCHAR(255) NOT NULL,
name
description TEXT,DECIMAL(10,2) NOT NULL,
price TIMESTAMP DEFAULT CURRENT_TIMESTAMP
created_at
);
-- Benefits:
Data persistence
✅
✅ ACID transactionsaccess
✅ Concurrent Data relationships
✅ Backup/recovery
✅ ✅ Scalable queries
Why MySQL? - Industry standard: Used by Facebook, YouTube, Twitter - Mature ecosystem: 25+ years of development - Excellent performance: Optimized for web applications - Great tooling: phpMyAdmin, MySQL Workbench, CLI tools - Cloud ready: Available on AWS RDS, Google Cloud SQL, etc.
🔧 MySQL Setup & Integration
Database setup in fullstack-minimal-app
Install MySQL Dependencies
# In backend directory
npm install mysql2 dotenv
Environment Configuration
Create .env
file in backend:
# Database connection
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=your_password
DB_NAME=review_aggregator
# Server config
PORT=4000
NODE_ENV=development
Database Configuration
// config/database.js
const mysql = require('mysql2/promise');
require('dotenv').config();
const dbConfig = {
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'review_aggregator',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
;
}
const pool = mysql.createPool(dbConfig);
// Test connection
async function testConnection() {
try {
const connection = await pool.getConnection();
console.log('✅ Database connected successfully');
.release();
connectioncatch (error) {
} console.error('❌ Database connection failed:', error.message);
process.exit(1);
}
}
.exports = { pool, testConnection }; module
🏗️ Database Schema Design
Design tables for our review aggregator
SQL Schema
-- Create database
CREATE DATABASE IF NOT EXISTS review_aggregator;
USE review_aggregator;
-- Products table
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
VARCHAR(255) NOT NULL,
name
description TEXT,DECIMAL(10,2) NOT NULL,
price category VARCHAR(100) DEFAULT 'Electronics',
TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
updated_at
);
-- Reviews table
CREATE TABLE reviews (
id INT PRIMARY KEY AUTO_INCREMENT,
INT NOT NULL,
product_id source VARCHAR(50) NOT NULL,
VARCHAR(100),
external_review_id VARCHAR(255),
author INT CHECK (rating BETWEEN 1 AND 5),
rating VARCHAR(500),
title
content TEXT,
review_date DATETIME,TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
fetched_at FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,
UNIQUE KEY unique_review (product_id, source, external_review_id)
);
-- Insert sample data
INSERT INTO products (name, description, price, category) VALUES
'Wireless Bluetooth Headphones', 'Premium noise-canceling headphones with 30-hour battery', 199.99, 'Electronics'),
('Smart Fitness Tracker', 'Advanced health and fitness monitoring device', 149.99, 'Electronics'),
('Gaming Mechanical Keyboard', 'RGB backlit mechanical keyboard for gaming', 89.99, 'Electronics'); (
Database Indexes for Performance
-- Optimize common queries
CREATE INDEX idx_reviews_product_id ON reviews(product_id);
CREATE INDEX idx_reviews_source ON reviews(source);
CREATE INDEX idx_reviews_rating ON reviews(rating);
CREATE INDEX idx_products_category ON products(category);
🔗 Database Integration in Express
Replace in-memory data with MySQL queries
Updated Product Routes
// routes/products.js
const express = require('express');
const { pool } = require('../config/database');
const router = express.Router();
// GET /api/products - Get all products
.get('/', async (req, res) => {
routertry {
const [rows] = await pool.execute(
'SELECT * FROM products ORDER BY created_at DESC'
;
)
.json({
ressuccess: true,
data: rows,
meta: {
total: rows.length
};
})catch (error) {
} console.error('Database error:', error);
.status(500).json({
ressuccess: false,
error: 'Failed to fetch products'
;
})
};
})
// GET /api/products/:id - Get specific product
.get('/:id', async (req, res) => {
routertry {
const [rows] = await pool.execute(
'SELECT * FROM products WHERE id = ?',
.params.id]
[req;
)
if (rows.length === 0) {
return res.status(404).json({
success: false,
error: 'Product not found'
;
})
}
.json({
ressuccess: true,
data: rows[0]
;
})catch (error) {
} console.error('Database error:', error);
.status(500).json({
ressuccess: false,
error: 'Failed to fetch product'
;
})
};
})
// POST /api/products - Create new product
.post('/', async (req, res) => {
routertry {
const { name, description, price, category } = req.body;
// Validation
if (!name || !price) {
return res.status(400).json({
success: false,
error: 'Name and price are required'
;
})
}
const [result] = await pool.execute(
'INSERT INTO products (name, description, price, category) VALUES (?, ?, ?, ?)',
, description || '', parseFloat(price), category || 'Electronics']
[name;
)
// Get the created product
const [newProduct] = await pool.execute(
'SELECT * FROM products WHERE id = ?',
.insertId]
[result;
)
.status(201).json({
ressuccess: true,
data: newProduct[0],
message: 'Product created successfully'
;
})catch (error) {
} console.error('Database error:', error);
.status(500).json({
ressuccess: false,
error: 'Failed to create product'
;
})
}; })
🤖 AI Practice: Review Database Operations
Prompt for AI:
Context: Express.js API with MySQL database integration
Task: Create review endpoints that persist data to MySQL
Database schema:
- reviews table with columns: id, product_id, source, external_review_id, author, rating, title, content, review_date, fetched_at
- Foreign key relationship to products table
Endpoints needed:
1. POST /api/products/:id/fetch - Simulate fetching and storing reviews
2. GET /api/products/:id/reviews - Get reviews from database with pagination
3. GET /api/products/:id/aggregate - Calculate stats from database
4. DELETE /api/products/:id/reviews/:reviewId - Delete a review
Requirements:
- Use mysql2/promise with prepared statements
- Proper error handling for database operations
- Prevent duplicate reviews (UNIQUE constraint handling)
- Input validation and sanitization
- Paginated responses for GET /reviews (limit/offset)
- Aggregate calculations using SQL GROUP BY
Mock external API simulation:
- Generate 5-10 random reviews per fetch
- Different sources (Amazon, BestBuy, Walmart)
- Random ratings and realistic review content
Output: Complete Express router code with MySQL integration
Test the generated code: Create reviews, fetch them, check aggregates
🔒 Production Patterns
Making your API production-ready
Input Validation & Sanitization
const { body, param, validationResult } = require('express-validator');
// Validation middleware
const validateProduct = [
body('name')
.isString()
.isLength({ min: 1, max: 255 })
.trim()
.escape(),
body('price')
.isFloat({ min: 0 })
.toFloat(),
body('description')
.optional()
.isString()
.isLength({ max: 1000 })
.trim()
.escape(),
body('category')
.optional()
.isString()
.isLength({ max: 100 })
.trim()
;
]
const handleValidationErrors = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
error: 'Validation failed',
details: errors.array()
;
})
}next();
;
}
// Usage in routes
.post('/', validateProduct, handleValidationErrors, async (req, res) => {
router// Safe to use req.body - it's validated and sanitized
; })
Database Transaction Management
// For operations that need to be atomic
.post('/:id/fetch', async (req, res) => {
routerconst connection = await pool.getConnection();
try {
await connection.beginTransaction();
// 1. Check if product exists
const [products] = await connection.execute(
'SELECT id FROM products WHERE id = ?',
.params.id]
[req;
)
if (products.length === 0) {
throw new Error('Product not found');
}
// 2. Simulate fetching reviews from external API
const mockReviews = generateMockReviews(req.params.id);
// 3. Insert reviews (ignoring duplicates)
for (const review of mockReviews) {
await connection.execute(
`INSERT IGNORE INTO reviews
(product_id, source, external_review_id, author, rating, title, content, review_date)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
[.product_id,
review.source,
review.external_review_id,
review.author,
review.rating,
review.title,
review.content,
review.review_date
review
];
)
}
await connection.commit();
.json({
ressuccess: true,
message: `Fetched ${mockReviews.length} new reviews`,
data: { count: mockReviews.length }
;
})
catch (error) {
} await connection.rollback();
console.error('Transaction error:', error);
.status(500).json({
ressuccess: false,
error: 'Failed to fetch reviews'
;
})finally {
} .release();
connection
}; })
API Rate Limiting
const rateLimit = require('express-rate-limit');
// Apply rate limiting to expensive operations
const fetchLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // limit each IP to 10 requests per windowMs
message: {
success: false,
error: 'Too many fetch requests, please try again later'
};
})
.post('/:id/fetch', fetchLimiter, async (req, res) => {
router// Protected endpoint
; })
🏁 Session Wrap-up
What You’ve Built Today:
Backend Fundamentals ✅
- Express.js server setup
- RESTful API design
- Route handling and middleware
- JSON API responses
- Error handling patterns
Database Integration ✅
- MySQL connection and pooling
- Database schema design
- SQL queries with prepared statements
- Transaction management
- Data relationships and constraints
Production Ready ✅
- Input validation and sanitization
- Database connection management
- Error handling and logging
- Rate limiting
- Environment configuration
Real API Endpoints ✅
- Product CRUD operations
- Review fetching and storage
- Aggregate statistics calculation
- Proper HTTP status codes
Next Steps: Practice session to build the complete review aggregator backend, then integrate with React frontend!
📚 Practice Session Preview
Immediately after this lecture: 1-hour hands-on practice building your complete review aggregator backend using the patterns learned today.
What you’ll build: - Complete Express API with MySQL persistence - Review fetching and aggregation endpoints - Production-ready error handling and validation - Database schema and sample data - API testing and documentation
This connects to: Previous session’s React frontend - you’ll have a working full-stack application!
🎉 You’re Now a Backend Developer!
Key Achievements: - 🚀 Built REST APIs with Express.js - 🗄️ Integrated MySQL database persistence - 🔒 Implemented production-ready patterns - 🧪 Tested APIs with multiple tools - 🤖 Used AI for rapid development - 🏗️ Designed maintainable code architecture
Next Steps: Complete the backend practice, then connect it to your React frontend for a full-stack application! 🚀