# node-hid: talking to USB and Bluetooth HID devices from Node without writing a driver

> node-hid is a native Node addon that exposes HID devices through the hidapi layer, with prebuilt binaries for the common platforms, an async API that does not block the event loop, and a device enumeration that the documentation warns is expensive. This looks at what it takes to use it and where it stops.

**node-hid/node-hid** — Access USB & Bluetooth HID devices through Node.js

- Repository: https://github.com/node-hid/node-hid
- Stars: 1,582 · Forks: 284
- Language: C++
- License: BSD-3-Clause
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/node-hid-node-hid

## The problem is that HID reports are not a byte stream

node-hid is a library for accessing USB and Bluetooth HID devices from Node.js, and the value it delivers is in the layer it stands on.

A HID device is not a serial port. It sends input reports, which have a report ID and a length, and it receives output reports through a different path from the one it uses for input, on some devices with a report ID that has to be prefixed. Doing that correctly, across the quirks of different device firmware, is the work this library takes off your hands. Underneath it sits hidapi, a cross-platform wrapper around the native HID backends, and the readme says the thread safety of hidapi is handled for you here to avoid crashes.

The audience is therefore hardware integrators and product teams rather than general backend developers. A game controller, a macro keypad, a keyboard with a custom interface, a sensor that reports over HID, an LED device you want to control: all of these are the target. The examples in `src/` make the range concrete. There is a script to display all HID devices in the system, one that reads a PS3 joystick and controls its LED and rumble motors, one for the Griffin PowerMate knob and its LED, one that fades colours on a blink(1) RGB LED, one for the Dreamcheeky Big Red Button, and one that reads and writes a Teensy running a RawHID Arduino sketch.

Those last two are the pattern worth noticing. The Teensy example is the one to copy when you are building your own device, because RawHID is how a microcontroller presents itself as a generic HID device with no driver at all.

## Prebuilds first, node-gyp second

For most standard use cases, on macOS, Windows and Linux x86, the installation is an ordinary npm install:

```bash
npm install node-hid
```

The reason that works is the prebuild pipeline. The project uses prebuild to compile and post binaries for the most common use cases, Linux, macOS and Windows on standard processor platforms, and the install script in the package manifest is a verification followed by a fallback:

```
"install": "pkg-prebuilds-verify ./binding-options.js || node-gyp rebuild"
```

So if a prebuild matches your platform and runtime, nothing is compiled. If it does not, the install compiles the binary from source, which means you need a working toolchain at install time. The `binding-options.js` file at the repository root is what the verification reads, and `binding.gyp` is the node-gyp build description.

A global install gives you the test program as a command:

```bash
npm install -g node-hid
hid-showdevices libusb
hid-showdevices hidraw
```

On Linux that distinction matters. The two invocations list the same devices through the two different backends, `hidraw` and `libusb`, and seeing which one finds your device tells you which backend you need. The readme has a Linux section on selecting the driver type, alongside one on udev device permissions, which is the other classic Linux problem with HID devices, since a udev rule is usually needed before a non-root process can open the device node.

The package also declares `"napi_versions": [4]`, and the readme explains why that matters: future versions of Node or Electron should work with no extra work, since node-hid is now based on NAPI. That is a significant improvement over the older node-gyp world, where every Node major release meant a new binary.

## Use the async API, the sync one is a trap at scale

Since 3.0.0 the library supports both the old synchronous API and a newer async one, and the readme recommends the async API to avoid node-hid blocking your code from executing. For prototyping or tiny applications it says this likely will not matter, but for npm libraries or larger applications it can be problematic.

The reason is concrete rather than stylistic. The sync API is limited to only being able to read up to the `UV_THREADPOOL_SIZE`, default 4, number of devices at once. Reading from several devices at once degrades the performance of your application, because there will be fewer than expected libuv workers available for Node and other libraries to use for other tasks.

The async API is identical in shape except that every method returns a `Promise` that must be handled, and the readme warns that any unhandled promise can crash your application. It also notes that it is safe to use the sync API for some devices and the async API for others in the same application.

