Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Addons C++ no Node.js: Guia Prático

Atualizado em: 16 de agosto de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

Os Addons C++ no Node.js permitem carregar código compilado como um módulo .node e expor funções, classes e objetos ao JavaScript. Eles são usados para integrar bibliotecas nativas, acessar APIs do sistema, trabalhar com hardware, executar algoritmos intensivos e reutilizar código existente.

Existem diferentes níveis de integração. O caminho recomendado para compatibilidade é Node-API, uma ABI estável fornecida pelo runtime. Também existem APIs baseadas em V8, libuv e headers internos, mas elas podem exigir mudanças e recompilação quando o Node.js é atualizado.

Neste guia, você aprenderá a organizar o projeto, configurar node-gyp, criar um addon com node-addon-api, compilar, carregar, trabalhar com argumentos, erros, classes, tarefas assíncronas, bibliotecas externas, prebuilds, segurança e testes.

O que é um addon nativo?

Um addon nativo é uma biblioteca dinâmica carregada pelo Node.js. A documentação oficial de addons C++ apresenta conceitos e exemplos. A documentação do node-gyp explica o sistema de build.

Para uma ABI estável, consulte Node-API no Node.js. Para threads JavaScript, veja Worker Threads no Node.js. O artigo de Buffer no Node.js ajuda na troca de bytes.

Quando criar um addon?

  • integrar uma biblioteca C ou C++ existente;
  • acessar dispositivo ou API específica do sistema;
  • usar codec, algoritmo ou banco nativo;
  • reduzir custo de uma rotina comprovadamente crítica;
  • oferecer bindings de um SDK.

Não use C++ apenas por expectativa de desempenho. Medição, complexidade e custo de distribuição precisam justificar a decisão.

Alternativas

Antes do addon, avalie:

  • JavaScript otimizado;
  • Worker Threads;
  • WebAssembly;
  • processo filho;
  • serviço separado;
  • biblioteca nativa já mantida.

O módulo Child Process no Node.js oferece isolamento maior quando uma falha nativa não deve derrubar a aplicação principal.

Estrutura do projeto

native-addon/
├── binding.gyp
├── package.json
├── src/
│   └── addon.cc
└── index.js

package.json

{
  "name": "native-addon-example",
  "version": "1.0.0",
  "gypfile": true,
  "main": "index.js"
}

Fixe versões de dependências e mantenha lockfile.

binding.gyp

{
  "targets": [
    {
      "target_name": "addon",
      "sources": ["src/addon.cc"]
    }
  ]
}

O target_name determina o nome do binário resultante.

Instalando node-gyp

npm install --save-dev node-gyp

O ambiente precisa de Python e toolchain C++ compatível com o sistema operacional.

Compilando

npx node-gyp configure
npx node-gyp build

O binário costuma aparecer em build/Release/addon.node.

Carregando o módulo

const addon = require(
  './build/Release/addon.node'
);

console.log(addon.answer());

Usando node-addon-api

O pacote node-addon-api oferece wrappers C++ sobre Node-API:

npm install node-addon-api

Ajuste o binding.gyp:

{
  "targets": [
    {
      "target_name": "addon",
      "sources": ["src/addon.cc"],
      "include_dirs": [
        "<!@(node -p \"require('node-addon-api').include\")"
      ],
      "dependencies": [
        "<!(node -p \"require('node-addon-api').gyp\")"
      ],
      "defines": ["NAPI_DISABLE_CPP_EXCEPTIONS"]
    }
  ]
}

Função simples

#include <napi.h>

Napi::Value Answer(
  const Napi::CallbackInfo& info
) {
  Napi::Env env = info.Env();
  return Napi::Number::New(env, 42);
}

Napi::Object Init(
  Napi::Env env,
  Napi::Object exports
) {
  exports.Set(
    "answer",
    Napi::Function::New(env, Answer)
  );
  return exports;
}

NODE_API_MODULE(addon, Init)

Recebendo argumentos

