turnix-js is a small, dependency-free wrapper around POST /credentials/ice. It is meant for server-side JavaScript: Node.js 18 or newer, Deno, Bun, or edge runtimes with fetch.
Package: turnix-js on npm · Source: github.com/turnixio/turnix-js
The SDK needs your API token. Do not bundle it into browser code. Call it from your backend and send only the resulting iceServers to the client.
Add Turnix (managed TURN/STUN servers) to the WebRTC app in this repository, using the turnix-js SDK on the Node.js backend.
Context about turnix-js and Turnix:
- Install: npm install turnix-js (ES module, Node.js 18+): import { TurnixIO } from "turnix-js".
- const turnix = new TurnixIO({ bearerToken: process.env.TURNIX_API_TOKEN }); const iceServers = await turnix.requestCredentials({ ttl, room, initiatorClient, receiverClient, preferredRegion, fixedRegion }) resolves to the iceServers array; on non-2xx it rejects with an Error containing the status and body.
- The token is a secret project API token. Use it only on the server, never send it to the browser, never log or commit it.
- room, initiatorClient and receiverClient may contain only letters, digits, "-" and "_".
- Docs: https://turnix.io/docs/sdks/javascript and https://turnix.io/docs/api-reference/credentials
Do the following:
1. Find the backend (Express, Fastify, Next.js route handlers, …) and where the browser creates RTCPeerConnection. Follow the existing conventions. If the backend is not JavaScript, stop and tell me to use the generic prompt at https://turnix.io/docs/getting-started/ai-agents instead.
2. Install turnix-js and create one TurnixIO instance at startup. Fail at startup with a clear message if TURNIX_API_TOKEN is missing.
3. Add an authenticated endpoint (for example GET /api/turn-credentials) that calls requestCredentials({ ttl: 3600, room, initiatorClient }) with sanitized call and user IDs, and returns { iceServers }. On errors, log the message without the token and return 502 (503 with "TURN usage limit reached" when the message contains 429).
4. In the browser, fetch the endpoint before creating each RTCPeerConnection and pass { iceServers }. Do not cache credentials in localStorage.
5. Add TURNIX_API_TOKEN to .env.example and the README, and a test that mocks TurnixIO and checks that the endpoint returns iceServers and requires login.
6. Summarize the changes and what I still need to do (setting TURNIX_API_TOKEN in production).
Install#
npm install turnix-js
The package is an ES module:
import { TurnixIO } from "turnix-js";
Request credentials#
import express from "express";
import { TurnixIO } from "turnix-js";
const turnix = new TurnixIO({ bearerToken: process.env.TURNIX_API_TOKEN });
const app = express();
app.get("/api/turn-credentials", requireLogin, async (req, res) => {
try {
const iceServers = await turnix.requestCredentials({
ttl: 3600,
room: "room-42",
initiatorClient: `user-${req.user.id}`,
});
res.json({ iceServers });
} catch (err) {
console.error(err.message); // e.g. "Failed to fetch ICE credentials: 429 {...}"
res.status(502).json({ error: "Could not get TURN credentials" });
}
});
In the browser:
const { iceServers } = await fetch("/api/turn-credentials").then((r) => r.json());
const pc = new RTCPeerConnection({ iceServers });
API#
new TurnixIO(options)#
| Option | Type | Default | Description |
|---|---|---|---|
bearerToken |
string |
required | Your project API token. Throws Error("Bearer token is required") if missing. |
apiUrl |
string |
https://turnix.io/api/v1/credentials/ice |
Full URL of the credentials endpoint. |
turnix.requestCredentials(options?)#
Returns a Promise that resolves to the iceServers array. All options are optional and map to the API parameters:
| Option | Sent as | Description |
|---|---|---|
ttl |
ttl |
Lifetime in seconds (whole number). |
room |
room |
Room label. |
initiatorClient |
initiator_client |
Label of the participant starting the connection. |
receiverClient |
receiver_client |
Label of the answering participant. |
preferredRegion |
preferred_region |
Region slug to prefer; falls back to GeoIP if unavailable. |
fixedRegion |
fixed_region |
Region slug to require; the call fails if unavailable. |
On a non-2xx response the promise rejects with an Error whose message contains the HTTP status and the response body, for example Failed to fetch ICE credentials: 422 {"error":"Region Offline"}.
Limitations#
The current version (0.1.4) has a few gaps. If you need any of these, call the HTTP API directly; it is a single fetch call.
- No
expiresAt. OnlyiceServersis returned. Track expiry yourself (Date.now() + ttl * 1000). - No client IP forwarding. The SDK cannot set
X-TURN-CLIENT-IP, so the region is chosen from your server's location unless you passpreferredRegionorfixedRegion. The README mentions aclientIpoption, but it is not implemented in 0.1.4. ttl: 0is not sent (falsy values are skipped), so the platform default applies.
Equivalent direct call with client IP forwarding:
const res = await fetch("https://turnix.io/api/v1/credentials/ice", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TURNIX_API_TOKEN}`,
"Content-Type": "application/json",
"X-TURN-CLIENT-IP": req.ip,
},
body: JSON.stringify({ ttl: 3600 }),
});
if (!res.ok) throw new Error(`Turnix ${res.status}: ${await res.text()}`);
const { iceServers, expiresAt } = await res.json();
Demo#
turnix-js-demo is a minimal Express + WebSocket signaling app that uses the SDK for a two-party video call.