Desde Node.js 26.9.0, você pode chamar uma função de uma biblioteca C diretamente do JavaScript com node:ffi: sem flags, sem node-gyp e sem compilar um addon nativo. O módulo apareceu na 26.1 atrás de um flag; o que mudou em 16 de setembro de 2026 é que agora vem ativado por padrão. Ainda está marcado como experimental e, no momento de publicar esta nota, está apenas na linha Current, não em LTS.
O que mudou no Node.js 26.9?
As notas de lançamento da 26.9.0 dizem sem rodeios: ffi: enable module by default (Matteo Collina) (nodejs/node#65475). A API não é nova — node:ffi foi adicionado na v26.1.0 —, mas até a 26.9 havia que ativá-la manualmente. Verificamos dos dois lados em uma máquina Linux x64: na 26.8.2, import 'node:ffi' falha com ERR_UNKNOWN_BUILTIN_MODULE a menos que você passe --experimental-ffi; na 26.9.0 o mesmo arquivo executa sem nenhum flag.
“Ativado por padrão” não significa “estável”. A documentação ainda marca node:ffi como Stability: 1 – Experimental, cada execução imprime um ExperimentalWarning e o módulo pode ser desativado com --no-experimental-ffi.
O que é FFI e o que substitui no Node.js?
FFI (foreign function interface) é o mecanismo que permite a uma linguagem chamar funções compiladas em outra: neste caso, JavaScript chamando uma função C que vive em um .so, um .dylib ou um .dll. Até agora, a forma padrão de fazer isso a partir do Node era escrever um addon nativo: um wrapper em C/C++, um passo de build com node-gyp e uma toolchain C++ em cada máquina que instala seu pacote (ou uma matriz de binários pré-compilados por sistema operacional e arquitetura), ou recorrer a um pacote FFI de terceiros do npm.
Com node:ffi, você descreve a assinatura de cada função em JavaScript e Node faz o marshalling em tempo de execução. Não há nada para compilar.
Como chamar uma biblioteca C a partir do Node.js sem compilar nada?
Você chama dlopen() com o caminho da biblioteca e um objeto que descreve os tipos de argumentos e de retorno de cada função. Aqui você tem três funções da biblioteca padrão C do sistema (Linux):
// ffi-demo.mjs
import { dlopen } from 'node:ffi';
{
using libc = dlopen('libc.so.6', {
strlen: { arguments: ['string'], return: 'uint64' },
getpid: { arguments: [], return: 'int32' },
abs: { arguments: ['int32'], return: 'int32' },
});
console.log(libc.functions.strlen('yoDEV')); // 5n
console.log(libc.functions.abs(-42)); // 42
console.log(libc.functions.getpid() === process.pid); // true
} // aqui a biblioteca se fecha automaticamente
node ffi-demo.mjs
```Executamos exatamente este código no Node 26.9.0 e obtivemos `5n`, `42` e `true`, precedidos pelo aviso de módulo experimental. Três detalhes que convém notar:
* **`using`**: o objeto que `dlopen()` retorna implementa gerenciamento explícito de recursos, então o handle da biblioteca é fechado ao terminar o bloco. Sem `using`, você o fecha com `lib.close()`.
* **Os inteiros de 64 bits são `bigint`.** `strlen` retorna um `size_t`, declarado como `uint64`, por isso o resultado é `5n` e não `5`. Segundo a documentação, o mesmo vale para os argumentos: os parâmetros `int64`/`uint64` recebem `bigint`.
* **As strings são copiadas.** Uma string de JavaScript passada como `string` é copiada para um buffer UTF-8 temporário terminado em NUL enquanto dura a chamada. Os ponteiros que uma função retorna chegam como endereços `bigint`.
Para sua própria biblioteca, `ffi.suffix` fornece a extensão da plataforma (`so`, `dylib` ou `dll`), então `` `./mylib.${suffix}` `` funciona nos três sistemas.
Os tipos suportados são as primitivas de C (de `int8` a `uint64`, `float32`, `float64`, `char`, `bool`), além de `pointer`, `string`, `buffer`, `arraybuffer` e `function` para callbacks. Os structs não estão na lista.
## node:ffi vs node-gyp: quando você ainda precisa de um addon nativo?
Você ainda precisa de um addon quando tem que escrever seu próprio código nativo. `node:ffi` resolve um caso específico: **a biblioteca de C já existe e você só quer chamá-la.** Para isso, elimina a etapa do `node-gyp`, o compilador de C++ na máquina de quem instala e a matriz de binários pré-compilados. Se, em vez disso, a lógica que você precisa ainda não existe em nenhuma biblioteca compilada, alguém tem que escrevê-la e compilá-la; FFI não muda isso.
## É seguro usar node:ffi?
É exatamente tão seguro quanto C, ou seja: não por padrão. A documentação é direta: "This API is unsafe." Se você declarar uma assinatura incorreta, passar um ponteiro inválido ou tocar memória já liberada, o processo pode travar ou a memória pode ser corrompida, e nada do lado de JavaScript vai te frear. `node:ffi` não mantém o controle da validade dos ponteiros, de quem é o proprietário da memória nem dos ciclos de vida. A assinatura que você escreve é uma promessa ao runtime, e o runtime acredita em você.
Também interage com o Permission Model do Node. Com `--permission`, qualquer chamada FFI lança `ERR_ACCESS_DENIED` a menos que você adicione `--allow-ffi`, e quando você faz isso o Node imprime um `SecurityWarning` que avisa que esse flag "could invalidate the permission model". É exato: o código nativo é executado fora de todo o sandbox que o modelo de permissões oferece.
## node:ffi funciona no Windows?
Sim, com limitações que a documentação detalha:
* `dlopen(null)` —carregar símbolos do próprio processo em vez de um arquivo— não é suportado no Windows.
* No Windows x64, o caminho de chamada rápida suporta no máximo 3 argumentos; funções com mais passam para o caminho genérico, que é mais lento.
* O módulo só existe em builds com suporte FFI. O `libffi` incluído não suporta `s390x`, entre alguns outros targets.
No Linux e macOS (x86-64), o caminho rápido suporta até 6 argumentos inteiros ou ponteiros e 8 de ponto flutuante.
## Como fazer um benchmark no Node.js com node:bench?
Com `node:bench`, o executor de benchmarks integrado que também chegou na 26.9, de James M Snell ([nodejs/node#65606](https://github.com/nodejs/node/pull/65606)). Diferentemente de FFI, **não** vem ativado por padrão: um commit posterior na mesma release o colocou atrás de `--experimental-bench`, e está marcado como Stability 1.0 – Early Development. Sem a flag, `node --bench` se recusa a executar.
Este é um benchmark da chamada a `strlen` acima, ao lado do `.length` nativo de JavaScript:
```js
// bench.mjs
import { dlopen } from 'node:ffi';
import { bench, suite } from 'node:bench';
const { functions } = dlopen('libc.so.6', {
strlen: { arguments: ['string'], return: 'uint64' },
});
const input = 'https://www.yodev.dev/';
suite('longitud de string', () => {
bench('strlen vía node:ffi', (b) => {
const ops = 100_000;
let total = 0n;
b.start();
for (let i = 0; i < ops; i++) total += functions.strlen(input);
b.end(ops);
if (total !== BigInt(ops * input.length)) throw new Error('resultado inesperado');
});
bench('String.prototype.length', (b) => {
const ops = 100_000;
let total = 0;
b.start();
for (let i = 0; i < ops; i++) total += input.length;
b.end(ops);
if (total !== ops * input.length) throw new Error('resultado inesperado');
});
});
node --experimental-bench --bench bench.mjs
```O padrão segue a documentação: `b.start()` e `b.end(ops)` delimitam a região medida, e a verificação final torna o resultado observável para que o motor não possa eliminar o trabalho ao otimizar. Por padrão, cada arquivo é executado em seu próprio processo filho, e o reporter `spec` imprime as amostras, a taxa média, um intervalo de confiança de 95% e a mediana.
Na nossa máquina de teste, a chamada FFI foi executada em cerca de 7 milhões de chamadas por segundo, e `.length` foi dois ordens de magnitude mais rápido. Considere essa segunda cifra com cuidado: um loop que lê o comprimento de uma string é exatamente o tipo de trabalho que o JIT pode simplificar, e a documentação de `node:bench` avisa explicitamente sobre isso. A lição se mantém a mesma: **cada chamada FFI tem um custo fixo real.** `node:ffi` vale a pena quando a função em C faz trabalho significativo em cada chamada — compressão, decodificação de imagens, uma API do sistema —, não quando você a chama milhões de vezes para algo que JavaScript já faz. Se você se interessa em medir desempenho em escala de projeto, no yoDEV temos um [guia prático de performance com Turborepo](https://www.yodev.dev/t/monorepos-a-escala-guia-pratica-de-performance-com-turborepo/2341).
## O node:ffi está no Node.js LTS? Você deveria usá-lo hoje?
Não: no momento da publicação desta nota, `node:ffi` está ativado por padrão apenas na linha Current (26.x); a LTS vigente é a 24.x. Para scripts, ferramentas internas e protótipos no Node 26, use-o: é o caminho mais curto entre "existe uma biblioteca em C para isso" e chamá-la. Para um pacote publicado no npm, ainda não. É experimental, não está em nenhuma linha LTS, e quem instalar seu pacote em um Node anterior vai receber `ERR_UNKNOWN_BUILTIN_MODULE`. Os números de versão desta nota vão envelhecer; o fato de que o core do Node agora traz FFI, não.
https://nodejs.org/en/blog/release/v26.9.0