Building REST APIs for Full-Stack Applications
2025-10-21
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
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
Why Express? The minimal, unopinionated web framework that powers 60%+ of Node.js web applications.
Letβs create a minimal Express server step by step
// 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}`);
});node server.js
# Visit http://localhost:3000 in your browser
# Or test with: curl http://localhost:3000In 3 steps, you have a working web server! π
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:
// 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
});Client Server
β β
β GET /api/users β
β ββββββββββββββββββββββ β
β β [Handler function runs]
β β
β { users: [...] } β
β ββββββββββββββββββββββ β
β β
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');
});Save the previous code as user-service.js, run it, then test:
http://localhost:3000/api/users orResponse:
Response:
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:
Response:
Verify deletion (GET all users again):
Now letβs explore our projectβs starting point
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:
Test it works:
Understanding the Express foundation
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;RESTful APIs follow predictable patterns
| 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 |
// 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// 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
}
}Letβs build the review aggregator API endpoints
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;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
Letβs test our API with different tools
# 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/aggregateCreate 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:
E. Bruno - Node.js & Express Backend Development