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