Node.js & Express Backend Development

Building REST APIs for Full-Stack Applications

UniversitΓ© de Toulon

LIS UMR CNRS 7020

2025-10-21

πŸ—ΊοΈ The Backend Journey

EXPRESS FOUNDATIONS     DATABASE & PRODUCTION
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Why Node/Expressβ”‚           β”‚ MySQL Setup     β”‚
β”‚ REST API Design β”‚    β†’      β”‚ Data Persistenceβ”‚
β”‚ Routes & Middle β”‚           β”‚ Error Handling  β”‚
β”‚ JSON Responses  β”‚           β”‚ Production Readyβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
   API Foundation               Database Ready

πŸ’‘ 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:

- βœ… Data persistence
- βœ… ACID transactions
- βœ… Concurrent access
- βœ… Data relationships
- βœ… Backup/recovery
- βœ… Scalable queries

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.

🎯 Bootstrap: Creating an Express Server from Scratch

Let’s create a minimal Express server step by step

Step 1: Initialize Project

# Create project directory
mkdir my-express-api
cd my-express-api

# Initialize npm project (creates package.json)
npm init -y

# Install Express
npm install express

Step 2: Create Your First Server (server.js)

// server.js - The simplest Express server
const express = require('express');
const app = express();
const PORT = 3000;

// Middleware to parse JSON
app.use(express.json());

// Your first endpoint!
app.get('/', (req, res) => {
  res.json({ message: 'Hello from Express!' });
});

// Start the server
app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

Step 3: Run It!

node server.js
# Visit http://localhost:3000 in your browser
# Or test with: curl http://localhost:3000

In 3 steps, you have a working web server! πŸŽ‰

🧩 Understanding Endpoints

What is an Endpoint?

An endpoint is a specific URL path in your API that responds to HTTP requests. Think of it as a β€œdoor” to a specific function in your application.

Anatomy of an Endpoint:

app.METHOD(PATH, HANDLER)
    β”‚      β”‚      β”‚
    β”‚      β”‚      └─ Function that handles the request
    β”‚      └──────── URL path (the "address")
    └─────────────── HTTP method (GET, POST, PUT, DELETE)

Example Breakdown

// GET endpoint - Retrieve data
app.get('/api/users', (req, res) => {
//  ↑     ↑           ↑    ↑
//  β”‚     β”‚           β”‚    └─ response object (to send back)
//  β”‚     β”‚           └────── request object (incoming data)
//  β”‚     └────────────────── The PATH (URL)
//  └──────────────────────── HTTP METHOD
  
  res.json({ users: ['Alice', 'Bob'] });
  //  ↑     ↑
  //  β”‚     └─ Data sent back to client
  //  └─────── Send JSON response
});

Request β†’ Endpoint β†’ Response

Client                    Server
  β”‚                          β”‚
  β”‚  GET /api/users          β”‚
  β”‚ ─────────────────────→   β”‚
  β”‚                          β”‚ [Handler function runs]
  β”‚                          β”‚
  β”‚   { users: [...] }       β”‚
  β”‚ ←─────────────────────   β”‚
  β”‚                          β”‚

πŸ” Endpoint Example: A Complete User Service

Let’s build a complete mini-service for managing users

const express = require('express');
const app = express();
app.use(express.json()); // Parse JSON bodies

// In-memory database (for learning)
let users = [
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' }
];
let nextId = 3;

// ════════════════════════════════════════════════
// ENDPOINT 1: GET all users
// ════════════════════════════════════════════════
app.get('/api/users', (req, res) => {
  res.json({
    success: true,
    count: users.length,
    data: users
  });
});

// ════════════════════════════════════════════════
// ENDPOINT 2: GET single user by ID
// ════════════════════════════════════════════════
app.get('/api/users/:id', (req, res) => {
  const id = parseInt(req.params.id); // URL parameter
  const user = users.find(u => u.id === id);
  
  if (!user) {
    return res.status(404).json({
      success: false,
      error: 'User not found'
    });
  }
  
  res.json({
    success: true,
    data: user
  });
});

// ════════════════════════════════════════════════
// ENDPOINT 3: POST create new user
// ════════════════════════════════════════════════
app.post('/api/users', (req, res) => {
  const { name, email } = req.body; // Request body data
  
  // Validation
  if (!name || !email) {
    return res.status(400).json({
      success: false,
      error: 'Name and email are required'
    });
  }
  
  const newUser = {
    id: nextId++,
    name,
    email
  };
  
  users.push(newUser);
  
  res.status(201).json({
    success: true,
    message: 'User created',
    data: newUser
  });
});

// ════════════════════════════════════════════════
// ENDPOINT 4: DELETE user
// ════════════════════════════════════════════════
app.delete('/api/users/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const index = users.findIndex(u => u.id === id);
  
  if (index === -1) {
    return res.status(404).json({
      success: false,
      error: 'User not found'
    });
  }
  
  users.splice(index, 1);
  
  res.json({
    success: true,
    message: 'User deleted'
  });
});

app.listen(3000, () => {
  console.log('User service running on http://localhost:3000');
});

Testing the User Service - Complete Examples

Save the previous code as user-service.js, run it, then test:

Test 1: GET All Users

  • open browser and visit: http://localhost:3000/api/users or
curl http://localhost:3000/api/users

Response:

{
  "success": true,
  "count": 2,
  "data": [
    { "id": 1, "name": "Alice", "email": "alice@example.com" },
    { "id": 2, "name": "Bob", "email": "bob@example.com" }
  ]
}

