Getting Started with NDNts in Node.js

This article shows how to get started with NDNts, Named Data Networking (NDN) libraries for the modern web. In particular, it demonstrates how to write a producer and a consumer application in Node.js using JavaScript programming language, and transmit a few Interest and Data packets via NFD forwarder on the local machine.

Code samples in this article were last updated on 2022-03-25 to reflect latest changes.

Prepare the System

This guide is written for Ubuntu 20.04 operating system. If you have a Windows PC, you can enable Windows Subsystem for Linux and install Ubuntu 20.04 from the Microsoft Store. If you have a Macbook or a Linux machine other than Ubuntu 20.04, you can install Vagrant, and create a virtual machine from bento/ubuntu-20.04 template. All steps below should be executed inside Ubuntu 20.04 environment.

To use NDNts, you must have Node.js. As of this writing, NDNts works best with Node.js 18.x, and you should install that version. The easiest way to install Node.js is through Node Version Manager (nvm). To install nvm and then install Node.js, type the following commands in Ubuntu 20.04 terminal:

$ wget -qO- | bash

$ nvm install 18 --latest-npm
Now using node v18.0.0 (npm v8.7.0)

You also need the NDN Forwarding Daemon (NFD) and ndnpeek program. They can be installed with the following commands:

$ echo "deb [arch=amd64 trusted=yes] focal main" \
    | sudo tee /etc/apt/sources.list.d/nfd-nightly.list
$ sudo apt install nfd ndnpeek

Now create a directory for our new project, and then open a code editor such as Visual Studio Code at this directory. If you have installed Ubuntu 20.04 from Microsoft Store, you can type explorer.exe . at Ubuntu 20.04 terminal to open Windows Explorer, and then right click to select Open with Code. If you have installed Ubuntu 20.04 as a Vagrant virtual machine, you can create the project directory under /vagrant, and the directory should show up alongside where you placed the Vagrantfile.

Install NDNts

NDNts is a group of Node.js packages. Although they have been published to NPM registry, NPM releases happen rather infrequently. Thus, it's best to install nightly builds to obtain the latest features.

