Skip to content

cURL to Node.js Converter

Convert a cURL command to Node.js code using only the built-in https module — no dependency to install, Content-Length and status checks handled for you.

cURL command

bash syntax

Node.js (https)

request.mjs

Paste a command to see the generated code.

Parsing and code generation happen in your browser. Tokens and credentials inside the command are never transmitted.

About the cURL to Node.js Converter

Paste a cURL command and get working Node.js code using the built-in `node:https` module — no dependency to install, nothing to add to package.json. It is the right target for a script, a serverless handler, or anywhere you would rather not pull in a HTTP client just to make one request.

The raw client is lower-level than fetch or Axios, and the generated code handles the parts that are easy to get wrong. Bodies are serialised before the request so `Content-Length` can be set — omit it and many servers will hang or return a 411. The response is collected from its `data` events and resolved through a promise, so the result is `await`-able instead of nested in callbacks. The status code is checked explicitly, because the raw client treats a 500 as a perfectly successful request.

If your Node version is recent enough, the global `fetch` is usually the better choice — see the cURL to fetch converter. This page exists for the cases where it is not: pinned older runtimes, code that needs an `https.Agent` for mTLS or proxying, or a hot path where you want the stream rather than a buffered body.

  • Uses only built-in modules — `node:https` or `node:http`, picked from the URL scheme
  • Bodies serialised up front with an explicit `Content-Length`
  • Callback API wrapped in a promise so the request is `await`-able
  • Explicit status-code check, which the raw client does not perform
  • `-L` and `--insecure` reported honestly rather than silently ignored
  • Shares one converter with the fetch, Axios and Python targets — switch tabs without retyping the command

How to use it

  1. Copy a cURL command — from an API's docs, your shell history, or Copy as cURL (bash) in browser DevTools.
  2. Paste it into the left pane. Multi-line commands with backslash continuations work as-is.
  3. Read the generated Node code on the right; it updates as you type.
  4. Save it as an `.mjs` file, or use `.js` inside a package with `"type": "module"` — the output uses ESM imports and top-level await.
  5. Move any token or password into `process.env` before committing the script.

Real-world use cases

Backend & Node.js developers

Write a small script, a Lambda or Cloud Function handler, or a CLI tool where pulling in axios or node-fetch just to make one request is not worth the dependency.

DevOps & platform engineers

Turn a curl health-check or webhook call from a runbook into a cron job or startup script that runs on a minimal container image with no npm install step available.

Security & platform engineers needing mTLS

Start from the generated request and add a custom `https.Agent` for client certificates, connection pooling or a corporate proxy — the kind of transport-level control higher-level clients hide.

Developers on pinned or older runtimes

Target a Node version where the global fetch is unavailable or behind a flag, and get working request code that has no dependency on it.

QA & CI engineers

Write a zero-dependency smoke-test script for a pipeline where adding packages means an extra install step and a slower, less reliable CI run.

Examples

A JSON POST

Input
curl -X POST https://api.example.com/users \
  -H 'Content-Type: application/json' \
  -d '{"name":"Ada","active":true}'
Output
import https from "node:https";

const url = new URL("https://api.example.com/users");

const payload = JSON.stringify({
  "name": "Ada",
  "active": true
});

const options = {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Content-Length": Buffer.byteLength(payload),
  },
};

const body = await new Promise((resolve, reject) => {
  const req = https.request(url, options, (res) => {
    let data = "";
    res.setEncoding("utf8");
    res.on("data", (chunk) => {
      data += chunk;
    });
    res.on("end", () => {
      if (res.statusCode >= 400) {
        reject(new Error(`Request failed: ${res.statusCode} ${res.statusMessage}`));
        return;
      }
      resolve(data);
    });
  });

  req.on("error", reject);
  req.write(payload);
  req.end();
});

console.log(JSON.parse(body));

Content-Length uses Buffer.byteLength, not string length — they differ for any non-ASCII character, and a wrong value truncates the body.

A plain GET with headers

Input
curl https://api.example.com/v1/orders?status=open \
  -H 'Authorization: Bearer tok_123' \
  -H 'Accept: application/json'
