For a web dashboard, make the authenticated quote request on your server and return only the fields your UI needs. Native fetch lets a Node.js application do this without adding an HTTP client dependency. The API key remains in the server environment.
Create a small server-side adapter
Use a Node.js runtime that provides native fetch and AbortSignal.timeout; see the Node.js globals reference for runtime support. Save the example as quote.mjs, set API_KEY and NAVQUOTE_API_BASE, and run node quote.mjs. The proposed base URL is https://api.navquote.com; the examples are pending validation against that hostname.
const base = process.env.NAVQUOTE_API_BASE;
const key = process.env.API_KEY;
if (!base || !key) throw new Error("Set NAVQUOTE_API_BASE and API_KEY");
async function getQuote(symbol) {
const url = new URL(`/api/quote/${encodeURIComponent(symbol)}`, base);
const response = await fetch(url, {
headers: { Authorization: `Bearer ${key}` },
signal: AbortSignal.timeout(30_000),
});
if (!response.ok) throw new Error(`Quote HTTP ${response.status}`);
const result = await response.json();
if (result.success !== true) throw new Error("Quote API request failed");
const quote = result.data?.data;
if (!Number.isFinite(quote?.lp)) throw new Error("Missing last price");
if (!Number.isFinite(quote?.lp_time) || quote.lp_time <= 0) {
throw new Error("Missing market timestamp");
}
return {
symbol: result.data.symbol,
price: quote.lp,
currency: quote.currency_code ?? null,
marketTime: new Date(quote.lp_time * 1000).toISOString(),
receivedAt: new Date().toISOString(),
degraded: result.degraded === true,
};
}
try {
console.log(JSON.stringify(await getQuote("NASDAQ:AAPL"), null, 2));
} catch (error) {
console.error(error instanceof Error ? error.message : "Quote unavailable");
process.exitCode = 1;
}
Understand the two success checks
fetch does not reject simply because a server returns an HTTP error status. Check response.ok before reading a successful result. Next inspect the API’s success flag: a parsed JSON body is not itself proof that the requested instrument returned data.
The timeout limits how long this individual request can wait. It does not implement retries, queueing or rate control. A dashboard server should also bound concurrent upstream work so a burst of page views does not create one upstream request per visitor. Cache scope and duration depend on your freshness requirements and data usage permissions.
Return a deliberate frontend response
The adapter returns a small object containing a numeric price, currency, two timestamps and the degraded flag. If your frontend requires currency, make a missing currency an explicit unavailable state instead of guessing USD. JavaScript dates use milliseconds; multiplying lp_time by 1,000 happens only at that conversion boundary.
Put this adapter behind your own authenticated application route. Do not ship the source environment variables to the browser, place the key in a URL, or log an entire request object containing the authorization header. The sample is a command-line program; it does not create that application route for you.
Choose a refresh policy
For a report loaded occasionally, requesting a snapshot when needed may be sufficient. For a watchlist that stays open, compare a shared polling job with WebSocket or SSE. Polling frequency is an application decision, not evidence that the underlying market data updates at that rate.
On failure, return an explicit status to the UI. If you retain a previous value, preserve its original market time. Replacing that timestamp with the current time would make stale data appear new.