Learn

Navigate through learn topics

APIs

Understanding Application Programming Interfaces, REST principles and API design patterns

Last updated: 8/15/2025

Master the art of building and consuming APIs - the communication layer that powers modern applications and enables system integration.

What are APIs?

The Core Concept

Application Programming Interfaces for communication between systems

An API is like a waiter in a restaurant. You (the client) don't go into the kitchen (the server) to get your food. Instead, you tell the waiter (the API) what you want and they bring it back to you. The waiter knows the restaurant's menu and can communicate your order to the kitchen.

Real-world analogy: Think of APIs like electrical outlets. You don't need to understand how electricity is generated or transmitted - you just plug in your device and it works. APIs provide a standard way for different systems to communicate without needing to understand each other's internal workings.

Types of APIs

Web APIs (HTTP APIs)

APIs that communicate over the web

Web APIs use HTTP (HyperText Transfer Protocol) to send requests and receive responses between clients and servers.

Common protocols:

  • REST: Representational State Transfer
  • GraphQL: Query language for APIs
  • gRPC: High-performance RPC framework
  • SOAP: Simple Object Access Protocol (legacy)

Library/Framework APIs

Code interfaces within applications

These are the functions and classes you use when programming.

// Example: JavaScript Array API
const numbers = [1, 2, 3, 4, 5];

// Using the Array API methods
numbers.push(6);           // Add element
numbers.pop();             // Remove last element
numbers.map(x => x * 2);   // Transform elements
numbers.filter(x => x > 3); // Filter elements

Operating System APIs

System-level interfaces

APIs that allow applications to interact with the operating system.

# Example: Python OS API
import os

# File operations
os.listdir('.')           # List directory contents
os.mkdir('new_folder')    # Create directory
os.remove('file.txt')     # Delete file

REST APIs

REST Principles

The foundation of modern web APIs

Representational State Transfer is an architectural style for designing networked applications.

Key principles:

  • Stateless: Each request contains all information needed
  • Client-Server: Separation of concerns
  • Cacheable: Responses can be cached
  • Uniform Interface: Consistent way to interact
  • Layered System: Can be built in layers

HTTP Methods

The verbs of REST APIs

GET: Retrieve data

GET /api/users/123
GET /api/products?category=electronics

POST: Create new data

POST /api/users
Content-Type: application/json

{
  "name": "John Doe",
  "email": "john@example.com"
}

PUT: Update existing data (complete replacement)

PUT /api/users/123
Content-Type: application/json

{
  "name": "John Smith",
  "email": "john.smith@example.com"
}

PATCH: Partial update

PATCH /api/users/123
Content-Type: application/json

{
  "email": "newemail@example.com"
}

DELETE: Remove data

DELETE /api/users/123

HTTP Status Codes

Standard responses from servers

2xx Success:

  • 200 OK: Request succeeded
  • 201 Created: Resource created successfully
  • 204 No Content: Request succeeded, no content returned

3xx Redirection:

  • 301 Moved Permanently: Resource moved to new location
  • 304 Not Modified: Resource hasn't changed

4xx Client Error:

  • 400 Bad Request: Invalid request
  • 401 Unauthorized: Authentication required
  • 403 Forbidden: Access denied
  • 404 Not Found: Resource doesn't exist
  • 422 Unprocessable Entity: Request valid but can't be processed

5xx Server Error:

  • 500 Internal Server Error: Server error
  • 502 Bad Gateway: Gateway error
  • 503 Service Unavailable: Service temporarily unavailable

API Design Best Practices

URL Structure

Clean, intuitive endpoints

Good examples:

GET    /api/users              # List all users
GET    /api/users/123          # Get specific user
POST   /api/users              # Create new user
PUT    /api/users/123          # Update user
DELETE /api/users/123          # Delete user

GET    /api/users/123/orders   # Get user's orders
POST   /api/users/123/orders   # Create order for user

Bad examples:

GET    /api/getUsers
POST   /api/createUser
GET    /api/user?id=123
POST   /api/user/update

Request/Response Format

Consistent data structure

Request format:

{
  "name": "John Doe",
  "email": "john@example.com",
  "age": 30
}

Response format:

