A Node-API no Node.js é uma interface C estável para criar addons nativos que podem continuar compatíveis entre diferentes versões do runtime. Em vez de depender diretamente das APIs internas do V8, um addon usa funções e tipos definidos pelo Node.js, reduzindo a necessidade de recompilar a cada atualização.
Node-API é útil quando JavaScript sozinho não atende requisitos de desempenho, integração com bibliotecas C ou C++, acesso a hardware, codecs, bancos embarcados ou APIs de sistema. O ganho vem acompanhado de complexidade: gerenciamento de memória, erros, threads, builds por plataforma e riscos de segurança.
Neste guia, você aprenderá a estrutura de um addon, valores napi, funções exportadas, erros, referências, buffers, callbacks assíncronos, thread-safe functions, object wrap, versionamento, build, distribuição e testes.
O que é Node-API?
Node-API, anteriormente chamada N-API, fornece uma ABI para addons nativos. A documentação oficial de Node-API descreve funções, versões e padrões. O projeto node-addon-api no GitHub oferece wrappers C++ sobre a API C.
Para entender o sistema de módulos, consulte Module API no Node.js. Para trabalho em threads, veja Worker Threads no Node.js. O artigo sobre Buffer no Node.js ajuda na troca de dados binários.
Quando usar Node-API?
- integrar biblioteca nativa existente;
- acessar dispositivo ou driver;
- executar algoritmo intensivo otimizado;
- implementar codec;
- criar bridge para SDK do sistema;
- reutilizar código C ou C++.
Antes de criar um addon, avalie WebAssembly, Worker Threads ou processo separado. Código nativo pode derrubar todo o processo se houver acesso inválido à memória.
Estrutura mínima em C
#include <node_api.h>
napi_value Add(
napi_env env,
napi_callback_info info
) {
napi_value result;
napi_create_int32(env, 42, &result);
return result;
}
NAPI_MODULE_INIT() {
napi_value fn;
napi_create_function(
env,
"add",
NAPI_AUTO_LENGTH,
Add,
NULL,
&fn
);
napi_set_named_property(env, exports, "add", fn);
return exports;
}O módulo exporta uma função JavaScript que retorna 42.
napi_env
napi_env representa o ambiente da chamada. Ele deve ser usado apenas no contexto e thread permitidos. Não armazene para uso arbitrário em outra thread.
napi_value
Valores JavaScript são representados por napi_value. Eles não são ponteiros comuns e devem ser manipulados pelas funções da API.
Recebendo argumentos
size_t argc = 2;
napi_value argv[2];
napi_value this_arg;
napi_get_cb_info(
env,
info,
&argc,
argv,
&this_arg,
NULL
);Valide a quantidade e os tipos antes de converter.
Verificando tipos
napi_valuetype type;
napi_typeof(env, argv[0], &type);
if (type != napi_number) {
napi_throw_type_error(
env,
NULL,
"O primeiro argumento deve ser número"
);
return NULL;
}Convertendo números
double value;
napi_get_value_double(env, argv[0], &value);Considere limites de precisão, NaN e Infinity.
Strings
Primeiro consulte o tamanho, depois aloque:
size_t length;
napi_get_value_string_utf8(
env,
argv[0],
NULL,
0,
&length
);
char* text = malloc(length + 1);
napi_get_value_string_utf8(
env,
argv[0],
text,
length + 1,
&length
);Libere a memória em todos os caminhos de erro. Defina limite antes de alocar entrada enorme.
Macros de verificação
#define NAPI_CALL(env, call) do { \
napi_status status = (call); \
if (status != napi_ok) { \
const napi_extended_error_info* info; \
napi_get_last_error_info((env), &info); \
napi_throw_error((env), NULL, info->error_message); \
return NULL; \
} \
} while (0)Centralizar checagem reduz caminhos esquecidos, mas não exponha mensagens internas sensíveis.
Objetos
napi_value object;
napi_create_object(env, &object);
napi_value name;
napi_create_string_utf8(
env,
"Node-API",
NAPI_AUTO_LENGTH,
&name
);
napi_set_named_property(
env,
object,
"name",
name
);Arrays
napi_value array;
napi_create_array_with_length(env, 3, &array);Use napi_set_element() para preencher.
Buffers
void* data;
napi_value buffer;
napi_create_buffer(
env,
1024,
&data,
&buffer
);O Node.js gerencia a memória desse Buffer. Não use o ponteiro depois que o valor puder ser coletado.
External Buffer
É possível criar Buffer sobre memória externa com callback finalizador. Isso exige ownership claro para evitar double free ou use-after-free.
ArrayBuffer e TypedArray
Node-API fornece funções para consultar ArrayBuffer e TypedArray. Valide offset, tamanho e tipo antes de acessar bytes.
Referências
napi_ref reference;
napi_create_reference(
env,
value,
1,
&reference
);Referências mantêm acesso a valores além da chamada atual. Elas precisam ser removidas com napi_delete_reference().
Referência fraca
Uma contagem inicial zero pode representar referência que não impede garbage collection. Teste se o valor ainda existe antes de usar.
Handles e escopos
Chamadas que criam muitos valores podem usar handle scopes:
napi_handle_scope scope;
napi_open_handle_scope(env, &scope);
// criar valores
napi_close_handle_scope(env, scope);Isso permite liberar handles temporários antes do final de uma operação longa.
Escopo escapável
Quando um valor precisa sair do escopo, use escapable handle scope conforme a documentação.
Erros JavaScript
napi_throw_error(
env,
"ERR_NATIVE_OPERATION",
"Falha na operação nativa"
);Depois de lançar, retorne e interrompa o caminho normal.
Exceção pendente
Use funções para verificar se existe exceção pendente. Não continue criando resultados como se a operação tivesse sucesso.
Promises
napi_deferred deferred;
napi_value promise;
napi_create_promise(
env,
&deferred,
&promise
);Depois, resolva ou rejeite na thread JavaScript apropriada.
Trabalho assíncrono
napi_create_async_work() permite executar trabalho fora da thread principal e concluir no event loop.
napi_async_work work;
napi_create_async_work(
env,
NULL,
resource_name,
Execute,
Complete,
context,
&work
);
napi_queue_async_work(env, work);Função Execute
Execute roda fora da thread JavaScript. Não chame APIs napi que exigem env nessa fase. Trabalhe apenas com dados nativos.
Função Complete
Complete roda na thread apropriada e pode criar valores, resolver Promise e liberar o work.
Cancelamento
napi_cancel_async_work() solicita cancelamento de trabalho ainda não iniciado. Código em execução precisa de mecanismo cooperativo próprio.
Thread-safe function
Uma thread nativa externa não pode chamar JavaScript diretamente. Use napi_threadsafe_function para enfileirar dados de forma segura.
Backpressure em thread-safe function
Defina tamanho máximo da fila e comportamento bloqueante ou não bloqueante. Uma fonte nativa rápida pode consumir toda a memória.
Finalização da thread-safe function
Libere cada aquisição e finalize quando não houver produtores. Erros nessa contagem podem impedir shutdown ou causar acesso inválido.
AsyncResource e contexto
Operações nativas assíncronas devem manter contexto para observabilidade. Consulte AsyncResource no Node.js.
Object Wrap
napi_wrap() associa um ponteiro nativo a um objeto JavaScript. Um finalizador libera o recurso quando o objeto é coletado.
Ownership
Defina claramente quem possui:
- memória nativa;
- handle do sistema;
- referência JavaScript;
- fila assíncrona;
- callback finalizador.
Finalizadores
Finalizadores não devem executar trabalho longo ou depender de ordem previsível de garbage collection. Para recursos críticos, exponha método close() explícito.
Versão da Node-API
O runtime expõe versões da API. Compile para a menor versão que contém os recursos necessários para ampliar compatibilidade.
Features condicionais
Quando uma função depende de versão recente, detecte em build ou runtime e forneça fallback claro.
node-addon-api
O wrapper C++ oferece classes como Napi::Env, Napi::Value e Napi::ObjectWrap. Ele melhora ergonomia, mas continua exigindo tratamento de memória e threads.
Build com node-gyp
Um arquivo binding.gyp descreve fontes e flags:
{
"targets": [
{
"target_name": "addon",
"sources": ["src/addon.c"]
}
]
}Carregando o addon
const addon = require('./build/Release/addon.node');Para pacotes distribuídos, use resolução compatível com a plataforma e arquitetura.
Prebuilds
Oferecer binários pré-compilados evita exigir compilador no computador do usuário. Crie matriz para sistemas e arquiteturas suportados.
Fallback de compilação
Quando não houver prebuild, o pacote pode compilar localmente. Documente requisitos de Python, compilador e headers.
Containers e libc
Linux glibc e musl podem exigir artefatos diferentes. Teste Alpine e distribuições baseadas em glibc separadamente.
Assinatura e cadeia de suprimentos
Binários nativos possuem alto privilégio dentro do processo. Publique checksums, proteja CI e limite quem pode gerar releases.
Segurança de memória
Erros em C ou C++ podem causar:
- segmentation fault;
- corrupção de heap;
- leitura fora de limite;
- use-after-free;
- double free;
- execução de código.
Use sanitizers em testes e fuzzing em parsers.
Validação de entrada
Valide tamanhos antes de converter para tipos menores. Não confie em comprimentos recebidos do JavaScript.
Thread safety
Nem toda biblioteca nativa é thread-safe. Proteja estado ou confine cada instância a uma thread.
Event loop
Não execute trabalho pesado diretamente no callback JavaScript. Use async work ou Worker Thread para evitar bloquear a aplicação.
Testes
Cubra:
- tipos válidos e inválidos;
- strings vazias e enormes;
- buffers pequenos e grandes;
- erros nativos;
- Promise resolvida e rejeitada;
- cancelamento;
- shutdown;
- garbage collection;
- múltiplas threads;
- plataformas suportadas.
Sanitizers
Use AddressSanitizer, UndefinedBehaviorSanitizer e ferramentas da plataforma em builds de teste.
Erros comuns
- Ignorar napi_status: o código continua após falha.
- Usar env em thread externa: o processo pode falhar.
- Esquecer referência: valores são coletados ou vazam.
- Bloquear o event loop: latência aumenta.
- Sem limite de fila: memória cresce.
- Distribuir um único binário: plataformas incompatíveis falham.
- Confiar apenas em GC: recursos críticos ficam abertos.
Boas práticas
- Prefira Node-API a APIs internas do V8.
- Cheque todo napi_status.
- Valide entradas.
- Defina ownership.
- Use async work.
- Use thread-safe functions.
- Limite filas.
- Ofereça close explícito.
- Teste com sanitizers.
- Publique prebuilds seguros.
Conclusão
A Node-API no Node.js oferece uma ABI estável para integrar código nativo sem depender diretamente das mudanças internas do V8.
Ela permite addons potentes, mas transfere ao desenvolvedor responsabilidades de memória, threads, builds e segurança. Com validação rigorosa, async work, ownership explícito, testes com sanitizers e artefatos por plataforma, Node-API pode conectar o ecossistema JavaScript a bibliotecas nativas sem sacrificar compatibilidade operacional.




