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 succeeded201 Created: Resource created successfully204 No Content: Request succeeded, no content returned
3xx Redirection:
301 Moved Permanently: Resource moved to new location304 Not Modified: Resource hasn't changed
4xx Client Error:
400 Bad Request: Invalid request401 Unauthorized: Authentication required403 Forbidden: Access denied404 Not Found: Resource doesn't exist422 Unprocessable Entity: Request valid but can't be processed
5xx Server Error:
500 Internal Server Error: Server error502 Bad Gateway: Gateway error503 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:
- Create new request
- Set method (GET, POST, etc.)
- Enter URL
- Add headers and body
- Send request
- 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:
- Broken Object Level Authorization: Accessing other users' data
- Broken User Authentication: Weak authentication mechanisms
- Excessive Data Exposure: Returning too much data
- Lack of Rate Limiting: Allowing abuse
- 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:
- Import your OpenAPI specification
- Test each endpoint
- Verify responses
- 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!