JavaScript / Node
Node.js 및 브라우저에서 Veacon API 호출. fetch 네이티브, 재시도 유틸 포함.
Last updated: 2026-04-23
별도 SDK 없이 fetch 네이티브로 사용. Node.js 18+ 또는 모던 브라우저 전제.
환경 변수
bash
# .env.local
VEACON_API_KEY=veacon_pk_live_...
프론트엔드 (브라우저) 에는 절대 API Key 를 넣지 마세요. Next.js API route 또는 서버 함수에서 호출하고, 클라이언트에는 결과 JSON 만 전달하는 패턴을 사용하세요.
1. 기본 호출
ts
type PulseRow = {
region: string;
category: string;
period: string;
product_type: 'rental' | 'meeting' | 'combined';
avg_price: number | null;
median_price: number | null;
sample_size: number;
demand_index: number | null;
confidence: 'low' | 'medium' | 'high';
};
async function getPulse(region: string, category: string, period: string): Promise<PulseRow[]> {
const params = new URLSearchParams({ region, category, period });
const r = await fetch(`https://veacon.io/api/v1/markets/pulse?${params}`, {
headers: { 'X-API-Key': process.env.VEACON_API_KEY! },
});
if (!r.ok) {
const body = await r.json().catch(() => ({}));
throw new Error(`${body?.error?.code ?? r.status}: ${body?.error?.message ?? 'unknown'}`);
}
const { data } = await r.json();
return data as PulseRow[];
}
const rows = await getPulse('강남권', 'office', '2026-01');
console.log(rows[0].avg_price);
2. 재시도 + Rate Limit 대응
ts
async function fetchWithRetry(url: string, init: RequestInit, maxAttempts = 4): Promise<Response> {
let attempt = 0;
while (attempt < maxAttempts) {
const r = await fetch(url, init);
if (r.ok) return r;
if (r.status === 429) {
const retryAfter = Number(r.headers.get('Retry-After') ?? '1');
await sleep(retryAfter * 1000);
attempt++;
continue;
}
if (r.status >= 500 && attempt < maxAttempts - 1) {
await sleep(Math.min(10_000, 1000 * 2 ** attempt));
attempt++;
continue;
}
// 4xx (other than 429) — do not retry
return r;
}
throw new Error('Max retries exceeded');
}
function sleep(ms: number) {
return new Promise((r) => setTimeout(r, ms));
}
3. 쿼터 소진 알림
ts
async function pulse(region: string, category: string, period: string) {
const r = await fetch(
`https://veacon.io/api/v1/markets/pulse?region=${encodeURIComponent(region)}&category=${category}&period=${period}`,
{ headers: { 'X-API-Key': process.env.VEACON_API_KEY! } },
);
const used = Number(r.headers.get('X-Quota-Used'));
const limit = Number(r.headers.get('X-Quota-Limit'));
// 쿼터 80% 도달 시 Slack/이메일 경고
if (used / limit > 0.8) {
console.warn(`Veacon quota at ${((used / limit) * 100).toFixed(1)}%`);
// notifyOps(...)
}
return r.json();
}
4. Next.js API Route (프록시 패턴)
클라이언트는 /api/pulse 만 호출 — 실제 API Key 는 서버에 감춰짐.
ts
// app/api/pulse/route.ts
import { NextResponse } from 'next/server';
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const region = searchParams.get('region');
const category = searchParams.get('category');
const period = searchParams.get('period');
if (!region || !category || !period) {
return NextResponse.json({ error: 'missing params' }, { status: 400 });
}
const r = await fetch(
`https://veacon.io/api/v1/markets/pulse?region=${encodeURIComponent(region)}&category=${category}&period=${period}`,
{ headers: { 'X-API-Key': process.env.VEACON_API_KEY! }, next: { revalidate: 3600 } },
);
// 쿼터 헤더 통과 (클라이언트도 보이게)
const headers = new Headers();
for (const h of ['X-Quota-Limit', 'X-Quota-Used', 'X-Quota-Resets-At']) {
const v = r.headers.get(h);
if (v) headers.set(h, v);
}
return NextResponse.json(await r.json(), { status: r.status, headers });
}
5. React hook (클라이언트용)
ts
import { useEffect, useState } from 'react';
export function usePulse(region: string, category: string, period: string) {
const [state, setState] = useState<
| { status: 'loading' }
| { status: 'ok'; data: unknown[] }
| { status: 'error'; message: string }
>({ status: 'loading' });
useEffect(() => {
const q = new URLSearchParams({ region, category, period });
fetch(`/api/pulse?${q}`)
.then(async (r) => {
const body = await r.json();
if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`);
setState({ status: 'ok', data: body.data });
})
.catch((e) => setState({ status: 'error', message: e.message }));
}, [region, category, period]);
return state;
}
참고
- Rate limits — 재시도 로직 근거
- Authentication — 키 관리 best practice