Napi::Value Add(
  const Napi::CallbackInfo& info
) {
  Napi::Env env = info.Env();

  if (info.Length() < 2 ||
      !info[0].IsNumber() ||
      !info[1].IsNumber()) {
    Napi::TypeError::New(
      env,
      "Dois números são obrigatórios"
    ).ThrowAsJavaScriptException();
    return env.Null();
  }

  double a = info[0].As<Napi::Number>();
  double b = info[1].As<Napi::Number>();

  return Napi::Number::New(env, a + b);
}

Validação

Valide tipo, tamanho, faixa e encoding antes de converter. Uma string ou Buffer enorme pode provocar alocações perigosas.

Strings

std::string value = info[0]
  .As<Napi::String>()
  .Utf8Value();

A conversão pode alocar memória. Defina tamanho máximo no JavaScript e no C++.

Buffers

Napi::Buffer<uint8_t> buffer =
  info[0].As<Napi::Buffer<uint8_t>>();

uint8_t* data = buffer.Data();
size_t length = buffer.Length();

Não mantenha o ponteiro além da vida segura do Buffer sem criar referência ou copiar os bytes.

Retornando Buffer

auto result = Napi::Buffer<uint8_t>::New(
  env,
  length
);

std::memcpy(
  result.Data(),
  source,
  length
);

Memória externa

É possível criar Buffer sobre memória externa com finalizador. Defina ownership: apenas uma parte deve liberar o ponteiro.

Erros

Napi::Error::New(
  env,
  "Falha nativa"
).ThrowAsJavaScriptException();

Depois de lançar, interrompa o caminho normal. Não continue usando valores inválidos.

Exceções C++

O projeto pode habilitar ou desabilitar exceções. Use uma estratégia consistente e converta falhas para erros JavaScript na fronteira.

Classes nativas

Napi::ObjectWrap permite expor uma classe:

class Counter : public Napi::ObjectWrap<Counter> {
 public:
  static Napi::Function GetClass(Napi::Env env);

  Counter(const Napi::CallbackInfo& info)
    : Napi::ObjectWrap<Counter>(info), value_(0) {}

 private:
  int64_t value_;
};

O objeto JavaScript fica associado à instância C++.

Destrutor

O destrutor pode liberar memória, mas recursos importantes devem ter método close() explícito. Garbage collection não possui momento previsível.

Operação assíncrona

Não execute trabalho pesado na thread JavaScript. Use Napi::AsyncWorker ou APIs de async work:

class HashWorker : public Napi::AsyncWorker {
 public:
  HashWorker(
    Napi::Function& callback,
    std::vector<uint8_t> input
  ) : Napi::AsyncWorker(callback),
      input_(std::move(input)) {}

  void Execute() override {
    result_ = calculateHash(input_);
  }

  void OnOK() override {
    Callback().Call({
      Env().Null(),
      Napi::String::New(Env(), result_)
    });
  }
};

Execute()

Execute roda fora da thread do JavaScript. Não crie objetos Napi nessa fase.

OnOK e OnError

Esses métodos voltam ao contexto apropriado. Crie valores JavaScript e chame callbacks apenas ali.

Promises

Também é possível expor uma Promise por meio de Deferred. Resolva ou rejeite na thread correta.

Threads externas

Uma biblioteca nativa pode chamar um callback em sua própria thread. Use thread-safe functions, nunca chame JavaScript diretamente.

Fila limitada

Defina limite da thread-safe function. Uma câmera, socket ou sensor pode produzir eventos mais rápido que o JavaScript consome.

AsyncResource

Para contexto e observabilidade, consulte AsyncResource no Node.js.

Bibliotecas externas

Adicione include_dirs, libraries e condições no binding.gyp. Evite caminhos absolutos específicos da máquina.

Condições por sistema

"conditions": [
  ["OS=='win'", {
    "defines": ["PLATFORM_WINDOWS"]
  }],
  ["OS=='linux'", {
    "defines": ["PLATFORM_LINUX"]
  }]
]

Arquiteturas

x64, arm64 e outras arquiteturas exigem binários próprios. Não copie um artefato entre plataformas.

