Build an India Gold Price App with JavaScript
Build a live India gold-rate app from INR per-gram 24K and 22K prices, with server-side caching, Indian number formatting, GST estimates, and clear limits.
Read →Learn how a live gold price chart combines spot quotes, OHLC bars, UTC timestamps, and controlled polling, with a working TypeScript SVG example.
A live gold price chart is built from two related data sets: a current spot quote for the headline number and historical OHLC bars for the plotted series. The client loads a window of bars, sorts them by time, renders their values, then refreshes only the newest observation. The chart should also show whether that newest bar is still forming.
That distinction matters. A spot quote can change between two chart bars, while the close of a forming bar is provisional. Treating both as the same value produces duplicate points, misleading timestamps, or a line that appears fresher than its underlying data.
Most gold charts need four small pieces:
XAU-USD-SPOT and 1dThe chart renderer is only the last step. The important work happens before drawing: validating numbers, ordering observations, retaining timestamps, and deciding how to treat a forming bar.
The spot endpoint answers “what is the current derived price?” A daily bar answers “what were the open, high, low, and close values for this UTC day?”
GET https://api.goldprice.dev/v1/spot/XAU-USD-SPOT
GET https://api.goldprice.dev/v1/bars?symbol=XAU-USD-SPOT&interval=1d&from=2026-07-25&to=2026-08-23
The spot response includes price, computed_at, and is_stale. Use those fields for the large current-price label and its freshness state.
Each bar includes bar_start, open, high, low, close, volume, and is_closed. Gold spot bars can have volume: null, so volume should not be required to draw a price chart. A bar with is_closed: false is still forming. Its OHLC values may change on the next refresh.
Bars are returned newest first. Most drawing code expects time to move from left to right, so reverse or sort the data before creating points.
Keep decimal prices as strings while they are in your data model. Convert them to JavaScript numbers only at the drawing boundary, where SVG coordinates require arithmetic.
type GoldBar = {
bar_start: string;
open: string | null;
high: string | null;
low: string | null;
close: string | null;
volume: string | null;
is_closed: boolean;
};
type BarsResponse = {
bars: GoldBar[];
next_cursor: string | null;
};
type ChartPoint = {
time: string;
close: number;
isClosed: boolean;
};
async function loadPoints(from: string, to: string): Promise<ChartPoint[]> {
const url = new URL("https://api.goldprice.dev/v1/bars");
url.search = new URLSearchParams({
symbol: "XAU-USD-SPOT",
interval: "1d",
from,
to,
}).toString();
const response = await fetch(url);
if (!response.ok) throw new Error(`Gold bars failed: ${response.status}`);
const page = (await response.json()) as BarsResponse;
return page.bars
.filter((bar) => bar.close !== null && Number.isFinite(Number(bar.close)))
.map((bar) => ({
time: bar.bar_start,
close: Number(bar.close),
isClosed: bar.is_closed,
}))
.sort((a, b) => Date.parse(a.time) - Date.parse(b.time));
}
This example requests one page because a recent daily chart fits within a short range. A larger history view should continue with next_cursor until it has enough observations or the cursor is null.
SVG is enough to explain the chart math without tying the data layer to a package. The function below maps time to the horizontal axis and closing price to the vertical axis. It returns a path string for a <path> element.
function linePath(points: ChartPoint[], width: number, height: number): string {
if (points.length === 0) return "";
const values = points.map((point) => point.close);
const min = Math.min(...values);
const max = Math.max(...values);
const span = max - min || 1;
return points
.map((point, index) => {
const x = points.length === 1 ? width / 2 : (index / (points.length - 1)) * width;
const y = height - ((point.close - min) / span) * height;
return `${index === 0 ? "M" : "L"} ${x.toFixed(2)} ${y.toFixed(2)}`;
})
.join(" ");
}
Use the result in a component or browser view:
<svg viewBox="0 0 720 280" role="img" aria-label="Gold price in US dollars">
<path
d={linePath(points, 720, 280)}
fill="none"
stroke="currentColor"
strokeWidth="2"
/>
</svg>
The data array remains independent of SVG. You can later pass the same normalized points to Canvas, a mobile chart view, or a charting package without changing the fetch and refresh rules.
OHLC bars are available over REST. Poll /v1/bars/latest for the freshest settled or forming bar. Do not append every response: repeated responses often refer to the same bar_start.
type LatestBarResponse = { bar: GoldBar | null };
async function refreshLatest(current: ChartPoint[]): Promise<ChartPoint[]> {
const response = await fetch(
"https://api.goldprice.dev/v1/bars/latest?symbol=XAU-USD-SPOT&interval=1d",
{ cache: "no-store" },
);
if (!response.ok) throw new Error(`Latest bar failed: ${response.status}`);
const { bar } = (await response.json()) as LatestBarResponse;
if (!bar || bar.close === null || !Number.isFinite(Number(bar.close))) return current;
const next: ChartPoint = {
time: bar.bar_start,
close: Number(bar.close),
isClosed: bar.is_closed,
};
const index = current.findIndex((point) => point.time === next.time);
if (index === -1) return [...current, next].sort((a, b) => Date.parse(a.time) - Date.parse(b.time));
const copy = current.slice();
copy[index] = next;
return copy;
}
Run that function on a controlled timer, pause when the page is hidden, and ensure only one request is active at a time. The appropriate polling interval depends on the chart interval and the experience you are building. Faster polling cannot make an upstream observation newer.
The line chart and the headline price can update on different schedules. Fetch the current quote separately and show the timestamp that belongs to it.
type SpotQuote = {
symbol: string;
price: string;
computed_at: string;
is_stale: boolean;
};
async function loadSpot(): Promise<SpotQuote> {
const response = await fetch(
"https://api.goldprice.dev/v1/spot/XAU-USD-SPOT",
{ cache: "no-store" },
);
if (!response.ok) throw new Error(`Gold spot failed: ${response.status}`);
return response.json() as Promise<SpotQuote>;
}
Label a stale quote instead of silently presenting it as current. Keep computed_at visible in tooltips, metadata, or a nearby “updated” label. Never replace it with the browser fetch time; the two timestamps describe different events.
A correct chart can stay unchanged. No new market observation is not a rendering error, and the client should not fabricate points to make the line reach the present moment.
Keep the last valid spot quote with its original computed_at. Keep the last bar with its real bar_start and is_closed value. If your visual design needs empty calendar periods, render them as axis space or gaps, not copied prices presented as new observations.
The same rule applies after a failed request. Continue showing the last known good data with its timestamp, or show an error if no valid data has loaded. Never cache an HTTP error as a zero price.
Before shipping a live gold chart, verify these behaviors:
XAU-USD-SPOT as the canonical symbolbar_start matches, append only when it is newis_closed: false visually or in a tooltipvolume to be nullcomputed_at and is_stale fieldsA chart is trustworthy when every point can be traced back to its observation time and state. Once those rules are part of the data layer, the choice of SVG, Canvas, or chart package becomes a presentation detail.
For endpoint parameters and pagination, see the historical bars guide. For live tick subscriptions and the distinction between ticks and bars, see the WebSocket guide.
related guides
Build a live India gold-rate app from INR per-gram 24K and 22K prices, with server-side caching, Indian number formatting, GST estimates, and clear limits.
Read →Build a gold jewelry price calculator from live per-gram karat prices, with exact decimal math, caching, margins, and clear valuation limits.
Read →Build a reproducible local-currency gold backtest from settled XAU/USD bars and dated FX observations, without inserting future rates into historical rows.
Read →goldprice.dev
Live gold prices, historical OHLC, and multi-source aggregation — available via REST and SSE.