Modern web applications constantly communicate with servers to send and receive data.
Examples include:
JavaScript communicates with servers using APIs.
This module covers:
API stands for:
Application Programming Interface
An API allows different applications or software systems to communicate with each other.
For example:
Frontend
↓
API
↓
Backend
The frontend does not usually access the database directly.
Instead, it communicates with a backend through an API.
Think about a restaurant.
Customer
↓
Waiter
↓
Kitchen
The customer asks the waiter for food.
The waiter sends the request to the kitchen.
The kitchen prepares the food.
The waiter brings the result back.
In web development:
Frontend
↓
API
↓
Backend
↓
Database
We can compare them like this:
| Restaurant | Web Application |
|---|---|
| Customer | Frontend |
| Waiter | API |
| Kitchen | Backend |
| Ingredients | Database |
| Food | Response |
The API acts as the communication layer between systems.
A typical API request follows this process:
Frontend
↓
Send Request
↓
API / Server
↓
Process Request
↓
Database
↓
Server Creates Response
↓
Frontend Receives Response
↓
Update UI
For example:
GET /users
The frontend asks:
Give me the users.
The server might respond with:
[
{
"id": 1,
"name": "Samir"
},
{
"id": 2,
"name": "Ram"
}
]
JavaScript can then use this data to update the webpage.
When working with APIs, two important concepts are:
Client
Server
The client sends requests.
Examples:
Browser
React Application
Mobile Application
Next.js Frontend
The server receives requests, processes them, and sends responses.
Examples:
Node.js
Express
Django
Laravel
Spring Boot
Communication:
Client
↓
HTTP Request
↓
Server
↓
HTTP Response
↓
Client
Most web APIs communicate using:
HTTP
HTTP stands for:
Hypertext Transfer Protocol
It defines how clients and servers communicate over the web.
Example:
Browser
↓
HTTP Request
↓
Server
↓
HTTP Response
↓
Browser
An HTTP request can contain several pieces of information.
For example:
Method
URL
Headers
Body
Example:
POST /users
Content-Type: application/json
{
"name": "Samir"
}
Here:
POST
is the HTTP method.
/users
is the endpoint.
The JSON contains the data being sent.
The server sends an HTTP response.
A response commonly contains:
Status Code
Headers
Body
Example:
201 Created
Body:
{
"id": 1,
"name": "Samir"
}
The Fetch API is the modern built-in browser API for making HTTP requests.
It is commonly used to:
Get Data
Send Data
Update Data
Delete Data
Fetch is Promise-based.
Basic syntax:
fetch(url);
fetch() returns a:
Promise
A GET request retrieves data.
fetch(
"https://api.example.com/users"
);
However, this only gives us a Promise.
We need to process the response.
fetch(
"https://api.example.com/users"
)
.then(response => {
return response.json();
})
.then(data => {
console.log(data);
});
Possible output:
[
{
id: 1,
name: "Samir"
}
]
The process looks like:
fetch()
↓
HTTP Request
↓
Server
↓
HTTP Response
↓
Response Object
↓
response.json()
↓
JavaScript Data
For example:
const response =
await fetch(
"/api/users"
);
At this point:
response
is a Response object.
We can then convert its JSON body:
const data =
await response.json();
Modern JavaScript commonly uses Fetch with async/await.
async function getUsers() {
const response =
await fetch(
"/api/users"
);
const data =
await response.json();
console.log(data);
}
This is easier to read than long .then() chains.
async function getUsers() {
try {
const response =
await fetch(
"/api/users"
);
if (!response.ok) {
throw new Error(
"Failed to fetch users"
);
}
const users =
await response.json();
console.log(users);
} catch (error) {
console.error(
error.message
);
}
}
This is a common modern pattern.
HTTP provides different methods for different operations.
The most common are:
| Method | Purpose |
|---|---|
| GET | Retrieve data |
| POST | Create data |
| PUT | Replace/update data |
| PATCH | Partially update data |
| DELETE | Remove data |
These methods are commonly connected to CRUD operations.
CRUD stands for:
Create
Read
Update
Delete
Mapping CRUD to HTTP:
| CRUD | HTTP Method |
|---|---|
| Create | POST |
| Read | GET |
| Update | PUT / PATCH |
| Delete | DELETE |
Visual:
Create → POST
Read → GET
Update → PUT / PATCH
Delete → DELETE
GET retrieves information.
Example:
const response =
await fetch(
"/api/users"
);
const users =
await response.json();
Request:
GET /api/users
Possible response:
[
{
"id": 1,
"name": "Samir"
}
]
We can include an ID in the URL.
const response =
await fetch(
"/api/users/1"
);
Request:
GET /api/users/1
Possible response:
{
"id": 1,
"name": "Samir"
}
POST is commonly used to create new data.
Example:
const response =
await fetch(
"/api/users",
{
method: "POST",
headers: {
"Content-Type":
"application/json"
},
body: JSON.stringify({
name: "Samir",
age: 21
})
}
);
Request:
POST /api/users
Body:
{
"name": "Samir",
"age": 21
}
JSON.stringify()?JavaScript objects cannot simply be sent as JSON request text without serialization.
We might have:
const user = {
name: "Samir",
age: 21
};
Convert it:
JSON.stringify(user);
Result:
{"name":"Samir","age":21}
So:
JavaScript Object
↓
JSON.stringify()
↓
JSON String
↓
Send to Server
When sending JSON, we usually specify:
headers: {
"Content-Type":
"application/json"
}
This tells the server:
The request body contains JSON.
PUT is commonly used to replace or fully update a resource.
Example:
const response =
await fetch(
"/api/users/1",
{
method: "PUT",
headers: {
"Content-Type":
"application/json"
},
body: JSON.stringify({
name: "Samir",
age: 22
})
}
);
Request:
PUT /api/users/1
PATCH is commonly used to partially update a resource.
Suppose the user is:
{
"name": "Samir",
"age": 21,
"city": "Jhapa"
}
We only want to update the age.
await fetch(
"/api/users/1",
{
method: "PATCH",
headers: {
"Content-Type":
"application/json"
},
body: JSON.stringify({
age: 22
})
}
);
Only the specified field needs to change.
| PUT | PATCH |
|---|---|
| Usually replaces/full update | Partial update |
| Sends complete representation | Sends changed fields |
PUT /users/1 |
PATCH /users/1 |
Example:
Current User
name = Samir
age = 21
city = Jhapa
PUT might send:
{
"name": "Samir",
"age": 22,
"city": "Jhapa"
}
PATCH might send only:
{
"age": 22
}
The exact behavior depends on how the API is designed.
DELETE removes data.
Example:
const response =
await fetch(
"/api/users/1",
{
method: "DELETE"
}
);
Request:
DELETE /api/users/1
Meaning:
Delete user with ID 1
Fetch accepts an options object.
fetch(url, {
method: "POST",
headers: {},
body: "...",
credentials: "include"
});
Common options:
| Option | Purpose |
|---|---|
method |
HTTP method |
headers |
Request metadata |
body |
Data sent to server |
credentials |
Cookie/auth handling |
mode |
Request mode such as CORS |
signal |
Cancel/abort support |
Fetch returns a Response object.
Example:
const response =
await fetch(
"/api/users"
);
Useful properties include:
response.status
response.statusText
response.ok
response.headers
response.statusReturns the HTTP status code.
console.log(
response.status
);
Possible output:
200
response.okReturns:
true
for successful HTTP status codes in the 200–299 range.
Example:
if (response.ok) {
console.log(
"Request successful"
);
}
A common mistake is assuming that Fetch automatically throws for every HTTP error.
For example, a server may return:
404 Not Found
or:
500 Internal Server Error
and fetch() can still resolve with a Response object.
Therefore, check:
if (!response.ok) {
throw new Error(
"Request failed"
);
}
Complete example:
async function getUser() {
try {
const response =
await fetch(
"/api/users/1"
);
if (!response.ok) {
throw new Error(
`HTTP Error: ${response.status}`
);
}
const user =
await response.json();
console.log(user);
} catch (error) {
console.error(
error.message
);
}
}
APIs can return different types of content.
For JSON:
await response.json();
For text:
await response.text();
For binary data:
await response.blob();
Common examples:
response.json()
response.text()
response.blob()
Most web APIs use JSON.
JSON stands for:
JavaScript Object Notation
It is one of the most common formats used to exchange data.
Example:
{
"id": 1,
"name": "Samir",
"age": 21
}
Typical flow:
Server
↓
JSON
↓
HTTP Response
↓
Frontend
↓
response.json()
↓
JavaScript Object
Headers provide additional information about a request.
Example:
const response =
await fetch(
"/api/users",
{
headers: {
"Content-Type":
"application/json",
"Authorization":
"Bearer token"
}
}
);
Common headers include:
Content-Type
Authorization
Accept
Some APIs require authentication.
A token may be sent through the Authorization header.
Example:
const response =
await fetch(
"/api/profile",
{
headers: {
Authorization:
`Bearer ${token}`
}
}
);
Flow:
Frontend
↓
Request + Token
↓
Server
↓
Verify Token
↓
Allow / Deny Request
Some authentication systems use cookies.
Fetch can include credentials using:
fetch(
"/api/profile",
{
credentials:
"include"
}
);
This is commonly used when authentication relies on cookies and the server is configured appropriately.
Before Fetch became common, JavaScript applications frequently used:
XMLHttpRequest
also called:
XHR
XHR is still available and can be found in older applications and libraries.
const xhr =
new XMLHttpRequest();
const xhr =
new XMLHttpRequest();
xhr.open(
"GET",
"/api/users"
);
xhr.send();
xhr.onload =
function() {
console.log(
xhr.responseText
);
};
Complete example:
const xhr =
new XMLHttpRequest();
xhr.open(
"GET",
"/api/users"
);
xhr.onload =
function() {
console.log(
xhr.responseText
);
};
xhr.send();
XHR may return JSON as text.
xhr.onload =
function() {
const data =
JSON.parse(
xhr.responseText
);
console.log(data);
};
const xhr =
new XMLHttpRequest();
xhr.open(
"POST",
"/api/users"
);
xhr.setRequestHeader(
"Content-Type",
"application/json"
);
xhr.onload =
function() {
console.log(
xhr.responseText
);
};
xhr.send(
JSON.stringify({
name: "Samir"
})
);
| XMLHttpRequest | Fetch API |
|---|---|
| Older API | Modern API |
| Event/callback based | Promise based |
| More verbose | Cleaner syntax |
| Common in legacy code | Common in modern code |
| Manual handling can be complex | Works naturally with async/await |
Fetch:
const response =
await fetch(
"/api/users"
);
const users =
await response.json();
XHR:
const xhr =
new XMLHttpRequest();
xhr.open(
"GET",
"/api/users"
);
xhr.onload =
function() {
console.log(
xhr.responseText
);
};
xhr.send();
For most modern browser code, Fetch is generally preferred.
REST stands for:
Representational State Transfer
REST is an architectural style commonly used when designing web APIs.
An API designed around REST principles is often called a:
REST API
or:
RESTful API
A simplified architecture looks like:
Client
↓
HTTP Request
↓
REST API
↓
Application Logic
↓
Database
↓
HTTP Response
↓
Client
REST APIs are commonly organized around resources.
Examples:
Users
Products
Orders
Posts
Comments
Hotels
Bookings
These resources may have URLs such as:
/users
/products
/orders
/posts
An endpoint is a specific API URL used to access a resource or operation.
Examples:
| Endpoint | Meaning |
|---|---|
/users |
User collection |
/users/1 |
User with ID 1 |
/products |
Product collection |
/products/10 |
Product with ID 10 |
/orders |
Order collection |
Suppose we have a users resource.
GET /users
GET /users/1
POST /users
PUT /users/1
PATCH /users/1
DELETE /users/1
Imagine an e-commerce application.
Resources:
/products
/users
/orders
/categories
Get products:
GET /products
Get product:
GET /products/10
Create product:
POST /products
Update product:
PATCH /products/10
Delete product:
DELETE /products/10
This creates a predictable API structure.
HTTP responses contain status codes that describe the result of the request.
Status codes are grouped into categories:
1xx → Informational
2xx → Success
3xx → Redirection
4xx → Client Error
5xx → Server Error
| Code | Meaning |
|---|---|
200 |
OK |
201 |
Created |
204 |
No Content |
Common successful request:
GET /users
↓
200 OK
Common after creating data:
POST /users
↓
201 Created
Successful request with no response body:
DELETE /users/1
↓
204 No Content
| Code | Meaning |
|---|---|
400 |
Bad Request |
401 |
Unauthorized |
403 |
Forbidden |
404 |
Not Found |
409 |
Conflict |
422 |
Unprocessable Content |
Example:
GET /users/999
↓
404 Not Found
These are commonly confused.
Usually means authentication is missing or invalid.
Who are you?
Example:
Missing / Invalid Login
↓
401
The server knows who you are, but you do not have permission.
You are authenticated,
but you cannot access this.
Example:
Normal User
↓
Admin Endpoint
↓
403
| Code | Meaning |
|---|---|
500 |
Internal Server Error |
502 |
Bad Gateway |
503 |
Service Unavailable |
504 |
Gateway Timeout |
Example:
Request
↓
Server crashes while processing
↓
500 Internal Server Error
An API may return:
{
"success": true,
"message": "User fetched successfully",
"data": {
"id": 1,
"name": "Samir"
}
}
JavaScript:
const response =
await fetch(
"/api/users/1"
);
const result =
await response.json();
console.log(
result.data.name
);
Output:
Samir
async function getUser() {
const response =
await fetch(
"/api/users/1"
);
if (!response.ok) {
throw new Error(
"User request failed"
);
}
const user =
await response.json();
console.log(user);
}
async function createUser() {
const response =
await fetch(
"/api/users",
{
method: "POST",
headers: {
"Content-Type":
"application/json"
},
body:
JSON.stringify({
name: "Samir",
age: 21
})
}
);
if (!response.ok) {
throw new Error(
"Failed to create user"
);
}
const user =
await response.json();
console.log(user);
}
async function updateUser() {
const response =
await fetch(
"/api/users/1",
{
method: "PATCH",
headers: {
"Content-Type":
"application/json"
},
body:
JSON.stringify({
age: 22
})
}
);
if (!response.ok) {
throw new Error(
"Failed to update user"
);
}
}
async function deleteUser() {
const response =
await fetch(
"/api/users/1",
{
method: "DELETE"
}
);
if (!response.ok) {
throw new Error(
"Failed to delete user"
);
}
console.log(
"User deleted"
);
}
Suppose we want to load products.
async function loadProducts() {
try {
console.log(
"Loading..."
);
const response =
await fetch(
"/api/products"
);
if (!response.ok) {
throw new Error(
"Failed to load products"
);
}
const products =
await response.json();
console.log(products);
} catch (error) {
console.error(
error.message
);
}
}
Workflow:
Frontend
↓
Fetch API
↓
GET /api/products
↓
Server
↓
Database
↓
Products
↓
JSON Response
↓
Frontend
↓
Update UI
HTML:
<ul id="users"></ul>
JavaScript:
async function loadUsers() {
try {
const response =
await fetch(
"/api/users"
);
if (!response.ok) {
throw new Error(
"Failed to load users"
);
}
const users =
await response.json();
const list =
document.querySelector(
"#users"
);
users.forEach(user => {
const item =
document.createElement(
"li"
);
item.textContent =
user.name;
list.appendChild(
item
);
});
} catch (error) {
console.error(
error.message
);
}
}
Flow:
Fetch API
↓
Receive JSON
↓
Convert to JavaScript Data
↓
Loop Through Data
↓
Create DOM Elements
↓
Display Data
This combines several JavaScript concepts:
Functions
+
Async/Await
+
Fetch
+
Arrays
+
Objects
+
DOM
When working with APIs, you may encounter:
CORS
CORS stands for:
Cross-Origin Resource Sharing
Suppose your frontend runs on:
https://frontend.example
and the API runs on:
https://api.example
These are different origins.
The server must allow the frontend origin when cross-origin browser access is required.
Otherwise, the browser may block access to the response.
Conceptually:
Frontend
frontend.example
↓
Request
↓
API
api.example
↓
Is this origin allowed?
If allowed:
Response Accessible
If not:
Browser Blocks Access
CORS is controlled primarily through server response headers.
REST and SOAP are different approaches used for web services.
| REST | SOAP |
|---|---|
| Architectural style | Protocol |
| Commonly uses JSON | Uses XML messaging |
| Usually simpler for web APIs | More structured |
| Common in modern web apps | Common in some enterprise systems |
| Works naturally with HTTP | Defines stricter messaging rules |
For modern frontend development, REST APIs are very common.
Use async/await for readable asynchronous code:
const response =
await fetch(url);
Check HTTP responses:
if (!response.ok) {
throw new Error(
"Request failed"
);
}
Handle failures:
try {
// request
} catch (error) {
console.error(error);
}
When sending JSON:
headers: {
"Content-Type":
"application/json"
}
Convert objects before sending:
JSON.stringify(data);
Parse JSON responses:
const data =
await response.json();
async function getUsers() {
const response =
await fetch(
"/api/users"
);
return response.json();
}
async function createUser(
user
) {
const response =
await fetch(
"/api/users",
{
method: "POST",
headers: {
"Content-Type":
"application/json"
},
body:
JSON.stringify(
user
)
}
);
return response.json();
}
async function updateUser(
id,
data
) {
const response =
await fetch(
`/api/users/${id}`,
{
method: "PATCH",
headers: {
"Content-Type":
"application/json"
},
body:
JSON.stringify(
data
)
}
);
return response.json();
}
async function deleteUser(
id
) {
const response =
await fetch(
`/api/users/${id}`,
{
method: "DELETE"
}
);
return response.ok;
}
Together:
getUsers()
↓
GET
createUser()
↓
POST
updateUser()
↓
PATCH
deleteUser()
↓
DELETE
A typical modern frontend works like this:
User
↓
Clicks Button
↓
JavaScript Function
↓
Fetch API
↓
HTTP Request
↓
Backend API
↓
Application Logic
↓
Database
↓
JSON Response
↓
JavaScript
↓
Update UI
For example:
User clicks "Add to Cart"
↓
POST /api/cart
↓
Server validates product
↓
Database updates cart
↓
JSON response
↓
Frontend updates cart count
| Concept | Purpose |
|---|---|
| API | Communication between systems |
| Client | Sends requests |
| Server | Processes requests |
| HTTP | Communication protocol |
| Request | Data sent to server |
| Response | Data returned by server |
| Fetch API | Modern browser HTTP requests |
| XMLHttpRequest | Older HTTP request API |
| REST | Common API architectural style |
| Endpoint | API resource URL |
| GET | Retrieve data |
| POST | Create data |
| PUT | Replace/update data |
| PATCH | Partially update data |
| DELETE | Remove data |
| JSON | Common data exchange format |
| Headers | Request/response metadata |
| Status Code | Describes request result |
| CORS | Controls cross-origin browser access |
Modern web applications constantly communicate with APIs.
The basic flow is:
Frontend
↓
Fetch API
↓
HTTP Request
↓
REST API
↓
Server
↓
Database
↓
JSON Response
↓
Frontend
↓
UI Update
The four fundamental CRUD operations map to HTTP methods:
Create
↓
POST
Read
↓
GET
Update
↓
PUT / PATCH
Delete
↓
DELETE
A common modern JavaScript API pattern is:
async function getUsers() {
try {
const response =
await fetch(
"/api/users"
);
if (!response.ok) {
throw new Error(
"Request failed"
);
}
const data =
await response.json();
console.log(data);
} catch (error) {
console.error(
error.message
);
}
}
Remember the complete process:
fetch()
↓
Promise
↓
await
↓
Response
↓
Check response.ok
↓
response.json()
↓
JavaScript Data
↓
Use Data
Understanding APIs is essential for modern frontend development because authentication, products, users, payments, dashboards, social media feeds, search, bookings, and almost every data-driven web application rely on communication between the frontend and backend.