The listing call looks like this:

```js
var HID = require("node-hid");
var devices = await HID.devicesAsync();
```

The result is an array with one object per available HID device, and the fields of interest are `vendorId` and `productId`, which uniquely identify a device. That pair is what you use to find the device you care about among everything else attached to the machine.

## Enumeration is the expensive part, and the readme says so

There is a section in the readme that is unusual for a library documentation file, and it is worth reading before you write any code.

`HID.devices()`, `HID.devicesAsync()`, `new HID.HID()` and `HIDAsync.open()` are all described as relatively costly, each causing a USB, and potentially Bluetooth, enumeration. Doing either can slow down the read and write that you do in parallel with a device, and cause other USB devices to slow down too. The readme attributes this to how USB works, which is the honest answer: enumeration is a bus operation and everybody on the bus pays for it.

The consequence for application design is straightforward. If you are polling `HID.devices()` or `HID.devicesAsync()`, or other inefficient methods, to detect device plug and unplug, you should instead use node-usb, which uses OS-specific, non-bus enumeration ways to detect plug and unplug.

That recommendation is a real design constraint rather than a footnote. The common pattern of opening the device and, if it fails, polling for it again turns every reconnect into a bus enumeration, and on a machine with a dozen USB peripherals that is a latency and battery cost paid by everything attached. The answer is to open the device once, keep it open, and let a separate, cheaper mechanism tell you when something changed.

This is also where the boundary with node-usb sits. node-hid is for HID devices specifically, while node-usb is the separate project the readme recommends for plug and unplug detection. Most projects that think they need both actually need one, and the readme is clear about which is which.

## The prebuild matrix, and what it means for your platform

The platform table is the practical constraint, and the footnotes are the interesting part.

Windows x86 and x64 and macOS 10.9 or later are straightforward. On Linux there is x64, x86, ARM for Raspberry Pi and other single board computers, ARM64, MIPSel and PPC64, and the footnotes say that prebuilt binaries are not provided for the x86, MIPSel and PPC64 entries, while the prebuilt binaries that do exist for Linux ARM, ARM64 and the SBC targets are built on Debian 10 Buster.

That Debian 10 detail is the kind of thing that saves an afternoon. A prebuilt binary linked against an old glibc will run on a newer system, but not the other way around, so a binary built on Buster works on anything newer and fails on older or on a distribution with an unusual library set. If your deployment is a current minimal container image, you may be compiling from source, which is fine but should be a decision rather than a surprise.

The runtime versions are listed as Node v10 upwards, with the table showing Node v10 to and Node v20, and Electron v3 upwards with the table showing Electron v3 to and Electron v24. The readme is explicit that future versions should work with no extra work because the module is NAPI based now, and the manifest requires Node `>=10.16`.

For an Electron application there is one more wrinkle. The readme has a section on Electron projects using node-hid and another for NW.js projects, which is the point at which a native module in your dependency tree becomes an ABI problem: the Electron build has to match the runtime, and a prebuild for Node is not a prebuild for Electron.

## Where node-hid is the wrong tool

Four limits are clear.

The first is class scope. This is a HID library. A device that is not in the HID class, a USB camera, a serial adapter, a disk, anything that needs a vendor driver, is out of scope. Choosing node-hid for a non-HID device is a category error that shows up as an empty device list.

The second is the native module. A native addon in your dependency tree means a toolchain at install time on any platform without a matching prebuild, and an ABI match on any runtime that is not plain Node, Electron above all. For a library you publish on npm, that is a support burden that comes with every consumer, and it is why the async and thread safety work in the library matters so much, since the alternative would be for each consumer to solve it.

The third is Bluetooth. The description covers USB and Bluetooth HID devices, but the Bluetooth side is a platform capability rather than something the library provides, and the platform table only lists operating systems and architectures. If your target is a Bluetooth keyboard or a Bluetooth HID peripheral, the practical question is what the host OS exposes, and the answer differs enough between platforms that it needs checking before you design around it.

