Writing Rust bindings for Node.js with Neon
Tags: rust, node
Published: 2023-09-21 13:23:27

**Written by [Oduah Chigozie](https://blog.logrocket.com/author/oduahchigozie/)✏️**

While Node.js is a powerful JavaScript runtime environment, it has its limitations — especially in terms of performance and efficiency. Tools like Neon can enhance those aspects of Node.js applications, leveraging the power of Rust to provide a more streamlined experience. 

Similar to WebAssembly (Wasm), Neon allows you to use Rust in your project. However, while Wasm [converts your Rust code to Wasm](https://blog.logrocket.com/getting-started-with-webassembly-and-rust/), Neon compiles your Rust code to native code. This means the native code Neon produces runs faster than WebAssembly. 

Unlike WebAssembly, Neon allows you to use almost all — if not all — of Rust's features. This enables you to bring Rust's benefits into your Node.js project, such as its speed, efficiency, ecosystem, and more. 

In this article, we’ll look into what Neon is and how you can use it in your application. Jump ahead:

*   [What is Neon?](#what-neon)
*   [How to create a Neon project](#how-create-neon-project)
*   [Breaking down Neon's project structure](#brekaing-down-neon-project-structure)
*   [Examples of what you can do with Neon](#examples-what-do-neon)

To follow along with the practical examples demonstrated in this article, you’ll need Node.js and Rust installed. 

You can check out the code samples below in [this GitHub repository](https://github.com/GhoulKingR/rust-bindings-with-neon).

## What is Neon? <a name="what-neon">

Neon allows developers to create native bindings for Node.js applications with Rust. Native bindings are libraries compiled to execute directly on your system’s hardware. You can also call them native libraries. 

Native bindings operate when a piece of code interacts with them. They are [similar to Node modules](https://blog.logrocket.com/publishing-node-modules-typescript-es-modules/), but compile to native code. 

You can use native libraries to speed up some necessary project processes. Any code interacting with a native library will experience the performance benefits of that library running directly on the system.

## How to create a Neon project <a name="how-create-neon-project">

Creating a Neon project is simple, but as previously mentioned, you need to have Node.js and Rust installed on your system. Neon depends heavily on these two programming languages. 

If you have everything ready, open your terminal and type in this command:

```bash
npm init neon my-project
```

Your terminal should be in the folder where you want your project because this command creates a new folder with your project name — the last string of text without whitespace at the end of the command — as its name. In this case, `my-project` would be the project name. 

The project folder will have this structure:

```plaintext
.
├── Cargo.toml
├── README.md
├── package.json
└── src
    └── lib.rs
```

If you’re interested in optimizing a Node project’s structure, check out our guide to [best practices for Node.js project architecture](https://blog.logrocket.com/node-js-project-architecture-best-practices/). Otherwise, let’s take a closer look at the unique structure of our Neon project.

## Neon project structure <a name="brekaing-down-neon-project-structure">

If you look at the project structure, you’ll notice that the project seems like a combination of Node.js and Rust library projects. This is how all Neon projects are structured. 

You’ll write all the Rust bindings in the Rust `src/lib.rs` library file, while your JavaScript files can live anywhere in the project folder. 

Let’s look at the Rust library file in more detail.

### The Rust library file: `lib.rs`

The Rust library file contains the following code:

```rust
use neon::prelude::*;

fn hello(mut cx: FunctionContext) -> JsResult<JsString> {
   Ok(cx.string("hello node"))
}

#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
   cx.export_function("hello", hello)?;
   Ok(())
}
```

This code has three components:

*   Line one imports all the data types and structures used by the rest of the code
*   Lines three through five define a function called `hello` that Neon can export into the JavaScript environment
*   Lines seven through eleven define the primary or `main` function allowing Neon to export all your functions into the JavaScript environment

Now let’s go into more detail about the `hello` and `main` functions. 

Let’s start with the `hello` function’s argument, `cx`. `cx` is a reference to the JavaScript context through a function. With this argument, you can make function-like interactions and create and initialize values accessible from the JavaScript environment. 

In this function, `cx` creates a string value of `"hello node"` with the type `JsString` in the Rust environment. 

Now, look at the `hello` function’s return type — `JsResult<JsString>`. This indicates Neon expects the function to have a `JsResult` type when you’re exporting it, so you must wrap the value you want to return in a `JsResult` type. 

The `hello` function has its return value wrapped in `Ok`, a variant of Rust’s built-in `Result` type. `JsResult` implements the `Result` type so that the function can accept `Ok` just fine. 

Next, let’s look at the `main` function. This is the function that Neon needs to work with to know which functions to export to JavaScript. 

The `main` function can have another name, but you need to mark that function with the `#[neon::main]` attribute so Neon knows what role the function should play. 

Like in the `hello` function, `cx` is a reference to the JavaScript context. But in the `main` function, `cx` allows modular-based interactions and exports `hello` to the JavaScript environment. You can also create values and export those values using this `cx`. 

Finally, let’s look at the `main` function’s return type: `NeonResult<()>`. As our primary Neon function, the `main` function must return a `NeonResult` type. `NeonResult` is an extended version of Rust’s built-in `Result` type, which fits better into the function’s role.

### Interacting with the Rust library in JavaScript

Now, let’s interact with the Rust library using JavaScript. Before you do that, you first need to install the JavaScript dependencies with `npm i` and compile the Rust library with `npm run build`. 

Once you’ve done so, you can test it with Node’s interactive shell:

```javascript
% node  
Welcome to Node.js v18.16.0.
Type ".help" for more information.
> const mod = require(".")
undefined
> mod.hello()
'hello node'
>
```

You can also try it in a normal JavaScript file. Create an `index.js` file, copy the code below into it, and run `node index.js`:

```javascript
const mod = require(".");

console.log(mod.hello());
```

## Examples of what you can do with Neon <a name="examples-what-do-neon">

In this section, I’ll give a few examples demonstrating what you can do with Neon. I’ll give you the Rust function, the JavaScript interaction, and the outputs.

### Creating objects

Having the ability to create objects enables you to build much more complex data structures with your Rust function. This, in turn, increases the scope of what you can do with Rust in your Node.js project. 

This example shows how you can create an empty JavaScript object and store string and number data in its fields. Here’s the function in Rust:

```rust
fn get_user(mut cx: FunctionContext) -> JsResult<JsObject> {
   / Create an empty object
    let obj = cx.empty_object();

    // Create values to store in the object
    let name = cx.string("Chigozie");
    let age = cx.number(19);

    // Store these values in the object
    obj.set(&mut cx, "name", name)?;
    obj.set(&mut cx, "age", age)?;

   Ok(obj)
}

// `main` function
cx.export_function("getUser", get_user)?;
```

To interact with this Rust function from our Node.js environment, we can use the following JavaScript code:

```javascript
const mod = require(".");
console.log(mod.getUser());
```

Then, when executed, the output should display the following:

```javascript
% node index.js
{ name: 'Chigozie', age: 19 }
```

### Reading and writing files

Reading and writing files is one of Rust's standout features, thanks to its emphasis on safety, performance, and expressiveness. 

JavaScript is great for web and lightweight applications. However, if you're building a data-intensive application that requires high performance, Rust’s speed and efficiency can make it more suitable than JavaScript. 

The following is an example of file I/O in Rust:

```rust
use std::fs::File;
use std::io::prelude::*;

fn write_file(mut cx: FunctionContext) -> JsResult<JsBoolean> {
   if let Ok(mut file) = File::create("foo.txt") {
       if let Ok(()) = file.write_all(b"Hello, world!") {
           Ok(cx.boolean(true))
       } else {
           Ok(cx.boolean(false))
       }
   } else {
       Ok(cx.boolean(false))
   }
}

fn read_file(mut cx: FunctionContext) -> JsResult<JsString> {
   match File::open("foo.txt") {
       Ok(mut file) => {
           let mut contents = String::new();
           if let Ok(_) = file.read_to_string(&mut contents) {
               Ok(cx.string(contents))
           } else {
               Ok(cx.string(""))
           }
       }
       Err(_) => Ok(cx.string("")),
   }
}

// `main` function
cx.export_function("readFile", read_file)?;
cx.export_function("writeFile", write_file)?;
```

Here’s the JavaScript interaction you’d use to bridge the gap between Rust and Node.js to use the above functions in your application:

```javascript
const mod = require(".");

if (mod.writeFile()) {
 console.log(mod.readFile());
} else {
 console.log("couldn't write file");
}
```

After running the script, you should see this output:

```javascript
% node index.js
Hello, world!
```

### Accessing function arguments

Sometimes, you need to pass arguments to your Rust function, and you need your function to handle arguments that it receives from the JavaScript environment. 

This example shows how you can make your Rust function handle arguments from JavaScript. Let’s start with the Rust code:

```rust
fn add(mut cx: FunctionContext) -> JsResult<JsNumber> {
   let num1 = cx
       .argument::<JsNumber>(0)? // Access the first argument
       .value(&mut cx);
   let num2 = cx
       .argument::<JsNumber>(1)? // Access the second argument
       .value(&mut cx);

   Ok(cx.number(num1 + num2))
}

// `main` function
cx.export_function("add", add)?;
```

Here’s how you’d invoke the Rust function using JavaScript:

```javascript
console.log(mod.add(1, 3));
```

Finally, you should get this output:

```javascript
% node index.js
4
```

## Conclusion

Neon is a powerful tool that dramatically improves Node.js projects with the power and safety of Rust. Not only that, but it also allows you to use all of Rust’s features. I hope this guide has helped you a lot with understanding it. You can [check out the GitHub repo for this tutorial](https://github.com/GhoulKingR/rust-bindings-with-neon) to see the code examples we used. If you have any questions about using Neon as a bridge between Node.js and Rust, feel free to comment them below. Thanks for reading!

---

## 200’s only  Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, [try LogRocket](https://lp.logrocket.com/blg/node-signup).

[![LogRocket Signup](https://blog.logrocket.com/wp-content/uploads/2019/12/network-request-filter-2-1.png)](https://lp.logrocket.com/blg/node-signup)

[LogRocket](https://lp.logrocket.com/blg/node-signup) is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. [Start monitoring for free](https://lp.logrocket.com/blg/node-signup).