JSON Basics
JSON (JavaScript Object Notation) is a lightweight data format used for data exchange between client and server.
{
"name": "John Doe",
"age": 30,
"isActive": true,
"email": null,
"hobbies": ["reading", "coding", "gaming"],
"address": {
"street": "123 Main St",
"city": "New York",
"country": "USA"
},
"orders": [
{ "id": 1, "product": "Book", "price": 29.99 },
{ "id": 2, "product": "Laptop", "price": 999.99 }
]
}
// JSON.parse() - convert JSON string to JavaScript object
const jsonString = '{"name": "John", "age": 30}';
const obj = JSON.parse(jsonString);
console.log(obj.name); // "John"
// JSON.stringify() - convert JavaScript object to JSON string
const user = { name: "Jane", age: 25, active: true };
const json = JSON.stringify(user);
console.log(json); // '{"name":"Jane","age":25,"active":true}'
// Pretty print with indentation
const prettyJson = JSON.stringify(user, null, 2);
console.log(prettyJson);
// {
// "name": "Jane",
// "age": 25,
// "active": true
// }
// Selective stringify with replacer
const filtered = JSON.stringify(user, ["name", "age"]);
console.log(filtered); // '{"name":"Jane","age":25}'
// Transform during stringify
const transformed = JSON.stringify(user, (key, value) => {
if (typeof value === "string") {
return value.toUpperCase();
}
return value;
});
// Handle dates (they become strings)
const data = {
created: new Date(),
name: "Event"
};
console.log(JSON.stringify(data));
// {"created":"2024-01-15T10:30:00.000Z","name":"Event"}
// Parse with reviver (transform back)
const parsed = JSON.parse(json, (key, value) => {
if (key === "created") {
return new Date(value);
}
return value;
});
// Deep clone with JSON (loses functions, undefined, symbols)
const clone = JSON.parse(JSON.stringify(original));
- Keys must be strings (double quotes)
- No trailing commas allowed
- No comments allowed
- Functions, undefined, Symbol are not serialized
- Dates become strings
Fetch API Basics
The Fetch API provides a modern way to make HTTP requests.
// Basic GET request
async function getUsers() {
const response = await fetch("https://api.example.com/users");
const data = await response.json();
return data;
}
// With error handling
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error("Fetch error:", error);
throw error;
}
}
// POST request
async function createUser(userData) {
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(userData)
});
return response.json();
}
// PUT request (update)
async function updateUser(id, userData) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(userData)
});
return response.json();
}
// PATCH request (partial update)
async function patchUser(id, changes) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(changes)
});
return response.json();
}
// DELETE request
async function deleteUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: "DELETE"
});
if (!response.ok) {
throw new Error("Delete failed");
}
return true;
}
Request Options
Configure requests with headers, credentials, caching, and more.
// Full options object
const options = {
method: "POST", // GET, POST, PUT, PATCH, DELETE
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer token123",
"Accept": "application/json",
"X-Custom-Header": "value"
},
body: JSON.stringify(data), // String, FormData, Blob, etc.
mode: "cors", // cors, no-cors, same-origin
credentials: "include", // include, same-origin, omit
cache: "no-cache", // default, no-cache, reload, force-cache
redirect: "follow", // follow, error, manual
signal: controller.signal // AbortController signal
};
const response = await fetch(url, options);
// Using Headers object
const headers = new Headers();
headers.append("Content-Type", "application/json");
headers.append("Authorization", "Bearer token");
// Working with response
console.log(response.ok); // true if 200-299
console.log(response.status); // 200
console.log(response.statusText); // "OK"
console.log(response.url); // Final URL (after redirects)
console.log(response.type); // "basic", "cors", etc.
// Response headers
console.log(response.headers.get("Content-Type"));
// Iterate headers
for (const [key, value] of response.headers) {
console.log(`${key}: ${value}`);
}
// Different response types
const json = await response.json(); // Parse as JSON
const text = await response.text(); // Get as string
const blob = await response.blob(); // Get as Blob
const buffer = await response.arrayBuffer(); // Get as ArrayBuffer
const formData = await response.formData(); // Get as FormData
// Check content type before parsing
const contentType = response.headers.get("Content-Type");
if (contentType?.includes("application/json")) {
const data = await response.json();
} else {
const text = await response.text();
}
Authentication
Handle API authentication with tokens and credentials.
// Bearer Token Authentication
async function fetchWithAuth(url, token) {
return fetch(url, {
headers: {
"Authorization": `Bearer ${token}`
}
});
}
// API Key Authentication
async function fetchWithApiKey(url, apiKey) {
return fetch(url, {
headers: {
"X-API-Key": apiKey
}
});
}
// Basic Authentication
async function fetchWithBasicAuth(url, username, password) {
const credentials = btoa(`${username}:${password}`);
return fetch(url, {
headers: {
"Authorization": `Basic ${credentials}`
}
});
}
// Login and store token
async function login(email, password) {
const response = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password })
});
if (!response.ok) {
throw new Error("Login failed");
}
const { token, user } = await response.json();
// Store token (be careful with localStorage for sensitive data)
localStorage.setItem("authToken", token);
return user;
}
// Token refresh
async function refreshToken() {
const refreshToken = localStorage.getItem("refreshToken");
const response = await fetch("/api/auth/refresh", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refreshToken })
});
if (!response.ok) {
// Redirect to login
window.location.href = "/login";
return;
}
const { token } = await response.json();
localStorage.setItem("authToken", token);
return token;
}
// Authenticated fetch wrapper
async function authFetch(url, options = {}) {
const token = localStorage.getItem("authToken");
const response = await fetch(url, {
...options,
headers: {
...options.headers,
"Authorization": `Bearer ${token}`
}
});
// Handle token expiration
if (response.status === 401) {
const newToken = await refreshToken();
// Retry with new token
return fetch(url, {
...options,
headers: {
...options.headers,
"Authorization": `Bearer ${newToken}`
}
});
}
return response;
}
Error Handling
Handle different types of errors when working with APIs.
// Custom API Error class
class ApiError extends Error {
constructor(message, status, data = null) {
super(message);
this.name = "ApiError";
this.status = status;
this.data = data;
}
}
// Comprehensive fetch wrapper
async function apiFetch(url, options = {}) {
try {
const response = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
...options.headers
}
});
// Try to parse JSON (might fail for empty responses)
let data;
const contentType = response.headers.get("Content-Type");
if (contentType?.includes("application/json")) {
data = await response.json();
}
// Handle HTTP errors
if (!response.ok) {
throw new ApiError(
data?.message || response.statusText,
response.status,
data
);
}
return data;
} catch (error) {
// Network error (no response at all)
if (error instanceof TypeError) {
throw new ApiError("Network error - check your connection", 0);
}
// Re-throw ApiError
if (error instanceof ApiError) {
throw error;
}
// Unknown error
throw new ApiError("An unexpected error occurred", 500);
}
}
// Usage with specific error handling
async function loadUserProfile(userId) {
try {
const user = await apiFetch(`/api/users/${userId}`);
return user;
} catch (error) {
if (error instanceof ApiError) {
switch (error.status) {
case 401:
redirectToLogin();
break;
case 403:
showError("You don't have permission to view this");
break;
case 404:
showError("User not found");
break;
case 429:
showError("Too many requests, please wait");
break;
case 500:
showError("Server error, please try again");
break;
default:
showError(error.message);
}
}
return null;
}
}
// Retry with exponential backoff
async function fetchWithRetry(url, options = {}, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await apiFetch(url, options);
} catch (error) {
const isLastAttempt = i === retries - 1;
const shouldRetry = error.status >= 500 || error.status === 0;
if (isLastAttempt || !shouldRetry) {
throw error;
}
// Wait with exponential backoff
const delay = Math.pow(2, i) * 1000;
await new Promise(r => setTimeout(r, delay));
}
}
}
API Client Patterns
Build reusable API clients for organized code.
// API Client class
class ApiClient {
constructor(baseUrl, options = {}) {
this.baseUrl = baseUrl;
this.defaultHeaders = options.headers || {};
this.timeout = options.timeout || 30000;
}
async request(endpoint, options = {}) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
...options,
headers: {
"Content-Type": "application/json",
...this.defaultHeaders,
...options.headers
},
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new ApiError(error.message, response.status);
}
return response.json();
} catch (error) {
clearTimeout(timeoutId);
throw error;
}
}
get(endpoint, options) {
return this.request(endpoint, { ...options, method: "GET" });
}
post(endpoint, data, options) {
return this.request(endpoint, {
...options,
method: "POST",
body: JSON.stringify(data)
});
}
put(endpoint, data, options) {
return this.request(endpoint, {
...options,
method: "PUT",
body: JSON.stringify(data)
});
}
patch(endpoint, data, options) {
return this.request(endpoint, {
...options,
method: "PATCH",
body: JSON.stringify(data)
});
}
delete(endpoint, options) {
return this.request(endpoint, { ...options, method: "DELETE" });
}
setHeader(key, value) {
this.defaultHeaders[key] = value;
}
}
// Create API client instance
const api = new ApiClient("https://api.example.com", {
headers: {
"Authorization": `Bearer ${getToken()}`
}
});
// Resource-specific modules
const usersApi = {
getAll: () => api.get("/users"),
getById: (id) => api.get(`/users/${id}`),
create: (data) => api.post("/users", data),
update: (id, data) => api.put(`/users/${id}`, data),
delete: (id) => api.delete(`/users/${id}`)
};
const postsApi = {
getAll: (params) => api.get(`/posts?${new URLSearchParams(params)}`),
getById: (id) => api.get(`/posts/${id}`),
create: (data) => api.post("/posts", data),
update: (id, data) => api.put(`/posts/${id}`, data),
delete: (id) => api.delete(`/posts/${id}`)
};
// Usage
const users = await usersApi.getAll();
const newUser = await usersApi.create({ name: "John" });
const posts = await postsApi.getAll({ page: 1, limit: 10 });
WebSocket API
WebSocket provides full-duplex communication over a single TCP connection, enabling real-time features like chat, live updates, and multiplayer games.
Basic WebSocket Connection
// Create WebSocket connection
const socket = new WebSocket('wss://api.example.com/ws');
// Connection opened
socket.addEventListener('open', (event) => {
console.log('Connected to WebSocket server');
// Send a message
socket.send(JSON.stringify({
type: 'subscribe',
channel: 'notifications'
}));
});
// Listen for messages
socket.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
switch (data.type) {
case 'notification':
showNotification(data.payload);
break;
case 'update':
updateUI(data.payload);
break;
}
});
// Handle errors
socket.addEventListener('error', (error) => {
console.error('WebSocket error:', error);
});
// Connection closed
socket.addEventListener('close', (event) => {
console.log('Disconnected:', event.code, event.reason);
if (event.code !== 1000) { // Abnormal closure
// Attempt to reconnect
setTimeout(() => reconnect(), 3000);
}
});
// Send data
function send(type, payload) {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ type, payload }));
}
}
// Close connection
function disconnect() {
socket.close(1000, 'User disconnected');
}
Robust WebSocket Client
class WebSocketClient {
constructor(url, options = {}) {
this.url = url;
this.options = {
reconnectInterval: 3000,
maxReconnectAttempts: 5,
heartbeatInterval: 30000,
...options
};
this.socket = null;
this.reconnectAttempts = 0;
this.listeners = new Map();
this.heartbeatTimer = null;
}
connect() {
this.socket = new WebSocket(this.url);
this.socket.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
this.startHeartbeat();
this.emit('connected');
};
this.socket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'pong') return; // Heartbeat response
this.emit('message', data);
this.emit(data.type, data.payload);
};
this.socket.onclose = (event) => {
this.stopHeartbeat();
this.emit('disconnected', event);
if (event.code !== 1000 && this.reconnectAttempts < this.options.maxReconnectAttempts) {
this.scheduleReconnect();
}
};
this.socket.onerror = (error) => {
this.emit('error', error);
};
}
scheduleReconnect() {
this.reconnectAttempts++;
const delay = this.options.reconnectInterval * Math.pow(2, this.reconnectAttempts - 1);
console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
setTimeout(() => this.connect(), delay);
}
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
this.send('ping', {});
}, this.options.heartbeatInterval);
}
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
send(type, payload) {
if (this.socket?.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify({ type, payload }));
}
}
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
emit(event, data) {
this.listeners.get(event)?.forEach(cb => cb(data));
}
disconnect() {
this.stopHeartbeat();
this.socket?.close(1000);
}
}
// Usage
const ws = new WebSocketClient('wss://api.example.com/ws');
ws.on('connected', () => ws.send('subscribe', { channel: 'updates' }));
ws.on('notification', (data) => showNotification(data));
ws.on('error', (err) => console.error('WS Error:', err));
ws.connect();
GraphQL Introduction
GraphQL is a query language for APIs that lets clients request exactly the data they need. Unlike REST, a single endpoint handles all requests.
GraphQL Queries
// Simple GraphQL client
async function graphql(query, variables = {}) {
const response = await fetch('https://api.example.com/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`
},
body: JSON.stringify({ query, variables })
});
const { data, errors } = await response.json();
if (errors) {
throw new Error(errors.map(e => e.message).join(', '));
}
return data;
}
// Query - Get specific fields
const userData = await graphql(`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
posts {
id
title
createdAt
}
}
}
`, { id: '123' });
// Mutation - Create data
const newPost = await graphql(`
mutation CreatePost($input: PostInput!) {
createPost(input: $input) {
id
title
author {
name
}
}
}
`, {
input: {
title: 'My New Post',
content: 'Post content here...',
published: true
}
});
// Fragment - Reusable field selections
const postsWithUser = await graphql(`
fragment UserFields on User {
id
name
avatar
}
query GetPosts {
posts {
id
title
author {
...UserFields
}
}
}
`);
- REST: Multiple endpoints, fixed data structure, potential over/under-fetching
- GraphQL: Single endpoint, request exactly what you need, strongly typed schema
- Choose REST for: Simple CRUD, caching needs, public APIs
- Choose GraphQL for: Complex data relationships, mobile apps, flexible UIs
Server-Sent Events (SSE)
SSE provides a simple way to receive real-time updates from a server over HTTP. Unlike WebSocket, it's unidirectional (server to client only) but simpler to implement.
// Connect to SSE endpoint
const eventSource = new EventSource('/api/events', {
withCredentials: true // Include cookies
});
// Default message event
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Message:', data);
};
// Named events
eventSource.addEventListener('notification', (event) => {
const notification = JSON.parse(event.data);
showNotification(notification);
});
eventSource.addEventListener('update', (event) => {
const update = JSON.parse(event.data);
updateDashboard(update);
});
// Connection opened
eventSource.onopen = () => {
console.log('SSE connection established');
};
// Handle errors
eventSource.onerror = (error) => {
console.error('SSE error:', error);
if (eventSource.readyState === EventSource.CLOSED) {
console.log('Connection closed, will auto-reconnect');
}
};
// Close connection
function disconnect() {
eventSource.close();
}
// SSE with custom headers (using fetch + ReadableStream)
async function sseWithHeaders(url, headers) {
const response = await fetch(url, { headers });
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { value, done } = await reader.read();
if (done) break;
const text = decoder.decode(value);
const lines = text.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
handleEvent(data);
}
}
}
}
Summary
JSON.parse
Convert JSON string to JavaScript object
JSON.stringify
Convert object to JSON string
Fetch API
Modern HTTP requests with Promises
Response Handling
Check response.ok, parse appropriately
Authentication
Bearer tokens, API keys, refresh flow
Error Handling
Custom errors, retry logic, timeouts