A Full Guide Understand Everything About APIS With Examples
March 26, 2026
TL;DR
APIs (Application Programming Interfaces) let programs talk to each other. REST is the most common pattern (HTTP + JSON), but GraphQL (query language for data), WebSockets (real-time), and gRPC (fast RPC) serve different needs. Authentication uses tokens (Bearer, API keys) or OAuth for user delegation. Errors return status codes (2xx success, 4xx client error, 5xx server error). Master these patterns and you can consume any API confidently.
An API is a contract: "Call me with X data, I'll give you Y result." Without APIs, everything would be isolated. With them, your app talks to payment processors, maps services, AI models, and databases. This guide covers the mental model (what's actually happening), the most common patterns (REST, GraphQL, WebSocket), and practical examples you can run today. Whether you're integrating a third-party service or building an API for others to use, understanding these patterns makes you dangerous.
The Mental Model: What's an API?
Simplified: An API is a function on someone else's server that you call over the internet.
// Local function
function getUser(userId) {
return database.query(`SELECT * FROM users WHERE id = ${userId}`);
}
// API (same thing, but over HTTP)
fetch('https://api.example.com/users/123')
.then(res => res.json());
Key difference: With local functions, you call them directly. With APIs, you send a request over HTTP, and the server sends back a response.
Every API request has:
- Method (what action: GET, POST, PUT, DELETE)
- Endpoint (where: /users/123)
- Headers (metadata: authentication, content type)
- Body (optional data payload)
Every API response has:
- Status code (success or failure: 200, 404, 500)
- Headers (metadata about the response)
- Body (the actual data, usually JSON)
REST: The Most Common Pattern
REST (Representational State Transfer) is the industry standard. It uses HTTP verbs to describe actions on resources (nouns).
REST Basics
GET /posts → Fetch all posts
GET /posts/123 → Fetch post #123
POST /posts → Create a new post
PUT /posts/123 → Update post #123
DELETE /posts/123 → Delete post #123
Why verbs + nouns matter:
- GET is safe (doesn't change data)
- DELETE is dangerous (removes data)
- POST creates; PUT updates
REST Example: Fetch Data
// Simple GET request
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(res => res.json())
.then(data => console.log(data));
// Output: { id: 1, title: "...", body: "...", userId: 1 }
REST Example: Create Data (POST)
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'My Post',
body: 'This is the content',
userId: 1
})
})
.then(res => res.json())
.then(data => console.log(data));
// Output: { id: 101, title: "My Post", body: "This is the content", userId: 1 }
REST Example: Update Data (PUT)
fetch('https://jsonplaceholder.typicode.com/posts/1', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: 1,
title: 'Updated Title',
body: 'Updated body',
userId: 1
})
})
.then(res => res.json())
.then(data => console.log(data));
REST Example: Delete Data
fetch('https://jsonplaceholder.typicode.com/posts/1', {
method: 'DELETE'
})
.then(res => res.json())
.then(data => console.log('Deleted:', data));
GraphQL: Query Language for APIs
GraphQL lets you request exactly the data you need—nothing more, nothing less.
REST vs GraphQL
REST:
GET /posts/1
→ { id, title, body, userId, createdAt, updatedAt, authorEmail, ... }
(you get everything; wasteful)
GraphQL:
query {
post(id: 1) {
title
body
author { name email }
}
}
→ { post: { title: "...", body: "...", author: { name: "...", email: "..." } } }
(you get only what you asked for)
GraphQL Example
const query = `
query {
posts {
id
title
author {
name
}
}
}
`;
fetch('https://api.example.com/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query })
})
.then(res => res.json())
.then(data => console.log(data.data.posts));
Advantages:
- No overfetching (get only what you need)
- No underfetching (no multiple round-trips)
- Strong typing (schema describes what's available)
Disadvantages:
- More complex to learn
- File uploads awkward
- Query complexity can burden servers
WebSocket: Real-Time Communication
WebSocket maintains an open connection for real-time updates (chat, notifications, live data).
WebSocket Example
const ws = new WebSocket('wss://api.example.com/live');
ws.addEventListener('open', () => {
console.log('Connected');
ws.send(JSON.stringify({ action: 'subscribe', channel: 'prices' }));
});
ws.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
console.log('Price update:', data);
});
ws.addEventListener('close', () => {
console.log('Disconnected');
});
Use cases:
- Real-time chat
- Live stock prices, cryptocurrency
- Live notifications
- Collaborative tools (Google Docs-style)
Difference from REST:
- REST is request-response (client asks, server answers, connection closes)
- WebSocket is two-way (client and server send data anytime)
Authentication: How APIs Know Who You Are
1. API Key
Simple token sent with each request.
fetch('https://api.example.com/data', {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
})
Pros: Simple, works for server-to-server Cons: If leaked, anyone can use it
2. OAuth 2.0 (User Delegation)
User logs in on your app, grants permission to access their data on another service (Gmail, GitHub, Spotify). Your app never sees their password.
// User clicks "Sign in with Google"
// Google authenticates user, sends your app a token
// Your app uses that token to fetch user's Google data
fetch('https://www.googleapis.com/calendar/v3/calendars/primary/events', {
headers: { 'Authorization': `Bearer ${googleToken}` }
})
Pros: User controls what data you access; you never handle passwords Cons: More complex setup
3. Session Cookies
Server sets a cookie; browser automatically sends it with each request.
// Login endpoint sets a cookie
fetch('https://api.example.com/login', {
method: 'POST',
credentials: 'include', // Send cookies
body: JSON.stringify({ username, password })
})
// Subsequent requests automatically include the cookie
fetch('https://api.example.com/me', {
credentials: 'include'
})
Pros: Works well for web apps; cookie is automatically sent Cons: CSRF attacks possible; less suitable for mobile/API clients
HTTP Status Codes: Understanding Responses
| Code | Meaning | Example |
|---|---|---|
| 2xx | Success | |
| 200 | OK (request succeeded) | GET /posts → returns posts |
| 201 | Created (new resource created) | POST /posts → creates post |
| 204 | No Content (success, no data returned) | DELETE /posts/1 |
| 3xx | Redirect | |
| 301 | Moved Permanently | Old URL → new URL |
| 304 | Not Modified (cached response OK) | If-Modified-Since header |
| 4xx | Client Error | |
| 400 | Bad Request (malformed request) | Missing required field |
| 401 | Unauthorized (authentication required) | Forgot API key |
| 403 | Forbidden (you don't have permission) | Non-admin accessing admin endpoint |
| 404 | Not Found (resource doesn't exist) | GET /posts/999 |
| 429 | Too Many Requests (rate limited) | Made too many requests too fast |
| 5xx | Server Error | |
| 500 | Internal Server Error (something broke) | Bug in server code |
| 503 | Service Unavailable (temporarily down) | Maintenance or overload |
Golden rule: If status is 2xx, the request succeeded. If 4xx or 5xx, something went wrong.
Error Handling: When Things Go Wrong
async function fetchData(url) {
try {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
return await res.json();
} catch (err) {
console.error('API error:', err.message);
// Handle gracefully: show user message, retry, etc.
}
}
Common scenarios:
- Network error: Can't reach the server (timeout, no internet). Retry logic helps.
- 4xx error: Bad request on your end (fix the request)
- 5xx error: Server broken (retry or escalate)
- Rate limiting (429): Too many requests. Wait before retrying.
Rate Limiting: Play Nice
APIs often limit requests to prevent abuse.
// Check headers for rate limit info
const remaining = res.headers.get('X-RateLimit-Remaining');
const resetTime = res.headers.get('X-RateLimit-Reset');
if (remaining === '0') {
console.log(`Rate limited. Reset at ${new Date(resetTime * 1000)}`);
}
Best practice: Check remaining quota before each request; back off gracefully when limited.
Real-World Example: Building a GitHub User Lookup
async function getGitHubUser(username) {
const url = `https://api.github.com/users/${username}`;
try {
const res = await fetch(url);
if (res.status === 404) {
throw new Error('User not found');
}
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const user = await res.json();
console.log(`${user.name} has ${user.public_repos} repos`);
} catch (err) {
console.error('Error:', err.message);
}
}
getGitHubUser('torvalds'); // Linus Torvalds
Conclusion
APIs are how modern software talks. REST dominates for simplicity; GraphQL excels for flexibility; WebSocket enables real-time. Authentication varies by use case (API keys, OAuth, cookies). Status codes tell you success or failure; error handling makes your app resilient. With these patterns mastered, you can integrate any third-party service, build APIs others consume, and debug network issues confidently. Start with a simple REST API (like JSONPlaceholder), then graduate to real services (GitHub, Stripe, OpenAI) as you grow comfortable.