Output
import https from "node:https";

const url = new URL("https://api.example.com/v1/orders?status=open");

const options = {
  method: "GET",
  headers: {
    "Authorization": "Bearer tok_123",
    "Accept": "application/json",
  },
};

const body = await new Promise((resolve, reject) => {
  const req = https.request(url, options, (res) => {
    let data = "";
    res.setEncoding("utf8");
    res.on("data", (chunk) => {
      data += chunk;
    });
    res.on("end", () => {
      if (res.statusCode >= 400) {
        reject(new Error(`Request failed: ${res.statusCode} ${res.statusMessage}`));
        return;
      }
      resolve(data);
    });
  });

  req.on("error", reject);
  req.end();
});

console.log(JSON.parse(body));

No body, so there is no req.write and no Content-Length. The Accept header is what makes the output parse the response as JSON.

Basic auth

Input
curl -u ada:hunter2 https://api.example.com/me
Output
const options = {
  method: "GET",
  auth: "ada:hunter2",
};

node:https takes basic credentials as a single "user:password" string and does the base64 encoding itself.

Common errors

MessageCauseFix
Request hangs and never resolves`req.end()` was never called, so Node keeps the request open waiting for more body to be written.Always finish with req.end(). The generated code does this on every path.
411 Length RequiredA body was written without a Content-Length header, and the server does not accept chunked encoding.Set Content-Length to Buffer.byteLength(payload), as the generated code does.
Body arrives truncated or with mangled charactersContent-Length was computed with payload.length. That is the character count, not the byte count, and any non-ASCII character makes the two differ.Use Buffer.byteLength for the header, and res.setEncoding("utf8") when reading the response.
Response body is empty even though the request succeededThe `data` events were never consumed, or the code read `res` synchronously instead of waiting for `end`.Accumulate chunks on data and resolve on end — the shape the generated code uses.
A 500 response is treated as successnode:https only rejects on transport errors. Any HTTP status, including 500, is a completed request.Check res.statusCode explicitly. The generated code rejects on anything 400 and above.
Redirect returns a 301 body instead of the resourcecurl's -L was in the command, but node:https does not follow redirects at all.Read the Location header and issue a second request, or use fetch, which follows redirects by default.

Frequently asked questions

Is my cURL command sent anywhere?

No. Parsing and code generation both run in your browser as you type. Bearer tokens, cookies and passwords inside the command never leave the page, and nothing is logged or stored.

Should I use this or the built-in fetch?

Prefer fetch on any Node version where it is available — it is far less code and follows redirects for you. Use node:https when you are pinned to an older runtime, need a custom https.Agent for mTLS, connection pooling or a proxy, or want to stream the response instead of buffering it into a string.

Why is the code wrapped in a promise?

node:https is a callback and event-emitter API, so the response arrives in pieces across data and end events. Wrapping it once gives you a single awaitable value and lets errors propagate through normal try/catch instead of an error listener.

Do I need `.mjs` or a type: module package?

Yes, for the output as written — it uses ESM import syntax and top-level await. If you are on CommonJS, change the import to const https = require("node:https") and wrap the call in an async function.

Why doesn't it follow redirects?

node:https has no redirect handling of its own; that is a feature of higher-level clients. If the command had -L, the converter says so in a comment at the top rather than generating code that quietly behaves differently from your curl command.

How are file uploads handled?

A -F field with an @filename becomes a commented-out form.append line. Because the raw client writes bytes rather than a FormData object, the generated code serialises the form through Response first, which computes the multipart boundary and the matching Content-Type.

Does it use node:http or node:https?

Whichever the URL scheme calls for — node:https for an https:// URL, node:http for a plain http:// one. The import line and the module the request is built from both switch automatically; you do not need to edit the generated code to match the URL you pasted.

Can I stream the response instead of buffering it into a string?

The generated code buffers the body, accumulating data events into one string before resolving — the shape most scripts need. For a large download or a proxy, replace the data/end handling with res.pipe(destination) directly on the response stream; the request setup, headers and status check above it stay the same.

Last updated