Test 2: GET Single User

curl http://localhost:3000/api/users/1

Response:

{
  "success": true,
  "data": { "id": 1, "name": "Alice", "email": "alice@example.com" }
}

Test 3: POST Create New User

You cannot use browser for POST, so use curl or vscode REST client (https://marketplace.visualstudio.com/items?itemName=humao.rest-client).

curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Charlie",
    "email": "charlie@example.com"
  }'

Response:

{
  "success": true,
  "message": "User created",
  "data": { "id": 3, "name": "Charlie", "email": "charlie@example.com" }
}

Test 4: DELETE User

curl -X DELETE http://localhost:3000/api/users/2

Response:

{
  "success": true,
  "message": "User deleted"
}

Verify deletion (GET all users again):

curl http://localhost:3000/api/users
# Charlie and Alice remain, Bob is gone!

οΏ½πŸš€ fullstack-minimal-app Backend Tour

Now let’s explore our project’s 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();
const { products, reviews, getNextProductId, getNextReviewId } = require('../data/mockData');
const { fetchReviewsFromSources } = require('../services/mockScraper');

// GET /api/products - Get all products
router.get('/', (req, res) => {
  try {
    res.json({
      success: true,
      data: products,
      count: products.length
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Failed to fetch products'
    });
  }
});

// POST /api/products - Create new product
router.post('/', (req, res) => {
  try {
    const { name, description, price, image_url } = req.body;
    
    // Basic validation
    if (!name || !price) {
      return res.status(400).json({
        success: false,
        error: 'Name and price are required'
      });
    }
    
    const newProduct = {
      id: getNextProductId(),
      name,
      description: description || '',
      price: parseFloat(price),
      image_url: image_url || '',
      created_at: new Date()
    };
    
    products.push(newProduct);
    
    res.status(201).json({
      success: true,
      data: newProduct
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Failed to create product'
    });
  }
});

// POST /api/products/:id/fetch - Fetch reviews from external sources for a product
router.post('/:id/fetch', async (req, res, next) => {
  try {
    const productId = parseInt(req.params.id);
    const product = products.find(p => p.id === productId);
    if (!product) {
      return res.status(404).json({ success: false, error: 'Product not found' });
    }

    const scrapedReviews = await fetchReviewsFromSources(productId, req.body || {});
    let added = 0;
    let skipped = 0;

    for (const s of scrapedReviews) {
      const exists = reviews.find(r =>
        r.product_id === productId &&
        r.source === s.source &&
        r.external_id === s.external_id
      );
      if (exists) {
        skipped++;
        continue;
      }
      reviews.push({
        id: getNextReviewId(),
        product_id: productId,
        ...s,
        fetched_at: new Date()
      });
      added++;
    }

    res.json({
      success: true,
      message: `Fetched ${scrapedReviews.length} reviews`,
      data: { added, skipped, total: scrapedReviews.length }
    });
  } catch (err) {
    next(err);
  }
});

// GET /api/products/:id - Get single product
router.get('/:id', (req, res) => {
  try {
    const productId = parseInt(req.params.id);
    const product = products.find(p => p.id === productId);
    
    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'
    });
  }
});

// GET /api/products/:id/reviews - List reviews for a product
router.get('/:id/reviews', (req, res) => {
  const productId = parseInt(req.params.id);
  const productReviews = reviews.filter(r => r.product_id === productId);
  res.json({ success: true, data: productReviews, count: productReviews.length });
});

// GET /api/products/:id/aggregate - Aggregated stats for a product
router.get('/:id/aggregate', (req, res) => {
  try {
    const productId = parseInt(req.params.id);
    const productReviews = reviews.filter(r => r.product_id === productId);

    if (productReviews.length === 0) {
      return res.json({
        success: true,
        data: {
          overall: { average_rating: 0, total_reviews: 0, min_rating: 0, max_rating: 0 },
          by_source: [],
          rating_histogram: { "5": 0, "4": 0, "3": 0, "2": 0, "1": 0 }
        }
      });
    }

    const totalRating = productReviews.reduce((sum, r) => sum + r.rating, 0);
    const avgRating = totalRating / productReviews.length;
    const minRating = Math.min(...productReviews.map(r => r.rating));
    const maxRating = Math.max(...productReviews.map(r => r.rating));

    const bySource = {};
    productReviews.forEach(r => {
      bySource[r.source] ??= { total: 0, count: 0 };
      bySource[r.source].total += r.rating;
      bySource[r.source].count += 1;
    });

    const sourceBreakdown = Object.keys(bySource).map(source => ({
      source,
      average_rating: Math.round((bySource[source].total / bySource[source].count) * 10) / 10,
      review_count: bySource[source].count
    }));

    const histogram = { "5": 0, "4": 0, "3": 0, "2": 0, "1": 0 };
    productReviews.forEach(r => {
      const bucket = Math.round(r.rating).toString();
      if (histogram[bucket] !== undefined) histogram[bucket]++;
    });

    res.json({
      success: true,
      data: {
        overall: {
          average_rating: Math.round(avgRating * 10) / 10,
          total_reviews: productReviews.length,
          min_rating: minRating,
          max_rating: maxRating
        },
        by_source: sourceBreakdown,
        rating_histogram: histogram
      }
    });
  } catch {
    res.status(500).json({ success: false, error: 'Failed to calculate aggregate statistics' });
  }
});

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: 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