node-libcurl: the whole of libcurl from Node.js
libcurl bindings for Node.js
At a glance
- What is it?
- node-libcurl is a native Node addon that binds libcurl directly, so a JavaScript program gets the entire protocol set of the C library, from HTTP and HTTP2 to FTP, SFTP, SMTP, IMAP, and the authentication schemes that go with them. It ships prebuilt binaries, an event based API that mirrors the C one, and a newer promise based helper called curly.
- Who is it for?
- node-libcurl fits a Node service that needs a protocol the JavaScript HTTP clients do not cover, or needs libcurl's authentication and resume behaviour, and that is prepared to take on a native dependency. It does not fit a browser bundle, because the library depends on native code and says so plainly, and it does not fit a project that wants a dependency free install.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 49 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Every protocol libcurl speaks
node-libcurl is a set of libcurl bindings for Node.js, and the README leads with the claim of being the fastest feature rich URL transfer library for Node.js.
The case for it is the protocol list, which is quoted from libcurl itself. The library supports DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet, and TFTP. It also supports SSL certificates, HTTP POST and PUT, FTP uploading, HTTP form based upload, proxies, cookies, user and password authentication with Basic, Digest, NTLM, Negotiate, and Kerberos, file transfer resume, and HTTP proxy tunnelling.
Read that list as a set of requirements rather than a feature list. A Node application that has to speak SFTP, or to authenticate with NTLM against a corporate proxy, or to resume an interrupted file transfer, is dealing with problems that a fetch wrapper does not solve, because the protocol is not HTTP. This library does not implement any of it, it embeds the C library that already does, and exposes it.
There is a cost to that, and the README states it immediately: this library cannot be used in a browser, because it depends on native code. That is not a limitation to work around, it is a category, and the right question is whether a Node service is what you are building.
The project is MIT licensed, the default branch is develop, and the last push was on 2026-09-04, with releases v5.1.2 in June 2026, v5.1.1 earlier that month, and v5.1.0 at the end of May, so the 5.x line is moving quickly.
The topic list gives the rest of the picture, with HTTP2, HTTP3, QUIC, and prebuilt binaries all named.
Two APIs, one of them experimental
There are two ways to make a request, and the README is clear about the difference in maturity.
The older one is the Curl class, which mirrors the C library. You construct it, set options, register callbacks, and perform.
const { Curl } = require('node-libcurl');
const curl = new Curl();
curl.setOpt('URL', 'www.google.com');
curl.setOpt('FOLLOWLOCATION', true);
curl.on('end', function (statusCode, data, headers) {
console.info(statusCode);
console.info('---');
console.info(data.length);
console.info('---');
console.info(this.getInfo( 'TOTAL_TIME'));
this.close();
});
curl.on('error', curl.close.bind(curl));
curl.perform();The end callback receives the status code, the data, and the headers, and the getInfo call reaches into the transfer statistics, with the total time being the example. The error handler is bound to close, which is the pattern the rest of the examples use as well, because the handle has to be released whichever way the transfer ends.
The newer one is the curly helper, and it is the one most people will use.
const { curly } = require('node-libcurl');
const { statusCode, data, headers } = await curly.get('https://www.google.com')The README marks this API as experimental and subject to change without a major version bump, which is worth knowing before a codebase depends on it.
One detail is convenient and easy to miss. Any option can be passed using its full name or a lower camel case form, so the C constant FOLLOWLOCATION and the JavaScript spelling followLocation are the same option. That keeps the examples from looking like a different library from the one the C documentation describes.
Headers, forms, and multipart bodies
The examples in the README cover the three request body shapes an application actually meets, and each maps onto a specific libcurl option.
Headers are an array of strings, passed with the HTTPHEADER option.
curl.setOpt(Curl.option.HTTPHEADER,
['Content-Type: application/x-amz-json-1.1'])A form submission sets the post flag and the POSTFIELDS option to a string, and the example uses the Node query string module to build it.
const querystring = require('querystring');
const { Curl } = require('node-libcurl');
const curl = new Curl();
const close = curl.close.bind(curl);
curl.setOpt(Curl.option.URL, '127.0.0.1/upload');
curl.setOpt(Curl.option.POST, true)
curl.setOpt(Curl.option.POSTFIELDS, querystring.stringify({
field: 'value',
}));
curl.on('end', close);
curl.on('error', close);A JSON post is the same option with a JSON string, plus the content type header, and the response is read from the data property.
A multipart upload is the one that differs in shape, because the body is a list of parts rather than a string, and each part is an object with a name and either a file path or its contents, and optionally a type.
const { Curl } = require('node-libcurl');
const curl = new Curl();
const close = curl.close.bind(curl);
curl.setOpt(Curl.option.URL, '127.0.0.1/upload.php');
curl.setOpt(Curl.option.HTTPPOST, [
{ name: 'input-name', file: '/file/path', type: 'text/html' },
{ name: 'input-name2', contents: 'field-contents' }
]);
curl.on('end', close);
curl.on('error', close);The file form is what makes this worth using rather than assembling the body by hand, since libcurl reads the file from disk and streams it.
Binary data and the utf8 decoding trap
There is a section of the README on binary data, and it exists because of a specific behaviour that catches people out.
By default, the Curl instance tries to decode the received data and the headers into utf8 strings. For a JSON response or an HTML page that is exactly what you want, and for a PNG, an archive, or a protobuf body it is not, because the decode will mangle the bytes.
There are two ways out, and the README gives both. You can pass your own WRITEFUNCTION, which is the callback that receives the raw buffer, the size, and the element count, and in which you can do whatever you like with the bytes.
curl.setOpt('WRITEFUNCTION', (buffer, size, nmemb) => {
// something
})Or you can enable one of two feature flags, which switch the decoding off.
curl.enable(CurlFeature.NoDataParsing)
// or
curl.enable(CurlFeature.Raw)The two flags are not identical, and the difference is about how much libcurl does with the payload on its way to you. The practical rule is that a binary endpoint needs one of them, and that the symptom without it is a response body that looks like a string with replacement characters in it.
The special notes section also calls out the READFUNCTION option, which is the same idea for uploads rather than downloads, and the common issues section exists because there are enough of these sharp edges to warrant one.
The example set in the repository follows the same logic. There is a dedicated example for binary handling through a custom write function, one for streams, one for aborting a transfer, one for cookies, one for the info and statistics, one for debug and progress callbacks, one for SSL, and a set for FTP covering download, full upload, resume upload, and wildcard matching.
Prebuilt binaries, vcpkg, and the native build
The distribution story is the part of a native addon that decides whether it gets used, and the package manifest is unusually explicit about it.
There is a binary section that names the module, where the compiled binding is placed, the remote path template, the naming pattern for the archive, and the host that serves it. The archive name carries the module version, the Node ABI, the platform, the architecture, and the C library, so a prebuilt binary is only correct for one exact combination of those five things.
That is worth understanding before you install, because it is why a native addon sometimes has to be compiled on the machine rather than downloaded, and why a Node version bump can turn into a build.
The dependency on libcurl itself is handled with vcpkg, and the repository carries the vcpkg configuration and a template for it, along with the manifest that lists the sources to build. The scripts directory is part of the published files, which is what lets the install step fetch or build libcurl on the target machine.
The build of the addon itself is node-gyp, with the binding.gyp file at the root, and the scripts include the node-pre-gyp commands for generating a compile commands database, which is what the clang and clangd configuration in the repository is for.
The package is a commonjs package whose main entry is the built index and whose types entry is the generated declaration file, and the type generation is checked with the api extractor, with a configuration file at the root, so a change to the public surface is something the tooling can see.
The CI badge points at AppVeyor, which is the older of the two build services and is a common choice for native addons that need a Windows toolchain, and the repository also has an electron directory with its own notes in the README about building there, because an electron application has its own ABI and its own build.
The example set, and what it tells you
The examples directory is the best catalogue of what the library does, and it is organised by number so that it reads as a progression.
The first few are the basics. A plain request with the Curl class, the same with streams, the easy curly form, the curly form with streams, and a curly form with an abort. Then there is a multiple example, a share example, and one that reports the curl version, which is the quickest way to find out which libcurl you are actually linked against.
Then the request body examples, a post with data, and three file upload variants, one with a post, one with a put, and one streaming the file. Then the stateful ones, cookies, a duplicate handle example, and one that reads the transfer info.
The observability examples are a debug callback and a progress callback, and they matter more than their number suggests, because a native transfer that takes a long time and gives no progress is impossible to reason about.
After that come the protocol features, an SSL example, a send and receive methods example, and four FTP examples, a download, a full upload, a resume upload, and a wildcard function match. The FTP set is the clearest argument for the library existing at all, because resumable upload and wildcard matching are exactly the things a general purpose HTTP client will not do for you.
The rest of the repository supports all of it. There is a benchmark directory and a vitest configuration with a separate stress configuration, a tools directory with a brute force leak test that runs the inspector with garbage collection exposed, an issue-multi-crash-promise.js file at the root for a known failure mode, and the usual supporting files, including an editor configuration, a formatting configuration, a commit message configuration, and hooks.
The MIT licence and the funding links are in the README, and the project is maintained by Jonathan Cardoso, with a Discord server and a help section that points at the common issues document and the debugging document.
Editorial conclusion
node-libcurl fits a Node service that needs a protocol the JavaScript HTTP clients do not cover, or needs libcurl's authentication and resume behaviour, and that is prepared to take on a native dependency. It does not fit a browser bundle, because the library depends on native code and says so plainly, and it does not fit a project that wants a dependency free install. Verify first that the prebuilt binary for the Node ABI, the platform, the architecture, and the C library in use is published for that combination, because the naming scheme in the manifest shows how many of those there are, and that the protocol you need is enabled in the libcurl build you are linking against.
Frequently asked questions
What does node-libcurl add over a fetch based HTTP client?
It binds libcurl itself, so a Node program gets the whole protocol set of the C library, including DICT, FILE, FTP, FTPS, Gopher, IMAP, LDAP, POP3, RTMP, RTSP, SCP, SFTP, SMTP, Telnet, and TFTP, along with Basic, Digest, NTLM, Negotiate, and Kerberos authentication, cookies, proxies, and resumable file transfer.
Can node-libcurl be used in a browser?
No. The README states that the library cannot be used in a browser because it depends on native code, and the package is a native addon built with node-gyp rather than a pure JavaScript package.
What is the difference between the Curl class and the curly helper in node-libcurl?
The Curl class is the event based API that mirrors the C library, with set options, register callbacks for the end and the error, and perform. The curly helper is the promise based form, where a get or a post returns a promise resolving to the status code, the data, and the headers. The README marks the curly API as experimental and subject to change without a major version bump.
How do I receive binary data with node-libcurl?
Two ways. Pass your own WRITEFUNCTION option to receive the raw buffer, or enable the NoDataParsing or the Raw feature flag. The default behaviour of the Curl instance is to decode the received data and the headers into utf8 strings, which mangles a binary payload.
How are the native binaries for node-libcurl distributed?
As prebuilt archives named with the module version, the Node ABI, the platform, the architecture, and the C library, downloaded from the GitHub releases. The libcurl dependency itself is handled with vcpkg, whose configuration and template are in the repository, and the addon is built with node-gyp from the binding.gyp file at the root.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jcmais-node-libcurl)Was this useful?