Part 8 – Building RESTful APIs with Express

RESTful APIs are the backbone of modern web applications. This guide will walk you through building a complete REST API using Express.js with proper architecture and best practices.

1. REST Fundamentals

REST Principles

  • Client-Server: Separation of concerns
  • Stateless: Each request contains all needed information
  • Cacheable: Responses define cacheability
  • Uniform Interface: Consistent resource identification

HTTP Methods

  • GET: Retrieve resources
  • POST: Create resources
  • PUT/PATCH: Update resources
  • DELETE: Remove resources

2. Basic API Structure

const express = require('express');
const app = express();

// Middleware
app.use(express.json());

// Routes
app.get('/api/v1/products', (req, res) => {
    // Return list of products
});

app.post('/api/v1/products', (req, res) => {
    // Create new product
});

// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`API running on port ${PORT}`));

3. Proper Resource Routing

Route Organization

// routes/products.js
const express = require('express');
const router = express.Router();

router.get('/', (req, res) => { /* GET all */ });
router.post('/', (req, res) => { /* POST create */ });
router.get('/:id', (req, res) => { /* GET one */ });
router.put('/:id', (req, res) => { /* PUT update */ });
router.delete('/:id', (req, res) => { /* DELETE */ });

module.exports = router;

// In main app.js
const productsRouter = require('./routes/products');
app.use('/api/v1/products', productsRouter);

4. CRUD Operations Implementation

// Example using in-memory array (replace with DB in production)
let products = [
    { id: 1, name: 'Product 1', price: 10.99 }
];

// GET all products
router.get('/', (req, res) => {
    res.json(products);
});

// GET single product
router.get('/:id', (req, res) => {
    const product = products.find(p => p.id === parseInt(req.params.id));
    if (!product) return res.status(404).json({ message: 'Product not found' });
    res.json(product);
});

// POST create product
router.post('/', (req, res) => {
    const { name, price } = req.body;
    if (!name || !price) {
        return res.status(400).json({ message: 'Name and price are required' });
    }

    const newProduct = {
        id: products.length + 1,
        name,
        price
    };

    products.push(newProduct);
    res.status(201).json(newProduct);
});

5. Request Validation and Sanitization

Manual Validation

router.post('/', (req, res) => {
    const { name, price } = req.body;

    // Validation
    if (!name || name.trim().length === 0) {
        return res.status(400).json({ error: 'Product name is required' });
    }

    if (!price || isNaN(price) || price <= 0) {
        return res.status(400).json({ error: 'Valid price is required' });
    }

    // Proceed with creation...
});

Using express-validator

const { body, validationResult } = require('express-validator');

router.post('/', 
    [
        body('name').trim().notEmpty().withMessage('Name is required'),
        body('price').isFloat({ gt: 0 }).withMessage('Price must be positive')
    ],
    (req, res) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            return res.status(400).json({ errors: errors.array() });
        }

        // Proceed with valid data...
    }
);

6. API Best Practices

Response Standards

{
    "status": "success",
    "data": {
        "id": 1,
        "name": "Product 1"
    },
    "meta": {
        "timestamp": "2023-05-20T12:00:00Z",
        "version": "1.0"
    }
}

Essential Practices

  • Use proper HTTP status codes
  • Version your API (/api/v1/...)
  • Implement pagination for large datasets
  • Use consistent naming conventions
  • Document your API (Swagger/OpenAPI)

7. Complete API Example

// File: app.js
const express = require('express');
const morgan = require('morgan');
const helmet = require('helmet');
const cors = require('cors');
const { notFound, errorHandler } = require('./middleware');

const app = express();

// Middleware
app.use(morgan('dev'));
app.use(helmet());
app.use(cors());
app.use(express.json());

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

// Error handling
app.use(notFound);
app.use(errorHandler);

// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`API running on port ${PORT}`));

// File: routes/products.js
const express = require('express');
const router = express.Router();
const { 
    getProducts,
    getProduct,
    createProduct,
    updateProduct,
    deleteProduct
} = require('../controllers/products');

router.route('/')
    .get(getProducts)
    .post(createProduct);

router.route('/:id')
    .get(getProduct)
    .put(updateProduct)
    .delete(deleteProduct);

module.exports = router;

// File: controllers/products.js
exports.getProducts = async (req, res) => {
    // Implementation...
};

exports.createProduct = async (req, res) => {
    // Implementation...
};

Next: Middleware in Express.js →

API Development Checklist

  • ✅ Proper HTTP methods and status codes
  • ✅ Request validation and sanitization
  • ✅ Consistent response format
  • ✅ Error handling middleware
  • ✅ Security headers (CORS, Helmet)
  • ✅ API documentation

Leave a Comment

Your email address will not be published. Required fields are marked *