Sandbox version
For experimental use only. Proceed with caution.
Tutorials
Build a Node.js API Proxy
Put Honeycluster traffic behind your own auth and rate-limits with a small Express forwarder — useful for enterprise keys or for throttling your own users against the shared-tier fair-use ceiling.

The public honeycluster.io endpoint is keyless, so a proxy isn't required for basic access — browsers can talk to it directly. But you'll still want a server-side proxy when:

  • You're on a private / enterprise plan with an API key. Keys grant project-wide access and must never ship to the browser.
  • You want to authorize your own users before they consume Honeycluster capacity (session cookie, JWT, internal bearer token) so random traffic can't burn your shared-tier budget.
  • You want to pre-filter or rewrite requests — e.g. force a subset of allowed method values, inject default params, or add caching that's shared across all your users.

This tutorial builds that proxy with Express and xrpl.js.

1. Dependencies
##
Bash
pnpm add express xrpl
pnpm add -D @types/express typescript tsx
2. The server
##
TypeScript
// server.ts
import express from 'express'
import { Client } from 'xrpl'

const app = express()
const PORT = Number(process.env.PORT ?? 4000)

// Public cluster — no key. For a private endpoint, add:
//   { headers: { 'X-API-Key': process.env.HONEYCLUSTER_API_KEY! } }
const client = new Client('wss://honeycluster.io')

await client.connect()

app.get('/api/ledger/:index', async (req, res) => {
  const ledger_index = Number(req.params.index)
  if (!Number.isFinite(ledger_index)) {
    return res.status(400).json({ error: 'ledger_index must be numeric' })
  }

  try {
    const { result } = await client.request({
      command: 'ledger',
      ledger_index,
      transactions: true,
      expand: true,
    })
    res.json(result.ledger)
  } catch (err) {
    res.status(502).json({ error: (err as Error).message })
  }
})

app.listen(PORT, () => console.log(`proxy listening on :${PORT}`))

Two things to notice:

  1. The xrpl client is opened once, at startup, and reused for every request. Creating a new WebSocket per call would be slow and chew through connection budget.
  2. Any API key lives only in process.env. It never travels to the caller, never appears in response headers, and never lands in client-side code.
3. Authenticate your callers
##

If you're exposing this proxy to an untrusted client (like a browser), add your own authorization middleware before the proxy handlers — session cookies, a JWT from your auth service, or a short-lived per-user token:

TypeScript
app.use('/api', (req, res, next) => {
  const token = req.header('authorization')?.replace(/^Bearer /, '')
  if (!token || !verifyUserToken(token)) {
    return res.status(401).json({ error: 'unauthorized' })
  }
  next()
})

Without this, anyone on the internet can hit your proxy and consume capacity under your IP (or, for private plans, spend your credits).

4. Cache what you can
##

Historical ledger data is immutable — the same ledger index always returns the same payload. Cache aggressively to cut upstream traffic (and, on private plans, credit spend):

TypeScript
import { LRUCache } from 'lru-cache'

const ledgerCache = new LRUCache<number, unknown>({ max: 500 })

app.get('/api/ledger/:index', async (req, res) => {
  const ledger_index = Number(req.params.index)
  const cached = ledgerCache.get(ledger_index)
  if (cached) return res.json(cached)

  const { result } = await client.request({
    command: 'ledger',
    ledger_index,
    transactions: true,
    expand: true,
  })
  ledgerCache.set(ledger_index, result.ledger)
  res.json(result.ledger)
})

For larger deployments, swap the in-memory LRU for Redis and share the cache across pods.

5. Forward errors faithfully
##

Honeycluster's errors are already structured. Don't mask them — proxy them through so clients see the real cause:

TypeScript
try {
  // ...call client.request...
} catch (err: any) {
  const status = err?.data?.error === 'notFound' ? 404 : 502
  res.status(status).json({
    code: err?.data?.error ?? 'UPSTREAM_ERROR',
    message: err?.message ?? 'Upstream request failed',
  })
}

See the Error Codes reference for the full list of codes your proxy might relay.