{
  "success": true,
  "data": {
    "id": 123,
    "name": "John Doe",
    "email": "john@example.com",
    "age": 30,
    "created_at": "2024-01-15T10:30:00Z"
  },
  "message": "User created successfully"
}

Error Handling

Consistent error responses

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Email is required",
    "details": {
      "field": "email",
      "issue": "Field cannot be empty"
    }
  }
}

Authentication and Authorization

API Keys

Simple authentication method

GET /api/users
Authorization: Bearer sk_live_abc123def456

Pros: Simple to implement and use Cons: No user context, hard to revoke

JWT (JSON Web Tokens)

Stateless authentication

GET /api/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Structure:

header.payload.signature

Example payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 1516242622
}

OAuth 2.0

Industry standard for authorization

Flow types:

  • Authorization Code: Most secure, for web applications
  • Client Credentials: Server-to-server communication
  • Password Grant: Legacy, not recommended for new apps
  • Implicit Grant: Legacy, not recommended

API Documentation

OpenAPI (Swagger)

Standard for API documentation

openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
  description: API for managing users

paths:
  /users:
    get:
      summary: List users
      responses:
        '200':
          description: List of users
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
    
    post:
      summary: Create user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserInput'
      responses:
        '201':
          description: User created

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        email:
          type: string
    UserInput:
      type: object
      required:
        - name
        - email
      properties:
        name:
          type: string
        email:
          type: string

Documentation Tools

Making APIs discoverable

Popular tools:

  • Swagger UI: Interactive documentation
  • Postman: API testing and documentation
  • Insomnia: API design and testing
  • Redoc: Beautiful documentation generator

API Testing

Manual Testing

Using tools to test APIs

Postman example:

  1. Create new request
  2. Set method (GET, POST, etc.)
  3. Enter URL
  4. Add headers and body
  5. Send request
  6. Review response

Automated Testing

Programmatic API testing

// Example with Jest and Supertest
const request = require('supertest');
const app = require('../app');

describe('User API', () => {
  test('GET /api/users returns users', async () => {
    const response = await request(app)
      .get('/api/users')
      .expect(200);
    
    expect(response.body.success).toBe(true);
    expect(Array.isArray(response.body.data)).toBe(true);
  });

  test('POST /api/users creates user', async () => {
    const userData = {
      name: 'Test User',
      email: 'test@example.com'
    };

    const response = await request(app)
      .post('/api/users')
      .send(userData)
      .expect(201);
    
    expect(response.body.data.name).toBe(userData.name);
    expect(response.body.data.email).toBe(userData.email);
  });
});

Rate Limiting and Throttling

Why Rate Limiting?

Protecting your API from abuse

Benefits:

  • Prevents abuse and spam
  • Ensures fair usage
  • Protects server resources
  • Generates revenue (for paid tiers)

Implementation Strategies

Token Bucket Algorithm:

class RateLimiter {
  constructor(tokensPerSecond, bucketSize) {
    this.tokensPerSecond = tokensPerSecond;
    this.bucketSize = bucketSize;
    this.tokens = bucketSize;
    this.lastRefill = Date.now();
  }

  allowRequest() {
    this.refillTokens();
    
    if (this.tokens > 0) {
      this.tokens--;
      return true;
    }
    
    return false;
  }

  refillTokens() {
    const now = Date.now();
    const timePassed = (now - this.lastRefill) / 1000;
    const tokensToAdd = timePassed * this.tokensPerSecond;
    
    this.tokens = Math.min(this.bucketSize, this.tokens + tokensToAdd);
    this.lastRefill = now;
  }
}

Response headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1642233600

API Versioning

Why Version APIs?

Managing changes without breaking existing clients

Strategies:

  • URL versioning: /api/v1/users
  • Header versioning: Accept: application/vnd.api+json;version=1
  • Query parameter: /api/users?version=1

Versioning Best Practices

Semantic versioning:

  • Major version: Breaking changes
  • Minor version: New features, backward compatible
  • Patch version: Bug fixes, backward compatible

Deprecation strategy:

GET /api/v1/users
Deprecation: true
Sunset: 2025-12-31
Link: </api/v2/users>; rel="successor-version"

GraphQL APIs

What is GraphQL?

Query language for APIs

GraphQL allows clients to request exactly the data they need, nothing more and nothing less.

Example query:

