Node.js & Express Backend Development

Building REST APIs for Full-Stack Applications

UniversitΓ© de Toulon

LIS UMR CNRS 7020

2025-10-11

Node.js & Express Backend πŸš€

2-Hour Focus: APIs β†’ Database β†’ Production Ready

🎯 Today’s Mission

What We’re Building (2 Hours)

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;

app.listen(PORT, () => {
  console.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
app.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

// Routes
app.use('/api/products', require('./routes/products'));

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString });
});

// Catch-all error handler
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Something went wrong!' });
});

module.exports = app;

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
GET    /api/products           // Get all products
POST   /api/products           // Create new product

// Specific resources
GET    /api/products/:id       // Get one product
PUT    /api/products/:id       // Update product
DELETE /api/products/:id       // Delete product

// Nested resources
GET    /api/products/:id/reviews    // Get product reviews
POST   /api/products/:id/reviews    // Add review to product

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
router.get('/', (req, res) => {
  try {
    res.json({
      success: true,
      data: products,
      meta: {
        total: products.length
      }
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Failed to fetch products'
    });
  }
});

// GET /api/products/:id - Get specific product
router.get('/:id', (req, res) => {
  try {
    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'
      });
    }
    
    res.json({
      success: true,
      data: product
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Failed to fetch product'
    });
  }
});

// POST /api/products - Create new product
router.post('/', (req, res) => {
  try {
    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++,
      name,
      description: description || '',
      price: parseFloat(price),
      category: category || 'Uncategorized'
    };
    
    products.push(newProduct);
    
    res.status(201).json({
      success: true,
      data: newProduct,
      message: 'Product created successfully'
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Failed to create product'
    });
  }
});

module.exports = router;

πŸ€– AI Practice: Review Endpoints

Generate Review API 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

βœ… Hour 1 Checkpoint

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
❌ No backup/recovery
❌ Not scalable

Database Solution: Persistent Data

-- Data survives server restarts
CREATE TABLE products (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(255) NOT NULL,
  description TEXT,
  price DECIMAL(10,2) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Benefits:
βœ… Data persistence
βœ… ACID transactions
βœ… Concurrent access
βœ… 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');
    connection.release();
  } catch (error) {
    console.error('❌ Database connection failed:', error.message);
    process.exit(1);
  }
}

module.exports = { pool, testConnection };

πŸ—οΈ 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,
  name VARCHAR(255) NOT NULL,
  description TEXT,
  price DECIMAL(10,2) NOT NULL,
  category VARCHAR(100) DEFAULT 'Electronics',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- Reviews table
CREATE TABLE reviews (
  id INT PRIMARY KEY AUTO_INCREMENT,
  product_id INT NOT NULL,
  source VARCHAR(50) NOT NULL,
  external_review_id VARCHAR(100),
  author VARCHAR(255),
  rating INT CHECK (rating BETWEEN 1 AND 5),
  title VARCHAR(500),
  content TEXT,
  review_date DATETIME,
  fetched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  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
router.get('/', async (req, res) => {
  try {
    const [rows] = await pool.execute(
      'SELECT * FROM products ORDER BY created_at DESC'
    );
    
    res.json({
      success: true,
      data: rows,
      meta: {
        total: rows.length
      }
    });
  } catch (error) {
    console.error('Database error:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to fetch products'
    });
  }
});

// GET /api/products/:id - Get specific product
router.get('/:id', async (req, res) => {
  try {
    const [rows] = await pool.execute(
      'SELECT * FROM products WHERE id = ?',
      [req.params.id]
    );
    
    if (rows.length === 0) {
      return res.status(404).json({
        success: false,
        error: 'Product not found'
      });
    }
    
    res.json({
      success: true,
      data: rows[0]
    });
  } catch (error) {
    console.error('Database error:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to fetch product'
    });
  }
});

// POST /api/products - Create new product
router.post('/', async (req, res) => {
  try {
    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 (?, ?, ?, ?)',
      [name, description || '', parseFloat(price), category || 'Electronics']
    );
    
    // Get the created product
    const [newProduct] = await pool.execute(
      'SELECT * FROM products WHERE id = ?',
      [result.insertId]
    );
    
    res.status(201).json({
      success: true,
      data: newProduct[0],
      message: 'Product created successfully'
    });
  } catch (error) {
    console.error('Database error:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to create product'
    });
  }
});

πŸ€– AI Practice: Review Database Operations

Generate Database-Backed Review Endpoints

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
router.post('/', validateProduct, handleValidationErrors, async (req, res) => {
  // Safe to use req.body - it's validated and sanitized
});

Database Transaction Management

// For operations that need to be atomic
router.post('/:id/fetch', async (req, res) => {
  const connection = await pool.getConnection();
  
  try {
    await connection.beginTransaction();
    
    // 1. Check if product exists
    const [products] = await connection.execute(
      'SELECT id FROM products WHERE id = ?',
      [req.params.id]
    );
    
    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 (?, ?, ?, ?, ?, ?, ?, ?)`,
        [
          review.product_id,
          review.source,
          review.external_review_id,
          review.author,
          review.rating,
          review.title,
          review.content,
          review.review_date
        ]
      );
    }
    
    await connection.commit();
    
    res.json({
      success: true,
      message: `Fetched ${mockReviews.length} new reviews`,
      data: { count: mockReviews.length }
    });
    
  } catch (error) {
    await connection.rollback();
    console.error('Transaction error:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to fetch reviews'
    });
  } finally {
    connection.release();
  }
});

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'
  }
});

router.post('/:id/fetch', fetchLimiter, async (req, res) => {
  // 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! πŸš€