Making HTTP requests is one of the most common tasks in programming — fetching data from an API, submitting a form, calling a webhook, or checking a service's health. This guide covers GET, POST, PUT, and DELETE requests in JavaScript, Python, Go, and PHP, with real error handling and JSON examples.
Quick reference: HTTP methods
| Method | Purpose | Has body? |
|---|---|---|
| GET | Read data | No |
| POST | Create resource | Yes |
| PUT | Replace resource | Yes |
| PATCH | Partial update | Yes |
| DELETE | Remove resource | No |
JavaScript — fetch API (browser + Node 18+)
The fetch API is available in all modern browsers and in Node.js 18+. It returns a Promise.
GET request
async function getUser(id) {
const res = await fetch(`https://api.example.com/users/${id}`);
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
return res.json(); // parses JSON response
}
const user = await getUser(42);
console.log(user.name);
POST request with JSON body
async function createUser(data) {
const res = await fetch("https://api.example.com/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.message ?? `HTTP ${res.status}`);
}
return res.json();
}
const newUser = await createUser({ name: "Ana", email: "ana@example.com" });
PUT and DELETE
// PUT — replace
await fetch(`https://api.example.com/users/42`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Ana", email: "ana@example.com" }),
});
// DELETE
await fetch(`https://api.example.com/users/42`, { method: "DELETE" });
With authentication
const res = await fetch("https://api.example.com/secure", {
headers: { Authorization: `Bearer ${token}` },
});
axios (alternative)
axios has a shorter syntax and throws automatically on non-2xx:
import axios from "axios";
const { data } = await axios.get("https://api.example.com/users/42");
const { data: newUser } = await axios.post("https://api.example.com/users", {
name: "Ana",
email: "ana@example.com",
});
Python — requests library
requests is the de-facto standard for Python HTTP. Install once:
pip install requests
GET request
import requests
def get_user(user_id: int) -> dict:
res = requests.get(f"https://api.example.com/users/{user_id}")
res.raise_for_status() # raises HTTPError on 4xx/5xx
return res.json()
user = get_user(42)
print(user["name"])
POST request with JSON body
import requests
def create_user(name: str, email: str) -> dict:
res = requests.post(
"https://api.example.com/users",
json={"name": name, "email": email}, # auto-sets Content-Type
)
res.raise_for_status()
return res.json()
new_user = create_user("Ana", "ana@example.com")
PUT and DELETE
# PUT
res = requests.put(
"https://api.example.com/users/42",
json={"name": "Ana", "email": "ana@example.com"},
)
res.raise_for_status()
# DELETE
res = requests.delete("https://api.example.com/users/42")
res.raise_for_status()
With headers and timeout
res = requests.get(
"https://api.example.com/secure",
headers={"Authorization": f"Bearer {token}"},
timeout=10, # seconds — always set a timeout
)
Session (reuse connection, shared headers)
session = requests.Session()
session.headers.update({"Authorization": f"Bearer {token}"})
res1 = session.get("https://api.example.com/users")
res2 = session.get("https://api.example.com/posts")
Go — net/http (stdlib)
Go's standard library handles HTTP with no extra dependencies.
GET request
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func getUser(id int) (*User, error) {
url := fmt.Sprintf("https://api.example.com/users/%d", id)
res, err := http.Get(url)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP %d", res.StatusCode)
}
var user User
if err := json.NewDecoder(res.Body).Decode(&user); err != nil {
return nil, err
}
return &user, nil
}
POST request with JSON body
import (
"bytes"
"encoding/json"
"net/http"
)
func createUser(name, email string) (*User, error) {
payload, _ := json.Marshal(map[string]string{
"name": name,
"email": email,
})
res, err := http.Post(
"https://api.example.com/users",
"application/json",
bytes.NewReader(payload),
)
if err != nil {
return nil, err
}
defer res.Body.Close()
var user User
json.NewDecoder(res.Body).Decode(&user)
return &user, nil
}
Custom client with timeout and headers
import (
"net/http"
"time"
)
client := &http.Client{Timeout: 10 * time.Second}
req, _ := http.NewRequest("GET", "https://api.example.com/secure", nil)
req.Header.Set("Authorization", "Bearer "+token)
res, err := client.Do(req)
PUT and DELETE
// PUT
req, _ := http.NewRequest("PUT", "https://api.example.com/users/42", bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
res, err := client.Do(req)
// DELETE
req, _ := http.NewRequest("DELETE", "https://api.example.com/users/42", nil)
res, err := client.Do(req)
PHP — cURL + json_decode
PHP has cURL built-in. Wrap it in a small helper to keep repetition minimal.
GET request
<?php
function http_get(string $url, array $headers = []): array {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 10,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status < 200 || $status >= 300) {
throw new \RuntimeException("HTTP $status");
}
return json_decode($body, true, 512, JSON_THROW_ON_ERROR);
}
$user = http_get("https://api.example.com/users/42", [
"Authorization: Bearer $token",
]);
POST request with JSON body
<?php
function http_post(string $url, array $data, array $headers = []): array {
$json = json_encode($data, JSON_THROW_ON_ERROR);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $json,
CURLOPT_HTTPHEADER => array_merge(
["Content-Type: application/json", "Content-Length: " . strlen($json)],
$headers
),
CURLOPT_TIMEOUT => 10,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status < 200 || $status >= 300) {
throw new \RuntimeException("HTTP $status");
}
return json_decode($body, true, 512, JSON_THROW_ON_ERROR);
}
$newUser = http_post("https://api.example.com/users", [
"name" => "Ana",
"email" => "ana@example.com",
]);
PUT and DELETE
<?php
function http_request(string $method, string $url, ?array $data = null): array {
$ch = curl_init($url);
$opts = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_TIMEOUT => 10,
];
if ($data !== null) {
$json = json_encode($data);
$opts[CURLOPT_POSTFIELDS] = $json;
$opts[CURLOPT_HTTPHEADER] = ["Content-Type: application/json"];
}
curl_setopt_array($ch, $opts);
$body = curl_exec($ch);
curl_close($ch);
return json_decode($body, true) ?? [];
}
// PUT
http_request("PUT", "https://api.example.com/users/42", ["name" => "Ana"]);
// DELETE
http_request("DELETE", "https://api.example.com/users/42");
Quick reference table
| Task | JavaScript | Python | Go | PHP |
|---|---|---|---|---|
| GET | fetch(url) |
requests.get(url) |
http.Get(url) |
curl_exec($ch) |
| POST JSON | fetch(url,{method:'POST',body:JSON.stringify(d)}) |
requests.post(url, json=d) |
http.Post(url, "application/json", buf) |
CURLOPT_POST + CURLOPT_POSTFIELDS |
| Set header | headers:{Authorization:'Bearer '+t} |
headers={"Authorization":"Bearer "+t} |
req.Header.Set(...) |
CURLOPT_HTTPHEADER |
| Timeout | AbortSignal.timeout(10000) |
timeout=10 |
client.Timeout=10*time.Second |
CURLOPT_TIMEOUT |
| Check status | if (!res.ok) throw... |
res.raise_for_status() |
if res.StatusCode != 200 |
if ($status >= 400) |
| Parse JSON | res.json() |
res.json() |
json.NewDecoder(res.Body).Decode(&v) |
json_decode($body, true) |
6 common mistakes
1. Forgetting to check the status code (JavaScript)
fetch does not throw on HTTP errors like 404 or 500. Always check res.ok or res.status:
// BAD — succeeds even on 404
const data = await fetch(url).then(r => r.json());
// GOOD
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
2. Setting no timeout
A slow server will hang your process indefinitely. Always set a timeout in production:
# Python — always pass timeout
requests.get(url, timeout=10)
// Go — always use a client with Timeout
client := &http.Client{Timeout: 10 * time.Second}
3. Forgetting Content-Type on POST
If you send JSON without Content-Type: application/json, many servers will reject the body or treat it as a plain string.
4. Not calling defer res.Body.Close() in Go
Omitting this leaks file descriptors. Always close the body, even if you don't read it:
res, err := http.Get(url)
if err != nil { ... }
defer res.Body.Close() // always
5. Using curl_exec without checking for errors in PHP
// BAD — $body is false on network error
$body = curl_exec($ch);
// GOOD
$body = curl_exec($ch);
if ($body === false) {
throw new \RuntimeException(curl_error($ch));
}
6. Leaking API keys in client-side JavaScript
Never put secret API keys in browser JavaScript. Move those requests to a server-side route (Next.js API route, Express middleware, etc.) and pass only the public data to the client.
FAQ
What is the difference between GET and POST?
GET requests data (no body, safe to repeat). POST submits data and typically creates or changes something on the server. Use GET for reads, POST/PUT/PATCH/DELETE for writes.
When should I use axios instead of fetch?
fetch is sufficient for most cases. Choose axios when you want: automatic JSON serialisation, easier request/response interceptors, or you need to support environments without a native fetch (older Node.js, some test runners).
How do I send query string parameters?
Append them manually or use URLSearchParams:
const params = new URLSearchParams({ page: "2", limit: "20" });
const res = await fetch(`https://api.example.com/users?${params}`);
res = requests.get("https://api.example.com/users", params={"page": 2, "limit": 20})
How do I upload a file?
Use multipart/form-data. In JavaScript:
const form = new FormData();
form.append("file", fileInput.files[0]);
await fetch("/upload", { method: "POST", body: form });
In Python:
with open("photo.jpg", "rb") as f:
res = requests.post("/upload", files={"file": f})
What is CORS and why does my fetch fail?
CORS (Cross-Origin Resource Sharing) is a browser security policy. If the server doesn't include Access-Control-Allow-Origin in its response, the browser blocks the response. The fix is server-side: add the correct CORS headers. Fetching from your own API route avoids the issue.
How do I retry on failure?
Implement exponential backoff — wait longer before each retry:
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url);
if (res.ok) return res.json();
} catch {}
await new Promise(r => setTimeout(r, 2 ** i * 500)); // 500ms, 1s, 2s
}
throw new Error("Max retries reached");
}