Table of Contents
- What is an API?
- Understanding HTTP Methods
- HTTP Status Codes
- Your First API Call
- Understanding the Response
- Making Different Types of Requests
- Understanding JSON.stringify()
- Handling Errors Properly
- Using Async/Await
- Building a Complete Example
- Working with Query Parameters
- API Authentication Basics
- Best Practices
- Free APIs for Practice
- Common Mistakes to Avoid
- Troubleshooting Guide
Introduction
If you've ever wondered how apps communicate with each other, share data, or fetch information from the internet, the answer is APIs. In this tutorial, you'll learn what APIs are, how they work, and how to make your first API call.
What you'll learn:
- What an API is and why it matters
- Understanding HTTP methods and status codes
- Making API calls using JavaScript
- Handling API responses and errors
- Best practices for working with APIs
Prerequisites:
- Basic JavaScript knowledge
- A text editor (VS Code recommended)
- A web browser
- No special installations required
Time to complete: 30-40 minutes
What is an API?
API stands for Application Programming Interface. Think of it as a waiter in a restaurant:
- You (the user) sit at a table and want to order food
- The kitchen (the server) has all the food and knows how to prepare it
- The waiter (the API) takes your order to the kitchen and brings back your food
The API is the messenger that takes your request, tells the system what you want, and returns the response.
Real-World API Examples
APIs are everywhere in modern applications:
- Weather apps use weather APIs to get current conditions
- Social media apps use APIs to post updates and fetch feeds
- Payment systems use APIs to process transactions
- Maps use APIs to display locations and directions
- E-commerce sites use APIs to check product inventory
Understanding HTTP Methods
APIs communicate using HTTP (Hypertext Transfer Protocol). When you interact with an API, you use specific methods to perform different actions:
The Four Main HTTP Methods
1. GET - Retrieve data
- Used to fetch information without changing anything
- Example: Getting a list of users, fetching weather data
- Like asking "Can I see the menu?"
2. POST - Create new data
- Used to send data to create something new
- Example: Creating a new user account, submitting a form
- Like saying "I want to place an order"
3. PUT - Update existing data
- Used to modify existing information
- Example: Updating your profile information
- Like saying "I want to change my order"
4. DELETE - Remove data
- Used to delete existing information
- Example: Deleting a post, removing a user
- Like saying "Cancel my order"
HTTP Status Codes
When you make an API request, the server responds with a status code that tells you what happened:
Success Codes (2xx)
- 200 OK - Request succeeded
- 201 Created - New resource was created successfully
- 204 No Content - Request succeeded but no content to return
Client Error Codes (4xx)
- 400 Bad Request - Your request was invalid
- 401 Unauthorized - You need to authenticate first
- 403 Forbidden - You don't have permission
- 404 Not Found - The resource doesn't exist
Server Error Codes (5xx)
- 500 Internal Server Error - Something went wrong on the server
- 503 Service Unavailable - Server is temporarily down
Your First API Call
Let's make your first API call using a free, public API. We'll use the JSONPlaceholder API, which provides fake data for testing.
Method 1: Using the Browser Console
Open your browser's developer console (F12 or right-click > Inspect > Console) and paste this code:
// Make a GET request to fetch a user
fetch('https://jsonplaceholder.typicode.com/users/1')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
What happens:
-
fetch()
sends a GET request to the API -
.then(response => response.json())
converts the response to JSON format -
.then(data => console.log(data))
displays the data in the console -
.catch()
handles any errors (see error handling section)
You should see user data with properties like name, email, and address.
Method 2: Creating an HTML File
Create a file called api-test.html
and add this code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API Tutorial</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
background: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
button {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background: #0056b3;
}
#result {
margin-top: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 5px;
white-space: pre-wrap;
font-family: monospace;
}
</style>
</head>
<body>
<div class="container">
<h1>My First API Call</h1>
<button onclick="fetchUser()">Fetch User Data</button>
<div id="result"></div>
</div>
<script>
function fetchUser() {
fetch('https://jsonplaceholder.typicode.com/users/1')
.then(response => response.json())
.then(data => {
document.getElementById('result').textContent =
JSON.stringify(data, null, 2);
})
.catch(error => {
document.getElementById('result').textContent =
'Error: ' + error.message;
});
}
</script>
</body>
</html>
Open this file in your browser and click the button to see the API response displayed on the page.
Understanding the Response
When you make an API call, you typically receive data in JSON format. Here's what a typical response looks like:
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"city": "Gwenborough",
"zipcode": "92998-3874"
},
"phone": "1-770-736-8031",
"website": "hildegard.org"
}
JSON Structure:
-
Objects are wrapped in curly braces
{}
-
Arrays are wrapped in square brackets
[]
-
Key-value pairs are separated by colons
:
- Data types include strings, numbers, booleans, objects, and arrays
Making Different Types of Requests
GET Request - Fetching Data
// Fetch all posts
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
POST Request - Creating Data
// Create a new post
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'My New Post',
body: 'This is the content of my post',
userId: 1
})
})
.then(response => response.json())
.then(data => console.log('Created:', data))
.catch(error => console.error('Error:', error));
Key differences for POST:
- Specify
method: 'POST'
- Include
headers
to tell the server we're sending JSON - Use
body
to send the data (must be stringified)
PUT Request - Updating Data
// Update an existing post
fetch('https://jsonplaceholder.typicode.com/posts/1', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: 1,
title: 'Updated Title',
body: 'Updated content',
userId: 1
})
})
.then(response => response.json())
.then(data => console.log('Updated:', data))
.catch(error => console.error('Error:', error));
DELETE Request - Removing Data
// Delete a post
fetch('https://jsonplaceholder.typicode.com/posts/1', {
method: 'DELETE'
})
.then(response => {
if (response.ok) {
console.log('Post deleted successfully');
}
})
.catch(error => console.error('Error:', error));
Understanding JSON.stringify()
What is stringification? It means converting JavaScript objects into text (strings). When you send data to an API, it needs to be in a text format that can travel over the internet.
Why Do We Need to Stringify?
The Problem
JavaScript objects exist only in your code's memory. They can't be sent over the internet directly.
// This is a JavaScript object (lives in your computer's memory)
const user = {
name: "John",
age: 25
};
// You CANNOT send this object directly to an API
// The internet doesn't understand JavaScript objects!
The Solution
Convert the object to a JSON string - plain text that can travel anywhere.
// Convert object to string
const userString = JSON.stringify(user);
// Result: '{"name":"John","age":25}'
// This is now plain text that can be sent over the internet
Visual Comparison
Before Stringification (JavaScript Object):
{
name: "John",
age: 25
}
↓ JSON.stringify() ↓
After Stringification (Text String):
'{"name":"John","age":25}'
Notice the difference:
- Object: No quotes around the whole thing
- String: Has quotes around everything, it's text
Complete Example
// Step 1: Create your data as a JavaScript object
const newPost = {
title: 'My New Post',
body: 'This is the content',
userId: 1
};
// Step 2: Make the API request
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // Tell server: "I'm sending JSON"
},
body: JSON.stringify(newPost) // Convert object to string HERE!
})
.then(response => response.json()) // Convert string back to object
.then(data => console.log(data));
JSON.stringify() vs JSON.parse()
These are opposites - they do reverse operations:
JSON.stringify() - Object → String
const myObject = { name: "Sarah", age: 30 };
const myString = JSON.stringify(myObject);
console.log(myString);
// Output: '{"name":"Sarah","age":30}'
console.log(typeof myString);
// Output: "string"
JSON.parse() - String → Object
const myString = '{"name":"Sarah","age":30}';
const myObject = JSON.parse(myString);
console.log(myObject);
// Output: { name: "Sarah", age: 30 }
console.log(typeof myObject);
// Output: "object"
Real-World Analogy
Think of it like sending a package:
Sending Data (POST request):
- You have items (JavaScript object)
- You pack them in a box (JSON.stringify)
- You ship the box over the internet
- The server receives and unpacks it
Receiving Data (GET request):
- Server sends a packed box (JSON string)
- You receive it over the internet
- You unpack it (response.json() does JSON.parse)
- You use the items (JavaScript object)
Common Stringify Mistakes
Mistake 1: Forgetting to Stringify
// ❌ WRONG
fetch(url, {
method: 'POST',
body: { name: "John" } // Not stringified!
})
// ✅ CORRECT
fetch(url, {
method: 'POST',
body: JSON.stringify({ name: "John" }) // Stringified!
})
Mistake 2: Not Setting Content-Type
// ❌ INCOMPLETE - missing header
fetch(url, {
method: 'POST',
body: JSON.stringify(data) // Stringified, but...
// Missing: headers with Content-Type!
})
// ✅ COMPLETE
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json' // Tell server it's JSON
},
body: JSON.stringify(data)
})
Testing Stringification
Try this in your browser console:
// Create an object
const person = {
name: "Alice",
age: 28,
hobbies: ["reading", "coding"]
};
// Stringify it
const stringified = JSON.stringify(person);
console.log("Stringified:", stringified);
console.log("Type:", typeof stringified); // "string"
// Parse it back
const parsed = JSON.parse(stringified);
console.log("Parsed back:", parsed);
console.log("Type:", typeof parsed); // "object"
Handling Errors Properly
Always handle errors when working with APIs. Networks can fail, APIs can go down, or you might send invalid data.
Basic Error Handling
fetch('https://jsonplaceholder.typicode.com/users/1')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Error:', error.message));
Comprehensive Error Handling
async function fetchUserWithErrorHandling(userId) {
try {
const response = await fetch(
`https://jsonplaceholder.typicode.com/users/${userId}`
);
// Check if response is successful
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
// Handle different types of errors
if (error.message.includes('Failed to fetch')) {
console.error('Network error - check your connection');
} else if (error.message.includes('404')) {
console.error('User not found');
} else {
console.error('An error occurred:', error.message);
}
return null;
}
}
// Usage
fetchUserWithErrorHandling(1);
Using Async/Await (Modern Approach)
The async/await
syntax makes API calls easier to read and write:
Old Way (Promises)
fetch('https://jsonplaceholder.typicode.com/users/1')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
New Way (Async/Await)
async function getUser() {
try {
const response = await fetch(
'https://jsonplaceholder.typicode.com/users/1'
);
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
getUser();
Benefits of async/await:
- More readable code
- Easier error handling with try/catch
- Looks similar to synchronous code
- Better for complex operations with multiple API calls
Building a Complete Example: User Directory
Let's build a simple user directory that displays data from an API:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>User Directory</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
color: white;
text-align: center;
margin-bottom: 30px;
font-size: 2.5rem;
}
.controls {
text-align: center;
margin-bottom: 30px;
}
button {
background: white;
color: #667eea;
border: none;
padding: 12px 30px;
border-radius: 25px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
.loading {
text-align: center;
color: white;
font-size: 1.2rem;
margin: 50px 0;
}
.error {
background: #ff6b6b;
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
margin: 20px 0;
}
.user-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.user-card {
background: white;
padding: 25px;
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
transition: transform 0.3s ease;
}
.user-card:hover {
transform: translateY(-5px);
}
.user-card h2 {
color: #667eea;
margin-bottom: 10px;
}
.user-card p {
color: #666;
margin: 5px 0;
}
.user-card .label {
font-weight: bold;
color: #333;
}
</style>
</head>
<body>
<div class="container">
<h1>👥 User Directory</h1>
<div class="controls">
<button onclick="loadUsers()">Load Users</button>
</div>
<div id="content"></div>
</div>
<script>
const contentDiv = document.getElementById('content');
async function loadUsers() {
try {
// Show loading message
contentDiv.innerHTML = '<div class="loading">Loading users...</div>';
// Fetch users from API
const response = await fetch(
'https://jsonplaceholder.typicode.com/users'
);
// Check if request was successful
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Parse JSON response
const users = await response.json();
// Display users
displayUsers(users);
} catch (error) {
// Show error message
contentDiv.innerHTML = `
<div class="error">
<h2>Oops! Something went wrong</h2>
<p>${error.message}</p>
<p>Please try again later.</p>
</div>
`;
console.error('Error loading users:', error);
}
}
function displayUsers(users) {
const html = `
<div class="user-grid">
${users.map(user => `
<div class="user-card">
<h2>${user.name}</h2>
<p><span class="label">Username:</span> ${user.username}</p>
<p><span class="label">Email:</span> ${user.email}</p>
<p><span class="label">Phone:</span> ${user.phone}</p>
<p><span class="label">Website:</span> ${user.website}</p>
<p><span class="label">Company:</span> ${user.company.name}</p>
<p><span class="label">City:</span> ${user.address.city}</p>
</div>
`).join('')}
</div>
`;
contentDiv.innerHTML = html;
}
</script>
</body>
</html>
What this example demonstrates:
- Loading state management
- Error handling
- Data transformation
- Dynamic HTML generation
- Responsive grid layout
Working with Query Parameters
APIs often accept query parameters to filter or customize results:
// Basic URL
const baseUrl = 'https://jsonplaceholder.typicode.com/posts';
// With query parameters
const urlWithParams = `${baseUrl}?userId=1&_limit=5`;
// Fetch posts by user 1, limited to 5 results
fetch(urlWithParams)
.then(response => response.json())
.then(data => console.log(data));
Building URLs with URLSearchParams
// Create URL with parameters programmatically
const baseUrl = 'https://jsonplaceholder.typicode.com/posts';
const params = new URLSearchParams({
userId: 1,
_limit: 5
});
const fullUrl = `${baseUrl}?${params.toString()}`;
// Result: https://jsonplaceholder.typicode.com/posts?userId=1&_limit=5
fetch(fullUrl)
.then(response => response.json())
.then(data => console.log(data));
API Authentication Basics
Many real-world APIs require authentication to protect data and track usage.
Common Authentication Methods
1. API Keys
const apiKey = 'your-api-key-here';
fetch('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${apiKey}`
}
})
.then(response => response.json())
.then(data => console.log(data));
2. Basic Authentication
const username = 'user';
const password = 'pass';
const credentials = btoa(`${username}:${password}`);
fetch('https://api.example.com/data', {
headers: {
'Authorization': `Basic ${credentials}`
}
})
.then(response => response.json())
.then(data => console.log(data));
Security Note: Never expose API keys in frontend code. Use environment variables and backend proxies for sensitive keys.
Best Practices for Working with APIs
1. Always Handle Errors
// Bad
fetch(url).then(r => r.json()).then(data => console.log(data));
// Good
fetch(url)
.then(response => {
if (!response.ok) throw new Error('Request failed');
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
2. Use Async/Await for Readability
// More readable than promise chains
async function getData() {
try {
const response = await fetch(url);
const data = await response.json();
return data;
} catch (error) {
console.error('Error:', error);
}
}
3. Don't Repeat Yourself - Create Helper Functions
// Reusable API call function
async function apiCall(url, options = {}) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API call failed:', error);
throw error;
}
}
// Usage
const users = await apiCall('https://jsonplaceholder.typicode.com/users');
4. Validate Data
async function getUser(userId) {
const data = await apiCall(`https://api.example.com/users/${userId}`);
// Validate the response has expected properties
if (!data || !data.id || !data.name) {
throw new Error('Invalid user data received');
}
return data;
}
5. Handle Loading States
let isLoading = false;
async function fetchData() {
if (isLoading) return; // Prevent duplicate requests
isLoading = true;
showLoadingSpinner();
try {
const data = await apiCall(url);
displayData(data);
} catch (error) {
showError(error);
} finally {
isLoading = false;
hideLoadingSpinner();
}
}
Free APIs for Practice
Here are some free public APIs you can use to practice:
No Authentication Required
-
JSONPlaceholder -
https://jsonplaceholder.typicode.com
- Fake posts, users, comments for testing
-
OpenWeather API -
https://openweathermap.org/api
(free tier)- Weather data for any location
-
Dog API -
https://dog.ceo/dog-api/
- Random dog images
-
REST Countries -
https://restcountries.com
- Information about countries
-
Advice Slip -
https://api.adviceslip.com
- Random advice quotes
Practice Projects
- Build a weather dashboard using OpenWeather API
- Create a random dog image generator
- Make a country information lookup tool
- Build a quote of the day app
Common Mistakes to Avoid
1. Forgetting to Parse JSON
// Wrong - response is not parsed
fetch(url).then(response => console.log(response));
// Right - parse JSON first
fetch(url)
.then(response => response.json())
.then(data => console.log(data));
2. Not Checking Response Status
// Wrong - doesn't check if request succeeded
fetch(url)
.then(response => response.json())
.then(data => console.log(data));
// Right - check status first
fetch(url)
.then(response => {
if (!response.ok) throw new Error('Failed');
return response.json();
})
.then(data => console.log(data));
3. Exposing Sensitive Data
// Never do this in production code
const apiKey = 'sk_live_12345'; // Exposed to everyone
fetch(`https://api.example.com?key=${apiKey}`);
// Use environment variables instead
const apiKey = process.env.API_KEY;
4. Not Handling Network Errors
// Wrong - no catch block
fetch(url).then(response => response.json());
// Right - always catch errors
fetch(url)
.then(response => response.json())
.catch(error => console.error('Network error:', error));
5. Forgetting to Stringify POST Data
See the detailed explanation in the Understanding JSON.stringify() section.
Troubleshooting Guide
Issue: CORS Error
Error Message: "Access to fetch has been blocked by CORS policy"
Solution:
- CORS errors occur when browsers block requests to different domains
- For learning: Use APIs that allow CORS (like JSONPlaceholder)
- For production: Configure CORS on your backend server
- Alternative: Use a CORS proxy for testing (not for production)
Issue: JSON Parse Error
Error Message: "Unexpected token in JSON"
Solution:
// Check if response is actually JSON
const response = await fetch(url);
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
const data = await response.json();
} else {
const text = await response.text();
console.log('Response is not JSON:', text);
}
Issue: Request Timeout
Solution:
// Add timeout to fetch request
const fetchWithTimeout = (url, timeout = 5000) => {
return Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), timeout)
)
]);
};
Next Steps
Now that you understand API basics, here's how to continue learning:
Immediate Practice
- Complete all the examples in this tutorial
- Modify the User Directory to display different data
- Try making POST, PUT, and DELETE requests
- Experiment with different public APIs
Intermediate Level
- Learn about REST API design principles
- Understand API rate limiting and pagination
- Study API versioning
- Learn about GraphQL as an alternative to REST
- Explore API testing tools (Postman, Insomnia)
Advanced Topics
- OAuth authentication flows
- WebSockets for real-time data
- Building your own API with Node.js/Express
- API documentation with Swagger/OpenAPI
- API security best practices
Key Takeaways
✅ APIs enable communication between different applications and services
✅ HTTP methods (GET, POST, PUT, DELETE) define what action to perform
✅ Status codes indicate whether requests succeeded or failed
✅ JSON.stringify() converts objects to strings for sending data
✅ Always handle errors - networks fail and APIs have issues
✅ Async/await makes API code more readable than promise chains
✅ Parse JSON responses before
Top comments (0)