From cURL to Multi-Language Code: Building a cURL Converter
From cURL to Multi-Language Code: Building a cURL Converter#
When debugging APIs, I often find myself using the “Copy as cURL” button in browser DevTools. The copied command is convenient, but when it’s time to write actual code, I have to manually convert it to JavaScript fetch or Python requests. So I built an automatic converter. Here’s how it works.
cURL Parsing: The Art of Regular Expressions#
cURL commands look simple, but parsing them has nuances. A typical command:
curl -X POST https://api.example.com/data \
-H "Content-Type: application/json" \
-H "Authorization: Bearer token123" \
-d '{"key":"value"}'
The core parsing logic needs to extract four parts: URL, HTTP method, headers, and body.
URL Extraction#
The URL can appear in multiple positions:
// Standard form
curl https://api.example.com/data
// With --url parameter
curl --url "https://api.example.com/data"
// With quotes
curl 'https://api.example.com/data'
The regex needs to cover these cases:
const urlMatch = curl.match(/curl\s+['"]?([^'"\s]+)['"]?/i)
|| curl.match(/--?url\s+['"]?([^'"\s]+)['"]?/i)
['"]? matches optional quotes, [^'"\s]+ matches non-quote non-whitespace URL characters.
HTTP Method Extraction#
Methods are specified with -X or --request:
const methodMatch = curl.match(/-X\s+(\w+)/i)
|| curl.match(/--request\s+(\w+)/i)
Default is GET; if matched, convert to uppercase.
Header Extraction#
Headers are passed with -H, and there can be multiple:
-H "Content-Type: application/json" -H "Authorization: Bearer token"
Use matchAll to extract all matches:
const headerMatches = curl.matchAll(/-H\s+['"]([^:]+):\s*([^'"]+)['"]/gi)
Array.from(headerMatches).forEach((match) => {
result.headers[match[1]] = match[2]
})
([^:]+) matches the header name, \s* matches optional spaces after the colon, ([^'"]+) matches the value.
Body Extraction#
Body is passed with --data, --data-raw, or -d:
const bodyMatch = curl.match(/--data(?:-raw)?\s+['"]([^'"]+)['"]/i)
|| curl.match(/-d\s+['"]([^'"]+)['"]/i)
(?:-raw)? is a non-capturing group, meaning the -raw suffix is optional.
Code Generation: Multi-Language Template Engine#
After parsing structured data, the next step is generating target language code. The core is a switch-case template engine.
JavaScript Fetch#
The most common target language:
case 'javascript-fetch':
return `fetch("${url}", {
method: "${method}",
headers: ${JSON.stringify(headers, null, 2).replace(/"/g, "'")}${body ? `,
body: ${JSON.stringify(body)}` : ''}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));`
A few details:
JSON.stringify(headers, null, 2)formats the headers object.replace(/"/g, "'")swaps double quotes for single quotes, more idiomatic in JS- Only add body field when body exists
Python Requests#
Python’s requests library has concise syntax:
case 'python-requests':
return `import requests
response = requests.${method.toLowerCase()}("${url}"${body ? `, json=${body}` : ''}${Object.keys(headers).length ? `, headers=${JSON.stringify(headers)}` : ''})
print(response.json())`
method.toLowerCase() converts POST to requests.post, GET to requests.get.
PHP cURL#
PHP’s cURL extension is closest to the original curl command:
case 'php-curl':
return `$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "${url}");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "${method}");${Object.keys(headers).length ? `
curl_setopt($ch, CURLOPT_HTTPHEADER, ${JSON.stringify(Object.entries(headers).map(([k, v]) => `${k}: ${v}`))});` : ''}${body ? `
curl_setopt($ch, CURLOPT_POSTFIELDS, ${JSON.stringify(body)});` : ''}
$response = curl_exec($ch);
curl_close($ch);
echo $response;`
PHP’s cURL API is imperative, each option set via curl_setopt.
Go net/http#
Go’s HTTP client code is longer but structured:
package main
import (
"fmt"
"net/http"
"io"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.example.com/data", strings.NewReader(`{"key":"value"}`))
req.Header.Add("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
Edge Cases I Encountered#
1. Nested Quotes#
Quotes in curl commands can nest:
curl -d '{"name":"John \"The Boss\" Doe"}' https://api.example.com
Simple regex can’t handle escaped quotes; you need a more complex parser or AST tools.
2. Multiline Commands#
curl commands can use backslashes for line continuation:
curl -X POST https://api.example.com \
-H "Content-Type: application/json" \
-d '{"key":"value"}'
Remove backslashes and newlines before parsing:
const cleanCurl = curl.replace(/\\\n/g, ' ')
3. Special Characters#
Query parameters in URLs can contain special characters:
curl "https://api.example.com/search?q=hello%20world&lang=en"
Generated code should preserve URL encoding or re-encode according to target language conventions.
4. File Uploads#
curl’s --form parameter is for file uploads:
curl -F "file=@photo.jpg" https://api.example.com/upload
This needs special handling to generate FormData or multipart code.
Performance: Real-time Preview#
Convert in real-time as user types, not on button click:
useEffect(() => {
const timer = setTimeout(() => {
try {
const parsed = parseCurl(curlInput)
const code = generateCode(parsed, selectedLang)
setOutput(code)
} catch (e) {
setOutput('Error parsing cURL command')
}
}, 300) // 300ms debounce
return () => clearTimeout(timer)
}, [curlInput, selectedLang])
300ms debounce prevents parsing on every keystroke.
The Result#
Based on these ideas, I built: cURL Converter
Features:
- Supports 11 programming languages (JavaScript, Python, PHP, Go, Rust, Java, C#, Ruby, Shell)
- Real-time conversion, no button needed
- One-click copy generated code
- Supports custom headers and body
The implementation isn’t complex, but getting parsing and generation details right takes effort. Hope this helps.
Related: HTTP Status Codes | API Tester