glibc e musl

Linux baseado em Alpine usa musl, enquanto muitas distribuições usam glibc. Disponibilize artefatos compatíveis ou compile no destino.

prebuilds

Ferramentas de prebuild podem gerar binários para várias plataformas e publicar junto ao pacote.

Instalação sem compilador

O instalador tenta baixar o binário correspondente. Se não houver, pode compilar localmente como fallback.

Compatibilidade ABI

Addons baseados diretamente em V8 possuem compatibilidade mais restrita. Node-API oferece estabilidade maior entre versões.

Electron

Electron pode usar headers e ABI próprios. Node-API reduz diferenças, mas ainda teste com as versões suportadas.

Single Executable

Addons .node normalmente continuam como arquivos externos. Planeje extração e paths em uma Single Executable no Node.js.

Segurança de memória

Falhas nativas podem causar crash ou vulnerabilidades:

  • buffer overflow;
  • use-after-free;
  • double free;
  • integer overflow;
  • race condition;
  • leitura fora de limite.

Sanitizers

Use AddressSanitizer e UndefinedBehaviorSanitizer em builds de teste. ThreadSanitizer ajuda em condições de corrida, embora exija configuração compatível.

Fuzzing

Parsers de arquivos, protocolos e imagens se beneficiam de fuzzing com entradas aleatórias e corpus real.

Dependências nativas

Mantenha bibliotecas atualizadas e acompanhe CVEs. Um pacote npm pode incluir binário vulnerável mesmo quando o JavaScript é simples.

Cadeia de suprimentos

Proteja:

  • runners de build;
  • tokens de publicação;
  • artefatos;
  • checksums;
  • assinaturas;
  • dependências de compilação.

Performance

Atravessar a fronteira JavaScript-C++ possui custo. Agrupe operações quando possível e evite chamadas nativas minúsculas em loops intensos.

Zero-copy

Compartilhar memória pode reduzir cópias, mas aumenta complexidade de ownership e duração. Prefira segurança até medir necessidade real.

Event loop

Um addon síncrono de 500 ms bloqueia todas as requisições da thread. Use async worker ou Worker Thread.

Shutdown

Cancele trabalho, pare threads externas, libere callbacks e feche handles. Veja Graceful Shutdown no Node.js.

Testes

Cubra:

  • tipos inválidos;
  • valores limites;
  • buffers vazios e grandes;
  • erros da biblioteca;
  • tarefas assíncronas;
  • cancelamento;
  • garbage collection;
  • shutdown;
  • plataformas e arquiteturas;
  • versões do Node.js.

CI

Execute matriz de sistemas, arquiteturas e versões LTS. Teste o pacote instalado a partir do artefato, não apenas o diretório de build.

Erros comuns

  • Usar V8 diretamente sem necessidade: compatibilidade fica frágil.
  • Bloquear o event loop: a aplicação perde capacidade.
  • Guardar ponteiro de Buffer: memória pode ser coletada.
  • Chamar JS de thread externa: ocorre comportamento indefinido.
  • Sem prebuilds: usuários precisam de toolchain.
  • Um binário para todos: plataformas falham.
  • Confiar no destrutor: recursos ficam abertos.

Boas práticas

  • Prefira Node-API.
  • Valide entradas.
  • Defina ownership.
  • Use trabalho assíncrono.
  • Limite filas.
  • Teste com sanitizers.
  • Crie prebuilds.
  • Proteja o pipeline.
  • Exponha close explícito.
  • Meça a fronteira nativa.

Conclusão

Os Addons C++ no Node.js conectam JavaScript a bibliotecas e recursos nativos, permitindo desempenho e integração que não seriam simples no runtime puro.

A escolha recomendada é Node-API, combinada com node-addon-api para ergonomia. O sucesso depende de memória segura, trabalho fora do event loop, builds por plataforma, prebuilds, testes com sanitizers e shutdown explícito. Sem esses cuidados, um pequeno erro nativo pode comprometer todo o processo.

Os 10 Melhores Cursos de Programação de 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita