Toolmingo
Web development7 min read

How to Make HTTP Requests in JavaScript, Python, Go, and PHP

A practical guide to making GET, POST, PUT, and DELETE HTTP requests with fetch, axios, requests, net/http, and cURL — with error handling and JSON examples.

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");
}

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools