Goldpreis-API in JavaScript: Node.js, TypeScript und Next.js
Goldpreis in JavaScript per API abrufen: fetch mit async/await, TypeScript-Typen, eine gecachte Express- oder Next.js-Route und ein sicherer API-Schlüssel.
Goldpreis-API in JavaScript: die erste Anfrage
Eine Goldpreis-API aus JavaScript aufzurufen erfordert nur einen fetch-Aufruf, eine Integration für den Produktivbetrieb braucht aber auch Typen, Fehlerbehandlung, einen Cache und einen sicheren Ort für den API-Schlüssel. Dieser Artikel baut all das mit MetalAPI, einer JSON-REST-API für Edelmetalle, Industriemetalle, Energie, Währungen und Krypto: zuerst ein einfaches Node.js-Skript, dann einen typisierten TypeScript-Client und schließlich eine kleine Express- oder Next.js-Route, die den Goldpreis an Ihr Frontend liefert, ohne den Schlüssel preiszugeben.
Node.js 18 und neuer bringen fetch mit, eine HTTP-Bibliothek ist also nicht nötig. Erstellen Sie im Dashboard einen Schlüssel (Schlüssel beginnen mit mk_live_), legen Sie ihn als METALAPI_KEY=mk_live_... in einer .env-Datei ab und führen Sie das Skript mit node --env-file=.env gold.mjs aus. Node.js ab Version 20.6 liest die Datei ein; in älteren Versionen exportieren Sie die Variable stattdessen in Ihrer Shell.
const params = new URLSearchParams({ base: "USD", currencies: "XAU" });
const res = await fetch(`https://metalapi.com/api/v1/latest?${params}`, {
headers: { "X-API-Key": process.env.METALAPI_KEY },
signal: AbortSignal.timeout(10_000),
});
const data = await res.json();
if (!data.success) throw new Error(`MetalAPI ${data.error.code}: ${data.error.info}`);
const price = data.rates[`${data.base}XAU`];
console.log(`Gold: ${price} ${data.base} per troy ounce`);
console.log(`Price time: ${new Date(data.timestamp * 1000).toISOString()}`);Mit den Beispielwerten aus der Dokumentation ist rates.USDXAU gleich 3997.89: der Preis einer Feinunze Gold in der Basiswährung. Dasselbe Objekt enthält auch rates.XAU, etwa 0.00025. Das ist der Kehrwert, also Unzen Gold pro Dollar. Diesen Wert versehentlich anzuzeigen ist der klassische Goldpreis-Fehler. Ändern Sie base in EUR, wird der Schlüssel zu EURXAU; ergänzen Sie unit=gram für einen Preis pro Gramm. timestamp ist der Zeitpunkt der Preisdaten in Unix-Sekunden, nicht der Zeitpunkt Ihrer Anfrage.
TypeScript-Typen für die Antwort der Goldpreis-API
Die Antwortformate sind klein und stabil und lassen sich daher leicht typisieren. Erfolgreiche Antworten haben success: true. Fehler haben success: false und ein error-Objekt mit einem numerischen code, einem type und einer info-Meldung. Eine Discriminated Union über success erlaubt TypeScript daher, zwischen beiden Fällen zu unterscheiden. Dieser Client wandelt Fehler außerdem in einen typisierten Error um, der bei Antworten wegen Anfragelimit den Wert von Retry-After behält:
const API = "https://metalapi.com/api/v1";
export interface RateLimit {
limit: number;
remaining: number;
used: number;
reset: number; // Unix-Sekunden
reset_date: string;
plan: string;
}
export interface LatestResponse {
success: true;
timestamp: number; // Zeitpunkt der Preisdaten, Unix-Sekunden
date: string;
base: string;
unit?: string; // vorhanden, wenn unit nicht troy_oz ist
rates: Record<string, number>; // "USDXAU": Preis, "XAU": Kehrwert
rate_limit: RateLimit;
}
export interface TimeframeResponse {
success: true;
start_date: string;
end_date: string;
base: string;
rates: Record<string, Record<string, number>>; // Schlüssel: YYYY-MM-DD
rate_limit: RateLimit;
}
export interface ErrorResponse {
success: false;
error: { code: number; type: string; info: string; upgrade_url?: string };
}
export class MetalApiError extends Error {
code: number;
type: string;
retryAfter?: number;
constructor(code: number, type: string, info: string, retryAfter?: number) {
super(`MetalAPI ${code} (${type}): ${info}`);
this.name = "MetalApiError";
this.code = code;
this.type = type;
this.retryAfter = retryAfter;
}
}
export async function metalapi<T extends { success: true }>(
path: string,
params: Record<string, string> = {},
): Promise<T> {
const res = await fetch(`${API}/${path}?${new URLSearchParams(params)}`, {
headers: { "X-API-Key": process.env.METALAPI_KEY ?? "" },
signal: AbortSignal.timeout(10_000),
});
const body = (await res.json().catch(() => null)) as T | ErrorResponse | null;
if (body === null) throw new MetalApiError(res.status, "http_error", res.statusText);
if (body.success === false) {
const retryAfter = Number(res.headers.get("Retry-After")) || undefined;
throw new MetalApiError(body.error.code, body.error.type, body.error.info, retryAfter);
}
return body;
}
export async function goldPrice(base = "USD", unit = "troy_oz") {
const data = await metalapi<LatestResponse>("latest", { base, currencies: "XAU", unit });
return { price: data.rates[`${data.base}XAU`], base: data.base, unit, asOf: new Date(data.timestamp * 1000) };
}await goldPrice("EUR", "gram") liefert den Euro-Preis pro Gramm mit seinem Zeitstempel, und metalapi<TimeframeResponse>("timeframe", { start_date, end_date, currencies: "XAU" }) liefert Tagesschlusskurse für bis zu 30 Tage pro Anfrage im Free-Tarif und 365 Tage in kostenpflichtigen Tarifen. Verzweigen Sie anhand von err.code, niemals anhand der Meldung. Wiederholen Sie nur 104 (das Limit pro Minute, nach retryAfter Sekunden), 500, 503 und Netzwerkfehler; diese fehlgeschlagenen Anfragen werden nicht berechnet. Die übrigen Codes erfordern stattdessen eine Korrektur: 101 oder 102 bedeuten einen fehlenden oder ungültigen Schlüssel, 105 ein aufgebrauchtes Monatskontingent und 106 einen Endpunkt, den Ihr Tarif nicht enthält.
Server oder Browser: Wohin der API-Schlüssel gehört
Alles in clientseitigem JavaScript ist öffentlich. Einen Schlüssel in einem React-Bundle, einem Inline-Skript oder einer Umgebungsvariablen mit NEXT_PUBLIC_ oder VITE_ kann jeder Besucher in den Entwicklertools des Browsers lesen, und jeder Seitenaufruf verbraucht eine Ihrer Anfragen. Es gibt drei Wege, Nutzern einen Goldpreis anzuzeigen:
- Eine eigene Server-Route (empfohlen). Der Browser ruft Ihren Endpunkt auf, und Ihr Server ruft MetalAPI mit dem Schlüssel auf und cacht das Ergebnis. Der Verbrauch bleibt bei einer Anfrage pro Cache-Aktualisierung, ganz gleich, wie viel Traffic Sie haben.
- Ein domainbeschränkter Schlüssel im Browser. Im Dashboard können Sie einen Schlüssel auf Ihre Domains beschränken. Browser-Anfragen, deren
OriginoderReferernicht passt, werden dann mit dem Fehler107abgelehnt, ohne berechnet zu werden. Das hindert andere Websites daran, den Schlüssel in den Browsern ihrer Besucher zu verwenden, aber nicht jemanden, der ihn in ein Skript kopiert: Anfragen ohneOriginoderReferersind immer erlaubt. Verwenden Sie dafür einen eigenen Schlüssel, nie Ihren Hauptschlüssel. Die Anleitung zur Authentifizierung enthält die Details. - Ein einbettbares Widget. Für eine einfache Preisanzeige braucht ein Widget weder Schlüssel noch Code, und Seitenaufrufe von Widgets werden nicht auf Ihr Kontingent angerechnet.
Eine gecachte Goldpreis-Route in Express
Eine Server-Route braucht einen Cache. Jeder Tarif aktualisiert die Preise in einem festen Intervall (täglich im Free-Tarif, alle 30 Minuten im Lite-Tarif, alle 10 Minuten im Standard-Tarif und alle 60 Sekunden in Advanced und Pro). Eine frühere Abfrage liefert also dieselben Zahlen und verbraucht nur Kontingent. Diese Route cacht jede Kombination aus Währung und Einheit für 10 Minuten, lässt gleichzeitige Anfragen einen einzigen Upstream-Aufruf teilen und liefert weiter den letzten gültigen Preis aus, wenn MetalAPI nicht erreichbar ist:
// npm install express, dann: METALAPI_KEY=mk_live_... npx tsx server.ts
import express from "express";
import { goldPrice } from "./metalapi";
const TTL_MS = 10 * 60_000; // Standard-Tarif: Preise werden alle 10 Minuten aktualisiert
const CURRENCIES = new Set(["USD", "EUR", "GBP"]);
const UNITS = new Set(["troy_oz", "gram"]);
type Quote = Awaited<ReturnType<typeof goldPrice>>;
const cache = new Map<string, { quote: Quote; expires: number }>();
const inflight = new Map<string, Promise<Quote>>();
function cachedGold(base: string, unit: string): Promise<Quote> {
const key = `${base}:${unit}`;
const hit = cache.get(key);
if (hit && Date.now() < hit.expires) return Promise.resolve(hit.quote);
let pending = inflight.get(key); // Single Flight: ein Upstream-Aufruf pro Schlüssel
if (!pending) {
pending = goldPrice(base, unit)
.then((quote) => {
cache.set(key, { quote, expires: Date.now() + TTL_MS });
return quote;
})
.catch((err) => {
if (!hit) throw err;
cache.set(key, { quote: hit.quote, expires: Date.now() + 60_000 }); // veraltet, in 1 Min. erneut versuchen
return hit.quote;
})
.finally(() => inflight.delete(key));
inflight.set(key, pending);
}
return pending;
}
const app = express();
app.get("/api/gold", async (req, res) => {
const base = String(req.query.currency ?? "USD").toUpperCase();
const unit = String(req.query.unit ?? "troy_oz");
if (!CURRENCIES.has(base) || !UNITS.has(unit)) {
res.status(400).json({ error: "Unsupported currency or unit" });
return;
}
try {
res.set("Cache-Control", "public, max-age=60").json(await cachedGold(base, unit));
} catch (err) {
console.error(err);
res.status(503).json({ error: "Gold price temporarily unavailable" });
}
});
app.listen(3000, () => console.log("http://localhost:3000/api/gold?currency=EUR&unit=gram"));Die Liste der erlaubten Werte ist für Ihr Kontingent genauso wichtig wie für die Sicherheit. Jede Kombination aus Währung und Einheit ist ein eigener Cache-Eintrag, der pro Aktualisierung eine Anfrage kostet. Sechs Kombinationen, die rund um die Uhr aktuell gehalten werden, verbrauchen also etwa 26.800 Anfragen im Monat. Beschränken Sie die Liste auf das, was Ihre Seiten anzeigen, oder rufen Sie mehrere Währungen in einer Anfrage ab und rechnen Sie lokal um, wie im Tutorial zu PHP und WordPress. Der Cache liegt im Arbeitsspeicher des Prozesses; bei mehreren Instanzen oder Serverless-Funktionen verlagern Sie ihn nach Redis oder in den Cache Ihrer Plattform.
Das Frontend ruft dann Ihre Route auf, niemals MetalAPI direkt:
<p>Gold: <strong id="gold">…</strong> <small id="gold-time"></small></p>
<script type="module">
const res = await fetch("/api/gold?currency=EUR&unit=gram");
if (res.ok) {
const q = await res.json();
const money = new Intl.NumberFormat(undefined, { style: "currency", currency: q.base });
document.getElementById("gold").textContent = `${money.format(q.price)} per gram`;
document.getElementById("gold-time").textContent = `as of ${new Date(q.asOf).toLocaleString()}`;
}
</script>Dieselbe Route in Next.js
Im Next.js App Router erledigt ein Route Handler dieselbe Aufgabe, und der Data Cache von Next.js kann den selbst geschriebenen Cache ersetzen. Speichern Sie den Client als lib/metalapi.ts und legen Sie den Schlüssel in .env.local als METALAPI_KEY ab, ohne das Präfix NEXT_PUBLIC_, damit er nur auf dem Server existiert:
import type { ErrorResponse, LatestResponse } from "@/lib/metalapi";
export async function GET() {
try {
const res = await fetch("https://metalapi.com/api/v1/latest?currencies=XAU,XAG", {
headers: { "X-API-Key": process.env.METALAPI_KEY ?? "" },
next: { revalidate: 600 }, // Standard-Tarif: Antwort 10 Minuten lang wiederverwenden
});
const data = (await res.json()) as LatestResponse | ErrorResponse;
if (data.success) {
return Response.json({ gold: data.rates.USDXAU, silver: data.rates.USDXAG, timestamp: data.timestamp });
}
console.error("MetalAPI error", data.error.code, data.error.info);
} catch (err) {
console.error(err);
}
return Response.json({ error: "Prices temporarily unavailable" }, { status: 503 });
}next: { revalidate: 600 } speichert die Upstream-Antwort bis zu 10 Minuten und teilt sie zwischen den Anfragen. Next.js speichert nur Antworten mit HTTP-Status 200, ein MetalAPI-Fehler wird also nie gecacht. Wenn Ihre App Cache Components aktiviert, verschieben Sie den fetch-Aufruf stattdessen in eine mit 'use cache' markierte Hilfsfunktion und legen deren Lebensdauer mit cacheLife fest.
Nächste Schritte
Erstellen Sie ein kostenloses Konto, um einen Schlüssel zu erhalten. Der Free-Tarif umfasst 100 Anfragen pro Monat mit täglicher Aktualisierung und ohne Kreditkarte, genug, um alles oben Gezeigte zu entwickeln und zu testen. Wenn Ihre Route live geht, wählen Sie einen Tarif nach Aktualität und Volumen: Ein Cache-Eintrag mit 10 Minuten Laufzeit verbraucht etwa 4.500 Anfragen im Monat und passt damit in die 10.000 des Standard-Tarifs. Die Preisseite vergleicht alle Tarife, und Goldpreis-API in Python behandelt dieselbe API für Skripte, pandas und Charts.
Häufige Fragen
Kann ich die Goldpreis-API direkt aus dem Browser aufrufen?
Ja. Die API beantwortet Cross-Origin-Anfragen, fetch funktioniert also auch aus einer Webseite heraus. Verwenden Sie einen auf Ihre Domains beschränkten Schlüssel, und denken Sie daran, dass Besucher ihn trotzdem lesen können und jeder Seitenaufruf eine Anfrage kostet. Eine Server-Route mit Cache ist günstiger und hält den Schlüssel privat.
Warum zeigt meine Seite 0,00025 statt des Goldpreises an?
Sie lesen rates.XAU, den Kehrwert. Lesen Sie rates.USDXAU oder rates[base + "XAU"], wenn die Basiswährung variiert. Mit den Beispielwerten ergibt 1 ÷ 0,0002501319 den Wert 3.997,89.
Wie erhalte ich den Goldpreis in Euro oder pro Gramm in JavaScript?
Übergeben Sie base=EUR und unit=gram. Der Preis ist dann rates.EURXAU in Euro pro Gramm, und die Antwort enthält unit: "gram". unit akzeptiert außerdem troy_oz (Standard), kilogram, ounce, pound, metric_ton und short_ton. Vermeiden Sie für Gold ounce, denn das ist die Avoirdupois-Unze.
Wie oft sollte ich den Goldpreis aktualisieren?
Höchstens einmal pro Aktualisierungsintervall Ihres Tarifs: täglich im Free-Tarif, stündlich im Lite-Tarif (alle 30 Minuten wären etwa 1.490 Anfragen im Monat bei einem Kontingent von 1.000), alle 10 Minuten im Standard-Tarif. Am Wochenende bleiben die Preise unverändert, denn Spot-Gold wird von Freitag 17:00 bis Sonntag 17:00 New Yorker Zeit nicht gehandelt.
Kann ich Goldpreise in Echtzeit ohne Polling erhalten?
Ab dem Pro-Tarif sendet /stream Preisaktualisierungen per Server-Sent Events, sodass Sie /latest nie abfragen müssen. Für Schwellenwert-Logik, etwa wenn Gold die Marke von 4.000 $ überschreitet, rufen Preisalarme per Webhook (ab dem Advanced-Tarif) Ihren Server auf, sobald die Bedingung erfüllt ist.
Probieren Sie es mit Ihren eigenen Daten aus
Erstellen Sie ein kostenloses Konto und senden Sie Ihre erste Anfrage in unter zwei Minuten. 100 Anfragen pro Monat, keine Kreditkarte.
Weiterlesen
XAU/USD Bedeutung: Wofür XAU, XAG, XPT und XPD stehen
XAU/USD einfach erklärt: warum Gold einen ISO-4217-Währungscode hat, wofür XAG, XPT und XPD stehen und wie Sie die Kursrichtung in Preisen und API-Daten lesen.
Kostenlose Goldpreis-API: Was 100 Anfragen im Monat leisten
Kostenlose Goldpreis-API ehrlich erklärt: was der Free-Tarif von MetalAPI enthält, wie 100 Anfragen im Monat mit Caching reichen und wann ein Upgrade lohnt.
Goldpreis-API in Python: Preise abrufen, cachen und plotten
Goldpreis-API in Python nutzen: Live-Goldpreise mit requests abrufen, Fehler und Wiederholungen behandeln, Antworten cachen und Zeitreihen mit pandas plotten.