What is a REST API?
A REST API (Representational State Transfer Application Programming Interface) is an architectural style for building web services that use HTTP requests to access and manipulate data. REST APIs enable communication between client applications and servers through a standardized set of operations (GET, POST, PUT, DELETE, etc.) over HTTP/HTTPS protocols.
REST Principles
1. Client-Server Architecture
// Client makes request
const response = await fetch('https://api.example.com/users/123');
// Server processes and responds
// {
// "id": 123,
// "name": "John Doe",
// "email": "john@example.com"
// }
2. Stateless Communication
Each request contains all information needed:
// Each request is independent
const request1 = await fetch('https://api.example.com/users/1', {
headers: {
'Authorization': 'Bearer token123',
'Content-Type': 'application/json'
}
});
const request2 = await fetch('https://api.example.com/users/2', {
headers: {
'Authorization': 'Bearer token123', // Auth included in every request
'Content-Type': 'application/json'
}
});
3. Cacheable Responses
// Server indicates cacheability
Response Headers:
Cache-Control: max-age=3600
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
// Client can cache and reuse
const response = await fetch('https://api.example.com/products', {
headers: {
'If-None-Match': '"33a64df551425fcc55e4d42a148795d9f25f89d4"'
}
});
if (response.status === 304) {
// Use cached version
return cachedData;
}
4. Uniform Interface
// Standard HTTP methods map to CRUD operations
GET /api/users // Read all users
GET /api/users/123 // Read specific user
POST /api/users // Create new user
PUT /api/users/123 // Update user (full)
PATCH /api/users/123 // Update user (partial)
DELETE /api/users/123 // Delete user
5. Layered System
Client → [Proxy/Load Balancer] → [API Gateway] → [Application Server] → [Database]
Each layer is independent and doesn’t know about layers beyond the next one.
6. Code on Demand (Optional)
// Server can send executable code
const response = await fetch('https://api.example.com/widget');
const { script } = await response.json();
// Client executes server-provided code
eval(script); // In practice, use safer alternatives
HTTP Methods in REST
GET - Retrieve Data
// Get all resources
const users = await fetch('https://api.example.com/users')
.then(r => r.json());
// Get specific resource
const user = await fetch('https://api.example.com/users/123')
.then(r => r.json());
// Query parameters for filtering
const activeUsers = await fetch('https://api.example.com/users?status=active&role=admin')
.then(r => r.json());
POST - Create Resource
const newUser = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({
name: 'Jane Smith',
email: 'jane@example.com',
role: 'admin'
})
});
const created = await newUser.json();
// Response: { id: 456, name: 'Jane Smith', ... }
PUT - Update Resource (Full Replacement)
const updated = await fetch('https://api.example.com/users/123', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({
id: 123,
name: 'John Updated',
email: 'john.updated@example.com',
role: 'user',
active: true
})
});
PATCH - Update Resource (Partial)
const patched = await fetch('https://api.example.com/users/123', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({
email: 'new.email@example.com' // Only update email
})
});
DELETE - Remove Resource
const deleted = await fetch('https://api.example.com/users/123', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer token123'
}
});
if (deleted.status === 204) {
console.log('User deleted successfully');
}
REST APIs with CorsProxy
Solving CORS Issues
// Direct request blocked by CORS
fetch('https://external-api.com/data')
.catch(error => {
// Error: CORS policy blocked
});
// Route through CorsProxy
const data = await fetch(
'https://corsproxy.io/?url=https://external-api.com/data',
{
headers: {
'x-cors-api-key': process.env.CORS_API_KEY
}
}
).then(r => r.json());
API Client with Proxy
class ProxiedAPIClient {
private baseURL: string;
private proxyURL = 'https://corsproxy.io/';
private apiKey: string;
constructor(baseURL: string, apiKey: string) {
this.baseURL = baseURL;
this.apiKey = apiKey;
}
private async request(
endpoint: string,
options: RequestInit = {}
): Promise<Response> {
const url = `${this.proxyURL}${this.baseURL}${endpoint}`;
return fetch(url, {
...options,
headers: {
'x-cors-api-key': process.env.CORS_API_KEY!,
'Content-Type': 'application/json',
...options.headers
}
});
}
async get(endpoint: string) {
const response = await this.request(endpoint);
return response.json();
}
async post(endpoint: string, data: any) {
const response = await this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data)
});
return response.json();
}
async put(endpoint: string, data: any) {
const response = await this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data)
});
return response.json();
}
async delete(endpoint: string) {
const response = await this.request(endpoint, {
method: 'DELETE'
});
return response.status === 204 ? null : response.json();
}
}
// Usage
const api = new ProxiedAPIClient('https://api.example.com', 'api-key');
const users = await api.get('/users');
const newUser = await api.post('/users', { name: 'John' });
Rate Limiting and Retry Logic
class ResilientAPIClient {
private async fetchWithRetry(
url: string,
options: RequestInit = {},
maxRetries = 3
): Promise<Response> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(
`https://corsproxy.io/?url=${url}`,
{
...options,
headers: {
'x-cors-api-key': process.env.CORS_API_KEY!,
...options.headers
}
}
);
// Handle rate limiting
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
const delay = retryAfter ? parseInt(retryAfter) * 1000 : 1000 * (attempt + 1);
console.log(`Rate limited, retrying after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
if (!response.ok && response.status >= 500) {
throw new Error(`Server error: ${response.status}`);
}
return response;
} catch (error) {
if (attempt === maxRetries - 1) throw error;
const delay = 1000 * Math.pow(2, attempt); // Exponential backoff
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error('Max retries exceeded');
}
async get(url: string) {
const response = await this.fetchWithRetry(url);
return response.json();
}
}
REST API Best Practices
1. Versioning
// URL versioning (most common)
GET https://api.example.com/v1/users
GET https://api.example.com/v2/users
// Header versioning
fetch('https://api.example.com/users', {
headers: {
'API-Version': '2.0'
}
});
// Media type versioning
fetch('https://api.example.com/users', {
headers: {
'Accept': 'application/vnd.company.v2+json'
}
});
2. Pagination
// Offset-based pagination
const page1 = await fetch(
'https://api.example.com/users?limit=20&offset=0'
);
const page2 = await fetch(
'https://api.example.com/users?limit=20&offset=20'
);
// Cursor-based pagination (better for large datasets)
const response = await fetch(
'https://api.example.com/users?cursor=eyJpZCI6MTIzfQ=='
);
const data = await response.json();
// {
// items: [...],
// nextCursor: 'eyJpZCI6MTQzfQ==',
// hasMore: true
// }
3. Filtering and Sorting
// Filtering
GET /api/products?category=electronics&price_min=100&price_max=500
// Sorting
GET /api/products?sort=price&order=desc
// Multiple sort fields
GET /api/products?sort=category,price&order=asc,desc
// Implementation
class APIClient {
async getProducts(filters: {
category?: string;
priceMin?: number;
priceMax?: number;
sort?: string;
order?: 'asc' | 'desc';
}) {
const params = new URLSearchParams();
if (filters.category) params.append('category', filters.category);
if (filters.priceMin) params.append('price_min', filters.priceMin.toString());
if (filters.priceMax) params.append('price_max', filters.priceMax.toString());
if (filters.sort) params.append('sort', filters.sort);
if (filters.order) params.append('order', filters.order);
const response = await fetch(
`https://corsproxy.io/?url=https://api.example.com/products?${params}`,
{
headers: {
'x-cors-api-key': process.env.CORS_API_KEY!
}
}
);
return response.json();
}
}
4. Error Handling
// Standard HTTP status codes
200 OK // Success
201 Created // Resource created
204 No Content // Success, no response body
400 Bad Request // Invalid request
401 Unauthorized // Authentication required
403 Forbidden // Insufficient permissions
404 Not Found // Resource doesn't exist
429 Too Many Requests // Rate limit exceeded
500 Internal Error // Server error
503 Service Unavailable // Temporary unavailability
// Error response format
interface APIError {
error: {
code: string;
message: string;
details?: any;
};
}
// Client-side error handling
async function handleAPIRequest(url: string) {
const response = await fetch(`https://corsproxy.io/?url=${url}`, {
headers: {
'x-cors-api-key': process.env.CORS_API_KEY!
}
});
if (!response.ok) {
const error: APIError = await response.json();
switch (response.status) {
case 400:
throw new Error(`Bad Request: ${error.error.message}`);
case 401:
throw new Error('Unauthorized: Please authenticate');
case 403:
throw new Error('Forbidden: Insufficient permissions');
case 404:
throw new Error('Not Found: Resource does not exist');
case 429:
throw new Error('Rate limit exceeded');
case 500:
throw new Error('Server error: Please try again later');
default:
throw new Error(`API Error: ${error.error.message}`);
}
}
return response.json();
}
5. Authentication
// Bearer Token (most common)
fetch('https://corsproxy.io/?url=https://api.example.com/protected', {
headers: {
'x-cors-api-key': process.env.CORS_API_KEY!,
'Authorization': `Bearer ${accessToken}`
}
});
// API Key
fetch('https://corsproxy.io/?url=https://api.example.com/data', {
headers: {
'x-cors-api-key': process.env.CORS_API_KEY!,
'X-API-Key': 'your-api-key'
}
});
// OAuth 2.0 Flow
class OAuth2Client {
private accessToken?: string;
private refreshToken?: string;
async authenticate(code: string) {
const response = await fetch(
'https://corsproxy.io/?url=https://oauth.example.com/token',
{
method: 'POST',
headers: {
'x-cors-api-key': process.env.CORS_API_KEY!,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
client_id: 'your-client-id',
client_secret: 'your-client-secret',
redirect_uri: 'https://yourapp.com/callback'
})
}
);
const tokens = await response.json();
this.accessToken = tokens.access_token;
this.refreshToken = tokens.refresh_token;
}
async refreshAccessToken() {
const response = await fetch(
'https://corsproxy.io/?url=https://oauth.example.com/token',
{
method: 'POST',
headers: {
'x-cors-api-key': process.env.CORS_API_KEY!,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: this.refreshToken!,
client_id: 'your-client-id',
client_secret: 'your-client-secret'
})
}
);
const tokens = await response.json();
this.accessToken = tokens.access_token;
}
async makeRequest(url: string) {
let response = await fetch(`https://corsproxy.io/?url=${url}`, {
headers: {
'x-cors-api-key': process.env.CORS_API_KEY!,
'Authorization': `Bearer ${this.accessToken}`
}
});
if (response.status === 401) {
await this.refreshAccessToken();
response = await fetch(`https://corsproxy.io/?url=${url}`, {
headers: {
'x-cors-api-key': process.env.CORS_API_KEY!,
'Authorization': `Bearer ${this.accessToken}`
}
});
}
return response.json();
}
}
REST API Documentation
OpenAPI/Swagger
// Fetch API schema
const schema = await fetch(
'https://corsproxy.io/?url=https://api.example.com/openapi.json',
{
headers: {
'x-cors-api-key': process.env.CORS_API_KEY!
}
}
).then(r => r.json());
// Generate TypeScript types from schema
interface User {
id: number;
name: string;
email: string;
created_at: string;
}
// Type-safe API client
class TypedAPIClient {
async getUser(id: number): Promise<User> {
const response = await fetch(
`https://corsproxy.io/?url=https://api.example.com/users/${id}`,
{
headers: {
'x-cors-api-key': process.env.CORS_API_KEY!
}
}
);
return response.json();
}
}
Testing REST APIs
// Integration tests
import { describe, it, expect } from 'vitest';
describe('API Client', () => {
const api = new ProxiedAPIClient('https://api.example.com', 'test-key');
it('should fetch users', async () => {
const users = await api.get('/users');
expect(Array.isArray(users)).toBe(true);
});
it('should create user', async () => {
const newUser = await api.post('/users', {
name: 'Test User',
email: 'test@example.com'
});
expect(newUser).toHaveProperty('id');
expect(newUser.name).toBe('Test User');
});
it('should handle errors', async () => {
await expect(api.get('/nonexistent')).rejects.toThrow();
});
});