To install NDNts nightly builds, create a file package.json in the project directory, and then paste the following content:

  "private": true,
  "packageManager": "pnpm@6.32.9",
  "dependencies": {
    "@ndn/cli-common": "",
    "@ndn/endpoint": "",
    "@ndn/packet": ""

Then, type corepack pnpm install at the terminal. A minute later, all packages and their dependencies are installed automatically.

The Producer

Create a file producer.mjs and paste the following content:

import { openUplinks } from "@ndn/cli-common";
import { Endpoint } from "@ndn/endpoint";
import { Data } from "@ndn/packet";

// openUplinks() creates a connection to the "uplink", in this case the local NFD forwarder.
// It returns a Promise, so remember to await it.
await openUplinks();

// Endpoint is a centerpiece of NDNts. You can use it to create a producer or a consumer.
// It is similar to, but more powerful than, "face" in other NDN libraries.
// You'll soon see some of its powers.
const endpoint = new Endpoint();

// endpoint.produce() creates a producer.
// The first argument is the name prefix.
// The second argument is a callback function that is invoked for each incoming Interest;
// this must be an async function that returns a Promise.
endpoint.produce("/add", async (interest) => {
  console.log(`Got Interest ${}`);
  // This producer is a calculator. It expects Interest name to have three
  // components: "add", x, and y. If it's not, reject the Interest.
  if ( !== 3) {
    console.log("Wrong name length.");

  // Extract x and y numbers, then compute the sum.
  const x = Number.parseInt(, 10);
  const y = Number.parseInt(, 10);
  const sum = x + y;
  console.log(`${x} + ${y} = ${sum}`);

  // Make a Data packet that has the same name as the Interest.
  const data = new Data(;
  data.freshnessPeriod = 1000;
  data.content = new TextEncoder().encode(`${sum}\n`);

  // Sending the Data is as simple as returning it from the function.
  return data;
// options

console.log("Producer running, press CTRL+C to stop");

Run this script:

$ node ./producer.mjs
Producer running, press CTRL+C to stop

Open another terminal window, and run ndnpeek (a simple consumer):

$ ndnpeek -fp /add/12345678/87654321

It prints the Data payload, which is the sum of the two numbers passed into the name.

The Consumer

Create a file consumer.mjs and paste the following content:

import { closeUplinks, openUplinks } from "@ndn/cli-common";
import { Endpoint } from "@ndn/endpoint";
import { Interest } from "@ndn/packet";

// Connect to NFD.
await openUplinks();

const endpoint = new Endpoint();

// Parse x and y from command line arguments.
const [x, y] = process.argv.slice(2);

// Make an Interest packet, asking the producer to compute x+y.
const interest = new Interest(`/add/${x}/${y}`);
interest.mustBeFresh = true;

try {
  // Send the Interest, and wait for Data to come back.
  const data = await endpoint.consume(interest);

  // Print the Data payload.
} catch (err) {
  // In case of Data retrieval failure, show what went wrong.

// Disconnect from NFD, so that Node.js can exit normally.

Keep the producer running in the first terminal window. In the second terminal, run this consumer script, sending an Interest for 1 + 1:

$ node ./consumer.mjs 1 1

This is how you compute 1 + 1 using NDN.

Consumer Retransmissions

Now let me show you one of the powers in Endpoint: consumer retransmissions. As you may already know, NDN's communication model is receiver-driver: the network provides a best-effort service, while the consumer is ultimately responsible for retransmitting its Interests if the Data does not arrive. In other libraries, your application may have to deal with retransmissions yourself, while in NDNts, retransmissions can be enabled with a single flag.

Go to the first terminal window, press CTRL+C to stop the producer. Then, run the consumer again, asking for 1 + 2:

$ node ./consumer.mjs 1 2
Error: Interest rejected: expire @consume(/8=add/8=1/8=2)

You got an error: the Interest has been rejected, because the producer isn't running.

To enable retransmissions, change endpoint.consume line of the consumer script as:

const data = await endpoint.consume(interest, { retx: 100 });

This allows NDNts to retransmit the Interest for up to 100 times, if the initial Interest does not receive a reply.

After this modification, run the consumer. This time, add NDNTS_PKTTRACE=1 environment variable, so that you can see the packet exchanges.

$ NDNTS_PKTTRACE=1 node ./consumer.mjs 1 2
consume(/8=add/8=1/8=2) >I /8=add/8=1/8=2[F]
Unix(/run/nfd.sock) <I /8=add/8=1/8=2[F]
consume(/8=add/8=1/8=2) >I /8=add/8=1/8=2[F]
Unix(/run/nfd.sock) <I /8=add/8=1/8=2[F]
consume(/8=add/8=1/8=2) >I /8=add/8=1/8=2[F]
Unix(/run/nfd.sock) <I /8=add/8=1/8=2[F]
consume(/8=add/8=1/8=2) >I /8=add/8=1/8=2[F]
Unix(/run/nfd.sock) <I /8=add/8=1/8=2[F]

As you can see from the log messages, the consumer does not fail right away, but keeps retransmitting the Interest. While the consumer is still running, go to the first terminal window and start the producer again. After that, the consumer should receive the Data and exit.

$ NDNTS_PKTTRACE=1 node ./consumer.mjs 1 2
+Face Unix(/run/nfd.sock)
Unix(/run/nfd.sock) +Prefix /
+Face consume(/8=add/8=1/8=2)
consume(/8=add/8=1/8=2) >I /8=add/8=1/8=2[F]
Unix(/run/nfd.sock) <I /8=add/8=1/8=2[F]
consume(/8=add/8=1/8=2) >I /8=add/8=1/8=2[F]
Unix(/run/nfd.sock) <I /8=add/8=1/8=2[F]
Unix(/run/nfd.sock) >D /8=add/8=1/8=2
consume(/8=add/8=1/8=2) <D /8=add/8=1/8=2
-Face Unix(/run/nfd.sock)
-Face consume(/8=add/8=1/8=2)

A word on /8=add/8=1/8=2: this is the same name as /add/1/2, written in canonical format. NDN Packet Format v0.3 introduces typed name component, which allows a name component to have a type number between 1 and 65535. When a name is written in its canonical format, the type number appears in each component. NDNts by default prints names in the canonical format, although the AltUriFormat class can be used to print in the alternate format such as /add/1/2. The Name constructor, however, only accepts canonical format, with a special case that allows omitting type number "8".

Producer Parallelism

Another powerful feature of Endpoint, on the producer side, is controlling parallelism of the producer callback function. To demonstrate this effect, we'd make the "x + y" function a bit slower. In the producer script, insert this line just above return data;:

// Simulate 100 milliseconds processing delay.
await new Promise((r) => setTimeout(r, 100));

We also need a consumer that sends many requests at once, bulk.js:

import { closeUplinks, openUplinks } from "@ndn/cli-common";
import { Endpoint } from "@ndn/endpoint";
import { Interest } from "@ndn/packet";

await openUplinks();
const endpoint = new Endpoint();

const interests = [];
for (let i = 0; i < 100; ++i) {
  const x = Math.trunc(Math.random() * 1000000);
  const y = Math.trunc(Math.random() * 1000000);
  const interest = new Interest(`/add/${x}/${y}`);
  interest.mustBeFresh = true;

const t0 =;
const settled = await Promise.allSettled( => endpoint.consume(interest, { retx: 5 })),
const t1 =;
const nFulfilled = settled.filter(({ status }) => status === "fulfilled").length;
console.log(`${nFulfilled} fulfilled in ${t1 - t0}ms`);


Start the producer in the first terminal, and run node ./bulk.js in the second terminal. You would see something like:

$ node ./bulk.mjs
100 fulfilled in 10524ms

All 100 requests were successful, and they took a total of 10.5 seconds.

Now we modify the producer script, replace // options line near the end with:

{ concurrency: 10 }

This tells endpoint to invoke at most 10 instances of the producer callback function in parallel.

Run the bulk consumer again:

$ node ./bulk.mjs
100 fulfilled in 1098ms

Much faster: 100 requests took a total of 1.1 seconds.

As of this writing, this feature is unique to NDNts. Other NDN libraries either let the application process one Interest at a time, or pass all requests to the application callback and you have to handle parallelism yourself.


This article is a getting started guide of NDNts libraries in Node.js environment. If you followed along, you have installed NDNts in Node.js 16.x, and learned how to write a producer and consumer using the Endpoint type. You also witnessed two powerful features of NDNts Endpoint type.

Source code in this article can be downloaded from NDNts-starter repository.