The fourth is the device detection story, which the readme already tells you is not solved by this library. Hotplug detection, and the enumeration cost that comes with it, is a separate problem with a separate answer.

On maintenance, the record is healthy and recent. v3.2.0 was published on 2025-06-03, v3.3.0 on 2026-02-02, and v3.4.0 on 2026-07-18, with the last push to the repository on 2026-07-20.

## Two licence names, and why the file in the root wins

There is a small inconsistency in the licensing information that is worth resolving before you vendor the code.

The licence is listed as BSD-3-Clause in the project metadata, and there is a `LICENSE-bsd.txt` file at the root. The package manifest, however, declares the licence as `(MIT OR X11)`, and the published `files` list includes `LICENSE*`, so whatever licence files are present ship with the package.

Since the library is a native addon wrapping hidapi, the licence of the wrapped code matters as well, which is why the `hidapi` directory is in the package file list, and the repository carries it as a submodule with a `prepublishOnly` script that runs `git submodule update --init` so the submodule is present at publish time.

The practical advice is to read the licence files in the root of the repository and in the submodule rather than to rely on a metadata field, and to note that the `MIT OR X11` declaration in the manifest is the one your package manager and your dependency scanner will report.

The project also carries a `Publishing.md` describing the release process, and the repository has the usual GitHub workflows, including a build workflow whose badge sits at the top of the readme, and a `src/` directory with the example programs, the CI test entry point at `src/test-ci.js`, and the TypeScript definitions for the API in `nodehid.d.ts`.

## Conclusion

node-hid fits a Node or Electron project that needs to talk to a specific HID device, a controller, a keyboard, a macro pad or a sensor, and that wants the parsing and report handling done for it. It does not fit a project that needs generic USB access, and it is the wrong choice if you cannot ship a native module, because the fallback is compiling at install time. Before you adopt it, check that your platform and runtime combination has a prebuild, because the install script only falls back to `node-gyp rebuild` when the prebuild verification fails, and prefer the async API, since the sync one can only read a handful of devices at a time before it starves the thread pool.

## FAQ

### How do I install node-hid?

Run `npm install node-hid` for the standard platforms of macOS, Windows and Linux x86. The install script verifies a prebuilt binary with `pkg-prebuilds-verify ./binding-options.js` and only falls back to `node-gyp rebuild` when no prebuild matches, so a source build means your platform or runtime combination is not covered by the prebuilds.

### Should I use the sync or the async API in node-hid?

The async API. The readme recommends it to avoid blocking, and notes the sync API can only read up to the UV_THREADPOOL_SIZE, default 4, devices at once, which starves the thread pool for everything else in the process. The async API is the same shape, with every method returning a Promise that must be handled.

### How do I detect when a HID device is plugged in?

Not by polling node-hid. The readme warns that HID.devices(), HID.devicesAsync(), new HID.HID() and HIDAsync.open() each cause a USB and potentially Bluetooth enumeration that slows down other devices on the bus, and it points to node-usb, which detects plug and unplug using OS-specific, non-bus enumeration.

### Which platforms have prebuilt binaries for node-hid?

Windows x86 and x64, macOS 10.9 and later, and Linux x64, ARM and ARM64 including Raspberry Pi and other single board computers. Prebuilt binaries are not provided for Linux x86, MIPSel or PPC64, and the Linux ARM and ARM64 prebuilds are built on Debian 10 Buster.

### What licence is node-hid released under?

The package manifest declares `(MIT OR X11)` while the project metadata lists BSD-3-Clause, and the root carries a LICENSE-bsd.txt file. The package ships its licence files, and the hidapi submodule is included in the published files, so read those files when vendoring the code.

## Sources

- [Issues](https://github.com/node-hid/node-hid/issues)
- [License: BSD-3-Clause](https://github.com/node-hid/node-hid/blob/master/LICENSE)
- [node-hid/node-hid on GitHub](https://github.com/node-hid/node-hid)
- [README](https://github.com/node-hid/node-hid/blob/master/README.md)
- [Releases](https://github.com/node-hid/node-hid/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/node-hid-node-hid
