Skip to content

Node.js / JavaScript quickstart ​

Read a public BTC price and receive a WebSocket update with @siebly/hyperliquid-api in Node.js. No API key or TypeScript compiler is needed. Prices are BTC mid prices.

Create the project ​

Use Node.js 24 with npm. Download the complete Node.js JavaScript starter, or copy the files below.

sh
mkdir hyperliquid-node-quickstart
cd hyperliquid-node-quickstart

Copy the files below, or extract the downloaded archive into this directory (adjust the download path if needed):

sh
tar -xzf ~/Downloads/node.tar.gz

Install the dependencies after saving or extracting the files:

sh
npm install

The SDK installs from latest. The archive contains both an import example and a require example. Choose either command below; both make the same public requests.

Project files ​

package.json ​

json
{
  "name": "hyperliquid-node-quickstart",
  "private": true,
  "type": "module",
  "scripts": {
    "start": "node prices.js",
    "start:require": "node prices.cjs"
  },
  "dependencies": {
    "@siebly/hyperliquid-api": "latest"
  }
}

market.js ​

js
export const endpoints = {"rest": "https://api.hyperliquid.xyz/info", "websocket": "wss://api.hyperliquid.xyz/ws"};
export function client(sdk) { return new sdk.RestClient({}, {timeout:15000}); }
export async function read(client) { return (await client.getAllMids()).BTC; }
export function stream(sdk, logger) { return new sdk.WebsocketClient({}, logger); }
export const event = 'message';
export function value(message) { return message.channel === 'allMids' ? message.data?.mids?.BTC : undefined; }
export async function subscribe(ws) { await ws.subscribe({ type: 'allMids' }); }

price.js ​

js
import * as sdk from '@siebly/hyperliquid-api';
import * as market from './market.js';

function positivePrice(value) {
  if (!Number.isFinite(Number(value)) || Number(value) <= 0) {
    throw new Error('The exchange did not return a positive BTC price.');
  }
  return String(value);
}

export async function readPrice() {
  return positivePrice(await market.read(market.client(sdk)));
}

// Returns cleanup without waiting for subscription acknowledgement.
export function watchPrice(onPrice, onError) {
  const ws = market.stream(sdk);
  let stopped = false;
  const receive = message => {
    const value = market.value(message);
    if (!stopped && value !== undefined) {
      clearTimeout(deadline);
      try { onPrice(positivePrice(value)); } catch (error) { fail(error); }
    }
  };
  const fail = error => { if (!stopped) onError(error); };
  const deadline = setTimeout(() => fail(new Error('No BTC update within 30 seconds. Check network access.')), 30000);
  ws.on(market.event, receive);
  ws.on('exception', fail);
  // React may clean up an effect before its queued subscription starts.
  Promise.resolve().then(async () => {
    if (stopped) return;
    try {
      await market.subscribe(ws);
    } finally {
      // A connection may finish opening after cleanup was requested.
      if (stopped) ws.closeAll();
    }
  }).catch(fail);
  return () => {
    if (stopped) return;
    stopped = true;
    clearTimeout(deadline);
    ws.closeAll();
    ws.removeListener(market.event, receive);
    ws.removeListener('exception', fail);
  };
}

prices.js ​

js
import process from 'node:process';
import { readPrice, watchPrice } from './price.js';

async function main() {
  console.log('BTC REST price:', await readPrice());
  let stop;
  try {
    const price = await new Promise((resolve, reject) => {
      stop = watchPrice(resolve, reject);
    });
    console.log('BTC WebSocket price:', price);
  } finally {
    stop?.();
    console.log('WebSocket closed.');
  }
}
main().catch(error => {
  console.error(error.message || String(error));
  process.exitCode = 1;
});

market.cjs ​

js
const endpoints = {"rest": "https://api.hyperliquid.xyz/info", "websocket": "wss://api.hyperliquid.xyz/ws"};
function client(sdk) { return new sdk.RestClient({}, {timeout:15000}); }
async function read(client) { return (await client.getAllMids()).BTC; }
function stream(sdk, logger) { return new sdk.WebsocketClient({}, logger); }
const event = 'message';
function value(message) { return message.channel === 'allMids' ? message.data?.mids?.BTC : undefined; }
async function subscribe(ws) { await ws.subscribe({ type: 'allMids' }); }

module.exports = { endpoints, client, read, stream, event, value, subscribe };

price.cjs ​

js
const sdk = require('@siebly/hyperliquid-api');
const market = require('./market.cjs');

function positivePrice(value) {
  if (!Number.isFinite(Number(value)) || Number(value) <= 0) {
    throw new Error('The exchange did not return a positive BTC price.');
  }
  return String(value);
}

async function readPrice() {
  return positivePrice(await market.read(market.client(sdk)));
}

// Returns cleanup without waiting for subscription acknowledgement.
function watchPrice(onPrice, onError) {
  const ws = market.stream(sdk);
  let stopped = false;
  const receive = message => {
    const value = market.value(message);
    if (!stopped && value !== undefined) {
      clearTimeout(deadline);
      try { onPrice(positivePrice(value)); } catch (error) { fail(error); }
    }
  };
  const fail = error => { if (!stopped) onError(error); };
  const deadline = setTimeout(() => fail(new Error('No BTC update within 30 seconds. Check network access.')), 30000);
  ws.on(market.event, receive);
  ws.on('exception', fail);
  // React may clean up an effect before its queued subscription starts.
  Promise.resolve().then(async () => {
    if (stopped) return;
    try {
      await market.subscribe(ws);
    } finally {
      // A connection may finish opening after cleanup was requested.
      if (stopped) ws.closeAll();
    }
  }).catch(fail);
  return () => {
    if (stopped) return;
    stopped = true;
    clearTimeout(deadline);
    ws.closeAll();
    ws.removeListener(market.event, receive);
    ws.removeListener('exception', fail);
  };
}

module.exports = { readPrice, watchPrice };

prices.cjs ​

js
const process = require('node:process');
const { readPrice, watchPrice } = require('./price.cjs');

async function main() {
  console.log('BTC REST price:', await readPrice());
  let stop;
  try {
    const price = await new Promise((resolve, reject) => {
      stop = watchPrice(resolve, reject);
    });
    console.log('BTC WebSocket price:', price);
  } finally {
    stop?.();
    console.log('WebSocket closed.');
  }
}
main().catch(error => {
  console.error(error.message || String(error));
  process.exitCode = 1;
});

Run ​

For ES modules with import:

sh
npm start
# Equivalent: node prices.js

For CommonJS with require:

sh
npm run start:require
# Equivalent: node prices.cjs

Both examples print a REST price and the first valid WebSocket price, close the client in finally, then exit. SDK connection logs may also appear.

text
BTC REST price: 65000.5
BTC WebSocket price: 65001.2
WebSocket closed.

The .js files use import because package.json sets "type": "module". The .cjs files use require even in that project. These are Node.js module rules, not a TypeScript requirement.

Troubleshooting ​

  • If Node says require is not defined, use prices.cjs for the CommonJS example. If it rejects import, keep "type": "module" for the .js files.
  • If the SDK cannot be resolved, run npm install in the directory containing package.json.
  • For a rejected HTTP request or no update within 30 seconds, allow the HTTPS and WSS endpoints in market.js or market.cjs. Exchange regional restrictions apply.
  • See Runtime compatibility for recorded runtime versions and WebSocket subscriptions for continuous streams and shutdown.

Built by Siebly. Open source SDKs for exchange APIs.