🟫 ClodHost beta
Sign In
🎉 All services free during beta! 🎉 All services free during beta!

Building a REST API

Create API endpoints for your frontend or mobile app. Learn routing, validation, authentication, and best practices.

Reading time: 20 min Difficulty: Intermediate
View examples as:
Ask Claude Manual Code

Table of Contents

What is a REST API?

A REST API lets your frontend (or mobile app) communicate with your backend through HTTP requests. It follows these conventions:

Method Purpose Example
GET Retrieve data GET /api/users - List all users
POST Create new data POST /api/users - Create a user
PUT/PATCH Update existing data PUT /api/users/123 - Update user 123
DELETE Remove data DELETE /api/users/123 - Delete user 123

Project Setup

Let's create a Node.js API using Express.js - the most popular choice.

Initialize the Project

Example prompt:

"Create a REST API project with Express.js. Set up the folder structure with routes, controllers, and middleware directories. Include CORS for cross-origin requests, JSON body parsing, and environment variable support. Add a health check endpoint at GET /api/health."
# Initialize project
npm init -y
npm install express cors dotenv helmet

# Create folder structure
mkdir routes controllers middleware
touch app.js .env
// app.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
require('dotenv').config();

const app = express();

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

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

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

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

Defining Routes

Organize your routes by resource (users, products, orders, etc.).

Route Structure

Example prompt:

"Create a products API with the following endpoints: GET /api/products (list all with pagination), GET /api/products/:id (get one), POST /api/products (create), PUT /api/products/:id (update), DELETE /api/products/:id (delete). Use a controller pattern to keep routes clean."
// routes/products.js
const express = require('express');
const router = express.Router();
const controller = require('../controllers/products');

router.get('/', controller.list);           // GET /api/products
router.get('/:id', controller.getOne);       // GET /api/products/:id
router.post('/', controller.create);         // POST /api/products
router.put('/:id', controller.update);       // PUT /api/products/:id
router.delete('/:id', controller.remove);   // DELETE /api/products/:id

module.exports = router;

CRUD Endpoints

Implement the actual logic in your controllers.

List with Pagination

Example prompt:

"Implement the products controller. The list endpoint should support pagination with page and limit query params (default: page 1, limit 20). Include total count and total pages in the response. Also support filtering by category and sorting by price or name."
// controllers/products.js
const { query } = require('../db');

exports.list = async (req, res) => {
    const page = parseInt(req.query.page) || 1;
    const limit = parseInt(req.query.limit) || 20;
    const offset = (page - 1) * limit;
    const { category, sort } = req.query;

    let sql = 'SELECT * FROM products WHERE 1=1';
    const params = [];

    if (category) {
        sql += ' AND category = ?';
        params.push(category);
    }

    if (sort === 'price') sql += ' ORDER BY price ASC';
    else if (sort === '-price') sql += ' ORDER BY price DESC';
    else sql += ' ORDER BY created_at DESC';

    sql += ' LIMIT ? OFFSET ?';
    params.push(limit, offset);

    const products = await query(sql, params);
    const [{ total }] = await query('SELECT COUNT(*) as total FROM products');

    res.json({
        data: products,
        pagination: {
            page,
            limit,
            total,
            totalPages: Math.ceil(total / limit)
        }
    });
};

exports.getOne = async (req, res) => {
    const products = await query('SELECT * FROM products WHERE id = ?', [req.params.id]);
    if (products.length === 0) {
        return res.status(404).json({ error: 'Product not found' });
    }
    res.json(products[0]);
};

Create and Update

Example prompt:

"Implement create and update endpoints for products. Require name, price, and category. Return 201 status on create with the new product. Return 200 on update. Return 404 if product not found on update."
exports.create = async (req, res) => {
    const { name, price, category, description } = req.body;

    if (!name || !price || !category) {
        return res.status(400).json({ error: 'Name, price, and category required' });
    }

    const result = await query(
        'INSERT INTO products (name, price, category, description) VALUES (?, ?, ?, ?)',
        [name, price, category, description || null]
    );

    res.status(201).json({
        id: result.insertId,
        name, price, category, description
    });
};

exports.update = async (req, res) => {
    const { name, price, category, description } = req.body;
    const { id } = req.params;

    const result = await query(
        'UPDATE products SET name = ?, price = ?, category = ?, description = ? WHERE id = ?',
        [name, price, category, description, id]
    );

    if (result.affectedRows === 0) {
        return res.status(404).json({ error: 'Product not found' });
    }

    res.json({ id, name, price, category, description });
};

