Convert cURL to Python, Go, Node.js, PHP, or Rust — What Actually Changes
Copy-pasting a curl command from API documentation or a browser’s “Copy as cURL” devtools menu is fast — until you actually need that request running inside your application instead of a terminal. Translating curl’s flags (-X, -H, -d, -F, -u) into a specific language’s HTTP client is mechanical but easy to get subtly wrong by hand. Here’s what changes across five languages, using the same source command:
curl -X POST https://api.example.com/v1/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"name": "Alice", "email": "alice@example.com"}'
Python (requests)
import requests
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN',
}
data = '{"name": "Alice", "email": "alice@example.com"}'
response = requests.request('POST', 'https://api.example.com/v1/users', headers=headers, data=data)
print(response.status_code)
print(response.text)
requests.request() takes the method as its first argument, which maps cleanly onto curl’s -X. One deliberate choice worth calling out: the body is passed through as the exact raw string curl would send (data=), not re-parsed into a Python dict and reassembled with json=. That guarantees the bytes on the wire match curl’s byte-for-byte — reconstructing it as a dict risks silently reordering keys or reformatting numbers.
Go (net/http)
req, err := http.NewRequest("POST", "https://api.example.com/v1/users", strings.NewReader(`{"name": "Alice", "email": "alice@example.com"}`))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer YOUR_TOKEN")
client := &http.Client{}
resp, err := client.Do(req)
Go’s net/http needs headers set individually via req.Header.Set(...) after constructing the request — there’s no single options object like other languages have. The body becomes a backtick raw string (cleaner than escaping every quote in the JSON payload) passed to strings.NewReader.
Node.js (fetch)
const response = await fetch('https://api.example.com/v1/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN',
},
body: '{"name": "Alice", "email": "alice@example.com"}',
});
The closest 1:1 mapping of the five — fetch’s options object accepts method, headers, and body directly, which is essentially curl’s flags already shaped as an object. Worth knowing: this needs Node 18+, since that’s when fetch and FormData became built-in globals with no package install.
PHP (curl)
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.example.com/v1/users');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"name": "Alice", "email": "alice@example.com"}');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer YOUR_TOKEN',
]);
$response = curl_exec($ch);
PHP’s own curl extension is the most literal translation — it’s the same underlying library curl itself is built on, just called through curl_setopt() instead of command-line flags. Headers go in as "Name: value" strings in an array rather than repeated -H flags, and the method needs CURLOPT_CUSTOMREQUEST explicitly (PHP’s curl defaults to GET, same as curl itself does without -X).
Rust (reqwest)
let client = Client::new();
let response = client
.request(reqwest::Method::from_bytes(b"POST")?, "https://api.example.com/v1/users")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer YOUR_TOKEN")
.body(r#"{"name": "Alice", "email": "alice@example.com"}"#)
.send()
.await?;
Needs the tokio async runtime (#[tokio::main]) since reqwest’s API is async-first. Using Method::from_bytes(...) rather than reqwest’s named constructors (.get(), .post()) means any HTTP method from the original curl command — including unusual ones like PATCH or a custom verb — is handled the same way rather than needing special-cased branches per method.
What’s common across all five
Two details each generator gets right that are easy to miss doing this translation by hand:
-k/--insecuredisables TLS certificate verification —verify=Falsein Python, a customTransportin Go,danger_accept_invalid_certs(true)in Rust,CURLOPT_SSL_VERIFYPEER/VERIFYHOSTfalse in PHP. This should only ever be used against endpoints you explicitly trust (e.g. local dev with a self-signed cert) — never in production code talking to a real API.-u user:pass(Basic Auth) is handled by each language’s native mechanism rather than hand-computing the base64 header —auth=(user, pass)in requests,SetBasicAuthin Go,.basic_auth()in reqwest, computed viaBuffer.from(...).toString('base64')in Node,CURLOPT_USERPWDin PHP.
All five converters run entirely in your browser — the command you paste, including any tokens or credentials in it, never leaves your machine.
Try It Yourself
Convert a cURL command into a Python requests call.
Convert a cURL command into a Go net/http request.
Convert a cURL command into a Node.js fetch call.
Convert a cURL command into a PHP curl_setopt call.
Convert a cURL command into a Rust reqwest request.