query {
  user(id: 123) {
    name
    email
    posts {
      title
      content
    }
  }
}

Benefits:

  • Single endpoint
  • No over-fetching or under-fetching
  • Strong typing
  • Real-time updates with subscriptions

Considerations:

  • More complex than REST
  • Can lead to N+1 query problems
  • Caching is more challenging

gRPC APIs

What is gRPC?

High-performance RPC framework

gRPC uses Protocol Buffers and HTTP/2 for efficient communication between services.

Example proto file:

syntax = "proto3";

service UserService {
  rpc GetUser(GetUserRequest) returns (User);
  rpc CreateUser(CreateUserRequest) returns (User);
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
}

message User {
  int32 id = 1;
  string name = 2;
  string email = 3;
}

message GetUserRequest {
  int32 id = 1;
}

message CreateUserRequest {
  string name = 1;
  string email = 2;
}

Benefits:

  • Very fast
  • Strong typing
  • Bidirectional streaming
  • Code generation

Use cases:

  • Microservices communication
  • Real-time applications
  • High-performance requirements

API Security

Common Security Threats

OWASP Top 10 for APIs:

  1. Broken Object Level Authorization: Accessing other users' data
  2. Broken User Authentication: Weak authentication mechanisms
  3. Excessive Data Exposure: Returning too much data
  4. Lack of Rate Limiting: Allowing abuse
  5. Broken Function Level Authorization: Accessing admin functions

Security Best Practices

Input validation:

// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
  throw new Error('Invalid email format');
}

// Sanitise input
const sanitisedInput = input.replace(/[<>]/g, '');

HTTPS enforcement:

// Redirect HTTP to HTTPS
app.use((req, res, next) => {
  if (req.header('x-forwarded-proto') !== 'https') {
    res.redirect(`https://${req.header('host')}${req.url}`);
  } else {
    next();
  }
});

API Monitoring and Analytics

What to Monitor

Key metrics for API health

Performance metrics:

  • Response time
  • Throughput (requests per second)
  • Error rates
  • Availability (uptime)

Business metrics:

  • API usage by endpoint
  • User engagement
  • Revenue per API call
  • Geographic distribution

Monitoring Tools

Popular options:

  • Datadog: Comprehensive monitoring
  • New Relic: Application performance monitoring
  • Prometheus: Open-source monitoring
  • Grafana: Visualisation and alerting

Getting Started

Building Your First API

Step 1: Choose a framework

// Express.js example
const express = require('express');
const app = express();

app.use(express.json());

app.get('/api/users', (req, res) => {
  res.json({
    success: true,
    data: [
      { id: 1, name: 'John Doe' },
      { id: 2, name: 'Jane Smith' }
    ]
  });
});

app.listen(3000, () => {
  console.log('API running on port 3000');
});

Step 2: Add authentication

const jwt = require('jsonwebtoken');

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();
  });
};

app.get('/api/protected', authenticateToken, (req, res) => {
  res.json({ message: 'Protected data', user: req.user });
});

Step 3: Add validation

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

app.post('/api/users', [
  body('name').notEmpty().trim().escape(),
  body('email').isEmail().normalizeEmail()
], (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  
  // Process valid data
  res.status(201).json({ message: 'User created' });
});

Testing Your API

Using curl:

# GET request
curl -X GET http://localhost:3000/api/users

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

Using Postman:

  1. Import your OpenAPI specification
  2. Test each endpoint
  3. Verify responses
  4. Check error handling

Learning Resources

Documentation

  • REST API Tutorial
  • GraphQL Documentation
  • gRPC Documentation

Online Courses

  • API and Web Service Introduction courses
  • REST API Design courses

Books

  • RESTful Web Services
  • GraphQL in Action
  • gRPC: Up and Running

Summary

APIs are the backbone of modern software architecture, enabling systems to communicate and integrate seamlessly.

Key takeaways:

  • REST APIs follow standard HTTP methods and status codes
  • Design APIs with consistency and clarity in mind
  • Implement proper authentication and authorization
  • Document your APIs thoroughly
  • Monitor and test your APIs regularly
  • Consider GraphQL and gRPC for specific use cases

Remember: Good API design is about creating interfaces that are intuitive, reliable and maintainable for both developers and end users!