diff --git a/binding.gyp b/binding.gyp
index 4dc4cfd0..bb43e646 100644
--- a/binding.gyp
+++ b/binding.gyp
@@ -2,7 +2,13 @@
   "targets": [
     {
       "target_name": "tree_sitter_runtime_binding",
-      "dependencies": ["tree_sitter"],
+      "dependencies": [
+        "tree_sitter",
+        "<!(node -p \"require('node-addon-api').gyp\")"
+      ],
+      "defines": [
+        "NAPI_DISABLE_CPP_EXCEPTIONS=",
+      ],
       "sources": [
         "src/binding.cc",
         "src/conversions.cc",
@@ -17,6 +23,7 @@
         "vendor/tree-sitter/lib/include",
         "vendor/superstring",
         "<!(node -e \"require('nan')\")",
+        "<!@(node -p \"require('node-addon-api').include\")",
       ],
       'conditions': [
         ['OS == "mac"', {
@@ -25,9 +32,6 @@
           },
         }]
       ],
-      "cflags": [
-        "-std=c++0x",
-      ],
       'xcode_settings': {
         'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',
       },
diff --git a/index.js b/index.js
index f2f927cd..7d613225 100644
--- a/index.js
+++ b/index.js
@@ -266,6 +266,7 @@ Parser.prototype.parse = function(input, oldTree, {bufferSize, includedRanges}={
   } else {
     getText = getTextFromFunction
   }
+
   const tree = parse.call(
     this,
     input,
diff --git a/package.json b/package.json
index e394dcf3..8daa62f7 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
   "types": "tree-sitter.d.ts",
   "dependencies": {
     "nan": "^2.14.0",
+    "node-addon-api": "git+https://github.com/nodejs/node-addon-api.git",
     "prebuild-install": "^5.0.0"
   },
   "devDependencies": {
diff --git a/src/binding.cc b/src/binding.cc
index 4b7d2a31..9ef5ed6f 100644
--- a/src/binding.cc
+++ b/src/binding.cc
@@ -1,5 +1,4 @@
-#include <node.h>
-#include <v8.h>
+#include <napi.h>
 #include "./language.h"
 #include "./node.h"
 #include "./parser.h"
@@ -9,17 +8,18 @@
 
 namespace node_tree_sitter {
 
-using namespace v8;
+using namespace Napi;
 
-void InitAll(Local<Object> exports) {
+Object Init(Env env, Object exports) {
   InitConversions(exports);
-  node_methods::Init(exports);
-  language_methods::Init(exports);
-  Parser::Init(exports);
+  InitNode(exports);
+  InitLanguage(exports);
+  InitParser(exports);
+  InitTreeCursor(exports);
   Tree::Init(exports);
-  TreeCursor::Init(exports);
+  return exports;
 }
 
-NODE_MODULE(tree_sitter_runtime_binding, InitAll)
+NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
 
 }  // namespace node_tree_sitter
diff --git a/src/conversions.cc b/src/conversions.cc
index 92c92c67..4b0e1c85 100644
--- a/src/conversions.cc
+++ b/src/conversions.cc
@@ -1,35 +1,30 @@
-#include "./node.h"
-#include <nan.h>
+#include <napi.h>
 #include <tree_sitter/api.h>
-#include <v8.h>
+#include "./node.h"
 #include "./conversions.h"
 #include <cmath>
 
 namespace node_tree_sitter {
 
-using namespace v8;
-
-Nan::Persistent<String> row_key;
-Nan::Persistent<String> column_key;
-Nan::Persistent<String> start_index_key;
-Nan::Persistent<String> start_position_key;
-Nan::Persistent<String> end_index_key;
-Nan::Persistent<String> end_position_key;
+using namespace Napi;
 
 static unsigned BYTES_PER_CHARACTER = 2;
 static uint32_t *point_transfer_buffer;
 
-void InitConversions(Local<Object> exports) {
-  row_key.Reset(Nan::Persistent<String>(Nan::New("row").ToLocalChecked()));
-  column_key.Reset(Nan::Persistent<String>(Nan::New("column").ToLocalChecked()));
-  start_index_key.Reset(Nan::Persistent<String>(Nan::New("startIndex").ToLocalChecked()));
-  start_position_key.Reset(Nan::Persistent<String>(Nan::New("startPosition").ToLocalChecked()));
-  end_index_key.Reset(Nan::Persistent<String>(Nan::New("endIndex").ToLocalChecked()));
-  end_position_key.Reset(Nan::Persistent<String>(Nan::New("endPosition").ToLocalChecked()));
-
+void InitConversions(Object &exports) {
+  auto env = exports.Env();
   point_transfer_buffer = static_cast<uint32_t *>(malloc(2 * sizeof(uint32_t)));
-  auto js_point_transfer_buffer = ArrayBuffer::New(Isolate::GetCurrent(), point_transfer_buffer, 2 * sizeof(uint32_t));
-  Nan::Set(exports, Nan::New("pointTransferArray").ToLocalChecked(), Uint32Array::New(js_point_transfer_buffer, 0, 2));
+  auto js_point_transfer_buffer = ArrayBuffer::New(
+    env,
+    static_cast<void *>(point_transfer_buffer),
+    2 * sizeof(uint32_t)
+  );
+  exports.Set("pointTransferArray", Uint32Array::New(
+    env,
+    2,
+    js_point_transfer_buffer,
+    0
+  ));
 }
 
 void TransferPoint(const TSPoint &point) {
@@ -37,110 +32,116 @@ void TransferPoint(const TSPoint &point) {
   point_transfer_buffer[1] = point.column / 2;
 }
 
-Local<Object> RangeToJS(const TSRange &range) {
-  Local<Object> result = Nan::New<Object>();
-  Nan::Set(result, Nan::New(start_position_key), PointToJS(range.start_point));
-  Nan::Set(result, Nan::New(start_index_key), ByteCountToJS(range.start_byte));
-  Nan::Set(result, Nan::New(end_position_key), PointToJS(range.end_point));
-  Nan::Set(result, Nan::New(end_index_key), ByteCountToJS(range.end_byte));
+Object RangeToJS(Env env, const TSRange &range) {
+  Object result = Object::New(env);
+  result.Set("startPosition", PointToJS(env, range.start_point));
+  result.Set("startIndex", ByteCountToJS(env, range.start_byte));
+  result.Set("endPosition", PointToJS(env, range.end_point));
+  result.Set("endIndex", ByteCountToJS(env, range.end_byte));
   return result;
 }
 
-Nan::Maybe<TSRange> RangeFromJS(const Local<Value> &arg) {
-  if (!arg->IsObject()) {
-    Nan::ThrowTypeError("Range must be a {startPosition, endPosition, startIndex, endIndex} object");
-    return Nan::Nothing<TSRange>();
+optional<TSRange> RangeFromJS(const Value &arg) {
+  Env env = arg.Env();
+
+  if (!arg.IsObject()) {
+    TypeError::New(env, "Range must be a {startPosition, endPosition, startIndex, endIndex} object").ThrowAsJavaScriptException();
+    return optional<TSRange>();
   }
 
   TSRange result;
-
-  Local<Object> js_range = Local<Object>::Cast(arg);
+  Object js_range = arg.ToObject();
 
   #define INIT(field, key, Convert) { \
-    auto value = Nan::Get(js_range, Nan::New(key)); \
+    auto value = js_range.Get(key); \
     if (value.IsEmpty()) { \
-      Nan::ThrowTypeError("Range must be a {startPosition, endPosition, startIndex, endIndex} object"); \
-      return Nan::Nothing<TSRange>(); \
+      TypeError::New(env, "Range must be a {startPosition, endPosition, startIndex, endIndex} object").ThrowAsJavaScriptException(); \
+      return optional<TSRange>(); \
     } \
-    auto field = Convert(value.ToLocalChecked()); \
-    if (field.IsJust()) { \
-      result.field = field.FromJust(); \
+    auto field = Convert(value); \
+    if (field) { \
+      result.field = *field; \
     } else { \
-      return Nan::Nothing<TSRange>(); \
+      return optional<TSRange>(); \
     } \
   }
 
-  INIT(start_point, start_position_key, PointFromJS);
-  INIT(end_point, end_position_key, PointFromJS);
-  INIT(start_byte, start_index_key, ByteCountFromJS);
-  INIT(end_byte, end_index_key, ByteCountFromJS);
+  INIT(start_point, "startPosition", PointFromJS);
+  INIT(end_point, "endPosition", PointFromJS);
+  INIT(start_byte, "startIndex", ByteCountFromJS);
+  INIT(end_byte, "endIndex", ByteCountFromJS);
 
   #undef INIT
 
-  return Nan::Just(result);
+  return result;
 }
 
-Local<Object> PointToJS(const TSPoint &point) {
-  Local<Object> result = Nan::New<Object>();
-  Nan::Set(result, Nan::New(row_key), Nan::New<Number>(point.row));
-  Nan::Set(result, Nan::New(column_key), ByteCountToJS(point.column));
+Object PointToJS(Env env, const TSPoint &point) {
+  Object result = Object::New(env);
+  result["row"] = Number::New(env, point.row);
+  result["column"] = ByteCountToJS(env, point.column);
   return result;
 }
 
-Nan::Maybe<TSPoint> PointFromJS(const Local<Value> &arg) {
-  Local<Object> js_point;
-  if (!arg->IsObject() || !Nan::To<Object>(arg).ToLocal(&js_point)) {
-    Nan::ThrowTypeError("Point must be a {row, column} object");
-    return Nan::Nothing<TSPoint>();
+Number ByteCountToJS(Env env, uint32_t byte_count) {
+  return Number::New(env, byte_count / BYTES_PER_CHARACTER);
+}
+
+optional<TSPoint> PointFromJS(const Value &arg) {
+  Env env = arg.Env();
+
+  if (!arg.IsObject()) {
+    TypeError::New(env, "Point must be a {row, column} object").ThrowAsJavaScriptException();
+    return optional<TSPoint>();
   }
 
-  Local<Value> js_row;
-  if (!Nan::Get(js_point, Nan::New(row_key)).ToLocal(&js_row)) {
-    Nan::ThrowTypeError("Point must be a {row, column} object");
-    return Nan::Nothing<TSPoint>();
+  Object js_point = arg.ToObject();
+
+  Number js_row = js_point.Get("row").As<Number>();
+  if (!js_row.IsNumber()) {
+    TypeError::New(env, "Point must be a {row, column} object").ThrowAsJavaScriptException();
+    return optional<TSPoint>();
   }
 
-  Local<Value> js_column;
-  if (!Nan::Get(js_point, Nan::New(column_key)).ToLocal(&js_column)) {
-    Nan::ThrowTypeError("Point must be a {row, column} object");
-    return Nan::Nothing<TSPoint>();
+  Number js_column = js_point.Get("column").As<Number>();
+  if (!js_column.IsNumber()) {
+    TypeError::New(env, "Point must be a {row, column} object").ThrowAsJavaScriptException();
+    return optional<TSPoint>();
   }
 
   uint32_t row;
-  if (!std::isfinite(Nan::To<double>(js_row).FromMaybe(0))) {
+  if (!std::isfinite(js_row.DoubleValue())) {
     row = UINT32_MAX;
-  } else if (js_row->IsNumber()) {
-    row = Nan::To<uint32_t>(js_row).FromJust();
   } else {
-    Nan::ThrowTypeError("Point.row must be a number");
-    return Nan::Nothing<TSPoint>();
+    row = js_row.Uint32Value();
   }
 
   uint32_t column;
-  if (!std::isfinite(Nan::To<double>(js_column).FromMaybe(0))) {
+  if (!std::isfinite(js_column.DoubleValue())) {
     column = UINT32_MAX;
-  } else if (js_column->IsNumber()) {
-    column = Nan::To<uint32_t>(js_column).FromMaybe(0) * BYTES_PER_CHARACTER;
   } else {
-    Nan::ThrowTypeError("Point.column must be a number");
-    return Nan::Nothing<TSPoint>();
+    column = js_column.Uint32Value() * BYTES_PER_CHARACTER;
   }
 
-  return Nan::Just<TSPoint>({row, column});
+  return TSPoint{row, column};
 }
 
-Local<Number> ByteCountToJS(uint32_t byte_count) {
-  return Nan::New<Number>(byte_count / BYTES_PER_CHARACTER);
-}
+optional<uint32_t> ByteCountFromJS(const Value &arg) {
+  Env env = arg.Env();
 
-Nan::Maybe<uint32_t> ByteCountFromJS(const v8::Local<v8::Value> &arg) {
-  auto result = Nan::To<uint32_t>(arg);
-  if (!arg->IsNumber()) {
-    Nan::ThrowTypeError("Character index must be a number");
-    return Nan::Nothing<uint32_t>();
+  if (!arg.IsNumber()) {
+    if (!env.IsExceptionPending()) {
+      TypeError::New(env, "Character index must be a number").ThrowAsJavaScriptException();
+    }
+    return optional<uint32_t>();
   }
 
-  return Nan::Just<uint32_t>(result.FromJust() * BYTES_PER_CHARACTER);
+  Number js_number = arg.ToNumber();
+  if (!std::isfinite(js_number.DoubleValue())) {
+    return UINT32_MAX;
+  } else {
+    return js_number.Uint32Value() * BYTES_PER_CHARACTER;
+  }
 }
 
 }  // namespace node_tree_sitter
diff --git a/src/conversions.h b/src/conversions.h
index 69bcfb51..649561b5 100644
--- a/src/conversions.h
+++ b/src/conversions.h
@@ -1,25 +1,22 @@
 #ifndef NODE_TREE_SITTER_CONVERSIONS_H_
 #define NODE_TREE_SITTER_CONVERSIONS_H_
 
-#include <nan.h>
-#include <v8.h>
+#include <napi.h>
 #include <tree_sitter/api.h>
+#include "./optional.h"
 
 namespace node_tree_sitter {
 
-void InitConversions(v8::Local<v8::Object> exports);
-v8::Local<v8::Object> RangeToJS(const TSRange &);
-v8::Local<v8::Object> PointToJS(const TSPoint &);
+void InitConversions(Napi::Object &);
 void TransferPoint(const TSPoint &);
-v8::Local<v8::Number> ByteCountToJS(uint32_t);
-Nan::Maybe<TSPoint> PointFromJS(const v8::Local<v8::Value> &);
-Nan::Maybe<uint32_t> ByteCountFromJS(const v8::Local<v8::Value> &);
-Nan::Maybe<TSRange> RangeFromJS(const v8::Local<v8::Value> &);
 
-extern Nan::Persistent<v8::String> row_key;
-extern Nan::Persistent<v8::String> column_key;
-extern Nan::Persistent<v8::String> start_key;
-extern Nan::Persistent<v8::String> end_key;
+Napi::Object RangeToJS(Napi::Env, const TSRange &);
+Napi::Object PointToJS(Napi::Env, const TSPoint &);
+Napi::Number ByteCountToJS(Napi::Env, uint32_t);
+
+optional<TSPoint> PointFromJS(const Napi::Value &);
+optional<uint32_t> ByteCountFromJS(const Napi::Value &);
+optional<TSRange> RangeFromJS(const Napi::Value &);
 
 }  // namespace node_tree_sitter
 
diff --git a/src/language.cc b/src/language.cc
index bdfb90f1..208bd8d6 100644
--- a/src/language.cc
+++ b/src/language.cc
@@ -1,92 +1,89 @@
 #include "./language.h"
-#include <nan.h>
+#include <napi.h>
 #include <tree_sitter/api.h>
 #include <vector>
 #include <string>
-#include <v8.h>
+#include "./util.h"
 
 namespace node_tree_sitter {
-namespace language_methods {
 
 using std::vector;
-using namespace v8;
-
-const TSLanguage *UnwrapLanguage(const v8::Local<v8::Value> &value) {
-  if (value->IsObject()) {
-    Local<Object> arg = Local<Object>::Cast(value);
-    if (arg->InternalFieldCount() == 1) {
-      const TSLanguage *language = (const TSLanguage *)Nan::GetInternalFieldPointer(arg, 0);
-      if (language) {
-        uint16_t version = ts_language_version(language);
-        if (
-          version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION ||
-          version > TREE_SITTER_LANGUAGE_VERSION
-        ) {
-          std::string message =
-            "Incompatible language version. Compatible range: " +
-            std::to_string(TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION) + " - " +
-            std::to_string(TREE_SITTER_LANGUAGE_VERSION) + ". Got: " +
-            std::to_string(ts_language_version(language));
-          Nan::ThrowError(Nan::RangeError(message.c_str()));
-          return nullptr;
-        }
-        return language;
-      }
+using namespace Napi;
+
+
+const TSLanguage *UnwrapLanguage(const Napi::Value &value) {
+  Env env = value.Env();
+
+  const TSLanguage *language = static_cast<const TSLanguage *>(
+    GetInternalFieldPointer(value)
+  );
+
+  if (language) {
+    uint16_t version = ts_language_version(language);
+    if (
+      version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION ||
+      version > TREE_SITTER_LANGUAGE_VERSION
+    ) {
+      std::string message =
+        "Incompatible language version. Compatible range: " +
+        std::to_string(TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION) + " - " +
+        std::to_string(TREE_SITTER_LANGUAGE_VERSION) + ". Got: " +
+        std::to_string(ts_language_version(language));
+      RangeError::New(env, message.c_str()).ThrowAsJavaScriptException();
+      return nullptr;
     }
+    return language;
   }
-  Nan::ThrowTypeError("Invalid language object");
+
+  TypeError::New(env, "Invalid language object").ThrowAsJavaScriptException();
   return nullptr;
 }
 
-static void GetNodeTypeNamesById(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value GetNodeTypeNamesById(const CallbackInfo &info) {
+  Env env = info.Env();
+
   const TSLanguage *language = UnwrapLanguage(info[0]);
-  if (!language) return;
+  if (!language) return env.Null();
 
-  auto result = Nan::New<Array>();
+  Array result = Array::New(env);
   uint32_t length = ts_language_symbol_count(language);
   for (uint32_t i = 0; i < length; i++) {
     const char *name = ts_language_symbol_name(language, i);
     TSSymbolType type = ts_language_symbol_type(language, i);
     if (type == TSSymbolTypeRegular) {
-      Nan::Set(result, i, Nan::New(name).ToLocalChecked());
+      result[i] = String::New(env, name);
     } else {
-      Nan::Set(result, i, Nan::Null());
+      result[i] = env.Null();
     }
   }
 
-  info.GetReturnValue().Set(result);
+  return result;
 }
 
-static void GetNodeFieldNamesById(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value GetNodeFieldNamesById(const CallbackInfo &info) {
+  Env env = info.Env();
+
   const TSLanguage *language = UnwrapLanguage(info[0]);
-  if (!language) return;
+  if (!language) return env.Null();
 
-  auto result = Nan::New<Array>();
+  Array result = Array::New(env);
   uint32_t length = ts_language_field_count(language);
   for (uint32_t i = 0; i < length + 1; i++) {
     const char *name = ts_language_field_name_for_id(language, i);
     if (name) {
-      Nan::Set(result, i, Nan::New(name).ToLocalChecked());
+      result[i] = String::New(env, name);
     } else {
-      Nan::Set(result, i, Nan::Null());
+      result[i] = env.Null();
     }
   }
-  info.GetReturnValue().Set(result);
-}
 
-void Init(Local<Object> exports) {
-  Nan::Set(
-    exports,
-    Nan::New("getNodeTypeNamesById").ToLocalChecked(),
-    Nan::GetFunction(Nan::New<FunctionTemplate>(GetNodeTypeNamesById)).ToLocalChecked()
-  );
+  return result;
+}
 
-  Nan::Set(
-    exports,
-    Nan::New("getNodeFieldNamesById").ToLocalChecked(),
-    Nan::GetFunction(Nan::New<FunctionTemplate>(GetNodeFieldNamesById)).ToLocalChecked()
-  );
+void InitLanguage(Object &exports) {
+  Env env = exports.Env();
+  exports["getNodeTypeNamesById"] = Function::New(env, GetNodeTypeNamesById);
+  exports["getNodeFieldNamesById"] = Function::New(env, GetNodeFieldNamesById);
 }
 
-}  // namespace language_methods
 }  // namespace node_tree_sitter
diff --git a/src/language.h b/src/language.h
index 76f7ac28..cde411fd 100644
--- a/src/language.h
+++ b/src/language.h
@@ -1,20 +1,16 @@
 #ifndef NODE_TREE_SITTER_LANGUAGE_H_
 #define NODE_TREE_SITTER_LANGUAGE_H_
 
-#include <nan.h>
-#include <v8.h>
-#include <node_object_wrap.h>
+#include <napi.h>
 #include <tree_sitter/api.h>
 #include "./tree.h"
 
 namespace node_tree_sitter {
-namespace language_methods {
 
-void Init(v8::Local<v8::Object>);
+void InitLanguage(Napi::Object &);
 
-const TSLanguage *UnwrapLanguage(const v8::Local<v8::Value> &);
+const TSLanguage *UnwrapLanguage(const Napi::Value &);
 
-}  // namespace language_methods
 }  // namespace node_tree_sitter
 
 #endif  // NODE_TREE_SITTER_LANGUAGE_H_
diff --git a/src/logger.cc b/src/logger.cc
index 5f0cacd1..2d1148b1 100644
--- a/src/logger.cc
+++ b/src/logger.cc
@@ -1,34 +1,34 @@
 #include "./logger.h"
 #include <string>
-#include <v8.h>
-#include <nan.h>
+#include <napi.h>
 #include <tree_sitter/api.h>
 
 namespace node_tree_sitter {
 
-using namespace v8;
+using namespace Napi;
 using std::string;
 
 void Logger::Log(void *payload, TSLogType type, const char *message_str) {
   Logger *debugger = (Logger *)payload;
-  Local<Function> fn = Nan::New(debugger->func);
-  if (!fn->IsFunction())
-    return;
+  Function fn = debugger->func.Value();
+  if (!fn.IsFunction()) return;
+  Env env = fn.Env();
 
   string message(message_str);
   string param_sep = " ";
   size_t param_sep_pos = message.find(param_sep, 0);
 
-  Local<String> type_name = Nan::New((type == TSLogTypeParse) ? "parse" : "lex").ToLocalChecked();
-  Local<String> name = Nan::New(message.substr(0, param_sep_pos)).ToLocalChecked();
-  Local<Object> params = Nan::New<Object>();
+  String type_name = String::New(
+    env,
+    type == TSLogTypeParse ? "parse" : "lex"
+  );
+  String name = String::New(env, message.substr(0, param_sep_pos));
+  Object params = Object::New(env);
 
   while (param_sep_pos != string::npos) {
     size_t key_pos = param_sep_pos + param_sep.size();
     size_t value_sep_pos = message.find(":", key_pos);
-
-    if (value_sep_pos == string::npos)
-      break;
+    if (value_sep_pos == string::npos) break;
 
     size_t val_pos = value_sep_pos + 1;
     param_sep = ", ";
@@ -36,29 +36,30 @@ void Logger::Log(void *payload, TSLogType type, const char *message_str) {
 
     string key = message.substr(key_pos, (value_sep_pos - key_pos));
     string value = message.substr(val_pos, (param_sep_pos - val_pos));
-    Nan::Set(params, Nan::New(key).ToLocalChecked(), Nan::New(value).ToLocalChecked());
+    params[key] = String::New(env, value);
   }
 
-  Local<Value> argv[3] = { name, params, type_name };
-  TryCatch try_catch(Isolate::GetCurrent());
-  Nan::Call(fn, fn->CreationContext()->Global(), 3, argv);
-  if (try_catch.HasCaught()) {
-    Local<Value> log_argv[2] = {
-      Nan::New("Error in debug callback:").ToLocalChecked(),
-      try_catch.Exception()
-    };
-
-    Local<Object> console = Local<Object>::Cast(Nan::Get(fn->CreationContext()->Global(), Nan::New("console").ToLocalChecked()).ToLocalChecked());
-    Local<Function> error_fn = Local<Function>::Cast(Nan::Get(console, Nan::New("error").ToLocalChecked()).ToLocalChecked());
-    Nan::Call(error_fn, console, 2, log_argv);
+  fn({ name, params, type_name });
+  if (env.IsExceptionPending()) {
+    Error error = env.GetAndClearPendingException();
+    Value console = env.Global()["console"];
+    if (console.IsObject()) {
+      Value console_error_fn = console.ToObject()["error"];
+      if (console_error_fn.IsFunction()) {
+        console_error_fn.As<Function>()({
+          String::New(env, "Error in debug callback:"),
+          error.Value()
+        });
+      }
+    }
   }
 }
 
-TSLogger Logger::Make(Local<Function> func) {
+TSLogger Logger::Make(Function func) {
   TSLogger result;
   Logger *logger = new Logger();
-  logger->func.Reset(Nan::Persistent<Function>(func));
-  result.payload = (void *)logger;
+  logger->func.Reset(func, 1);
+  result.payload = static_cast<void *>(logger);
   result.log = Log;
   return result;
 }
diff --git a/src/logger.h b/src/logger.h
index af68413f..105b4659 100644
--- a/src/logger.h
+++ b/src/logger.h
@@ -1,16 +1,15 @@
 #ifndef NODE_TREE_SITTER_LOGGER_H_
 #define NODE_TREE_SITTER_LOGGER_H_
 
-#include <v8.h>
-#include <nan.h>
+#include <napi.h>
 #include <tree_sitter/api.h>
 
 namespace node_tree_sitter {
 
 class Logger {
  public:
-  static TSLogger Make(v8::Local<v8::Function>);
-  Nan::Persistent<v8::Function> func;
+  static TSLogger Make(Napi::Function);
+  Napi::FunctionReference func;
   static void Log(void *, TSLogType, const char *);
 };
 
diff --git a/src/node.cc b/src/node.cc
index 14054544..e225bac9 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -1,36 +1,39 @@
 #include "./node.h"
-#include <nan.h>
 #include <tree_sitter/api.h>
+#include <napi.h>
 #include <vector>
-#include <v8.h>
 #include "./util.h"
 #include "./conversions.h"
 #include "./tree.h"
 #include "./tree_cursor.h"
 
 namespace node_tree_sitter {
-namespace node_methods {
 
 using std::vector;
-using namespace v8;
+using namespace Napi;
 
 static const uint32_t FIELD_COUNT_PER_NODE = 6;
 
 static uint32_t *transfer_buffer = nullptr;
 static uint32_t transfer_buffer_length = 0;
-static Nan::Persistent<Object> module_exports;
+static ObjectReference module_exports;
 static TSTreeCursor scratch_cursor = {nullptr, nullptr, {0, 0}};
 
-static inline void setup_transfer_buffer(uint32_t node_count) {
+static inline void setup_transfer_buffer(Env env, uint32_t node_count) {
   uint32_t new_length = node_count * FIELD_COUNT_PER_NODE;
   if (new_length > transfer_buffer_length) {
     transfer_buffer_length = new_length;
     transfer_buffer = static_cast<uint32_t *>(malloc(transfer_buffer_length * sizeof(uint32_t)));
-    auto js_transfer_buffer = ArrayBuffer::New(Isolate::GetCurrent(), transfer_buffer, transfer_buffer_length * sizeof(uint32_t));
-    Nan::Set(
-      Nan::New(module_exports),
-      Nan::New("nodeTransferArray").ToLocalChecked(),
-      Uint32Array::New(js_transfer_buffer, 0, transfer_buffer_length)
+    auto js_transfer_buffer = ArrayBuffer::New(
+      env,
+      transfer_buffer,
+      transfer_buffer_length * sizeof(uint32_t)
+    );
+    module_exports.Value()["nodeTransferArray"] = Uint32Array::New(
+      env,
+      transfer_buffer_length,
+      js_transfer_buffer,
+      0
     );
   }
 }
@@ -41,65 +44,76 @@ static inline bool operator<=(const TSPoint &left, const TSPoint &right) {
   return left.column <= right.column;
 }
 
-static void MarshalNodes(const Nan::FunctionCallbackInfo<Value> &info,
-                         const Tree *tree, const TSNode *nodes, uint32_t node_count) {
-  auto result = Nan::New<Array>();
-  setup_transfer_buffer(node_count);
+static Value MarshalNodes(
+  Env env,
+  const Tree *tree,
+  const TSNode *nodes,
+  uint32_t node_count
+) {
+  Array result = Array::New(env);
+  setup_transfer_buffer(env, node_count);
   uint32_t *p = transfer_buffer;
   for (unsigned i = 0; i < node_count; i++) {
     TSNode node = nodes[i];
     const auto &cache_entry = tree->cached_nodes_.find(node.id);
     if (cache_entry == tree->cached_nodes_.end()) {
-      MarshalNodeId(node.id, p);
+      MarshalPointer(node.id, p);
       p += 2;
       *(p++) = node.context[0];
       *(p++) = node.context[1];
       *(p++) = node.context[2];
       *(p++) = node.context[3];
       if (node.id) {
-        Nan::Set(result, i, Nan::New(ts_node_symbol(node)));
+        result[i] = Number::New(env, ts_node_symbol(node));
       } else {
-        Nan::Set(result, i, Nan::Null());
+        result[i] = env.Null();
       }
     } else {
-      Nan::Set(result, i, Nan::New(cache_entry->second->node));
+      result[i] = cache_entry->second->node.Value();
     }
   }
-  info.GetReturnValue().Set(result);
+  return result;
 }
 
-void MarshalNode(const Nan::FunctionCallbackInfo<Value> &info, const Tree *tree, TSNode node) {
+Value MarshalNode(
+  Env env,
+  const Tree *tree,
+  TSNode node
+) {
   const auto &cache_entry = tree->cached_nodes_.find(node.id);
   if (cache_entry == tree->cached_nodes_.end()) {
-    setup_transfer_buffer(1);
+    setup_transfer_buffer(env, 1);
     uint32_t *p = transfer_buffer;
-    MarshalNodeId(node.id, p);
+    MarshalPointer(node.id, p);
     p += 2;
     *(p++) = node.context[0];
     *(p++) = node.context[1];
     *(p++) = node.context[2];
     *(p++) = node.context[3];
     if (node.id) {
-      info.GetReturnValue().Set(Nan::New(ts_node_symbol(node)));
+      return Number::New(env, ts_node_symbol(node));
+    } else {
+      return env.Null();
     }
   } else {
-    info.GetReturnValue().Set(Nan::New(cache_entry->second->node));
+    return cache_entry->second->node.Value();
   }
 }
 
-void MarshalNullNode() {
+Value MarshalNullNode(Env env) {
   memset(transfer_buffer, 0, FIELD_COUNT_PER_NODE * sizeof(transfer_buffer[0]));
+  return env.Null();
 }
 
-TSNode UnmarshalNode(const Tree *tree) {
+TSNode UnmarshalNode(Env env, const Tree *tree) {
   TSNode result = {{0, 0, 0, 0}, nullptr, nullptr};
-  result.tree = tree->tree_;
-  if (!result.tree) {
-    Nan::ThrowTypeError("Argument must be a tree");
+  if (!tree) {
+    TypeError::New(env, "Argument must be a tree").ThrowAsJavaScriptException();
     return result;
   }
 
-  result.id = UnmarshalNodeId(&transfer_buffer[0]);
+  result.tree = tree->tree_;
+  result.id = UnmarshalPointer(&transfer_buffer[0]);
   result.context[0] = transfer_buffer[2];
   result.context[1] = transfer_buffer[3];
   result.context[2] = transfer_buffer[4];
@@ -107,350 +121,375 @@ TSNode UnmarshalNode(const Tree *tree) {
   return result;
 }
 
-static void ToString(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value ToString(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
-    const char *string = ts_node_string(node);
-    info.GetReturnValue().Set(Nan::New(string).ToLocalChecked());
-    free((char *)string);
+    char *string = ts_node_string(node);
+    String result = String::New(env, string);
+    free(string);
+    return result;
   }
+  return env.Undefined();
 }
 
-static void IsMissing(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value IsMissing(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
     bool result = ts_node_is_missing(node);
-    info.GetReturnValue().Set(Nan::New<Boolean>(result));
+    return Boolean::New(env, result);
   }
+  return env.Undefined();
 }
 
-static void HasChanges(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value HasChanges(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
     bool result = ts_node_has_changes(node);
-    info.GetReturnValue().Set(Nan::New<Boolean>(result));
+    return Boolean::New(env, result);
   }
+  return env.Undefined();
 }
 
-static void HasError(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value HasError(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
     bool result = ts_node_has_error(node);
-    info.GetReturnValue().Set(Nan::New<Boolean>(result));
+    return Boolean::New(env, result);
   }
+  return env.Undefined();
 }
 
-static void FirstNamedChildForIndex(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value FirstNamedChildForIndex(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
-    Nan::Maybe<uint32_t> byte = ByteCountFromJS(info[1]);
-    if (byte.IsJust()) {
-      MarshalNode(info, tree, ts_node_first_named_child_for_byte(node, byte.FromJust()));
-      return;
+    auto byte = ByteCountFromJS(info[1]);
+    if (byte) {
+      return MarshalNode(env, tree, ts_node_first_named_child_for_byte(node, *byte));
     }
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void FirstChildForIndex(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value FirstChildForIndex(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
 
   if (node.id && info.Length() > 1) {
-    Nan::Maybe<uint32_t> byte = ByteCountFromJS(info[1]);
-    if (byte.IsJust()) {
-      MarshalNode(info, tree, ts_node_first_child_for_byte(node, byte.FromJust()));
-      return;
+    optional<uint32_t> byte = ByteCountFromJS(info[1]);
+    if (byte) {
+      return MarshalNode(env, tree, ts_node_first_child_for_byte(node, *byte));
     }
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void NamedDescendantForIndex(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value NamedDescendantForIndex(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
 
   if (node.id) {
-    Nan::Maybe<uint32_t> maybe_min = ByteCountFromJS(info[1]);
-    Nan::Maybe<uint32_t> maybe_max = ByteCountFromJS(info[2]);
-    if (maybe_min.IsJust() && maybe_max.IsJust()) {
-      uint32_t min = maybe_min.FromJust();
-      uint32_t max = maybe_max.FromJust();
-      MarshalNode(info, tree, ts_node_named_descendant_for_byte_range(node, min, max));
-      return;
+    optional<uint32_t> maybe_min = ByteCountFromJS(info[1]);
+    if (maybe_min) {
+      optional<uint32_t> maybe_max = ByteCountFromJS(info[2]);
+      if (maybe_max) {
+        uint32_t min = *maybe_min;
+        uint32_t max = *maybe_max;
+        return MarshalNode(env, tree, ts_node_named_descendant_for_byte_range(node, min, max));
+      }
     }
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void DescendantForIndex(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value DescendantForIndex(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
 
   if (node.id) {
-    Nan::Maybe<uint32_t> maybe_min = ByteCountFromJS(info[1]);
-    Nan::Maybe<uint32_t> maybe_max = ByteCountFromJS(info[2]);
-    if (maybe_min.IsJust() && maybe_max.IsJust()) {
-      uint32_t min = maybe_min.FromJust();
-      uint32_t max = maybe_max.FromJust();
-      MarshalNode(info, tree, ts_node_descendant_for_byte_range(node, min, max));
-      return;
+    optional<uint32_t> maybe_min = ByteCountFromJS(info[1]);
+    if (maybe_min) {
+      optional<uint32_t> maybe_max = ByteCountFromJS(info[2]);
+      if (maybe_max) {
+        uint32_t min = *maybe_min;
+        uint32_t max = *maybe_max;
+        return MarshalNode(env, tree, ts_node_descendant_for_byte_range(node, min, max));
+      }
     }
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void NamedDescendantForPosition(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value NamedDescendantForPosition(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
 
   if (node.id) {
-    Nan::Maybe<TSPoint> maybe_min = PointFromJS(info[1]);
-    Nan::Maybe<TSPoint> maybe_max = PointFromJS(info[2]);
-    if (maybe_min.IsJust() && maybe_max.IsJust()) {
-      TSPoint min = maybe_min.FromJust();
-      TSPoint max = maybe_max.FromJust();
-      MarshalNode(info, tree, ts_node_named_descendant_for_point_range(node, min, max));
-      return;
+    optional<TSPoint> maybe_min = PointFromJS(info[1]);
+    optional<TSPoint> maybe_max = PointFromJS(info[2]);
+    if (maybe_min && maybe_max) {
+      TSPoint min = *maybe_min;
+      TSPoint max = *maybe_max;
+      return MarshalNode(env, tree, ts_node_named_descendant_for_point_range(node, min, max));
     }
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void DescendantForPosition(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value DescendantForPosition(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
 
   if (node.id) {
-    Nan::Maybe<TSPoint> maybe_min = PointFromJS(info[1]);
-    Nan::Maybe<TSPoint> maybe_max = PointFromJS(info[2]);
-    if (maybe_min.IsJust() && maybe_max.IsJust()) {
-      TSPoint min = maybe_min.FromJust();
-      TSPoint max = maybe_max.FromJust();
-      MarshalNode(info, tree, ts_node_descendant_for_point_range(node, min, max));
-      return;
+    optional<TSPoint> maybe_min = PointFromJS(info[1]);
+    if (maybe_min) {
+      optional<TSPoint> maybe_max = PointFromJS(info[2]);
+      if (maybe_max) {
+        TSPoint min = *maybe_min;
+        TSPoint max = *maybe_max;
+        return MarshalNode(env, tree, ts_node_descendant_for_point_range(node, min, max));
+      }
     }
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void Type(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value Type(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
 
   if (node.id) {
     const char *result = ts_node_type(node);
-    info.GetReturnValue().Set(Nan::New(result).ToLocalChecked());
+    return String::New(env, result);
   }
+  return env.Undefined();
 }
 
-static void TypeId(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value TypeId(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
 
   if (node.id) {
     TSSymbol result = ts_node_symbol(node);
-    info.GetReturnValue().Set(Nan::New(result));
+    return Number::New(env, result);
   }
+  return env.Undefined();
 }
 
-static void IsNamed(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value IsNamed(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
 
   if (node.id) {
     bool result = ts_node_is_named(node);
-    info.GetReturnValue().Set(Nan::New(result));
+    return Boolean::New(env, result);
   }
+  return env.Undefined();
 }
 
-static void StartIndex(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value StartIndex(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
 
   if (node.id) {
-    int32_t result = ts_node_start_byte(node) / 2;
-    info.GetReturnValue().Set(Nan::New<Integer>(result));
+    uint32_t result = ts_node_start_byte(node) / 2;
+    return Number::New(env, result);
   }
+  return env.Undefined();
 }
 
-static void EndIndex(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value EndIndex(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
 
   if (node.id) {
-    int32_t result = ts_node_end_byte(node) / 2;
-    info.GetReturnValue().Set(Nan::New<Integer>(result));
+    uint32_t result = ts_node_end_byte(node) / 2;
+    return Number::New(env, result);
   }
+  return env.Undefined();
 }
 
-static void StartPosition(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value StartPosition(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
-
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
     TransferPoint(ts_node_start_point(node));
   }
+  return env.Undefined();
 }
 
-static void EndPosition(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value EndPosition(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
-
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
     TransferPoint(ts_node_end_point(node));
   }
+  return env.Undefined();
 }
 
-static void Child(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value Child(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
-
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
-    if (!info[1]->IsUint32()) {
-      Nan::ThrowTypeError("Second argument must be an integer");
-      return;
+    if (info[1].IsNumber()) {
+      uint32_t index = info[1].As<Number>().Uint32Value();
+      return MarshalNode(env, tree, ts_node_child(node, index));
     }
-    uint32_t index = Nan::To<uint32_t>(info[1]).FromJust();
-    MarshalNode(info, tree, ts_node_child(node, index));
-    return;
+    TypeError::New(env, "Second argument must be an integer").ThrowAsJavaScriptException();
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void NamedChild(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value NamedChild(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
-
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
-    if (!info[1]->IsUint32()) {
-      Nan::ThrowTypeError("Second argument must be an integer");
-      return;
+    if (info[1].IsNumber()) {
+      uint32_t index = info[1].As<Number>().Uint32Value();
+      return MarshalNode(env, tree, ts_node_named_child(node, index));
     }
-    uint32_t index = Nan::To<uint32_t>(info[1]).FromJust();
-    MarshalNode(info, tree, ts_node_named_child(node, index));
-    return;
+    TypeError::New(env, "Second argument must be an integer").ThrowAsJavaScriptException();
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void ChildCount(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value ChildCount(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
-
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
-    info.GetReturnValue().Set(Nan::New(ts_node_child_count(node)));
+    return Number::New(env, ts_node_child_count(node));
   }
+  return env.Undefined();
 }
 
-static void NamedChildCount(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value NamedChildCount(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
-
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
-    info.GetReturnValue().Set(Nan::New(ts_node_named_child_count(node)));
+    return Number::New(env, ts_node_named_child_count(node));
   }
+  return env.Undefined();
 }
 
-static void FirstChild(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value FirstChild(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
-    MarshalNode(info, tree, ts_node_child(node, 0));
-    return;
+    return MarshalNode(env, tree, ts_node_child(node, 0));
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void FirstNamedChild(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value FirstNamedChild(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
-    MarshalNode(info, tree, ts_node_named_child(node, 0));
-    return;
+    return MarshalNode(env, tree, ts_node_named_child(node, 0));
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void LastChild(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value LastChild(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
     uint32_t child_count = ts_node_child_count(node);
     if (child_count > 0) {
-      MarshalNode(info, tree, ts_node_child(node, child_count - 1));
-      return;
+      return MarshalNode(env, tree, ts_node_child(node, child_count - 1));
     }
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void LastNamedChild(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value LastNamedChild(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
     uint32_t child_count = ts_node_named_child_count(node);
     if (child_count > 0) {
-      MarshalNode(info, tree, ts_node_named_child(node, child_count - 1));
-      return;
+      return MarshalNode(env, tree, ts_node_named_child(node, child_count - 1));
     }
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void Parent(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value Parent(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
-    MarshalNode(info, tree, ts_node_parent(node));
-    return;
+    return MarshalNode(env, tree, ts_node_parent(node));
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void NextSibling(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value NextSibling(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
-    MarshalNode(info, tree, ts_node_next_sibling(node));
-    return;
+    return MarshalNode(env, tree, ts_node_next_sibling(node));
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void NextNamedSibling(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value NextNamedSibling(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
-    MarshalNode(info, tree, ts_node_next_named_sibling(node));
-    return;
+    return MarshalNode(env, tree, ts_node_next_named_sibling(node));
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void PreviousSibling(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value PreviousSibling(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
-    MarshalNode(info, tree, ts_node_prev_sibling(node));
-    return;
+    return MarshalNode(env, tree, ts_node_prev_sibling(node));
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void PreviousNamedSibling(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value PreviousNamedSibling(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
   if (node.id) {
-    MarshalNode(info, tree, ts_node_prev_named_sibling(node));
-    return;
+    return MarshalNode(env, tree, ts_node_prev_named_sibling(node));
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
 struct SymbolSet {
@@ -459,62 +498,41 @@ struct SymbolSet {
   bool contains(TSSymbol symbol) { return symbols.find(symbol) != symbols.npos; }
 };
 
-bool symbol_set_from_js(SymbolSet *symbols, const Local<Value> &value, const TSLanguage *language) {
-  if (!value->IsArray()) {
-    Nan::ThrowTypeError("Argument must be a string or array of strings");
+bool symbol_set_from_js(SymbolSet *symbols, const Value &value, const TSLanguage *language) {
+  Env env = value.Env();
+  if (!value.IsArray()) {
+    TypeError::New(env, "Argument must be a string or array of strings").ThrowAsJavaScriptException();
     return false;
   }
-
+  Array js_types = value.As<Array>();
   unsigned symbol_count = ts_language_symbol_count(language);
-
-  Local<Array> js_types = Local<Array>::Cast(value);
-  for (unsigned i = 0, n = js_types->Length(); i < n; i++) {
-    Local<Value> js_node_type_value;
-    if (Nan::Get(js_types, i).ToLocal(&js_node_type_value)) {
-      Local<String> js_node_type;
-      if (Nan::To<String>(js_node_type_value).ToLocal(&js_node_type)) {
-        auto length = js_node_type->Utf8Length(
-          #if NODE_MAJOR_VERSION >= 12
-            Isolate::GetCurrent()
-          #endif
-        );
-
-        std::string node_type(length, '\0');
-        js_node_type->WriteUtf8(
-
-          // Nan doesn't wrap this functionality
-          #if NODE_MAJOR_VERSION >= 12
-            Isolate::GetCurrent(),
-          #endif
-
-          &node_type[0]
-        );
-
-        if (node_type == "ERROR") {
-          symbols->add(static_cast<TSSymbol>(-1));
-        } else {
-          for (TSSymbol j = 0; j < symbol_count; j++) {
-            if (node_type == ts_language_symbol_name(language, j)) {
-              symbols->add(j);
-            }
+  for (uint32_t i = 0, n = js_types.Length(); i < n; i++) {
+    Value js_node_type_value = js_types[i];
+    if (js_node_type_value.IsString()) {
+      String js_node_type = js_node_type_value.As<String>();
+      std::string node_type = js_node_type.Utf8Value();
+      if (node_type == "ERROR") {
+        symbols->add(static_cast<TSSymbol>(-1));
+      } else {
+        for (TSSymbol j = 0; j < symbol_count; j++) {
+          if (node_type == ts_language_symbol_name(language, j)) {
+            symbols->add(j);
           }
         }
-
-        continue;
       }
+    } else {
+      TypeError::New(env, "Argument must be a string or array of strings").ThrowAsJavaScriptException();
+      return false;
     }
-
-    Nan::ThrowTypeError("Argument must be a string or array of strings");
-    return false;
   }
-
   return true;
 }
 
-static void Children(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value Children(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
-  if (!node.id) return;
+  TSNode node = UnmarshalNode(env, tree);
+  if (!node.id) return env.Undefined();
 
   vector<TSNode> result;
   ts_tree_cursor_reset(&scratch_cursor, node);
@@ -525,13 +543,14 @@ static void Children(const Nan::FunctionCallbackInfo<Value> &info) {
     } while (ts_tree_cursor_goto_next_sibling(&scratch_cursor));
   }
 
-  MarshalNodes(info, tree, result.data(), result.size());
+  return MarshalNodes(env, tree, result.data(), result.size());
 }
 
-static void NamedChildren(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value NamedChildren(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
-  if (!node.id) return;
+  TSNode node = UnmarshalNode(env, tree);
+  if (!node.id) return env.Undefined();
 
   vector<TSNode> result;
   ts_tree_cursor_reset(&scratch_cursor, node);
@@ -544,30 +563,32 @@ static void NamedChildren(const Nan::FunctionCallbackInfo<Value> &info) {
     } while (ts_tree_cursor_goto_next_sibling(&scratch_cursor));
   }
 
-  MarshalNodes(info, tree, result.data(), result.size());
+  return MarshalNodes(env, tree, result.data(), result.size());
 }
 
-static void DescendantsOfType(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value DescendantsOfType(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
-  if (!node.id) return;
+  TSNode node = UnmarshalNode(env, tree);
+  if (!node.id) return env.Undefined();
 
   SymbolSet symbols;
-  if (!symbol_set_from_js(&symbols, info[1], ts_tree_language(node.tree))) return;
+  if (!symbol_set_from_js(&symbols, info[1], ts_tree_language(node.tree))) {
+    return env.Undefined();
+  }
 
   TSPoint start_point = {0, 0};
   TSPoint end_point = {UINT32_MAX, UINT32_MAX};
-
-  if (info.Length() > 2 && info[2]->IsObject()) {
+  if (info.Length() > 2 && info[2].IsObject()) {
     auto maybe_start_point = PointFromJS(info[2]);
-    if (maybe_start_point.IsNothing()) return;
-    start_point = maybe_start_point.FromJust();
+    if (!maybe_start_point) return env.Undefined();
+    start_point = *maybe_start_point;
   }
 
-  if (info.Length() > 3 && info[3]->IsObject()) {
+  if (info.Length() > 3 && info[3].IsObject()) {
     auto maybe_end_point = PointFromJS(info[3]);
-    if (maybe_end_point.IsNothing()) return;
-    end_point = maybe_end_point.FromJust();
+    if (!maybe_end_point) return env.Undefined();
+    end_point = *maybe_end_point;
   }
 
   vector<TSNode> found;
@@ -610,20 +631,20 @@ static void DescendantsOfType(const Nan::FunctionCallbackInfo<Value> &info) {
     }
   }
 
-  MarshalNodes(info, tree, found.data(), found.size());
+  return MarshalNodes(env, tree, found.data(), found.size());
 }
 
-static void ChildNodesForFieldId(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value ChildNodesForFieldId(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
-  if (!node.id) return;
+  TSNode node = UnmarshalNode(env, tree);
+  if (!node.id) return env.Undefined();
 
-  auto maybe_field_id = Nan::To<uint32_t>(info[1]);
-  if (!maybe_field_id.IsJust()) {
-    Nan::ThrowTypeError("Second argument must be an integer");
-    return;
+  if (!info[1].IsNumber()) {
+    TypeError::New(env, "Second argument must be an integer").ThrowAsJavaScriptException();
+    return env.Undefined();
   }
-  uint32_t field_id = maybe_field_id.FromJust();
+  uint32_t field_id = info[1].As<Number>().Uint32Value();
 
   vector<TSNode> result;
   ts_tree_cursor_reset(&scratch_cursor, node);
@@ -636,110 +657,111 @@ static void ChildNodesForFieldId(const Nan::FunctionCallbackInfo<Value> &info) {
     } while (ts_tree_cursor_goto_next_sibling(&scratch_cursor));
   }
 
-  MarshalNodes(info, tree, result.data(), result.size());
+  return MarshalNodes(env, tree, result.data(), result.size());
 }
 
-static void ChildNodeForFieldId(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value ChildNodeForFieldId(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
 
   if (node.id) {
-    auto maybe_field_id = Nan::To<uint32_t>(info[1]);
-    if (!maybe_field_id.IsJust()) {
-      Nan::ThrowTypeError("Second argument must be an integer");
-      return;
+    if (!info[1].IsNumber()) {
+      TypeError::New(env, "Second argument must be an integer").ThrowAsJavaScriptException();
+      return env.Undefined();
     }
-    uint32_t field_id = maybe_field_id.FromJust();
-    MarshalNode(info, tree, ts_node_child_by_field_id(node, field_id));
-    return;
+    uint32_t field_id = info[1].As<Number>().Uint32Value();
+    return MarshalNode(env, tree, ts_node_child_by_field_id(node, field_id));
   }
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void Closest(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value Closest(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
-  if (!node.id) return;
+  TSNode node = UnmarshalNode(env, tree);
+  if (!node.id) return env.Undefined();
 
   SymbolSet symbols;
-  if (!symbol_set_from_js(&symbols, info[1], ts_tree_language(node.tree))) return;
+  if (!symbol_set_from_js(&symbols, info[1], ts_tree_language(node.tree))) {
+    return env.Undefined();
+  }
 
   for (;;) {
     TSNode parent = ts_node_parent(node);
     if (!parent.id) break;
     if (symbols.contains(ts_node_symbol(parent))) {
-      MarshalNode(info, tree, parent);
-      return;
+      return MarshalNode(env, tree, parent);
     }
     node = parent;
   }
 
-  MarshalNullNode();
+  return MarshalNullNode(env);
 }
 
-static void Walk(const Nan::FunctionCallbackInfo<Value> &info) {
+static Value Walk(const CallbackInfo &info) {
+  Env env = info.Env();
   const Tree *tree = Tree::UnwrapTree(info[0]);
-  TSNode node = UnmarshalNode(tree);
+  TSNode node = UnmarshalNode(env, tree);
   TSTreeCursor cursor = ts_tree_cursor_new(node);
-  info.GetReturnValue().Set(TreeCursor::NewInstance(cursor));
-}
-
-void Init(Local<Object> exports) {
-  Local<Object> result = Nan::New<Object>();
-
-  FunctionPair methods[] = {
-    {"startIndex", StartIndex},
-    {"endIndex", EndIndex},
-    {"type", Type},
-    {"typeId", TypeId},
-    {"isNamed", IsNamed},
-    {"parent", Parent},
-    {"child", Child},
-    {"namedChild", NamedChild},
-    {"children", Children},
-    {"namedChildren", NamedChildren},
-    {"childCount", ChildCount},
-    {"namedChildCount", NamedChildCount},
-    {"firstChild", FirstChild},
-    {"lastChild", LastChild},
-    {"firstNamedChild", FirstNamedChild},
-    {"lastNamedChild", LastNamedChild},
-    {"nextSibling", NextSibling},
-    {"nextNamedSibling", NextNamedSibling},
-    {"previousSibling", PreviousSibling},
-    {"previousNamedSibling", PreviousNamedSibling},
-    {"startPosition", StartPosition},
-    {"endPosition", EndPosition},
-    {"isMissing", IsMissing},
-    {"toString", ToString},
-    {"firstChildForIndex", FirstChildForIndex},
-    {"firstNamedChildForIndex", FirstNamedChildForIndex},
-    {"descendantForIndex", DescendantForIndex},
-    {"namedDescendantForIndex", NamedDescendantForIndex},
-    {"descendantForPosition", DescendantForPosition},
-    {"namedDescendantForPosition", NamedDescendantForPosition},
-    {"hasChanges", HasChanges},
-    {"hasError", HasError},
-    {"descendantsOfType", DescendantsOfType},
-    {"walk", Walk},
-    {"closest", Closest},
-    {"childNodeForFieldId", ChildNodeForFieldId},
-    {"childNodesForFieldId", ChildNodesForFieldId},
-  };
-
-  for (size_t i = 0; i < length_of_array(methods); i++) {
-    Nan::Set(
-      result,
-      Nan::New(methods[i].name).ToLocalChecked(),
-      Nan::GetFunction(Nan::New<FunctionTemplate>(methods[i].callback)).ToLocalChecked()
-    );
-  }
+  return NewTreeCursor(cursor);
+}
+
+class NodeMethods : public ObjectWrap<NodeMethods> {
+  public:
+  NodeMethods(const Napi::CallbackInfo &info)
+    : Napi::ObjectWrap<NodeMethods>(info)
+    {}
+
+  static void Init(Napi::Env env, Object &exports) {
+    exports["NodeMethods"] = DefineClass(env, "NodeMethods", {
+      StaticMethod("startIndex", StartIndex, napi_writable),
+      StaticMethod("endIndex", EndIndex, napi_writable),
+      StaticMethod("type", Type, napi_writable),
+      StaticMethod("typeId", TypeId, napi_writable),
+      StaticMethod("isNamed", IsNamed, napi_writable),
+      StaticMethod("parent", Parent, napi_writable),
+      StaticMethod("child", Child, napi_writable),
+      StaticMethod("namedChild", NamedChild, napi_writable),
+      StaticMethod("children", Children, napi_writable),
+      StaticMethod("namedChildren", NamedChildren, napi_writable),
+      StaticMethod("childCount", ChildCount, napi_writable),
+      StaticMethod("namedChildCount", NamedChildCount, napi_writable),
+      StaticMethod("firstChild", FirstChild, napi_writable),
+      StaticMethod("lastChild", LastChild, napi_writable),
+      StaticMethod("firstNamedChild", FirstNamedChild, napi_writable),
+      StaticMethod("lastNamedChild", LastNamedChild, napi_writable),
+      StaticMethod("nextSibling", NextSibling, napi_writable),
+      StaticMethod("nextNamedSibling", NextNamedSibling, napi_writable),
+      StaticMethod("previousSibling", PreviousSibling, napi_writable),
+      StaticMethod("previousNamedSibling", PreviousNamedSibling, napi_writable),
+      StaticMethod("startPosition", StartPosition, napi_writable),
+      StaticMethod("endPosition", EndPosition, napi_writable),
+      StaticMethod("isMissing", IsMissing, napi_writable),
+      StaticMethod("toString", ToString, napi_writable),
+      StaticMethod("firstChildForIndex", FirstChildForIndex, napi_writable),
+      StaticMethod("firstNamedChildForIndex", FirstNamedChildForIndex, napi_writable),
+      StaticMethod("descendantForIndex", DescendantForIndex, napi_writable),
+      StaticMethod("namedDescendantForIndex", NamedDescendantForIndex, napi_writable),
+      StaticMethod("descendantForPosition", DescendantForPosition, napi_writable),
+      StaticMethod("namedDescendantForPosition", NamedDescendantForPosition, napi_writable),
+      StaticMethod("hasChanges", HasChanges, napi_writable),
+      StaticMethod("hasError", HasError, napi_writable),
+      StaticMethod("descendantsOfType", DescendantsOfType, napi_writable),
+      StaticMethod("walk", Walk, napi_writable),
+      StaticMethod("closest", Closest, napi_writable),
+      StaticMethod("childNodeForFieldId", ChildNodeForFieldId, napi_writable),
+      StaticMethod("childNodesForFieldId", ChildNodesForFieldId, napi_writable),
+    });
 
-  module_exports.Reset(exports);
-  setup_transfer_buffer(1);
+  }
+};
 
-  Nan::Set(exports, Nan::New("NodeMethods").ToLocalChecked(), result);
+void InitNode(Object &exports) {
+  Env env = exports.Env();
+  NodeMethods::Init(env, exports);
+  module_exports.Reset(exports, 1);
+  setup_transfer_buffer(env, 1);
 }
 
-}  // namespace node_methods
 }  // namespace node_tree_sitter
diff --git a/src/node.h b/src/node.h
index 6dcc34f0..1bb95859 100644
--- a/src/node.h
+++ b/src/node.h
@@ -1,31 +1,17 @@
 #ifndef NODE_TREE_SITTER_NODE_H_
 #define NODE_TREE_SITTER_NODE_H_
 
-#include <nan.h>
-#include <v8.h>
-#include <node_object_wrap.h>
+#include <napi.h>
 #include <tree_sitter/api.h>
 #include "./tree.h"
+#include "./util.h"
 
 namespace node_tree_sitter {
-namespace node_methods {
 
-void Init(v8::Local<v8::Object>);
-void MarshalNode(const Nan::FunctionCallbackInfo<v8::Value> &info, const Tree *, TSNode);
-TSNode UnmarshalNode(const Tree *tree);
+void InitNode(Napi::Object &exports);
+Napi::Value MarshalNode(Napi::Env, const Tree *, TSNode);
+TSNode UnmarshalNode(Napi::Env env, const Tree *tree);
 
-static inline const void *UnmarshalNodeId(const uint32_t *buffer) {
-  const void *result;
-  memcpy(&result, buffer, sizeof(result));
-  return result;
-}
-
-static inline void MarshalNodeId(const void *id, uint32_t *buffer) {
-  memset(buffer, 0, sizeof(uint64_t));
-  memcpy(buffer, &id, sizeof(id));
-}
-
-}  // namespace node_methods
 }  // namespace node_tree_sitter
 
 #endif  // NODE_TREE_SITTER_NODE_H_
diff --git a/src/optional.h b/src/optional.h
new file mode 100644
index 00000000..31366f2a
--- /dev/null
+++ b/src/optional.h
@@ -0,0 +1,29 @@
+#ifndef NODE_TREE_SITTER_OPTIONAL_H
+#define NODE_TREE_SITTER_OPTIONAL_H
+
+#include <utility>
+
+template <typename T> class optional {
+  T value;
+  bool is_some;
+
+public:
+  optional(T &&value) : value(std::move(value)), is_some(true) {}
+  optional(const T &value) : value(value), is_some(true) {}
+  optional() : value(T()), is_some(false) {}
+
+  T &operator*() { return value; }
+  const T &operator*() const { return value; }
+  const T *operator->() const { return &value; }
+  T *operator->() { return &value; }
+  operator bool() const { return is_some; }
+  bool operator==(const optional<T> &other) {
+    if (is_some) {
+      return other.is_some && value == other.value;
+    } else {
+      return !other.is_some;
+    }
+  }
+};
+
+#endif // NODE_TREE_SITTER_OPTIONAL_H
diff --git a/src/parser.cc b/src/parser.cc
index 52e1beac..533f92ee 100644
--- a/src/parser.cc
+++ b/src/parser.cc
@@ -2,11 +2,11 @@
 #include <string>
 #include <vector>
 #include <climits>
-#include <v8.h>
-#include <nan.h>
+#include <napi.h>
 #include "./conversions.h"
 #include "./language.h"
 #include "./logger.h"
+#include "./node.h"
 #include "./tree.h"
 #include "./util.h"
 #include "text-buffer-snapshot-wrapper.h"
@@ -14,445 +14,466 @@
 
 namespace node_tree_sitter {
 
-using namespace v8;
+using namespace Napi;
 using std::vector;
 using std::pair;
 
-Nan::Persistent<Function> Parser::constructor;
-
-class CallbackInput {
+class Parser : public ObjectWrap<Parser> {
  public:
-  CallbackInput(v8::Local<v8::Function> callback, v8::Local<v8::Value> js_buffer_size)
-    : callback(callback),
-      byte_offset(0),
-      partial_string_offset(0) {
-    uint32_t buffer_size = Nan::To<uint32_t>(js_buffer_size).FromMaybe(0);
-    if (buffer_size == 0) buffer_size = 32 * 1024;
-    buffer.resize(buffer_size);
-  }
+  static void Init(Object &exports) {
+    Napi::Env env = exports.Env();
+
+    Function ctor = DefineClass(env, "Parser", {
+      InstanceMethod("getLogger", &Parser::GetLogger, napi_writable),
+      InstanceMethod("setLogger", &Parser::SetLogger, napi_writable),
+      InstanceMethod("setLanguage", &Parser::SetLanguage, napi_writable),
+      InstanceMethod("printDotGraphs", &Parser::PrintDotGraphs, napi_writable),
+      InstanceMethod("parse", &Parser::Parse, napi_writable),
+      InstanceMethod("parseTextBuffer", &Parser::ParseTextBuffer, napi_writable),
+      InstanceMethod("parseTextBufferSync", &Parser::ParseTextBufferSync, napi_writable),
+    });
+
+    String s = String::New(env, "");
+    if (env.IsExceptionPending()) {
+      return;
+    }
 
-  TSInput Input() {
-    TSInput result;
-    result.payload = (void *)this;
-    result.encoding = TSInputEncodingUTF16;
-    result.read = Read;
-    return result;
+    napi_value value;
+    napi_valuetype type;
+    napi_status status = napi_get_property(
+      env,
+      s,
+      String::New(env, "slice"),
+      &value
+    );
+    assert(status == napi_ok);
+    status = napi_typeof(env, value, &type);
+    assert(status == napi_ok);
+
+    constructor.Reset(ctor, 1);
+    // string_slice.Reset(string_slice_fn.As<Function>(), 1);
+    exports["Parser"] = ctor;
+    exports["LANGUAGE_VERSION"] = Number::New(env, TREE_SITTER_LANGUAGE_VERSION);
   }
 
+  TSParser *parser_;
+  bool is_parsing_async_;
+
+  Parser(const CallbackInfo &info)
+    : Napi::ObjectWrap<Parser>(info),
+      parser_(ts_parser_new()),
+      is_parsing_async_(false)
+      {}
+
+  ~Parser() { ts_parser_delete(parser_); }
+
  private:
-  static const char * Read(void *payload, uint32_t byte, TSPoint position, uint32_t *bytes_read) {
-    CallbackInput *reader = (CallbackInput *)payload;
+  class CallbackInput {
+   public:
+    CallbackInput(Function callback, Napi::Value js_buffer_size)
+      : byte_offset(0) {
+      this->callback.Reset(callback, 1);
+      if (js_buffer_size.IsNumber()) {
+        buffer.resize(js_buffer_size.As<Number>().Uint32Value());
+      } else {
+        buffer.resize(32 * 1024);
+      }
+    }
 
-    if (byte != reader->byte_offset) {
-      reader->byte_offset = byte;
-      reader->partial_string_offset = 0;
-      reader->partial_string.Reset();
+    TSInput Input() {
+      TSInput result;
+      result.payload = (void *)this;
+      result.encoding = TSInputEncodingUTF16;
+      result.read = Read;
+      return result;
     }
 
-    *bytes_read = 0;
-    Local<String> result;
-    uint32_t start = 0;
-    if (reader->partial_string_offset) {
-      result = Nan::New(reader->partial_string);
-      start = reader->partial_string_offset;
-    } else {
-      Local<Function> callback = Nan::New(reader->callback);
-      uint32_t utf16_unit = byte / 2;
-      Local<Value> argv[2] = { Nan::New<Number>(utf16_unit), PointToJS(position) };
-      TryCatch try_catch(Isolate::GetCurrent());
-      auto maybe_result_value = Nan::Call(callback, callback->CreationContext()->Global(), 2, argv);
-      if (try_catch.HasCaught()) return nullptr;
-
-      Local<Value> result_value;
-      if (!maybe_result_value.ToLocal(&result_value)) return nullptr;
-      if (!result_value->IsString()) return nullptr;
-      if (!Nan::To<String>(result_value).ToLocal(&result)) return nullptr;
+   private:
+    static String slice(String s, uint32_t offset) {
+      return string_slice.Call(s, {Number::New(s.Env(), offset)}).As<String>();
     }
 
-    int utf16_units_read = result->Write(
+    static const char * Read(void *payload, uint32_t byte, TSPoint position, uint32_t *bytes_read) {
+      CallbackInput *reader = (CallbackInput *)payload;
+      Napi::Env env = reader->callback.Env();
 
-      // Nan doesn't wrap this functionality
-      #if NODE_MAJOR_VERSION >= 12
-        Isolate::GetCurrent(),
-      #endif
+      if (byte != reader->byte_offset) {
+        reader->byte_offset = byte;
+        reader->partial_string.Reset();
+      }
 
-      reader->buffer.data(),
-      start,
-      reader->buffer.size(),
-      String::NO_NULL_TERMINATION
-    );
-    int end = start + utf16_units_read;
-    *bytes_read = 2 * utf16_units_read;
+      *bytes_read = 0;
+      String result;
+      if (!reader->partial_string.IsEmpty()) {
+        result = reader->partial_string.Value().As<String>();
+      } else {
+        Function callback = reader->callback.Value();
+        Napi::Value result_value = callback({
+          ByteCountToJS(env, byte),
+          PointToJS(env, position),
+        });
+        if (env.IsExceptionPending()) return nullptr;
+        if (!result_value.IsString()) return nullptr;
+        result = result_value.As<String>();
+      }
 
-    reader->byte_offset += *bytes_read;
+      size_t length = 0;
+      size_t utf16_units_read = 0;
+      napi_status status;
+      status = napi_get_value_string_utf16(
+        env, result, nullptr, 0, &length
+      );
+      if (status != napi_ok) return nullptr;
+      status = napi_get_value_string_utf16(
+        env, result, (char16_t *)&reader->buffer[0], reader->buffer.size(), &utf16_units_read
+      );
+      if (status != napi_ok) return nullptr;
+
+      *bytes_read = 2 * utf16_units_read;
+      reader->byte_offset += *bytes_read;
+
+      if (utf16_units_read < length) {
+        reader->partial_string.Reset(slice(result, utf16_units_read));
+      } else {
+        reader->partial_string.Reset();
+      }
 
-    if (end < result->Length()) {
-      reader->partial_string_offset = end;
-      reader->partial_string.Reset(result);
-    } else {
-      reader->partial_string_offset = 0;
-      reader->partial_string.Reset();
+      return (const char *)reader->buffer.data();
     }
 
-    return (const char *)reader->buffer.data();
-  }
-
-  Nan::Persistent<v8::Function> callback;
-  std::vector<uint16_t> buffer;
-  size_t byte_offset;
-  Nan::Persistent<v8::String> partial_string;
-  size_t partial_string_offset;
-};
+    FunctionReference callback;
+    std::vector<uint16_t> buffer;
+    size_t byte_offset;
+    Reference<String> partial_string;
+  };
 
-class TextBufferInput {
-public:
-  TextBufferInput(const vector<pair<const char16_t *, uint32_t>> *slices)
-    : slices_(slices),
-      byte_offset(0),
-      slice_index_(0),
-      slice_offset_(0) {}
+  class TextBufferInput {
+  public:
+    TextBufferInput(const vector<pair<const char16_t *, uint32_t>> *slices)
+      : slices_(slices),
+        byte_offset(0),
+        slice_index_(0),
+        slice_offset_(0) {}
 
-  TSInput input() {
-    return TSInput{this, Read, TSInputEncodingUTF16};
-  }
+    TSInput input() {
+      return TSInput{this, Read, TSInputEncodingUTF16};
+    }
 
-private:
-  void seek(uint32_t byte_offset) {
-    this->byte_offset = byte_offset;
-
-    uint32_t total_length = 0;
-    uint32_t goal_index = byte_offset / 2;
-    for (unsigned i = 0, n = this->slices_->size(); i < n; i++) {
-      uint32_t next_total_length = total_length + this->slices_->at(i).second;
-      if (next_total_length > goal_index) {
-        this->slice_index_ = i;
-        this->slice_offset_ = goal_index - total_length;
-        return;
+  private:
+    void seek(uint32_t byte_offset) {
+      this->byte_offset = byte_offset;
+
+      uint32_t total_length = 0;
+      uint32_t goal_index = byte_offset / 2;
+      for (unsigned i = 0, n = this->slices_->size(); i < n; i++) {
+        uint32_t next_total_length = total_length + this->slices_->at(i).second;
+        if (next_total_length > goal_index) {
+          this->slice_index_ = i;
+          this->slice_offset_ = goal_index - total_length;
+          return;
+        }
+        total_length = next_total_length;
       }
-      total_length = next_total_length;
+
+      this->slice_index_ = this->slices_->size();
+      this->slice_offset_ = 0;
     }
 
-    this->slice_index_ = this->slices_->size();
-    this->slice_offset_ = 0;
-  }
+    static const char *Read(void *payload, uint32_t byte, TSPoint position, uint32_t *length) {
+      auto self = static_cast<TextBufferInput *>(payload);
 
-  static const char *Read(void *payload, uint32_t byte, TSPoint position, uint32_t *length) {
-    auto self = static_cast<TextBufferInput *>(payload);
+      if (byte != self->byte_offset) self->seek(byte);
 
-    if (byte != self->byte_offset) self->seek(byte);
+      if (self->slice_index_ == self->slices_->size()) {
+        *length = 0;
+        return "";
+      }
 
-    if (self->slice_index_ == self->slices_->size()) {
-      *length = 0;
-      return "";
+      auto &slice = self->slices_->at(self->slice_index_);
+      const char16_t *result = slice.first + self->slice_offset_;
+      *length = 2 * (slice.second - self->slice_offset_);
+      self->byte_offset += *length;
+      self->slice_index_++;
+      self->slice_offset_ = 0;
+      return reinterpret_cast<const char *>(result);
     }
 
-    auto &slice = self->slices_->at(self->slice_index_);
-    const char16_t *result = slice.first + self->slice_offset_;
-    *length = 2 * (slice.second - self->slice_offset_);
-    self->byte_offset += *length;
-    self->slice_index_++;
-    self->slice_offset_ = 0;
-    return reinterpret_cast<const char *>(result);
-  }
-
-  const vector<pair<const char16_t *, uint32_t>> *slices_;
-  uint32_t byte_offset;
-  uint32_t slice_index_;
-  uint32_t slice_offset_;
-};
-
-void Parser::Init(Local<Object> exports) {
-  Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
-  tpl->InstanceTemplate()->SetInternalFieldCount(1);
-  Local<String> class_name = Nan::New("Parser").ToLocalChecked();
-  tpl->SetClassName(class_name);
-
-  FunctionPair methods[] = {
-    {"getLogger", GetLogger},
-    {"setLogger", SetLogger},
-    {"setLanguage", SetLanguage},
-    {"printDotGraphs", PrintDotGraphs},
-    {"parse", Parse},
-    {"parseTextBuffer", ParseTextBuffer},
-    {"parseTextBufferSync", ParseTextBufferSync},
+    const vector<pair<const char16_t *, uint32_t>> *slices_;
+    uint32_t byte_offset;
+    uint32_t slice_index_;
+    uint32_t slice_offset_;
   };
 
-  for (size_t i = 0; i < length_of_array(methods); i++) {
-    Nan::SetPrototypeMethod(tpl, methods[i].name, methods[i].callback);
-  }
-
-  constructor.Reset(Nan::Persistent<Function>(Nan::GetFunction(tpl).ToLocalChecked()));
-  Nan::Set(exports, class_name, Nan::New(constructor));
-  Nan::Set(exports, Nan::New("LANGUAGE_VERSION").ToLocalChecked(), Nan::New<Number>(TREE_SITTER_LANGUAGE_VERSION));
-}
-
-Parser::Parser() : parser_(ts_parser_new()), is_parsing_async_(false) {}
-
-Parser::~Parser() { ts_parser_delete(parser_); }
-
-static bool handle_included_ranges(TSParser *parser, Local<Value> arg) {
-  uint32_t last_included_range_end = 0;
-  if (arg->IsArray()) {
-    auto js_included_ranges = Local<Array>::Cast(arg);
-    vector<TSRange> included_ranges;
-    for (unsigned i = 0; i < js_included_ranges->Length(); i++) {
-      Local<Value> range_value;
-      if (!Nan::Get(js_included_ranges, i).ToLocal(&range_value)) return false;
-      auto maybe_range = RangeFromJS(range_value);
-      if (!maybe_range.IsJust()) return false;
-      auto range = maybe_range.FromJust();
-      if (range.start_byte < last_included_range_end) {
-        Nan::ThrowRangeError("Overlapping ranges");
-        return false;
+  bool handle_included_ranges(class Value arg) {
+    Napi::Env env = arg.Env();
+    uint32_t last_included_range_end = 0;
+    if (arg.IsArray()) {
+      Array js_included_ranges = arg.As<Array>();
+      vector<TSRange> included_ranges;
+      for (unsigned i = 0; i < js_included_ranges.Length(); i++) {
+        Napi::Value range_value = js_included_ranges[i];
+        if (!range_value.IsObject()) return false;
+        optional<TSRange> range = RangeFromJS(range_value);
+        if (!range) return false;
+        if (range->start_byte < last_included_range_end) {
+          RangeError::New(env, "Overlapping ranges").ThrowAsJavaScriptException();
+          return false;
+        }
+        last_included_range_end = range->end_byte;
+        included_ranges.push_back(*range);
       }
-      last_included_range_end = range.end_byte;
-      included_ranges.push_back(range);
+      ts_parser_set_included_ranges(parser_, included_ranges.data(), included_ranges.size());
+    } else {
+      ts_parser_set_included_ranges(parser_, nullptr, 0);
     }
-    ts_parser_set_included_ranges(parser, included_ranges.data(), included_ranges.size());
-  } else {
-    ts_parser_set_included_ranges(parser, nullptr, 0);
+    return true;
   }
 
-  return true;
-}
-
-void Parser::New(const Nan::FunctionCallbackInfo<Value> &info) {
-  if (info.IsConstructCall()) {
-    Parser *parser = new Parser();
-    parser->Wrap(info.This());
-    info.GetReturnValue().Set(info.This());
-  } else {
-    Local<Object> self;
-    MaybeLocal<Object> maybe_self = Nan::New(constructor)->NewInstance(Nan::GetCurrentContext());
-    if (maybe_self.ToLocal(&self)) {
-      info.GetReturnValue().Set(self);
-    } else {
-      info.GetReturnValue().Set(Nan::Null());
+  Napi::Value SetLanguage(const CallbackInfo &info) {
+    auto env = info.Env();
+    if (is_parsing_async_) {
+      Error::New(env, "Parser is in use").ThrowAsJavaScriptException();
+      return env.Undefined();
     }
-  }
-}
 
-void Parser::SetLanguage(const Nan::FunctionCallbackInfo<Value> &info) {
-  Parser *parser = ObjectWrap::Unwrap<Parser>(info.This());
-  if (parser->is_parsing_async_) {
-    Nan::ThrowError("Parser is in use");
-    return;
+    const TSLanguage *language = UnwrapLanguage(info[0]);
+    if (language) ts_parser_set_language(parser_, language);
+    return info.This();
   }
 
-  const TSLanguage *language = language_methods::UnwrapLanguage(info[0]);
-  if (language) {
-    ts_parser_set_language(parser->parser_, language);
-    info.GetReturnValue().Set(info.This());
-  }
-}
+  Napi::Value Parse(const CallbackInfo &info) {
+    auto env = info.Env();
 
-void Parser::Parse(const Nan::FunctionCallbackInfo<Value> &info) {
-  Parser *parser = ObjectWrap::Unwrap<Parser>(info.This());
-  if (parser->is_parsing_async_) {
-    Nan::ThrowError("Parser is in use");
-    return;
-  }
+    if (is_parsing_async_) {
+      Error::New(env, "Parser is in use").ThrowAsJavaScriptException();
+      return env.Undefined();
+    }
 
-  if (!info[0]->IsFunction()) {
-    Nan::ThrowTypeError("Input must be a function");
-    return;
-  }
+    if (!info[0].IsFunction()) {
+      TypeError::New(env, "Input must be a function").ThrowAsJavaScriptException();
+      return env.Undefined();
+    }
 
-  Local<Function> callback = Local<Function>::Cast(info[0]);
+    Function callback = info[0].As<Function>();
 
-  Local<Object> js_old_tree;
-  const TSTree *old_tree = nullptr;
-  if (info.Length() > 1 && !info[1]->IsNull() && !info[1]->IsUndefined() && Nan::To<Object>(info[1]).ToLocal(&js_old_tree)) {
-    const Tree *tree = Tree::UnwrapTree(js_old_tree);
-    if (!tree) {
-      Nan::ThrowTypeError("Second argument must be a tree");
-      return;
+    const TSTree *old_tree = nullptr;
+    if (info.Length() > 1 && info[1].IsObject()) {
+      Object js_old_tree = info[1].As<Object>();
+      const Tree *tree = Tree::UnwrapTree(js_old_tree);
+      if (!tree) {
+        TypeError::New(env, "Second argument must be a tree").ThrowAsJavaScriptException();
+        return env.Undefined();
+      }
+      old_tree = tree->tree_;
     }
-    old_tree = tree->tree_;
+
+    auto buffer_size = env.Null();
+    if (info.Length() > 2) buffer_size = info[2];
+    if (!handle_included_ranges(info[3])) return env.Undefined();
+
+    CallbackInput callback_input(callback, buffer_size);
+    TSTree *tree = ts_parser_parse(parser_, old_tree, callback_input.Input());
+    auto result = Tree::NewInstance(env, tree);
+    return result;
   }
 
-  Local<Value> buffer_size = Nan::Null();
-  if (info.Length() > 2) buffer_size = info[2];
+  class ParseWorker : public Napi::AsyncWorker {
+    Parser *parser_;
+    TSTree *new_tree_;
+    TextBufferInput *input_;
+
+  public:
+    ParseWorker(Napi::Function &callback, Parser *parser, TextBufferInput *input) :
+      Napi::AsyncWorker(callback, "tree-sitter.parseTextBuffer"),
+      parser_(parser),
+      new_tree_(nullptr),
+      input_(input) {}
+
+    void Execute() {
+      TSLogger logger = ts_parser_logger(parser_->parser_);
+      ts_parser_set_logger(parser_->parser_, TSLogger{0, 0});
+      new_tree_ = ts_parser_parse(parser_->parser_, nullptr, input_->input());
+      ts_parser_set_logger(parser_->parser_, logger);
+    }
 
-  if (!handle_included_ranges(parser->parser_, info[3])) return;
+    void OnOK() {
+      parser_->is_parsing_async_ = false;
+      delete input_;
+      Callback()({Tree::NewInstance(Env(), new_tree_)});
+    }
+  };
 
-  CallbackInput callback_input(callback, buffer_size);
-  TSTree *tree = ts_parser_parse(parser->parser_, old_tree, callback_input.Input());
-  Local<Value> result = Tree::NewInstance(tree);
-  info.GetReturnValue().Set(result);
-}
+  const std::vector<std::pair<const char16_t *, uint32_t>> *
+  TextBufferSnapshotFromJS(Napi::Value value) {
+    auto env = value.Env();
+    if (!value.IsObject()) {
+      TypeError::New(env, "Expected a snapshot wrapper").ThrowAsJavaScriptException();
+      return nullptr;
+    }
 
-class ParseWorker : public Nan::AsyncWorker {
-  Parser *parser_;
-  TSTree *new_tree_;
-  TextBufferInput *input_;
-
-public:
-  ParseWorker(Nan::Callback *callback, Parser *parser, TextBufferInput *input) :
-    AsyncWorker(callback, "tree-sitter.parseTextBuffer"),
-    parser_(parser),
-    new_tree_(nullptr),
-    input_(input) {}
-
-  void Execute() {
-    TSLogger logger = ts_parser_logger(parser_->parser_);
-    ts_parser_set_logger(parser_->parser_, TSLogger{0, 0});
-    new_tree_ = ts_parser_parse(parser_->parser_, nullptr, input_->input());
-    ts_parser_set_logger(parser_->parser_, logger);
+    void *internal = GetInternalFieldPointer(value);
+    if (internal) {
+      return reinterpret_cast<TextBufferSnapshotWrapper *>(internal)->slices();
+    } else {
+      TypeError::New(env, "Expected a snapshot wrapper").ThrowAsJavaScriptException();
+      return nullptr;
+    }
   }
 
-  void HandleOKCallback() {
-    parser_->is_parsing_async_ = false;
-    delete input_;
-    Local<Value> argv[] = {Tree::NewInstance(new_tree_)};
-    callback->Call(1, argv, async_resource);
-  }
-};
+  Napi::Value ParseTextBuffer(const CallbackInfo &info) {
+    auto env = info.Env();
 
-void Parser::ParseTextBuffer(const Nan::FunctionCallbackInfo<Value> &info) {
-  Parser *parser = ObjectWrap::Unwrap<Parser>(info.This());
-  if (parser->is_parsing_async_) {
-    Nan::ThrowError("Parser is in use");
-    return;
-  }
+    if (is_parsing_async_) {
+      Error::New(env, "Parser is in use").ThrowAsJavaScriptException();
+      return env.Undefined();
+    }
 
-  Local<Object> js_old_tree;
-  const TSTree *old_tree = nullptr;
-  if (info.Length() > 2 && info[2]->IsObject() && Nan::To<Object>(info[2]).ToLocal(&js_old_tree)) {
-    const Tree *tree = Tree::UnwrapTree(js_old_tree);
-    if (!tree) {
-      Nan::ThrowTypeError("Second argument must be a tree");
-      return;
+    auto callback = info[0].As<Function>();
+
+    const TSTree *old_tree = nullptr;
+    if (info.Length() > 2 && info[2].IsObject()) {
+      Object js_old_tree = info[2].As<Object>();
+      const Tree *tree = Tree::UnwrapTree(js_old_tree);
+      if (!tree) {
+        TypeError::New(env, "Second argument must be a tree").ThrowAsJavaScriptException();
+        return env.Undefined();
+      }
+      old_tree = tree->tree_;
     }
-    old_tree = tree->tree_;
-  }
 
-  if (!handle_included_ranges(parser->parser_, info[3])) return;
+    if (!handle_included_ranges(info[3])) return env.Undefined();
 
-  auto snapshot = Nan::ObjectWrap::Unwrap<TextBufferSnapshotWrapper>(info[1].As<Object>());
-  auto input = new TextBufferInput(snapshot->slices());
+    auto snapshot = TextBufferSnapshotFromJS(info[1]);
+    if (!snapshot) return env.Undefined();
 
-  // If a `syncTimeoutMicros` option is passed, parse synchronously
-  // for the given amount of time before queuing an async task.
-  double js_sync_timeout = Nan::To<double>(info[4]).FromMaybe(-1);
-  if (js_sync_timeout > 0) {
-    size_t sync_timeout;
+    auto input = new TextBufferInput(snapshot);
 
-    // If the timeout is `Infinity`, then parse synchronously with no timeout.
-    if (js_sync_timeout && !std::isfinite(js_sync_timeout)) {
-      sync_timeout = 0;
-    } else {
-      auto maybe_sync_timeout = Nan::To<uint32_t>(info[4]);
-      if (maybe_sync_timeout.IsJust()) {
-        sync_timeout = maybe_sync_timeout.FromJust();
+    // If a `syncTimeoutMicros` option is passed, parse synchronously
+    // for the given amount of time before queuing an async task.
+    auto js_sync_timeout = info[4];
+    if (!js_sync_timeout.IsEmpty() && !js_sync_timeout.IsNull() && !js_sync_timeout.IsUndefined()) {
+      size_t sync_timeout;
+
+      // If the timeout is `Infinity`, then parse synchronously with no timeout.
+      if (!std::isfinite(js_sync_timeout.ToNumber().DoubleValue())) {
+        sync_timeout = 0;
+      } else if (js_sync_timeout.IsNumber()) {
+        sync_timeout = js_sync_timeout.As<Number>().Uint32Value();
       } else {
-        Nan::ThrowTypeError("The `syncTimeoutMicros` option must be a positive integer.");
-        return;
+        TypeError::New(env, "The `syncTimeoutMicros` option must be a positive integer.").ThrowAsJavaScriptException();
+        return env.Undefined();
       }
-    }
 
-    // Logging is disabled for this method, because we can't call the
-    // logging callback from an async worker.
-    TSLogger logger = ts_parser_logger(parser->parser_);
-    ts_parser_set_timeout_micros(parser->parser_, sync_timeout);
-    ts_parser_set_logger(parser->parser_, TSLogger{0, 0});
-    TSTree *result = ts_parser_parse(parser->parser_, old_tree, input->input());
-    ts_parser_set_timeout_micros(parser->parser_, 0);
-    ts_parser_set_logger(parser->parser_, logger);
-
-    if (result) {
-      delete input;
-      Local<Value> argv[] = {Tree::NewInstance(result)};
-      auto callback = info[0].As<Function>();
-      Nan::Call(callback, callback->CreationContext()->Global(), 1, argv);
-      return;
+      // Logging is disabled for this method, because we can't call the
+      // logging callback from an async worker.
+      TSLogger logger = ts_parser_logger(parser_);
+      ts_parser_set_timeout_micros(parser_, sync_timeout);
+      ts_parser_set_logger(parser_, TSLogger{0, 0});
+      TSTree *result = ts_parser_parse(parser_, old_tree, input->input());
+      ts_parser_set_timeout_micros(parser_, 0);
+      ts_parser_set_logger(parser_, logger);
+
+      if (result) {
+        delete input;
+        callback({Tree::NewInstance(env, result)});
+        return env.Undefined();
+      }
     }
+
+    is_parsing_async_ = true;
+    auto worker = new ParseWorker(callback, this, input);
+    worker->Queue();
+    return env.Undefined();
   }
 
-  auto callback = new Nan::Callback(info[0].As<Function>());
-  parser->is_parsing_async_ = true;
-  Nan::AsyncQueueWorker(new ParseWorker(
-    callback,
-    parser,
-    input
-  ));
-}
+  Napi::Value ParseTextBufferSync(const CallbackInfo &info) {
+    auto env = info.Env();
+
+    if (is_parsing_async_) {
+      Error::New(env, "Parser is in use").ThrowAsJavaScriptException();
+      return env.Undefined();
+    }
+
+    const TSTree *old_tree = nullptr;
+    if (info.Length() > 1 && info[1].IsObject()) {
+      const Tree *tree = Tree::UnwrapTree(info[1].As<Object>());
+      if (!tree) {
+        TypeError::New(env, "Second argument must be a tree").ThrowAsJavaScriptException();
+        return env.Undefined();
+      }
+      old_tree = ts_tree_copy(tree->tree_);
+    }
+
+    if (!handle_included_ranges(info[2])) return env.Undefined();
 
-void Parser::ParseTextBufferSync(const Nan::FunctionCallbackInfo<Value> &info) {
-  Parser *parser = ObjectWrap::Unwrap<Parser>(info.This());
-  if (parser->is_parsing_async_) {
-    Nan::ThrowError("Parser is in use");
-    return;
+    auto snapshot = TextBufferSnapshotFromJS(info[0]);
+    if (!snapshot) return env.Undefined();
+
+    TextBufferInput input(snapshot);
+    TSTree *result = ts_parser_parse(parser_, old_tree, input.input());
+    return Tree::NewInstance(env, result);
   }
 
-  Local<Object> js_old_tree;
-  const TSTree *old_tree = nullptr;
-  if (info.Length() > 1 && info[1]->IsObject() && Nan::To<Object>(info[1]).ToLocal(&js_old_tree)) {
-    const Tree *tree = Tree::UnwrapTree(js_old_tree);
-    if (!tree) {
-      Nan::ThrowTypeError("Second argument must be a tree");
-      return;
+  Napi::Value GetLogger(const CallbackInfo &info) {
+    auto env = info.Env();
+    TSLogger current_logger = ts_parser_logger(parser_);
+    if (current_logger.payload && current_logger.log == Logger::Log) {
+      Logger *logger = (Logger *)current_logger.payload;
+      return logger->func.Value();
+    } else {
+      return env.Null();
     }
-    old_tree = ts_tree_copy(tree->tree_);
   }
 
-  if (!handle_included_ranges(parser->parser_, info[2])) return;
+  Napi::Value SetLogger(const CallbackInfo &info) {
+    auto env = info.Env();
 
-  auto snapshot = Nan::ObjectWrap::Unwrap<TextBufferSnapshotWrapper>(info[0].As<Object>());
-  TextBufferInput input(snapshot->slices());
-  TSTree *result = ts_parser_parse(parser->parser_, old_tree, input.input());
-  info.GetReturnValue().Set(Tree::NewInstance(result));
-}
+    if (is_parsing_async_) {
+      Error::New(env, "Parser is in use").ThrowAsJavaScriptException();
+      return env.Undefined();
+    }
 
-void Parser::GetLogger(const Nan::FunctionCallbackInfo<Value> &info) {
-  Parser *parser = ObjectWrap::Unwrap<Parser>(info.This());
+    TSLogger current_logger = ts_parser_logger(parser_);
 
-  TSLogger current_logger = ts_parser_logger(parser->parser_);
-  if (current_logger.payload && current_logger.log == Logger::Log) {
-    Logger *logger = (Logger *)current_logger.payload;
-    info.GetReturnValue().Set(Nan::New(logger->func));
-  } else {
-    info.GetReturnValue().Set(Nan::Null());
-  }
-}
+    if (info[0].IsFunction()) {
+      if (current_logger.payload) delete (Logger *)current_logger.payload;
+      ts_parser_set_logger(parser_, Logger::Make(info[0].As<Function>()));
+    } else if (info[0].IsEmpty() || info[0].IsNull() || (info[0].IsBoolean() && !info[0].As<Boolean>())) {
+      if (current_logger.payload) delete (Logger *)current_logger.payload;
+      ts_parser_set_logger(parser_, { 0, 0 });
+    } else {
+      TypeError::New(env, "Logger callback must either be a function or a falsy value").ThrowAsJavaScriptException();
+    }
 
-void Parser::SetLogger(const Nan::FunctionCallbackInfo<Value> &info) {
-  Parser *parser = ObjectWrap::Unwrap<Parser>(info.This());
-  if (parser->is_parsing_async_) {
-    Nan::ThrowError("Parser is in use");
-    return;
+    return info.This();
   }
 
-  TSLogger current_logger = ts_parser_logger(parser->parser_);
-
-  if (info[0]->IsFunction()) {
-    if (current_logger.payload) delete (Logger *)current_logger.payload;
-    ts_parser_set_logger(parser->parser_, Logger::Make(Local<Function>::Cast(info[0])));
-  } else if (!Nan::To<bool>(info[0]).FromMaybe(true)) {
-    if (current_logger.payload) delete (Logger *)current_logger.payload;
-    ts_parser_set_logger(parser->parser_, { 0, 0 });
-  } else {
-    Nan::ThrowTypeError("Logger callback must either be a function or a falsy value");
-    return;
-  }
+  Napi::Value PrintDotGraphs(const CallbackInfo &info) {
+    auto env = info.Env();
 
-  info.GetReturnValue().Set(info.This());
-}
+    if (is_parsing_async_) {
+      Error::New(env, "Parser is in use").ThrowAsJavaScriptException();
+      return env.Undefined();
+    }
 
-void Parser::PrintDotGraphs(const Nan::FunctionCallbackInfo<Value> &info) {
-  Parser *parser = ObjectWrap::Unwrap<Parser>(info.This());
-  if (parser->is_parsing_async_) {
-    Nan::ThrowError("Parser is in use");
-    return;
-  }
+    if (info[0].IsBoolean() && info[0].As<Boolean>()) {
+      ts_parser_print_dot_graphs(parser_, 2);
+    } else {
+      ts_parser_print_dot_graphs(parser_, -1);
+    }
 
-  if (Nan::To<bool>(info[0]).FromMaybe(false)) {
-    ts_parser_print_dot_graphs(parser->parser_, 2);
-  } else {
-    ts_parser_print_dot_graphs(parser->parser_, -1);
+    return info.This();
   }
 
-  info.GetReturnValue().Set(info.This());
+  static Napi::FunctionReference constructor;
+  static Napi::FunctionReference string_slice;
+};
+
+void InitParser(Object &exports) {
+  Parser::Init(exports);
 }
 
+Napi::FunctionReference Parser::constructor;
+Napi::FunctionReference Parser::string_slice;
+
 }  // namespace node_tree_sitter
diff --git a/src/parser.h b/src/parser.h
index e01e1f11..ac838ead 100644
--- a/src/parser.h
+++ b/src/parser.h
@@ -1,35 +1,12 @@
 #ifndef NODE_TREE_SITTER_PARSER_H_
 #define NODE_TREE_SITTER_PARSER_H_
 
-#include <v8.h>
-#include <nan.h>
-#include <node_object_wrap.h>
+#include <napi.h>
 #include <tree_sitter/api.h>
 
 namespace node_tree_sitter {
 
-class Parser : public Nan::ObjectWrap {
- public:
-  static void Init(v8::Local<v8::Object> exports);
-
-  TSParser *parser_;
-  bool is_parsing_async_;
-
- private:
-  explicit Parser();
-  ~Parser();
-
-  static void New(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void SetLanguage(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void GetLogger(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void SetLogger(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void Parse(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void ParseTextBuffer(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void ParseTextBufferSync(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void PrintDotGraphs(const Nan::FunctionCallbackInfo<v8::Value> &);
-
-  static Nan::Persistent<v8::Function> constructor;
-};
+void InitParser(Napi::Object &exports);
 
 }  // namespace node_tree_sitter
 
diff --git a/src/tree.cc b/src/tree.cc
index bac04a2c..f949755a 100644
--- a/src/tree.cc
+++ b/src/tree.cc
@@ -1,6 +1,5 @@
 #include "./tree.h"
 #include <string>
-#include <v8.h>
 #include <nan.h>
 #include "./node.h"
 #include "./logger.h"
@@ -9,85 +8,65 @@
 
 namespace node_tree_sitter {
 
-using namespace v8;
-using node_methods::UnmarshalNodeId;
-
-Nan::Persistent<Function> Tree::constructor;
-Nan::Persistent<FunctionTemplate> Tree::constructor_template;
-
-void Tree::Init(Local<Object> exports) {
-  Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
-  tpl->InstanceTemplate()->SetInternalFieldCount(1);
-  Local<String> class_name = Nan::New("Tree").ToLocalChecked();
-  tpl->SetClassName(class_name);
-
-  FunctionPair methods[] = {
-    {"edit", Edit},
-    {"rootNode", RootNode},
-    {"printDotGraph", PrintDotGraph},
-    {"getChangedRanges", GetChangedRanges},
-    {"getEditedRange", GetEditedRange},
-    {"_cacheNode", CacheNode},
-  };
+using namespace Napi;
 
-  for (size_t i = 0; i < length_of_array(methods); i++) {
-    Nan::SetPrototypeMethod(tpl, methods[i].name, methods[i].callback);
-  }
+FunctionReference Tree::constructor;
 
-  Local<Function> ctor = Nan::GetFunction(tpl).ToLocalChecked();
+void Tree::Init(Object &exports) {
+  Napi::Env env = exports.Env();
 
-  constructor_template.Reset(tpl);
-  constructor.Reset(ctor);
-  Nan::Set(exports, class_name, ctor);
+  Function ctor = DefineClass(env, "Tree", {
+    InstanceMethod("edit", &Tree::Edit, napi_writable),
+    InstanceMethod("rootNode", &Tree::RootNode, napi_configurable),
+    InstanceMethod("printDotGraph", &Tree::PrintDotGraph),
+    InstanceMethod("getChangedRanges", &Tree::GetChangedRanges),
+    InstanceMethod("getEditedRange", &Tree::GetEditedRange),
+    InstanceMethod("_cacheNode", &Tree::CacheNode),
+  });
+
+  constructor.Reset(ctor, 1);
+  exports["Tree"] = ctor;
 }
 
-Tree::Tree(TSTree *tree) : tree_(tree) {}
+Tree::Tree(const Napi::CallbackInfo& info)
+  : Napi::ObjectWrap<Tree>(info),
+    tree_(nullptr) {}
 
 Tree::~Tree() {
-  ts_tree_delete(tree_);
+  if (tree_) ts_tree_delete(tree_);
   for (auto &entry : cached_nodes_) {
     entry.second->tree = nullptr;
   }
 }
 
-Local<Value> Tree::NewInstance(TSTree *tree) {
+Napi::Value Tree::NewInstance(Napi::Env env, TSTree *tree) {
   if (tree) {
-    Local<Object> self;
-    MaybeLocal<Object> maybe_self = Nan::NewInstance(Nan::New(constructor));
-    if (maybe_self.ToLocal(&self)) {
-      (new Tree(tree))->Wrap(self);
-      return self;
-    }
+    Object js_tree = constructor.Value().New({});
+    Tree::Unwrap(js_tree)->tree_ = tree;
+    return js_tree;
   }
-  return Nan::Null();
+  return env.Null();
 }
 
-const Tree *Tree::UnwrapTree(const Local<Value> &value) {
-  if (!value->IsObject()) return nullptr;
-  Local<Object> js_tree = Local<Object>::Cast(value);
-  if (!Nan::New(constructor_template)->HasInstance(js_tree)) return nullptr;
-  return ObjectWrap::Unwrap<Tree>(js_tree);
+const Tree *Tree::UnwrapTree(const Napi::Value &value) {
+  return Tree::Unwrap(value.As<Object>());
 }
 
-void Tree::New(const Nan::FunctionCallbackInfo<Value> &info) {}
-
-#define read_number_from_js(out, value, name)        \
-  maybe_number = Nan::To<uint32_t>(value);           \
-  if (maybe_number.IsNothing()) {                    \
-    Nan::ThrowTypeError(name " must be an integer"); \
-    return;                                          \
-  }                                                  \
-  *(out) = maybe_number.FromJust();
+#define read_number_from_js(out, value, name)                                \
+  if (!value.IsNumber()) {                                                   \
+    TypeError::New(env, name " must be an integer").ThrowAsJavaScriptException(); \
+    return env.Undefined();                                                  \
+  }                                                                          \
+  *(out) = value.As<Number>().Uint32Value();
 
 #define read_byte_count_from_js(out, value, name)   \
   read_number_from_js(out, value, name);            \
   (*out) *= 2
 
-void Tree::Edit(const Nan::FunctionCallbackInfo<Value> &info) {
-  Tree *tree = ObjectWrap::Unwrap<Tree>(info.This());
+Napi::Value Tree::Edit(const CallbackInfo &info) {
+  auto env = info.Env();
 
   TSInputEdit edit;
-  Nan::Maybe<uint32_t> maybe_number = Nan::Nothing<uint32_t>();
   read_number_from_js(&edit.start_point.row, info[0], "startPosition.row");
   read_byte_count_from_js(&edit.start_point.column, info[1], "startPosition.column");
   read_number_from_js(&edit.old_end_point.row, info[2], "oldEndPosition.row");
@@ -98,57 +77,53 @@ void Tree::Edit(const Nan::FunctionCallbackInfo<Value> &info) {
   read_byte_count_from_js(&edit.old_end_byte, info[7], "oldEndIndex");
   read_byte_count_from_js(&edit.new_end_byte, info[8], "newEndIndex");
 
-  ts_tree_edit(tree->tree_, &edit);
+  ts_tree_edit(tree_, &edit);
 
-  for (auto &entry : tree->cached_nodes_) {
-    Local<Object> js_node = Nan::New(entry.second->node);
+  for (auto &entry : cached_nodes_) {
+    Object js_node = entry.second->node.Value();
     TSNode node;
     node.id = entry.first;
     for (unsigned i = 0; i < 4; i++) {
-      Local<Value> node_field;
-      if (Nan::Get(js_node, i + 2).ToLocal(&node_field)) {
-        node.context[i] = Nan::To<uint32_t>(node_field).FromMaybe(0);
+      Napi::Value node_field = js_node[i + 2u];
+      if (node_field.IsNumber()) {
+        node.context[i] = node_field.As<Number>().Uint32Value();
       }
     }
 
     ts_node_edit(&node, &edit);
 
     for (unsigned i = 0; i < 4; i++) {
-      Nan::Set(js_node, i + 2, Nan::New(node.context[i]));
+      js_node[i + 2u] = Number::New(env, node.context[i]);
     }
   }
 
-  info.GetReturnValue().Set(info.This());
+  return info.This();
 }
 
-void Tree::RootNode(const Nan::FunctionCallbackInfo<Value> &info) {
-  Tree *tree = ObjectWrap::Unwrap<Tree>(info.This());
-  node_methods::MarshalNode(info, tree, ts_tree_root_node(tree->tree_));
+Napi::Value Tree::RootNode(const CallbackInfo &info) {
+  return MarshalNode(info.Env(), this, ts_tree_root_node(tree_));
 }
 
-void Tree::GetChangedRanges(const Nan::FunctionCallbackInfo<Value> &info) {
-  const Tree *tree = ObjectWrap::Unwrap<Tree>(info.This());
+Napi::Value Tree::GetChangedRanges(const CallbackInfo &info) {
+  auto env = info.Env();
   const Tree *other_tree = UnwrapTree(info[0]);
-  if (!other_tree) {
-    Nan::ThrowTypeError("Argument must be a tree");
-    return;
-  }
+  if (!other_tree) return env.Undefined();
 
   uint32_t range_count;
-  TSRange *ranges = ts_tree_get_changed_ranges(tree->tree_, other_tree->tree_, &range_count);
+  TSRange *ranges = ts_tree_get_changed_ranges(tree_, other_tree->tree_, &range_count);
 
-  Local<Array> result = Nan::New<Array>();
-  for (size_t i = 0; i < range_count; i++) {
-    Nan::Set(result, i, RangeToJS(ranges[i]));
+  Array result = Array::New(env);
+  for (unsigned i = 0; i < range_count; i++) {
+    result[i] = RangeToJS(env, ranges[i]);
   }
 
-  info.GetReturnValue().Set(result);
+  return result;
 }
 
-void Tree::GetEditedRange(const Nan::FunctionCallbackInfo<Value> &info) {
-  Tree *tree = ObjectWrap::Unwrap<Tree>(info.This());
-  TSNode root = ts_tree_root_node(tree->tree_);
-  if (!ts_node_has_changes(root)) return;
+Napi::Value Tree::GetEditedRange(const CallbackInfo &info) {
+  auto env = info.Env();
+  TSNode root = ts_tree_root_node(tree_);
+  if (!ts_node_has_changes(root)) return env.Undefined();
   TSRange result = {
     ts_node_start_point(root),
     ts_node_end_point(root),
@@ -190,17 +165,15 @@ void Tree::GetEditedRange(const Nan::FunctionCallbackInfo<Value> &info) {
   }
 
   ts_tree_cursor_delete(&cursor);
-  info.GetReturnValue().Set(RangeToJS(result));
+  return RangeToJS(env, result);
 }
 
-void Tree::PrintDotGraph(const Nan::FunctionCallbackInfo<Value> &info) {
-  Tree *tree = ObjectWrap::Unwrap<Tree>(info.This());
-  ts_tree_print_dot_graph(tree->tree_, stderr);
-  info.GetReturnValue().Set(info.This());
+Napi::Value Tree::PrintDotGraph(const CallbackInfo &info) {
+  ts_tree_print_dot_graph(tree_, stderr);
+  return info.This();
 }
 
-static void FinalizeNode(const v8::WeakCallbackInfo<Tree::NodeCacheEntry> &info) {
-  Tree::NodeCacheEntry *cache_entry = info.GetParameter();
+static void FinalizeNode(Env env, Tree::NodeCacheEntry *cache_entry) {
   assert(!cache_entry->node.IsEmpty());
   cache_entry->node.Reset();
   if (cache_entry->tree) {
@@ -210,26 +183,29 @@ static void FinalizeNode(const v8::WeakCallbackInfo<Tree::NodeCacheEntry> &info)
   delete cache_entry;
 }
 
-void Tree::CacheNode(const Nan::FunctionCallbackInfo<Value> &info) {
-  Tree *tree = ObjectWrap::Unwrap<Tree>(info.This());
-  Local<Object> js_node = Local<Object>::Cast(info[0]);
+Napi::Value Tree::CacheNode(const CallbackInfo &info) {
+  auto env = info.Env();
+  Object js_node = info[0].As<Object>();
 
-  Local<Value> js_node_field1, js_node_field2;
-  if (!Nan::Get(js_node, 0).ToLocal(&js_node_field1)) return;
-  if (!Nan::Get(js_node, 1).ToLocal(&js_node_field2)) return;
+  Napi::Value js_node_field1 = js_node[0u];
+  Napi::Value js_node_field2 = js_node[1u];
+  if (!js_node_field1.IsNumber() || !js_node_field2.IsNumber()) {
+    return env.Undefined();
+  }
   uint32_t key_parts[2] = {
-    Nan::To<uint32_t>(js_node_field1).FromMaybe(0),
-    Nan::To<uint32_t>(js_node_field2).FromMaybe(0)
+    js_node_field1.As<Number>().Uint32Value(),
+    js_node_field2.As<Number>().Uint32Value(),
   };
-  const void *key = UnmarshalNodeId(key_parts);
+  const void *key = UnmarshalPointer(key_parts);
 
-  auto cache_entry = new NodeCacheEntry{tree, key, {}};
-  cache_entry->node.Reset(info.GetIsolate(), js_node);
-  cache_entry->node.SetWeak(cache_entry, &FinalizeNode, Nan::WeakCallbackType::kParameter);
+  auto cache_entry = new NodeCacheEntry{this, key, {}};
+  cache_entry->node.Reset(js_node, 0);
+  js_node.AddFinalizer(&FinalizeNode, cache_entry);
 
-  assert(!tree->cached_nodes_.count(key));
+  assert(!cached_nodes_.count(key));
 
-  tree->cached_nodes_[key] = cache_entry;
+  cached_nodes_[key] = cache_entry;
+  return env.Undefined();
 }
 
 }  // namespace node_tree_sitter
diff --git a/src/tree.h b/src/tree.h
index 1d5962e2..98e4c5eb 100644
--- a/src/tree.h
+++ b/src/tree.h
@@ -1,43 +1,40 @@
 #ifndef NODE_TREE_SITTER_TREE_H_
 #define NODE_TREE_SITTER_TREE_H_
 
-#include <v8.h>
-#include <nan.h>
-#include <node_object_wrap.h>
+#include <napi.h>
 #include <unordered_map>
 #include <tree_sitter/api.h>
 
 namespace node_tree_sitter {
 
-class Tree : public Nan::ObjectWrap {
+class Tree : public Napi::ObjectWrap<Tree> {
  public:
-  static void Init(v8::Local<v8::Object> exports);
-  static v8::Local<v8::Value> NewInstance(TSTree *);
-  static const Tree *UnwrapTree(const v8::Local<v8::Value> &);
+  static void Init(Napi::Object &);
+  static Napi::Value NewInstance(Napi::Env, TSTree *);
+  static const Tree *UnwrapTree(const Napi::Value &);
+  Tree(const Napi::CallbackInfo& info);
+  ~Tree();
 
   struct NodeCacheEntry {
     Tree *tree;
     const void *key;
-    v8::Persistent<v8::Object> node;
+    Napi::ObjectReference node;
   };
 
   TSTree *tree_;
   std::unordered_map<const void *, NodeCacheEntry *> cached_nodes_;
 
  private:
-  explicit Tree(TSTree *);
-  ~Tree();
 
-  static void New(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void Edit(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void RootNode(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void PrintDotGraph(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void GetEditedRange(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void GetChangedRanges(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void CacheNode(const Nan::FunctionCallbackInfo<v8::Value> &);
+  Napi::Value New(const Napi::CallbackInfo &);
+  Napi::Value Edit(const Napi::CallbackInfo &);
+  Napi::Value RootNode(const Napi::CallbackInfo &);
+  Napi::Value PrintDotGraph(const Napi::CallbackInfo &);
+  Napi::Value GetEditedRange(const Napi::CallbackInfo &);
+  Napi::Value GetChangedRanges(const Napi::CallbackInfo &);
+  Napi::Value CacheNode(const Napi::CallbackInfo &);
 
-  static Nan::Persistent<v8::Function> constructor;
-  static Nan::Persistent<v8::FunctionTemplate> constructor_template;
+  static Napi::FunctionReference constructor;
 };
 
 }  // namespace node_tree_sitter
diff --git a/src/tree_cursor.cc b/src/tree_cursor.cc
index a1bb741c..c3f5679c 100644
--- a/src/tree_cursor.cc
+++ b/src/tree_cursor.cc
@@ -1,7 +1,6 @@
 #include "./tree_cursor.h"
-#include <nan.h>
 #include <tree_sitter/api.h>
-#include <v8.h>
+#include <napi.h>
 #include "./util.h"
 #include "./conversions.h"
 #include "./node.h"
@@ -9,163 +8,155 @@
 
 namespace node_tree_sitter {
 
-using namespace v8;
-
-Nan::Persistent<Function> TreeCursor::constructor;
-
-void TreeCursor::Init(v8::Local<v8::Object> exports) {
-  Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
-  Local<String> class_name = Nan::New("TreeCursor").ToLocalChecked();
-  tpl->SetClassName(class_name);
-  tpl->InstanceTemplate()->SetInternalFieldCount(1);
-
-  GetterPair getters[] = {
-    {"startIndex", StartIndex},
-    {"endIndex", EndIndex},
-    {"nodeType", NodeType},
-    {"nodeIsNamed", NodeIsNamed},
-    {"currentFieldName", CurrentFieldName},
-  };
-
-  FunctionPair methods[] = {
-    {"startPosition", StartPosition},
-    {"endPosition", EndPosition},
-    {"gotoParent", GotoParent},
-    {"gotoFirstChild", GotoFirstChild},
-    {"gotoFirstChildForIndex", GotoFirstChildForIndex},
-    {"gotoNextSibling", GotoNextSibling},
-    {"currentNode", CurrentNode},
-    {"reset", Reset},
-  };
-
-  for (size_t i = 0; i < length_of_array(getters); i++) {
-    Nan::SetAccessor(
-      tpl->InstanceTemplate(),
-      Nan::New(getters[i].name).ToLocalChecked(),
-      getters[i].callback);
+using namespace Napi;
+
+class TreeCursor : public Napi::ObjectWrap<TreeCursor> {
+ public:
+  static void Init(Napi::Object &exports) {
+    Napi::Env env = exports.Env();
+
+    Function ctor = DefineClass(env, "TreeCursor", {
+      InstanceAccessor("startIndex", &TreeCursor::StartIndex, nullptr),
+      InstanceAccessor("endIndex", &TreeCursor::EndIndex, nullptr),
+      InstanceAccessor("nodeType", &TreeCursor::NodeType, nullptr),
+      InstanceAccessor("nodeIsNamed", &TreeCursor::NodeIsNamed, nullptr),
+      InstanceAccessor("currentFieldName", &TreeCursor::CurrentFieldName, nullptr),
+
+      InstanceMethod("startPosition", &TreeCursor::StartPosition, napi_configurable),
+      InstanceMethod("endPosition", &TreeCursor::EndPosition, napi_configurable),
+      InstanceMethod("gotoParent", &TreeCursor::GotoParent),
+      InstanceMethod("gotoFirstChild", &TreeCursor::GotoFirstChild),
+      InstanceMethod("gotoFirstChildForIndex", &TreeCursor::GotoFirstChildForIndex),
+      InstanceMethod("gotoNextSibling", &TreeCursor::GotoNextSibling),
+      InstanceMethod("currentNode", &TreeCursor::CurrentNode, napi_configurable),
+      InstanceMethod("reset", &TreeCursor::Reset),
+    });
+
+    constructor.Reset(ctor, 1);
+    exports.Set("TreeCursor", ctor);
   }
 
-  for (size_t i = 0; i < length_of_array(methods); i++) {
-    Nan::SetPrototypeMethod(tpl, methods[i].name, methods[i].callback);
-  }
+  TreeCursor(const CallbackInfo &info)
+    : Napi::ObjectWrap<TreeCursor>(info),
+      cursor_({0, 0, {0, 0}})
+      {}
 
-  Local<Function> constructor_local = Nan::GetFunction(tpl).ToLocalChecked();
-  Nan::Set(exports, class_name, constructor_local);
-  constructor.Reset(Nan::Persistent<Function>(constructor_local));
-}
+  ~TreeCursor() { ts_tree_cursor_delete(&cursor_); }
 
-Local<Value> TreeCursor::NewInstance(TSTreeCursor cursor) {
-  Local<Object> self;
-  MaybeLocal<Object> maybe_self = Nan::New(constructor)->NewInstance(Nan::GetCurrentContext());
-  if (maybe_self.ToLocal(&self)) {
-    (new TreeCursor(cursor))->Wrap(self);
-    return self;
-  } else {
-    return Nan::Null();
+  Napi::Value GotoParent(const CallbackInfo &info) {
+    auto env = info.Env();
+    bool result = ts_tree_cursor_goto_parent(&cursor_);
+    return Boolean::New(env, result);
   }
-}
-
-TreeCursor::TreeCursor(TSTreeCursor cursor) : cursor_(cursor) {}
 
-TreeCursor::~TreeCursor() { ts_tree_cursor_delete(&cursor_); }
-
-void TreeCursor::New(const Nan::FunctionCallbackInfo<Value> &info) {
-  info.GetReturnValue().Set(Nan::Null());
-}
+  Napi::Value GotoFirstChild(const CallbackInfo &info) {
+    auto env = info.Env();
+    bool result = ts_tree_cursor_goto_first_child(&cursor_);
+    return Boolean::New(env, result);
+  }
 
-void TreeCursor::GotoParent(const Nan::FunctionCallbackInfo<Value> &info) {
-  TreeCursor *cursor = Nan::ObjectWrap::Unwrap<TreeCursor>(info.This());
-  bool result = ts_tree_cursor_goto_parent(&cursor->cursor_);
-  info.GetReturnValue().Set(Nan::New(result));
-}
+  Napi::Value GotoFirstChildForIndex(const CallbackInfo &info) {
+    auto env = info.Env();
+    auto js_index = info[0].As<Number>();
+    if (!js_index.IsNumber()) {
+      TypeError::New(env, "Argument must be an integer").ThrowAsJavaScriptException();
+      return env.Undefined();
+    }
+
+    uint32_t goal_byte = js_index.Uint32Value() * 2;
+    int64_t child_index = ts_tree_cursor_goto_first_child_for_byte(&cursor_, goal_byte);
+    if (child_index < 0) {
+      return env.Null();
+    } else {
+      return Number::New(env, child_index);
+    }
+  }
 
-void TreeCursor::GotoFirstChild(const Nan::FunctionCallbackInfo<Value> &info) {
-  TreeCursor *cursor = Nan::ObjectWrap::Unwrap<TreeCursor>(info.This());
-  bool result = ts_tree_cursor_goto_first_child(&cursor->cursor_);
-  info.GetReturnValue().Set(Nan::New(result));
-}
+  Napi::Value GotoNextSibling(const CallbackInfo &info) {
+    auto env = info.Env();
+    bool result = ts_tree_cursor_goto_next_sibling(&cursor_);
+    return Boolean::New(env, result);
+  }
 
-void TreeCursor::GotoFirstChildForIndex(const Nan::FunctionCallbackInfo<Value> &info) {
-  TreeCursor *cursor = Nan::ObjectWrap::Unwrap<TreeCursor>(info.This());
-  auto maybe_index = Nan::To<uint32_t>(info[0]);
-  if (maybe_index.IsNothing()) {
-    Nan::ThrowTypeError("Argument must be an integer");
-    return;
+  Napi::Value StartPosition(const CallbackInfo &info) {
+    auto env = info.Env();
+    TSNode node = ts_tree_cursor_current_node(&cursor_);
+    TransferPoint(ts_node_start_point(node));
+    return env.Undefined();
   }
-  uint32_t goal_byte = maybe_index.FromJust() * 2;
-  int64_t child_index = ts_tree_cursor_goto_first_child_for_byte(&cursor->cursor_, goal_byte);
-  if (child_index < 0) {
-    info.GetReturnValue().Set(Nan::Null());
-  } else {
-    info.GetReturnValue().Set(Nan::New(static_cast<uint32_t>(child_index)));
+
+  Napi::Value EndPosition(const CallbackInfo &info) {
+    auto env = info.Env();
+    TSNode node = ts_tree_cursor_current_node(&cursor_);
+    TransferPoint(ts_node_end_point(node));
+    return env.Undefined();
   }
-}
 
-void TreeCursor::GotoNextSibling(const Nan::FunctionCallbackInfo<Value> &info) {
-  TreeCursor *cursor = Nan::ObjectWrap::Unwrap<TreeCursor>(info.This());
-  bool result = ts_tree_cursor_goto_next_sibling(&cursor->cursor_);
-  info.GetReturnValue().Set(Nan::New(result));
-}
+  Napi::Value CurrentNode(const CallbackInfo &info) {
+    auto env = info.Env();
+    Napi::Value js_tree = info.This().As<Object>()["tree"];
+    const Tree *tree = Tree::UnwrapTree(js_tree.As<Object>());
+    TSNode node = ts_tree_cursor_current_node(&cursor_);
+    return MarshalNode(env, tree, node);
+  }
 
-void TreeCursor::StartPosition(const Nan::FunctionCallbackInfo<Value> &info) {
-  TreeCursor *cursor = Nan::ObjectWrap::Unwrap<TreeCursor>(info.This());
-  TSNode node = ts_tree_cursor_current_node(&cursor->cursor_);
-  TransferPoint(ts_node_start_point(node));
-}
+  Napi::Value Reset(const CallbackInfo &info) {
+    auto env = info.Env();
+    Napi::Value js_tree = info.This().As<Object>()["tree"];
+    const Tree *tree = Tree::UnwrapTree(js_tree.As<Object>());
+    TSNode node = UnmarshalNode(env, tree);
+    ts_tree_cursor_reset(&cursor_, node);
+    return env.Undefined();
+  }
 
-void TreeCursor::EndPosition(const Nan::FunctionCallbackInfo<Value> &info) {
-  TreeCursor *cursor = Nan::ObjectWrap::Unwrap<TreeCursor>(info.This());
-  TSNode node = ts_tree_cursor_current_node(&cursor->cursor_);
-  TransferPoint(ts_node_end_point(node));
-}
+  Napi::Value NodeType(const CallbackInfo &info) {
+    auto env = info.Env();
+    TSNode node = ts_tree_cursor_current_node(&cursor_);
+    return String::New(env, ts_node_type(node));
+  }
 
-void TreeCursor::CurrentNode(const Nan::FunctionCallbackInfo<Value> &info) {
-  TreeCursor *cursor = Nan::ObjectWrap::Unwrap<TreeCursor>(info.This());
-  Local<String> key = Nan::New<String>("tree").ToLocalChecked();
-  const Tree *tree = Tree::UnwrapTree(Nan::Get(info.This(), key).ToLocalChecked());
-  TSNode node = ts_tree_cursor_current_node(&cursor->cursor_);
-  node_methods::MarshalNode(info, tree, node);
-}
+  Napi::Value NodeIsNamed(const CallbackInfo &info) {
+    auto env = info.Env();
+    TSNode node = ts_tree_cursor_current_node(&cursor_);
+    return Boolean::New(env, ts_node_is_named(node));
+  }
 
-void TreeCursor::Reset(const Nan::FunctionCallbackInfo<Value> &info) {
-  TreeCursor *cursor = Nan::ObjectWrap::Unwrap<TreeCursor>(info.This());
-  Local<String> key = Nan::New<String>("tree").ToLocalChecked();
-  const Tree *tree = Tree::UnwrapTree(Nan::Get(info.This(), key).ToLocalChecked());
-  TSNode node = node_methods::UnmarshalNode(tree);
-  ts_tree_cursor_reset(&cursor->cursor_, node);
-}
+  Napi::Value CurrentFieldName(const CallbackInfo &info) {
+    auto env = info.Env();
+    const char *field_name = ts_tree_cursor_current_field_name(&cursor_);
+    if (field_name) {
+      return String::New(env, field_name);
+    } else {
+      return env.Undefined();
+    }
+  }
 
-void TreeCursor::NodeType(v8::Local<v8::String> prop, const Nan::PropertyCallbackInfo<v8::Value> &info) {
-  TreeCursor *cursor = Nan::ObjectWrap::Unwrap<TreeCursor>(info.This());
-  TSNode node = ts_tree_cursor_current_node(&cursor->cursor_);
-  info.GetReturnValue().Set(Nan::New(ts_node_type(node)).ToLocalChecked());
-}
+  Napi::Value StartIndex(const CallbackInfo &info) {
+    auto env = info.Env();
+    TSNode node = ts_tree_cursor_current_node(&cursor_);
+    return ByteCountToJS(env, ts_node_start_byte(node));
+  }
 
-void TreeCursor::NodeIsNamed(v8::Local<v8::String> prop, const Nan::PropertyCallbackInfo<v8::Value> &info) {
-  TreeCursor *cursor = Nan::ObjectWrap::Unwrap<TreeCursor>(info.This());
-  TSNode node = ts_tree_cursor_current_node(&cursor->cursor_);
+  Napi::Value EndIndex(const CallbackInfo &info) {
+    auto env = info.Env();
+    TSNode node = ts_tree_cursor_current_node(&cursor_);
+    return ByteCountToJS(env, ts_node_end_byte(node));
+  }
 
-  info.GetReturnValue().Set(Nan::New(ts_node_is_named(node)));
-}
+  TSTreeCursor cursor_;
+  static Napi::FunctionReference constructor;
+};
 
-void TreeCursor::CurrentFieldName(v8::Local<v8::String> prop, const Nan::PropertyCallbackInfo<v8::Value> &info) {
-  TreeCursor *cursor = Nan::ObjectWrap::Unwrap<TreeCursor>(info.This());
-  const char *field_name = ts_tree_cursor_current_field_name(&cursor->cursor_);
-  if (field_name) {
-    info.GetReturnValue().Set(Nan::New(field_name).ToLocalChecked());
-  }
+void InitTreeCursor(Object &exports) {
+  TreeCursor::Init(exports);
 }
 
-void TreeCursor::StartIndex(v8::Local<v8::String> prop, const Nan::PropertyCallbackInfo<v8::Value> &info) {
-  TreeCursor *cursor = Nan::ObjectWrap::Unwrap<TreeCursor>(info.This());
-  TSNode node = ts_tree_cursor_current_node(&cursor->cursor_);
-  info.GetReturnValue().Set(ByteCountToJS(ts_node_start_byte(node)));
+Napi::Value NewTreeCursor(TSTreeCursor cursor) {
+  Object js_cursor = TreeCursor::constructor.Value().New({});
+  TreeCursor::Unwrap(js_cursor)->cursor_ = cursor;
+  return js_cursor;
 }
 
-void TreeCursor::EndIndex(v8::Local<v8::String> prop, const Nan::PropertyCallbackInfo<v8::Value> &info) {
-  TreeCursor *cursor = Nan::ObjectWrap::Unwrap<TreeCursor>(info.This());
-  TSNode node = ts_tree_cursor_current_node(&cursor->cursor_);
-  info.GetReturnValue().Set(ByteCountToJS(ts_node_end_byte(node)));
-}
+FunctionReference TreeCursor::constructor;
 
-}
+}  // namespace node_tree_sitter
diff --git a/src/tree_cursor.h b/src/tree_cursor.h
index cd54e4bb..1bff0660 100644
--- a/src/tree_cursor.h
+++ b/src/tree_cursor.h
@@ -1,42 +1,13 @@
 #ifndef NODE_TREE_SITTER_TREE_CURSOR_H_
 #define NODE_TREE_SITTER_TREE_CURSOR_H_
 
-#include <v8.h>
-#include <nan.h>
-#include <node_object_wrap.h>
+#include <napi.h>
 #include <tree_sitter/api.h>
 
 namespace node_tree_sitter {
 
-class TreeCursor : public Nan::ObjectWrap {
- public:
-  static void Init(v8::Local<v8::Object> exports);
-  static v8::Local<v8::Value> NewInstance(TSTreeCursor);
-
- private:
-  explicit TreeCursor(TSTreeCursor);
-  ~TreeCursor();
-
-  static void New(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void GotoParent(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void GotoFirstChild(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void GotoFirstChildForIndex(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void GotoNextSibling(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void StartPosition(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void EndPosition(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void CurrentNode(const Nan::FunctionCallbackInfo<v8::Value> &);
-  static void Reset(const Nan::FunctionCallbackInfo<v8::Value> &);
-
-  static void NodeType(v8::Local<v8::String>, const Nan::PropertyCallbackInfo<v8::Value> &);
-  static void NodeIsNamed(v8::Local<v8::String>, const Nan::PropertyCallbackInfo<v8::Value> &);
-  static void CurrentFieldName(v8::Local<v8::String>, const Nan::PropertyCallbackInfo<v8::Value> &);
-  static void StartIndex(v8::Local<v8::String>, const Nan::PropertyCallbackInfo<v8::Value> &);
-  static void EndIndex(v8::Local<v8::String>, const Nan::PropertyCallbackInfo<v8::Value> &);
-
-  TSTreeCursor cursor_;
-  static Nan::Persistent<v8::Function> constructor;
-  static Nan::Persistent<v8::FunctionTemplate> constructor_template;
-};
+void InitTreeCursor(Napi::Object &);
+Napi::Value NewTreeCursor(TSTreeCursor);
 
 }  // namespace node_tree_sitter
 
diff --git a/src/util.h b/src/util.h
index 3e2ebbba..6c1ebd4d 100644
--- a/src/util.h
+++ b/src/util.h
@@ -1,17 +1,55 @@
-#include <nan.h>
+#ifndef NODE_TREE_SITTER_UTIL_H_
+#define NODE_TREE_SITTER_UTIL_H_
+
+#include <napi.h>
+#include <v8.h>
 
 namespace node_tree_sitter {
 
-#define length_of_array(a) (sizeof(a) / sizeof(a[0]))
+static inline const void *UnmarshalPointer(const uint32_t *buffer) {
+  const void *result;
+  memcpy(&result, buffer, sizeof(result));
+  return result;
+}
+
+static inline void MarshalPointer(const void *id, uint32_t *buffer) {
+  memset(buffer, 0, sizeof(uint64_t));
+  memcpy(buffer, &id, sizeof(id));
+}
+
+//=========================================================================
+// This library must be able to load parsers that were generated
+// using older versions of Tree-sitter, which did not use `napi`.
+// So we need to use the V8 APIs directly here.
+//
+// The following assertion and function were taken from Node's `napi` implementation:
+// github.com/nodejs/node/blob/53ca0b9ae145c430842bf78e553e3b6cbd2823aa/src/js_native_api_v8.h
 
-struct GetterPair {
-  const char *name;
-  Nan::GetterCallback callback;
-};
+static_assert(
+  sizeof(v8::Local<v8::Value>) == sizeof(napi_value),
+  "Cannot convert between v8::Local<v8::Value> and napi_value"
+);
 
-struct FunctionPair {
-  const char *name;
-  Nan::FunctionCallback callback;
-};
+static inline v8::Local<v8::Value> V8LocalValueFromJsValue(napi_value v) {
+  v8::Local<v8::Value> local;
+  memcpy(static_cast<void*>(&local), &v, sizeof(v));
+  return local;
+}
+
+//=========================================================================
+
+static inline void *GetInternalFieldPointer(Napi::Value value) {
+  if (value.IsObject()) {
+    v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(
+      V8LocalValueFromJsValue(value)
+    );
+    if (object->InternalFieldCount() == 1) {
+      return object->GetAlignedPointerFromInternalField(0);
+    }
+  }
+  return nullptr;
+}
 
 }  // namespace node_tree_sitter
+
+#endif  // NODE_TREE_SITTER_UTIL_H_
diff --git a/test/parser_test.js b/test/parser_test.js
index a1f6d179..b45f1dfa 100644
--- a/test/parser_test.js
+++ b/test/parser_test.js
@@ -178,7 +178,9 @@ describe("Parser", () => {
       const sourceCode = "[" + "0,".repeat(elementCount) + "]";
       const buffer = new TextBuffer(sourceCode)
 
-      const tree = await parser.parseTextBuffer(buffer);
+      const promise = parser.parseTextBuffer(buffer);
+      assert.equal(promise.constructor, Promise);
+      const tree = await promise;
       const arrayNode = tree.rootNode.firstChild.firstChild;
       assert.equal(arrayNode.type, "array");
       assert.equal(arrayNode.namedChildCount, elementCount);
diff --git a/test/tree_test.js b/test/tree_test.js
index fe4729ad..660c4d3b 100644
--- a/test/tree_test.js
+++ b/test/tree_test.js
@@ -147,7 +147,7 @@ describe("Tree", () => {
 
       assert.throws(() => {
         tree1.getChangedRanges({});
-      }, /Argument must be a tree/);
+      }, /Invalid argument/);
     })
   });
 
diff --git a/vendor/tree-sitter b/vendor/tree-sitter
index 80008b0b..3214de55 160000
--- a/vendor/tree-sitter
+++ b/vendor/tree-sitter
@@ -1 +1 @@
-Subproject commit 80008b0bccbddffc8e68f66a5f173ef71fd125e3
+Subproject commit 3214de55f06d292babfdc932d2ffbb66c7d16a33