Skip to main content

Overview

The Node.js C++ codebase follows specific style guidelines to maintain consistency and quality. This guide covers formatting, naming conventions, memory management, and best practices.

References

Coding guidelines are based on (in priority order):
  1. This document
  2. Google C++ Style Guide
  3. C++ Core Guidelines
Refer to the C++ codebase README for C++ idioms specific to Node.js.

Formatting

Pointer and Reference Declarations

Use left-leaning (C++ style) asterisks:
// Good
char* buffer;
const std::string& name;

// Bad
char *buffer;
const std::string &name;

Comments

Use C++ style comments (//) for both single-line and multi-line:
// A single-line comment.

// Multi-line comments
// should also use C++
// style comments.
Comments should:
  • Start with uppercase
  • End with a period

Indentation

2 Spaces for Blocks

if (foo)
  bar();

// Or with braces
if (foo) {
  bar();
  baz();
}
Braces are optional for single-statement bodies.
Namespaces receive no indentation.

4 Spaces for Continuations

VeryLongTypeName very_long_result = SomeValueWithAVeryLongName +
    SomeOtherValueWithAVeryLongName;
Operators come before the line break.

Function Arguments

Align arguments vertically:
void FunctionWithAVeryLongName(int parameter_with_a_very_long_name,
                               double other_parameter_with_a_very_long_name,
                               ...);
If that doesn’t work, break after ( with 4 spaces:
void FunctionWithAReallyReallyReallyLongNameSeriouslyStopIt(
    int okay_there_is_no_space_left_in_the_previous_line,
    ...);

Initialization Lists

HandleWrap::HandleWrap(Environment* env,
                       Local<Object> object,
                       uv_handle_t* handle,
                       AsyncWrap::ProviderType provider)
    : AsyncWrap(env, object, provider),
      state_(kInitialized),
      handle_(handle) {

Template Declarations

Add space after template:
template <typename T>
class FancyContainer {
  ...
};

Naming Conventions

PascalCase for Methods, Functions, and Classes

class FooBar {
 public:
  void DoSomething();
  static void DoSomethingButItsStaticInstead();

  void set_foo_flag(int flag_value);
  int foo_flag() const;  // Use const-correctness
};
Exception: Simple getters/setters use snake_case:
  • Getter: property_name()
  • Setter: set_property_name()

snake_case for Variables and Parameters

int FunctionThatDoesSomething(const char* important_string) {
  const char* pointer_into_string = important_string;
}

snake_case_ for Private Class Fields

class Foo {
 private:
  int counter_ = 0;
};
Note the trailing underscore.

snake_case for C-like Structs

struct foo_bar {
  int name;
};

Memory Management

Memory Allocation

Node.js provides allocation wrappers:
// Abort on Out-of-Memory
Malloc(size)
Calloc(count, size)

// Return nullptr on OOM
UncheckedMalloc(size)
UncheckedCalloc(count, size)

Use nullptr

Always use nullptr instead of NULL or 0:
// Good
if (ptr == nullptr)
  return nullptr;

// Bad
if (ptr == NULL)
  return 0;
See C++ Core Guidelines ES.47.

Explicit Pointer Comparisons

Use explicit comparisons with nullptr:
// Good
if (foo == nullptr)
if (foo != nullptr)

// Bad
if (foo)
if (!foo)

Smart Pointers

Use smart pointers to represent ownership:
// Prefer unique_ptr over shared_ptr
std::unique_ptr<Foo> FooFactory();
void FooConsumer(std::unique_ptr<Foo> ptr);

// Only use shared_ptr when sharing is necessary
std::shared_ptr<Foo> SharedFooFactory();
Never use std::auto_ptr - it’s deprecated.
See C++ Core Guidelines R.20 and R.21.

Avoid Non-const References

Non-const references obscure which values are modified:
class ExampleClass {
 public:
  explicit ExampleClass(OtherClass* other_ptr) 
      : pointer_to_other_(other_ptr) {}

  void SomeMethod(const std::string& input_param,
                  std::string* in_out_param);  // Pointer, not reference

  const std::string& get_foo() const { return foo_string_; }
  void set_foo(const std::string& new_value) { foo_string_ = new_value; }

  void ReplaceCharacterInFoo(char from, char to) {
    // Non-const reference OK here - method name indicates mutation
    for (char& character : foo_string_) {
      if (character == from)
        character = to;
    }
  }

 private:
  std::string foo_string_;
  OtherClass* pointer_to_other_;  // Or std::unique_ptr<OtherClass>
};

AliasedBuffers for TypedArrays

When manipulating TypedArrays from C++:
// Create an AliasedBuffer
AliasedBuffer<uint32_t, v8::Uint32Array> data;

// Modify through natural operator semantics
data[0] = 12345;
See aliased_buffer.h for details.

Type Casting

Prefer Named Casts

// Good
static_cast<double>(value)
reinterpret_cast<uv_handle_t*>(handle)

// Bad
(double)value
Cast guidelines:
  • Use static_cast<T> when valid
  • Use reinterpret_cast only when necessary
  • Avoid C-style casts
  • dynamic_cast doesn’t work (Node.js built without RTTI)
See C++ Core Guidelines ES.48 and ES.49.

Using auto

Be explicit about types when it aids readability:
// Good use of auto
for (const auto& item : some_map) {
  const KeyType& key = item.first;
  const ValType& value = item.second;
  // Clear types, avoids copies
}

// Prefer explicit types for clarity
const std::string name = GetName();
Use auto to avoid:
  • Noisy type names
  • Obvious types
  • Unimportant types

Include Directives

Don’t include *.h if *-inl.h is already included:
// Good
#include "util-inl.h"  // Already includes util.h

// Bad
#include "util.h"
#include "util-inl.h"

Error Handling

Avoid Throwing JavaScript Errors in C++

Return error data to JavaScript and construct errors there:
void Foo(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  
  // Type-checking in JavaScript, assertions in C++
  CHECK_EQ(args.Length(), 2);
  CHECK(args[0]->IsString());
  CHECK(args[1]->IsObject());

  int err = DoSomethingWith(args[0].As<String>());
  if (err) {
    // Put error data in context object
    Local<Object> ctx = args[1].As<Object>();
    Local<String> key = FIXED_ONE_BYTE_STRING(env->isolate(), "code");
    ctx->Set(env->context(), key, err).FromJust();
  } else {
    args.GetReturnValue().Set(something_to_return);
  }
}

env->SetMethod(target, "foo", Foo);
JavaScript wrapper:
exports.foo = function(str) {
  // Type-checking in JavaScript
  if (typeof str !== 'string') {
    throw new errors.codes.ERR_INVALID_ARG_TYPE('str', 'string');
  }

  const ctx = {};
  const result = binding.foo(str, ctx);
  if (ctx.code !== undefined) {
    throw new errors.codes.ERR_ERROR_NAME(ctx.code);
  }
  return result;
};

Avoid Throwing in Nested C++ Methods

Throw JavaScript exceptions (isolate()->ThrowException()) as close to the return to JavaScript as possible.
Node.js is built without C++ exception handling. Code using throw, try, or catch will break.

Code Examples

Complete Class Example

class HttpParser : public AsyncWrap {
 public:
  HttpParser(Environment* env, Local<Object> wrap);
  ~HttpParser() override;

  void Parse(const char* data, size_t len);
  
  size_t bytes_read() const { return bytes_read_; }
  void set_max_header_size(size_t size) { max_header_size_ = size; }

 private:
  void OnHeadersComplete();
  void OnMessageComplete();

  size_t bytes_read_ = 0;
  size_t max_header_size_ = 8192;
  llhttp_t parser_;
};

Function Example

void AfterWrite(uv_write_t* req, int status) {
  WriteWrap* req_wrap = WriteWrap::from_req(req);
  CHECK_NOT_NULL(req_wrap);
  
  HandleScope scope(req_wrap->env()->isolate());
  Context::Scope context_scope(req_wrap->env()->context());

  if (status == 0) {
    req_wrap->Done(0);
  } else {
    req_wrap->Done(status);
  }
}

Linting

Run the C++ linter:
make lint-cpp
The linter is based on Google’s cpplint but doesn’t catch all Node.js-specific rules. Manual review is still necessary.

Building Guide

Build Node.js from source

Testing Guide

Write and run tests

C++ Codebase README

C++ idioms and patterns

Google C++ Guide

Google C++ Style Guide

Quick Reference

// Pointers and references
char* ptr;              // Left-leaning asterisk
const Type& ref;        // Left-leaning ampersand

// Naming
class MyClass {};       // PascalCase
void DoSomething();     // PascalCase
int my_variable;        // snake_case
int private_field_;     // snake_case with trailing underscore

// Memory
if (ptr == nullptr)     // Explicit nullptr comparison
auto ptr = std::make_unique<T>();  // Smart pointers

// Comments
// C++ style comments only.
// Start with uppercase, end with period.

// Spacing
template <typename T>   // Space after template
if (condition) {        // No space before {
  statement;            // 2-space indent
}