exports.remove = async (req, res) => {
    const result = await query('DELETE FROM products WHERE id = ?', [req.params.id]);

    if (result.affectedRows === 0) {
        return res.status(404).json({ error: 'Product not found' });
    }

    res.status(204).send();
};

Input Validation

Always validate input data before processing it.

Using Joi for Validation

Example prompt:

"Add input validation to my products API using Joi. Validate that: name is a string 3-100 chars, price is a positive number, category is one of 'electronics', 'clothing', 'books', 'other'. Return clear error messages if validation fails."
const Joi = require('joi');

const productSchema = Joi.object({
    name: Joi.string().min(3).max(100).required(),
    price: Joi.number().positive().required(),
    category: Joi.string().valid('electronics', 'clothing', 'books', 'other').required(),
    description: Joi.string().max(1000).optional()
});

// Validation middleware
function validate(schema) {
    return (req, res, next) => {
        const { error } = schema.validate(req.body);
        if (error) {
            return res.status(400).json({
                error: 'Validation failed',
                details: error.details.map(d => d.message)
            });
        }
        next();
    };
}

// Apply to routes
router.post('/', validate(productSchema), controller.create);
router.put('/:id', validate(productSchema), controller.update);

API Authentication

Protect your API endpoints from unauthorized access.

JWT Authentication

Example prompt:

"Add JWT authentication to my API. Create a /api/auth/login endpoint that returns a JWT token. Create middleware that verifies the token from the Authorization header (Bearer token). Protect all product write endpoints (POST, PUT, DELETE) but leave GET endpoints public."
const jwt = require('jsonwebtoken');

// Login endpoint
router.post('/auth/login', async (req, res) => {
    const { email, password } = req.body;
    const user = await validateCredentials(email, password);

    if (!user) {
        return res.status(401).json({ error: 'Invalid credentials' });
    }

    const token = jwt.sign(
        { userId: user.id, email: user.email },
        process.env.JWT_SECRET,
        { expiresIn: '24h' }
    );

    res.json({ token });
});

// Auth middleware
function authMiddleware(req, res, next) {
    const authHeader = req.headers.authorization;

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
        return res.status(401).json({ error: 'No token provided' });
    }

    const token = authHeader.split(' ')[1];

    try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        req.user = decoded;
        next();
    } catch (error) {
        res.status(401).json({ error: 'Invalid token' });
    }
}

// Protect routes
router.post('/', authMiddleware, controller.create);
router.put('/:id', authMiddleware, controller.update);
router.delete('/:id', authMiddleware, controller.remove);

Error Handling

Handle errors consistently across your API.

Global Error Handler

Example prompt:

"Add a global error handler to my Express API. Catch all unhandled errors and return consistent JSON error responses. In production, don't expose stack traces. Log errors to the console with timestamps. Also add a 404 handler for unknown routes."
// 404 handler
app.use((req, res) => {
    res.status(404).json({ error: 'Endpoint not found' });
});

// Global error handler (must be last)
app.use((error, req, res, next) => {
    console.error(`[${new Date().toISOString()}]`, error);

    const statusCode = error.statusCode || 500;
    const message = error.message || 'Internal server error';

    res.status(statusCode).json({
        error: message,
        ...(process.env.NODE_ENV !== 'production' && { stack: error.stack })
    });
});

// Wrap async handlers to catch errors
const asyncHandler = fn => (req, res, next) =>
    Promise.resolve(fn(req, res, next)).catch(next);

// Use in routes
router.get('/', asyncHandler(controller.list));

API Documentation

Document your API so others (and future you) can use it.

Recommendation

Use Swagger/OpenAPI for interactive documentation. It auto-generates a UI where developers can test your endpoints.

OpenAPI with Swagger

Example prompt:

"Add Swagger documentation to my API using swagger-jsdoc and swagger-ui-express. Document all product endpoints with request/response examples. Make the docs available at /api/docs. Include authentication info showing how to use Bearer tokens."
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');

const swaggerSpec = swaggerJsdoc({
    definition: {
        openapi: '3.0.0',
        info: {
            title: 'My API',
            version: '1.0.0',
        },
        servers: [{ url: '/api' }],
        components: {
            securitySchemes: {
                bearerAuth: { type: 'http', scheme: 'bearer' }
            }
        }
    },
    apis: ['./routes/*.js']
});

app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));