REST API Fundamentals
REST (Representational State Transfer) is an architectural style for designing networked applications. REST APIs provide a standardized way for systems to communicate over HTTP, making them the backbone of modern web services and microservices architectures.
π REST Architectural Principles
π Stateless
Each request contains all information needed to process it. No server-side session state.
ποΈ Client-Server
Clear separation between client and server concerns.
π Uniform Interface
Consistent interface across all API endpoints.
ποΈ Resource-Based
Everything is treated as a resource with unique identifiers.
π Representation
Resources can have multiple representations (JSON, XML, etc.).
β‘ Cacheable
Responses should be cacheable when appropriate.
ποΈ Layered System
Architecture supports layered system design.
π§ Code on Demand (Optional)
Server can extend client functionality with code.
π§ HTTP Methods in REST
REST APIs use standard HTTP methods to perform operations on resources.
π GET
Retrieve resource(s)
- Safe operation (no side effects)
- Idempotent
- Cacheable
- Can be bookmarked
GET /api/users # Get all users
GET /api/users/123 # Get user with ID 123
GET /api/users?page=2 # Get paginated results
β¨ POST
Create new resource
- Not idempotent
- Not safe
- Not cacheable
- Server assigns resource ID
POST /api/users
{
"name": "John Doe",
"email": "john@example.com"
}
π PUT
Update entire resource
- Idempotent
- Not safe
- Not cacheable
- Client specifies resource ID
PUT /api/users/123
{
"name": "John Smith",
"email": "john@example.com"
}
π PATCH
Partial update of resource
- Not necessarily idempotent
- Not safe
- Not cacheable
- Only modified fields included
PATCH /api/users/123
{
"email": "johnsmith@example.com"
}
ποΈ DELETE
Remove resource
- Idempotent
- Not safe
- Not cacheable
- Soft delete often preferred
DELETE /api/users/123
π HEAD
Get resource metadata
- Same as GET but no body
- Check resource existence
- Get headers only
HEAD /api/users/123
βοΈ OPTIONS
Get communication options
- Discover allowed methods
- CORS preflight requests
- API capability discovery
OPTIONS /api/users
π― Resource Design Principles
Design resources that are intuitive and follow REST conventions.
π Noun-Based URLs
β
Good:
GET /api/users
GET /api/products
GET /api/orders
β Avoid:
GET /api/getUsers
GET /api/fetchProducts
GET /api/retrieveOrders
π Plural Nouns
β
Good:
GET /api/users
POST /api/users
β Avoid:
GET /api/user
POST /api/user
π Relationships
β
Good:
GET /api/users/123/posts
GET /api/posts/456/comments
β Avoid:
GET /api/usersPosts?userId=123
GET /api/postComments?postId=456
π·οΈ Consistent Naming
β
Good:
GET /api/user-profiles
GET /api/order-items
β Avoid:
GET /api/userProfiles
GET /api/order_items
API Design Patterns
ποΈ Common API Design Patterns
Established patterns for building robust and maintainable APIs.
π Collection Pattern
Handle collections of resources
// Get collection
GET /api/users
// Get specific resource
GET /api/users/123
// Create new resource
POST /api/users
// Update resource
PUT /api/users/123
// Delete resource
DELETE /api/users/123
// Search/Filter
GET /api/users?search=john&status=active
// Pagination
GET /api/users?page=2&limit=20
// Sorting
GET /api/users?sort=name&order=asc
π Subresource Pattern
Handle nested or related resources
// User posts
GET /api/users/123/posts
POST /api/users/123/posts
// Order items
GET /api/orders/456/items
POST /api/orders/456/items
// Product reviews
GET /api/products/789/reviews
POST /api/products/789/reviews
// Alternative: Separate endpoints
GET /api/posts?userId=123
GET /api/reviews?productId=789
π¬ Controller/Actions Pattern
Handle complex operations
// User actions
POST /api/users/123/reset-password
POST /api/users/123/verify-email
POST /api/users/123/deactivate
// Order actions
POST /api/orders/456/cancel
POST /api/orders/456/refund
POST /api/orders/456/ship
// Alternative: RPC-style
POST /api/rpc
{
"method": "cancelOrder",
"params": { "orderId": 456 }
}
π·οΈ Versioning Pattern
Handle API evolution
// URL versioning
GET /api/v1/users
GET /api/v2/users
// Header versioning
GET /api/users
Accept: application/vnd.api.v2+json
// Query parameter
GET /api/users?version=2
// Media type versioning
GET /api/users
Accept: application/vnd.myapp.v2+json
π Response Format Standards
Consistent response formats improve API usability and predictability.
π Successful Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"data": {
"id": 123,
"name": "John Doe",
"email": "john@example.com"
},
"meta": {
"timestamp": "2024-01-15T10:30:00Z",
"version": "1.0"
}
}
π Collection Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"data": [
{
"id": 123,
"name": "John Doe"
},
{
"id": 124,
"name": "Jane Smith"
}
],
"meta": {
"total": 150,
"page": 1,
"limit": 20,
"pages": 8
},
"links": {
"self": "/api/users?page=1",
"next": "/api/users?page=2",
"last": "/api/users?page=8"
}
}
β Error Response
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{
"field": "email",
"message": "Must be a valid email address"
}
]
},
"meta": {
"timestamp": "2024-01-15T10:30:00Z",
"requestId": "req-12345"
}
}
API Security Best Practices
π Authentication & Authorization
Secure your API endpoints with proper authentication and authorization mechanisms.
π API Keys
Simple key-based authentication
// Header-based
GET /api/users
X-API-Key: your-api-key-here
// Query parameter (less secure)
GET /api/users?api_key=your-api-key-here
// Server validation
app.use((req, res, next) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey || !isValidApiKey(apiKey)) {
return res.status(401).json({ error: 'Invalid API key' });
}
next();
});
π« JWT (JSON Web Tokens)
Stateless token-based authentication
// JWT creation
const jwt = require('jsonwebtoken');
const token = jwt.sign(
{ userId: 123, role: 'admin' },
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
// JWT validation middleware
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid token' });
}
req.user = user;
next();
});
};
π OAuth 2.0
Delegated authorization framework
// OAuth 2.0 flow types
// 1. Authorization Code Grant (recommended for web apps)
GET /oauth/authorize?response_type=code&client_id=123&redirect_uri=...
// 2. Implicit Grant (for SPAs)
GET /oauth/authorize?response_type=token&client_id=123&redirect_uri=...
// 3. Client Credentials (for service-to-service)
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=123&client_secret=abc
// 4. Resource Owner Password Credentials
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=user&password=pass&client_id=123
π‘οΈ Security Measures
Implement comprehensive security measures to protect your API.
π HTTPS Only
// Force HTTPS redirect
const forceHttps = (req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
res.redirect(`https://${req.header('host')}${req.url}`);
} else {
next();
}
};
// Security headers
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Strict-Transport-Security', 'max-age=31536000');
next();
});
π¦ Rate Limiting
const rateLimit = require('express-rate-limit');
// General API rate limiting
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.'
});
// Stricter limits for auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: 'Too many authentication attempts, please try again later.'
});
app.use('/api/', apiLimiter);
app.use('/api/auth/', authLimiter);
π§Ή Input Validation & Sanitization
const validator = require('validator');
const sanitizeHtml = require('sanitize-html');
// Input validation middleware
const validateUserInput = (req, res, next) => {
const { email, name } = req.body;
if (!email || !validator.isEmail(email)) {
return res.status(400).json({ error: 'Valid email required' });
}
if (!name || name.length < 2) {
return res.status(400).json({ error: 'Name must be at least 2 characters' });
}
// Sanitize HTML input
req.body.name = sanitizeHtml(name, {
allowedTags: [],
allowedAttributes: {}
});
next();
};
// SQL injection prevention (with parameterized queries)
const getUser = async (userId) => {
const query = 'SELECT * FROM users WHERE id = ?';
const [rows] = await db.execute(query, [userId]);
return rows[0];
};
π Request Logging & Monitoring
// Request logging middleware
app.use((req, res, next) => {
const timestamp = new Date().toISOString();
const logData = {
timestamp,
method: req.method,
url: req.url,
ip: req.ip,
userAgent: req.get('User-Agent')
};
console.log(JSON.stringify(logData));
// Log to monitoring service
monitoring.track('api_request', logData);
next();
});
// Error monitoring
app.use((error, req, res, next) => {
const errorData = {
timestamp: new Date().toISOString(),
error: error.message,
stack: error.stack,
url: req.url,
method: req.method
};
// Log error
console.error('API Error:', JSON.stringify(errorData));
// Send to error monitoring service
errorMonitoring.captureException(error, { req });
res.status(500).json({ error: 'Internal server error' });
});
API Performance Optimization
β‘ Caching Strategies
Implement effective caching to improve API performance and reduce server load.
π HTTP Caching
Browser and CDN caching
// Cache-Control headers
app.get('/api/users', (req, res) => {
res.set({
'Cache-Control': 'public, max-age=300', // 5 minutes
'ETag': generateETag(usersData)
});
// Only send data if not modified
if (req.get('If-None-Match') === generateETag(usersData)) {
return res.status(304).end();
}
res.json(usersData);
});
// Conditional requests
app.get('/api/users/:id', (req, res) => {
const user = getUserById(req.params.id);
res.set({
'Last-Modified': user.updatedAt.toUTCString(),
'ETag': generateETag(user)
});
// Check If-Modified-Since
const ifModifiedSince = req.get('If-Modified-Since');
if (ifModifiedSince && new Date(ifModifiedSince) >= user.updatedAt) {
return res.status(304).end();
}
res.json(user);
});
πΎ Application Caching
Server-side caching with Redis/Memcached
const redis = require('redis');
const client = redis.createClient();
// Cache middleware
const cache = (duration) => {
return async (req, res, next) => {
const key = `__express__${req.originalUrl}`;
// Try to get cached response
client.get(key, (err, cachedData) => {
if (cachedData) {
res.set('X-Cache', 'HIT');
return res.json(JSON.parse(cachedData));
}
// Cache miss - store original send function
const originalSend = res.json;
res.json = function(data) {
client.setex(key, duration, JSON.stringify(data));
res.set('X-Cache', 'MISS');
originalSend.call(this, data);
};
next();
});
};
};
// Usage
app.get('/api/users', cache(300), getUsersHandler);
// Cache invalidation
const invalidateUserCache = (userId) => {
client.del(`__express__/api/users`);
client.del(`__express__/api/users/${userId}`);
};
ποΈ Database Caching
Query result caching
// Query result caching
const cacheQuery = async (query, params, ttl = 300) => {
const key = `query:${query}:${JSON.stringify(params)}`;
// Check cache first
const cached = await redis.get(key);
if (cached) {
return JSON.parse(cached);
}
// Execute query
const result = await db.query(query, params);
// Cache result
await redis.setex(key, ttl, JSON.stringify(result));
return result;
};
// Usage
app.get('/api/reports/sales', async (req, res) => {
const { startDate, endDate } = req.query;
const salesData = await cacheQuery(
'SELECT * FROM sales WHERE date BETWEEN ? AND ?',
[startDate, endDate],
600 // 10 minutes
);
res.json(salesData);
});
π Pagination & Filtering
Handle large datasets efficiently with pagination and filtering.
π Offset-Based Pagination
// Simple offset pagination
app.get('/api/users', async (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const offset = (page - 1) * limit;
const [users, totalCount] = await Promise.all([
db.query('SELECT * FROM users LIMIT ? OFFSET ?', [limit, offset]),
db.query('SELECT COUNT(*) as count FROM users')
]);
res.json({
data: users,
meta: {
page,
limit,
total: totalCount[0].count,
pages: Math.ceil(totalCount[0].count / limit)
}
});
});
// Cursor-based pagination (better for large datasets)
app.get('/api/posts', async (req, res) => {
const cursor = req.query.cursor;
const limit = parseInt(req.query.limit) || 10;
let query = 'SELECT * FROM posts';
let params = [limit + 1]; // +1 to check if there are more
if (cursor) {
query += ' WHERE created_at < (SELECT created_at FROM posts WHERE id = ?)';
params.unshift(cursor);
}
query += ' ORDER BY created_at DESC LIMIT ?';
const posts = await db.query(query, params);
const hasNextPage = posts.length > limit;
const data = hasNextPage ? posts.slice(0, -1) : posts;
const nextCursor = hasNextPage ? data[data.length - 1].id : null;
res.json({
data,
meta: {
hasNextPage,
nextCursor
}
});
});
π Advanced Filtering
// Dynamic filtering
app.get('/api/products', async (req, res) => {
const { search, category, minPrice, maxPrice, sort, order } = req.query;
let query = 'SELECT * FROM products WHERE 1=1';
const params = [];
// Search filter
if (search) {
query += ' AND (name LIKE ? OR description LIKE ?)';
params.push(`%${search}%`, `%${search}%`);
}
// Category filter
if (category) {
query += ' AND category_id = ?';
params.push(category);
}
// Price range filter
if (minPrice) {
query += ' AND price >= ?';
params.push(minPrice);
}
if (maxPrice) {
query += ' AND price <= ?';
params.push(maxPrice);
}
// Sorting
const validSortFields = ['name', 'price', 'created_at'];
const sortField = validSortFields.includes(sort) ? sort : 'created_at';
const sortOrder = order === 'asc' ? 'ASC' : 'DESC';
query += ` ORDER BY ${sortField} ${sortOrder}`;
const products = await db.query(query, params);
res.json({ data: products });
});
ποΈ Compression & Optimization
Reduce response size and improve transfer speeds.
π¦ Response Compression
const compression = require('compression');
// Enable gzip compression
app.use(compression({
level: 6, // compression level (1-9)
threshold: 1024, // only compress responses > 1KB
filter: (req, res) => {
// Don't compress responses with this request header
if (req.headers['x-no-compression']) {
return false;
}
// Use compression filter function
return compression.filter(req, res);
}
}));
// Custom compression for specific endpoints
app.get('/api/large-dataset', compression({ level: 9 }), (req, res) => {
// Send large response - will be compressed
res.json(largeDataset);
});
π Response Size Optimization
// Selective field inclusion
app.get('/api/users', (req, res) => {
const { fields } = req.query; // e.g., ?fields=name,email
let users = getAllUsers();
if (fields) {
const fieldList = fields.split(',');
users = users.map(user => {
const filtered = {};
fieldList.forEach(field => {
if (user.hasOwnProperty(field)) {
filtered[field] = user[field];
}
});
return filtered;
});
}
res.json({ data: users });
});
// Data compression for large responses
const compressLargeResponse = (data, threshold = 1000) => {
if (JSON.stringify(data).length > threshold) {
// Return compressed data with metadata
return {
compressed: true,
data: pako.deflate(JSON.stringify(data)),
originalSize: JSON.stringify(data).length
};
}
return data;
};
API Documentation
π Documentation Tools & Standards
Create comprehensive API documentation that developers love to use.
π OpenAPI Specification
Standard for REST API documentation
openapi: 3.0.3
info:
title: User Management API
version: 1.0.0
description: API for managing users
servers:
- url: https://api.example.com/v1
description: Production server
paths:
/users:
get:
summary: Get all users
parameters:
- name: page
in: query
schema:
type: integer
minimum: 1
description: Page number
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
description: Number of items per page
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
meta:
$ref: '#/components/schemas/PaginationMeta'
components:
schemas:
User:
type: object
properties:
id:
type: integer
name:
type: string
email:
type: string
format: email
required:
- id
- name
- email
π§ Swagger/OpenAPI Tools
Generate interactive documentation
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'User API',
version: '1.0.0',
},
servers: [
{
url: 'http://localhost:3000',
description: 'Development server',
},
],
},
apis: ['./routes/*.js'], // files containing annotations
};
const specs = swaggerJsdoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
// Route with documentation
/**
* @swagger
* /users:
* get:
* summary: Retrieve a list of users
* responses:
* 200:
* description: A list of users
* content:
* application/json:
* schema:
* type: object
* properties:
* data:
* type: array
* items:
* $ref: '#/components/schemas/User'
*/
app.get('/users', getUsersHandler);
β¨ Documentation Best Practices
Make your API documentation clear, comprehensive, and developer-friendly.
π Clear Examples
// Good: Include working examples
// GET /api/users/123
// Response:
// {
// "data": {
// "id": 123,
// "name": "John Doe",
// "email": "john@example.com"
// }
// }
// Bad: Vague descriptions
// Returns user information
π¨ Error Documentation
// Document all possible error responses
/**
* @apiError (400 Bad Request) ValidationError Invalid input data
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 400 Bad Request
* {
* "error": {
* "code": "VALIDATION_ERROR",
* "message": "Invalid email format",
* "field": "email"
* }
* }
*/
π Version Information
// Include version history and migration guides
/**
* @api {get} /api/v2/users Get Users (v2)
* @apiVersion 2.0.0
* @apiDescription Retrieve users with enhanced filtering
*
* @apiParam {String} [status] User status (active, inactive, suspended)
* @apiParam {Date} [createdAfter] Filter users created after this date
*
* @apiSuccess {Object[]} data List of users
* @apiSuccess {Number} data.id User ID
* @apiSuccess {String} data.name User name
* @apiSuccess {String} data.status User status
*/
π§ͺ Interactive Testing
// Include API testing capabilities
// Use tools like:
- Swagger UI (interactive documentation)
- Postman Collections
- Insomnia workspaces
- curl examples for each endpoint
// Example curl command in docs:
/*
curl -X GET "https://api.example.com/users?page=1&limit=10" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json"
*/
API Testing & Debugging
π§ͺ API Testing Strategies
Comprehensive testing ensures your API works correctly and reliably.
π§ Unit Testing
Test individual API components
const request = require('supertest');
const app = require('../app');
describe('GET /api/users', () => {
it('should return all users', async () => {
const response = await request(app)
.get('/api/users')
.expect(200)
.expect('Content-Type', /json/);
expect(Array.isArray(response.body.data)).toBe(true);
expect(response.body.data.length).toBeGreaterThan(0);
});
it('should filter users by status', async () => {
const response = await request(app)
.get('/api/users?status=active')
.expect(200);
response.body.data.forEach(user => {
expect(user.status).toBe('active');
});
});
});
π Integration Testing
Test API interactions with external services
// Test with mocked external services
const nock = require('nock');
describe('Payment API Integration', () => {
it('should process payment successfully', async () => {
// Mock payment service
nock('https://api.paymentgateway.com')
.post('/charge')
.reply(200, {
transactionId: 'txn_123',
status: 'success'
});
const response = await request(app)
.post('/api/payments')
.send({
amount: 100,
cardToken: 'tok_123'
})
.expect(200);
expect(response.body.transactionId).toBe('txn_123');
expect(response.body.status).toBe('success');
});
});
π End-to-End Testing
Test complete user workflows
// E2E test with real browser
const puppeteer = require('puppeteer');
describe('User Registration Flow', () => {
let browser;
let page;
beforeAll(async () => {
browser = await puppeteer.launch();
page = await browser.newPage();
});
afterAll(async () => {
await browser.close();
});
it('should register new user successfully', async () => {
await page.goto('http://localhost:3000/register');
await page.type('#name', 'John Doe');
await page.type('#email', 'john@example.com');
await page.type('#password', 'password123');
await page.click('#register-btn');
await page.waitForSelector('.success-message');
const successMessage = await page.$eval('.success-message', el => el.textContent);
expect(successMessage).toContain('Registration successful');
});
});
π API Debugging Tools
Essential tools for debugging and monitoring API issues.
π¬ Postman
API testing and development tool
- Request/response inspection
- Automated testing
- Environment management
- API documentation generation
π Charles Proxy / Fiddler
HTTP debugging proxy
- Request/response interception
- Traffic analysis
- SSL certificate inspection
- Performance monitoring
π API Monitoring Tools
Monitor API performance and errors
- New Relic APM
- DataDog
- Application Insights
- Custom logging solutions
π¨ Error Handling & Monitoring
Implement robust error handling and monitoring for production APIs.
π Structured Error Responses
// Consistent error response format
class APIError extends Error {
constructor(code, message, statusCode = 500, details = null) {
super(message);
this.code = code;
this.statusCode = statusCode;
this.details = details;
this.timestamp = new Date().toISOString();
}
}
// Error handling middleware
app.use((error, req, res, next) => {
// Log error
console.error('API Error:', {
message: error.message,
stack: error.stack,
url: req.url,
method: req.method,
timestamp: new Date().toISOString()
});
// Send structured error response
const statusCode = error.statusCode || 500;
const errorResponse = {
error: {
code: error.code || 'INTERNAL_ERROR',
message: error.message,
details: error.details,
timestamp: error.timestamp || new Date().toISOString()
}
};
// Include stack trace in development
if (process.env.NODE_ENV === 'development') {
errorResponse.error.stack = error.stack;
}
res.status(statusCode).json(errorResponse);
});
π Health Checks & Monitoring
// Health check endpoint
app.get('/health', async (req, res) => {
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
checks: {}
};
try {
// Database check
await db.query('SELECT 1');
health.checks.database = 'healthy';
} catch (error) {
health.checks.database = 'unhealthy';
health.status = 'unhealthy';
}
try {
// External API check
await axios.get('https://external-api.com/health');
health.checks.externalAPI = 'healthy';
} catch (error) {
health.checks.externalAPI = 'unhealthy';
health.status = 'degraded';
}
const statusCode = health.status === 'healthy' ? 200 :
health.status === 'degraded' ? 200 : 503;
res.status(statusCode).json(health);
});
// Metrics collection
const responseTime = require('response-time');
app.use(responseTime((req, res, time) => {
// Log response time
console.log(`${req.method} ${req.url} took ${time}ms`);
// Send to monitoring service
monitoring.histogram('api_response_time', time, {
method: req.method,
endpoint: req.route?.path || req.url
});
}));
Panda Core REST API Tools
REST API Tools Suite
π JSON Formatter & Validator
Format, validate, and beautify JSON with syntax highlighting and error detection for API response testing and data validation.
π Base64 Encoder/Decoder
Safe Base64 conversion for text and files with client-side processing for API authentication and data encoding testing.