Desde Node.js 26.9.0 puedes llamar a una función de una librería de C directamente desde JavaScript con node:ffi: sin flags, sin node-gyp y sin compilar un addon nativo. El módulo apareció en la 26.1 detrás de un flag; lo que cambió el 16 de septiembre de 2026 es que ahora viene activado por defecto. Sigue marcado como experimental y, al momento de publicar esta nota, solo está en la línea Current, no en LTS.
¿Qué cambió en Node.js 26.9?
Las release notes de la 26.9.0 lo dicen sin rodeos: ffi: enable module by default (Matteo Collina) (nodejs/node#65475). La API no es nueva —node:ffi se agregó en la v26.1.0—, pero hasta la 26.9 había que activarla a mano. Lo comprobamos en ambos lados en una máquina Linux x64: en la 26.8.2, import 'node:ffi' falla con ERR_UNKNOWN_BUILTIN_MODULE salvo que pases --experimental-ffi; en la 26.9.0 el mismo archivo corre sin ningún flag.
“Activado por defecto” no significa “estable”. La documentación todavía marca node:ffi como Stability: 1 – Experimental, cada ejecución imprime un ExperimentalWarning y el módulo se puede desactivar con --no-experimental-ffi.
¿Qué es FFI y qué reemplaza en Node.js?
FFI (foreign function interface) es el mecanismo que permite a un lenguaje llamar funciones compiladas en otro: en este caso, JavaScript llamando a una función de C que vive en un .so, un .dylib o un .dll. Hasta ahora, la forma estándar de hacerlo desde Node era escribir un addon nativo: un wrapper en C/C++, un paso de build con node-gyp y un toolchain de C++ en cada máquina que instala tu paquete (o una matriz de binarios precompilados por sistema operativo y arquitectura), o bien recurrir a un paquete FFI de terceros desde npm.
Con node:ffi describes la firma de cada función en JavaScript y Node hace el marshalling en tiempo de ejecución. No hay nada que compilar.
¿Cómo llamar a una librería de C desde Node.js sin compilar nada?
Llamas a dlopen() con la ruta de la librería y un objeto que describe los tipos de argumentos y de retorno de cada función. Aquí tienes tres funciones de la librería estándar de C del 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
} // aquí la librería se cierra automáticamente
node ffi-demo.mjs
Corrimos exactamente este código en Node 26.9.0 y obtuvimos 5n, 42 y true, precedidos por el aviso de módulo experimental. Tres detalles que conviene notar:
using: el objeto que devuelvedlopen()implementa la gestión explícita de recursos, así que el handle de la librería se cierra al terminar el bloque. Sinusing, la cierras tú conlib.close().- Los enteros de 64 bits son
bigint.strlendevuelve unsize_t, declarado comouint64, por eso el resultado es5ny no5. Según la documentación, lo mismo vale para los argumentos: los parámetrosint64/uint64recibenbigint. - Los strings se copian. Un string de JavaScript pasado como
stringse copia a un buffer UTF-8 temporal terminado en NUL mientras dura la llamada. Los punteros que devuelve una función llegan como direccionesbigint.
Para tu propia librería, ffi.suffix te da la extensión de la plataforma (so, dylib o dll), así que `./mylib.${suffix}` funciona en los tres sistemas.
Los tipos soportados son las primitivas de C (de int8 a uint64, float32, float64, char, bool), más pointer, string, buffer, arraybuffer y function para callbacks. Los structs no están en la lista.
node:ffi vs node-gyp: ¿cuándo sigues necesitando un addon nativo?
Sigues necesitando un addon cuando tienes que escribir código nativo propio. node:ffi resuelve un caso concreto: la librería de C ya existe y solo quieres llamarla. Para eso, elimina el paso de node-gyp, el compilador de C++ en la máquina de quien instala y la matriz de binarios precompilados. Si en cambio la lógica que necesitas todavía no existe en ninguna librería compilada, alguien tiene que escribirla y compilarla; FFI no cambia eso.
¿Es seguro usar node:ffi?
Es exactamente tan seguro como C, es decir: no por defecto. La documentación es directa: “This API is unsafe.” Si declaras una firma incorrecta, pasas un puntero inválido o tocas memoria ya liberada, el proceso puede caerse o la memoria puede corromperse, y nada del lado de JavaScript te va a frenar. node:ffi no lleva la cuenta de la validez de los punteros, de quién es dueño de la memoria ni de los ciclos de vida. La firma que escribes es una promesa al runtime, y el runtime te cree.
También interactúa con el Permission Model de Node. Con --permission, cualquier llamada FFI lanza ERR_ACCESS_DENIED salvo que agregues --allow-ffi, y cuando lo haces Node imprime un SecurityWarning que avisa que ese flag “could invalidate the permission model”. Es exacto: el código nativo corre fuera de todo el sandbox que ofrece el modelo de permisos.
¿node:ffi funciona en Windows?
Sí, con límites que la documentación detalla:
dlopen(null)—cargar símbolos del propio proceso en lugar de un archivo— no está soportado en Windows.- En Windows x64, la ruta de llamada rápida admite como máximo 3 argumentos; las funciones con más pasan a la ruta genérica, que es más lenta.
- El módulo solo existe en builds con soporte FFI. El
libffiincluido no soportas390x, entre algunos otros targets.
En Linux y macOS (x86-64), la ruta rápida admite hasta 6 argumentos enteros o punteros y 8 de punto flotante.
¿Cómo hacer un benchmark en Node.js con node:bench?
Con node:bench, el runner de benchmarks integrado que también llegó en la 26.9, de James M Snell (nodejs/node#65606). A diferencia de FFI, no viene activado por defecto: un commit posterior en la misma release lo puso detrás de --experimental-bench, y está marcado como Stability 1.0 – Early Development. Sin el flag, node --bench se niega a correr.
Este es un benchmark de la llamada a strlen de arriba, al lado del .length nativo de JavaScript:
// 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
El patrón sigue la documentación: b.start() y b.end(ops) delimitan la región medida, y la verificación final hace que el resultado sea observable para que el motor no pueda eliminar el trabajo al optimizar. Por defecto, cada archivo corre en su propio proceso hijo, y el reporter spec imprime las muestras, la tasa media, un intervalo de confianza del 95 % y la mediana.
En nuestra máquina de prueba, la llamada FFI corrió a unos 7 millones de llamadas por segundo, y .length fue dos órdenes de magnitud más rápido. Toma esa segunda cifra con cuidado: un bucle que lee la longitud de un string es justo el tipo de trabajo que el JIT puede simplificar, y la documentación de node:bench lo advierte explícitamente. La lección se sostiene igual: cada llamada FFI tiene un costo fijo real. node:ffi vale la pena cuando la función de C hace trabajo significativo en cada llamada —compresión, decodificación de imágenes, una API del sistema—, no cuando la llamas millones de veces para algo que JavaScript ya hace. Si te interesa medir rendimiento a escala de proyecto, en yoDEV tenemos una guía práctica de performance con Turborepo.
¿Está node:ffi en Node.js LTS? ¿Deberías usarlo hoy?
No: al momento de publicar esta nota, node:ffi solo está activado por defecto en la línea Current (26.x); la LTS vigente es la 24.x. Para scripts, herramientas internas y prototipos sobre Node 26, úsalo: es el camino más corto entre “existe una librería de C para esto” y llamarla. Para un paquete publicado en npm, todavía no. Es experimental, no está en ninguna línea LTS, y quien instale tu paquete en un Node anterior va a recibir ERR_UNKNOWN_BUILTIN_MODULE. Los números de versión de esta nota van a envejecer; el hecho de que el core de Node ahora trae FFI, no.