The NATS client for JavaScript supports many standard runtimes out of the box:
- Node.js/Bun
- Deno
- Browser
The runtime is distributed in two different registries:
- npmjs.com - these are npm bundles that target Node.js and compatible runtimes (Bun)
- jsr.io - these are esm versions (typically targetting Deno or browser)
Note that the names of the bundles are the same in the two registries
@nats-io/<module>. The reason for this is that code that is properly written
will look the same and be compatible across the runtimes provided no additional
user-added dependencies prevent this. The module manifest
(package.json/deno.json) will provide the right mapping to the registry, your
code simply references the correct library regardless of registry.
Let's make a project for your favorite runtime, Note that this example is intended to create a simple development environment, not to explain NATS concepts, etc. For that refer to the README.md.
mkdir -p nats-dev/node
cd nats-dev/node
npm init esnext -y
# the tsx module allows to run typescript files directly by doing `tsx filename.ts`
npm install @nats-io/transport-node tsxmkdir nats-dev/bun
cd nats-dev/bun
bun init -y
bun add @nats-io/transport-nodemkdir nats-dev/deno
cd nats-dev/deno
deno init
deno add jsr:@nats-io/transport-denoIf you are writing your code in TypeScript under node using the tsc compiler,
you will need to configure tsc properly so it can find its way around the
module. You will want a configuration file that looks like this:
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"outDir": "lib/",
"moduleResolution": "nodenext",
"sourceMap": true,
"declaration": true,
"allowJs": true,
"removeComments": false,
"resolveJsonModule": true
},
"include": [
"src/**/*"
],
"exclude": [
"lib/**/*"
]
}Of key importance are the target, module, moduleResolution. The nodenext
identifies that the packages can contain exports, and will enable the compiler
and IDEs to properly resolve imports in your code.
Added to the nats-dev/<runtime>/index.ts you used above.
If using Deno, uncomment the second line, and comment out the first.
import { connect, deferred, nuid } from "@nats-io/transport-node";
// import { connect, deferred, nuid } from "@nats-io/transport-deno";
const nc = await connect({ servers: "demo.nats.io" });
console.log(`connected`);
const subj = nuid.next();
nc.subscribe(subj, {
callback: (err, msg) => {
console.log(msg.subject, msg.json());
},
});
let i = 0;
const d = deferred();
const timer = setInterval(() => {
i++;
nc.publish(subj, JSON.stringify({ ts: new Date().toISOString(), i }));
if (i === 10) {
clearInterval(timer);
d.resolve();
}
}, 1000);
await d;
await nc.drain();If you want to use a WebSocket connection, replace await connect() as follows:
import { wsconnect } from "@nats-io/transport-node";
// import { wsconnect } from "@nats-io/transport-deno";
const nc = await wsconnect({ servers: "demo.nats.io:8443" });Save it to index.ts
# Nodejs
tsx index.ts
# Bun
bun index.ts
# Deno
deno run -A index.tsYou should see some output similar to:
connected
29CODPRD5FJ0INNDXBAOC2 {
ts: "2024-11-13T17:59:22.853Z",
i: 1,
}
29CODPRD5FJ0INNDXBAOC2 {
ts: "2024-11-13T17:59:23.852Z",
i: 2,
}
...Congratulations. You have a working NATS project. Now go explore the documentation.
As discussed above, out of the box, the @nats-io/nats-core module provides a
W3C WebSocket transport, that you can use directly from Node.js, Bun, Deno and
most importantly in a Browser via the wsconnect() function.
The setup is similar as you have seen above, with the exception that you have to import and configure your Web frameworks libraries, which is beyond the scope of this document.
As samples, take a look at my examples for Next.js and React Native pointed to below. The base projects are generated by the stand tools adding a few components but showcase connection management, simple pub/sub, kv and object store monitoring.
Note that only latest versions of the frameworks are known to be compatible, and while I know that the samples work, but they may not if you have more complex setups (JavaScript ecosystem is an interesting world.).
wsconnect() returns a Promise<NatsConnection>. Every modern web framework
has first-class support for rendering UI that depends on async resources — use
it. Do not wrap the connection in component-local useState / useEffect;
that re-runs connect on every render and forces consumers to null-guard the
handle.
The recommended shape is a module-scope singleton promise, consumed via the
framework's async-resource primitive (React Suspense, Vue <Suspense>, Svelte
{#await}, etc.).
Framework docs:
- React
<Suspense>— https://react.dev/reference/react/Suspense - React
usehook — https://react.dev/reference/react/use - Next.js streaming and
loading.tsx— https://nextjs.org/docs/app/api-reference/file-conventions/loading - TanStack Query Suspense mode — https://tanstack.com/query/latest/docs/framework/react/guides/suspense
- Vue
<Suspense>— https://vuejs.org/guide/built-ins/suspense - Svelte
{#await}— https://svelte.dev/docs/svelte/await
Working examples (both apply this pattern):
- Next.js — aricart/nats-nextjs-example
- React Native (Expo) — aricart/nats-react-native
A number of free CDNs are available that make it possible to reference libraries distributed via NPM accessible as ESM modules by a simple URL. One such CDN is jsdelivr:
import { wsconnect } from "https://esm.run/@nats-io/nats-core";
import { jetstreamManager } from "https://esm.run/@nats-io/jetstream";
import { Kvm } from "https://esm.run/@nats-io/kv";
import { Objm } from "https://esm.run/@nats-io/obj";
import { Svcm } from "https://esm.run/@nats-io/services";
// Add your code hereSupported out of the box. The example app demonstrates the singleton + Suspense pattern described above and is the recommended starting point:
It uses the pages router and loads the NATS client via next/dynamic with
ssr: false (the WebSocket only runs in the browser).
Earlier versions of the libraries needed shims and workarounds. Recent React
Native + Expo work out of the box on Expo SDK 53+ — package exports are on by
default, so no metro.config.js tweaks are needed.
npx create-expo-app@latest
npx expo install @nats-io/nats-core @nats-io/jetstream @nats-io/kv @nats-io/objTwo caveats specific to React Native:
- Reanimated 4 peer dep. If your template includes
react-native-reanimated(the default Expo template does), installreact-native-workletssobabel-preset-expocan wire up the worklets plugin:npx expo install react-native-worklets. After installing, runnpx expo start --clearto flush Metro's babel cache. crypto.subtle.digestfor ObjectStore. Hermes does not expose Web Crypto's subtle API.Objm.createchecks forcrypto.subtle.digestas a precondition (the actual hashing usesjs-sha256), so a tiny stub at the top of your NATS module is enough — seelib/nats.tsin the example.
The sample app at aricart/nats-react-native shows the full setup, including the singleton + Suspense pattern described above.
While you can use a CDN to use the libraries in your browser, sometimes
it is more convenient to package and host your own, here's an example using
esbuild:
# install esbuild
npm install esbuild --globalmkdir mynats
cd mynats
npm init es6 -y
# Adding all the nats libraries here, but you can certainly reference the ones
# you need
npx jsr add @nats-io/nats-core
npx jsr add @nats-io/jetstream
npx jsr add @nats-io/kv
npx jsr add @nats-io/obj
npx jsr add @nats-io/servicesNow creating a simple file that re-exports all the libraries, "nats.js"
export * from "@nats-io/nats-core";
export * from "@nats-io/jetstream";
export * from "@nats-io/kv";
export * from "@nats-io/obj";
export * from "@nats-io/services";Create your own local bundled version of the libraries:
esbuild --format=esm --bundle --minify nats.js > nats.mjsCreate your own NATS application referencing the bundled modules (as
index.js):
import { jetstreamManager, Kvm, Objm, Svcm, wsconnect } from "./nats.mjs";
const nc = await wsconnect({ servers: "demo.nats.io:8443" });
console.log(`connected: ${nc.getServer()}`);
const jsm = await jetstreamManager(nc);
let streams = 0;
for await (const si of jsm.streams.list()) {
streams++;
}
console.log(`found ${streams} streams`);
let kvs = 0;
const kvm = new Kvm(nc);
for await (const k of kvm.list()) {
kvs++;
}
console.log(`${kvs} streams are kvs`);
let objs = 0;
const objm = new Objm(nc);
for await (const k of objm.list()) {
objs++;
}
console.log(`${objs} streams are object stores`);
let services = 0;
const svm = new Svcm(nc);
const c = svm.client();
for await (const s of await c.ping()) {
services++;
}
console.log(`${services} services were found`);
await nc.close();Here's a run of the above in Deno, which is acting just like a browser JavaScript runtime:
deno run -A index.js
connected: demo.nats.io:8443
found 278 streams
155 streams are kvs
27 streams are object stores
0 services were foundor in th browser directly (note the nats.mjs is referenced relative of the script file):
<html>
<body>
<script type="module" src="./index.js" />
</body>
</html>