# Što ćete naučiti#
Ovaj vodič za API integracije pokriva kako integrirati third-party i interne API-je u 2026. bez isporuke nepouzdanog, nesigurnog ili operativno skupog koda. Naučit ćete kriterije za odluku REST vs GraphQL, sigurnu autentikaciju, otpornu obradu grešaka i praktične strategije za rate limiting.
Svi primjeri koriste Next.js API routes (App Router) kako bi tajne ostale na serveru i kako bi se logika integracije centralizirala. Ako ste novi u Next.js, krenite s Uvod u Next.js kako biste razumjeli osnove routinga i izvršavanja na serveru.
# Zašto API integracije padaju (i što prvo popraviti)#
Većina incidenata s integracijama nisu situacije tipa “API je pao”. To su predvidivi problemi uzrokovani nedostatkom zaštitnih ograda: bez timeouta, naivni retry, loša klasifikacija grešaka i curenje tajni prema klijentu.
Konkretni obrasci kvarova koje najčešće viđamo:
- Bez timeouta → zahtjevi vise dok se server resursi ne iscrpe, raste tail latency.
- Slijepi retry → “retry oluje” pojačavaju outage; troškovi skaču kad se plaćeni API-ji retryaju bez kontrole.
- Nekonzistentna obrada grešaka → frontend ne može reagirati; korisnici vide generičke greške; podrška ne može trijažirati.
- Ignoriranje rate limita → burstovi uzrokuju 429, kaskadne retryje i lošiji UX.
- Prečaci u authu → tokeni cure u preglednik; kompromitirani ključevi vode do downtimea i financijskog rizika.
🎯 Ključna poruka: Integracije tretirajte kao rad s distribuiranim sustavima: dodajte timeoute, retry, rate limiting i observability prije nego dodate “featuree”.
# Arhitekturni obrasci za integracije u 2026.#
Dobar default u 2026. je “Backend-for-Frontend (BFF)” preko Next.js API ruta (ili server actions, kad je prikladno). Cilj je držati vjerodajnice third-party servisa izvan klijenta i standardizirati ponašanje oko grešaka i rate limita.
Najčešće arhitekture integracije#
| Pattern | Najbolje za | Prednosti | Nedostaci |
|---|---|---|---|
| Direktno klijent → third-party API | Javne API-je, bez tajni, nizak rizik | Najniža latencija, najjednostavnije | Otkiva obrasce korištenja, teško za zaštititi, nekonzistentne greške |
| Next.js API routes kao BFF | Većinu aplikacija | Tajne ostaju na serveru, centralizirana logika, konzistentan model grešaka | Dodatni hop, treba rate limiting i cache |
| Namjenski integration servis | Velike organizacije, mnogo potrošača | Jasno vlasništvo, reuse, skalabilno | Više infrastrukture i operativnog overhead-a |
| Event-driven (webhooks/queues) | Asinkroni workflowi, sinkronizacija sustava | Otporno, decoupled, podnosi spikeove | Više pokretnih dijelova, eventual consistency |
Ako radite customer-facing web/mobile aplikacije, BFF je obično najbrži način da isporučite sigurno. Ako trebate pomoć u dizajnu integration sloja za web i mobile, pogledajte naše usluge web & mobile development.
# REST vs GraphQL u 2026.: kako odabrati#
Oboje radi. Razlika je u tome kako upravljate oblikom podataka, cacheiranjem i governanceom.
REST: najbolje prakse i kada pobjeđuje#
REST je i dalje najčešći izbor za third-party integracije. Posebno je dobar kada:
- Imate stabilne resurse i predvidljive obrasce pristupa (npr.
/orders/:id). - Želite semantiku pogodnu za cache (ETagovi, CDN, HTTP caching).
- Trebate jednostavnije alate i observability (logovi se čisto mapiraju na endpoint-e).
REST zamke na koje treba paziti:
- Over-fetching/under-fetching što vodi do više zahtjeva.
- “Sprawl” verzioniranja (
/v1,/v2) kad promjene nisu backward compatible. - Nekonzistentni error payload-i među endpoint-ima.
GraphQL: najbolje prakse i kada pobjeđuje#
GraphQL je odličan izbor kada:
- Više klijenata (web, mobile, partner aplikacije) treba različita polja.
- Želite jedan endpoint s tipiziranom shemom i snažnim toolingom.
- Trebate kompoziciju podataka iz više izvora iza jednog API-ja.
GraphQL zamke na koje treba paziti:
- N+1 upiti bez DataLoader-style batchanja.
- Zlouporaba upita bez complexity/cost limita.
- Složenije cacheiranje (obično cacheirate na razini field/entity ili s persisted queries).
⚠️ Upozorenje: Ako odaberete GraphQL, rano uvedite limite dubine/kompleksnosti upita i persisted queries. Neograničeni upiti su čest uzrok produkcijskih incidenata.
Brza matrica odluke#
| Kriterij | REST | GraphQL |
|---|---|---|
| Jednostavnost integracije | Visoka | Srednja |
| Pogodnost za HTTP caching/CDN | Visoka | Srednja |
| Oblici podataka po mjeri klijenta | Srednja | Visoka |
| Rizik skupih upita | Nizak | Visok (bez limita) |
| Zrelost toolinga kod vendora | Vrlo visoka | Visoka |
Praktično pravilo: default na REST za third-party providere; izaberite GraphQL kad vaš proizvod ima više klijenata i vi kontrolirate serversku implementaciju.
# Autentikacija: sigurni obrasci koji prežive produkciju#
Većina modernih API-ja koristi OAuth 2.0 (client credentials ili authorization code) ili potpisane tokene (JWT). Cilj je spriječiti curenje tokena, rotirati vjerodajnice i izbjeći nepotrebne privilegije.
Opcije autentikacije koje ćete viđati u 2026.#
| Metoda | Tipičan slučaj | Gdje spremiti | Napomene |
|---|---|---|---|
| API Key | Jednostavni vendor API-ji | Server env varijable | Rotirajte; ograničite po IP/referreru ako je podržano |
| OAuth 2.0 Client Credentials | Server-to-server | Server env varijable | Dohvaćajte kratkoživuće access tokene; cacheirajte do isteka |
| OAuth 2.0 Authorization Code (PKCE) | Integracije vezane uz korisnika | Sigurno spremište sessiona | Koristite refresh tokene; obradite revocation |
| JWT (vi ih izdajete) | Vaši vlastiti API-ji | HttpOnly kolačići / auth headeri | Validirajte signature, issuer, audience, exp |
Next.js API ruta: OAuth client credentials s cacheiranjem tokena#
Ovaj primjer dohvaća access token s OAuth servera i cacheira ga u memoriji (dovoljno za jedan Node proces). Za serverless/multi-instance okruženja koristite Redis ili KV store.
// app/api/_lib/oauth.ts
type TokenResponse = { access_token: string; expires_in: number; token_type: string };
let cachedToken: { value: string; expiresAt: number } | null = null;
export async function getAccessToken() {
const now = Date.now();
if (cachedToken && cachedToken.expiresAt > now + 30_000) return cachedToken.value;
const res = await fetch(process.env.OAUTH_TOKEN_URL!, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: process.env.OAUTH_CLIENT_ID!,
client_secret: process.env.OAUTH_CLIENT_SECRET!,
scope: "read:orders",
}),
});
if (!res.ok) throw new Error(`token_request_failed:${res.status}`);
const data = (await res.json()) as TokenResponse;
cachedToken = { value: data.access_token, expiresAt: now + data.expires_in * 1000 };
return cachedToken.value;
}💡 Savjet: Preferirajte kratkoživuće access tokene. Ako token procuri, “blast radius” je manji nego kod dugotrajnih ključeva.
Nikad ne izlažite tajne pregledniku#
U Next.js, sve što je prefiksirano s NEXT_PUBLIC_ može završiti u client bundleovima. Third-party ključeve držite server-only i pozivajte third-party API kroz vaše API rute.
# Obrada grešaka: dizajn za retry, brži debug i UX#
Korisnike ne zanima da je “Stripe vratio 502.” Njih zanima da je plaćanje palo i je li sigurno pokušati ponovno. Inženjere zanima brzo utvrditi je li problem vaš bug, vendor incident ili rate limiting.
Produkcijski spremna taksonomija grešaka#
Koristite mali skup konzistentnih klasa grešaka:
| Kategorija | Primjeri | Retry? | Tipični HTTP |
|---|---|---|---|
| Validacija | nedostaju parametri, invalid state | Ne | 400 / 422 |
| Auth | neispravan token, nedostaje scope | Ne (dok se ne popravi) | 401 / 403 |
| Not found | resurs ne postoji | Ne | 404 |
| Rate limited | 429, quota exceeded | Da (nakon odgode) | 429 |
| Prolazno (transient) | timeouts, 502/503, mreža | Da (backoff) | 502 / 503 / 504 |
| Nepoznato | neklasificirani kvarovi | Možda | 500 |
Next.js API ruta: konzistentan error envelope + request ID#
Ova ruta poziva upstream REST API i vraća konzistentan oblik greške. Također prosljeđuje request ID radi sljedivosti.
// app/api/orders/[id]/route.ts
import { NextResponse } from "next/server";
import { getAccessToken } from "../../_lib/oauth";
function requestIdFrom(req: Request) {
return req.headers.get("x-request-id") ?? crypto.randomUUID();
}
export async function GET(req: Request, ctx: { params: Promise<{ id: string }> }) {
const requestId = requestIdFrom(req);
const { id } = await ctx.params;
if (!id) {
return NextResponse.json(
{ error: { code: "VALIDATION_ERROR", message: "Missing order id", requestId } },
{ status: 422, headers: { "x-request-id": requestId } }
);
}
try {
const token = await getAccessToken();
const upstream = await fetch(`${process.env.UPSTREAM_API_URL!}/orders/${id}`, {
headers: { Authorization: `Bearer ${token}`, "x-request-id": requestId },
cache: "no-store",
signal: AbortSignal.timeout(8_000),
});
if (upstream.status === 404) {
return NextResponse.json(
{ error: { code: "NOT_FOUND", message: "Order not found", requestId } },
{ status: 404, headers: { "x-request-id": requestId } }
);
}
if (!upstream.ok) {
return NextResponse.json(
{
error: {
code: "UPSTREAM_ERROR",
message: "Upstream API error",
status: upstream.status,
requestId,
},
},
{ status: 502, headers: { "x-request-id": requestId } }
);
}
const data = await upstream.json();
return NextResponse.json({ data, requestId }, { headers: { "x-request-id": requestId } });
} catch (err) {
const message = err instanceof Error ? err.message : "unknown_error";
const isTimeout = message.includes("timeout") || message.includes("AbortSignal");
return NextResponse.json(
{
error: {
code: isTimeout ? "TIMEOUT" : "INTEGRATION_ERROR",
message: isTimeout ? "Upstream request timed out" : "Integration failed",
requestId,
},
},
{ status: 504, headers: { "x-request-id": requestId } }
);
}
}Zašto je ovo važno:
- Frontend može prikazati konkretnije poruke i odlučiti je li “Pokušaj ponovno” prikladno.
- Podrška može tražiti
requestIdi brzo pronaći logove. - Engineering može razlikovati 404 vs upstream 5xx vs timeout.
# Retry, timeouti i idempotency (napravite ovo ili platite kasnije)#
Retry je potreban za prolazne greške, ali mora biti kontroliran. U 2026. mnogi API-ji se naplaćuju po zahtjevu; nekontrolirani retry može izravno povećati trošak.
Pravila koja rade u produkciji#
- Uvijek postavite timeout. Čest baseline je 5–10 sekundi za upstream pozive, niže za UX-kritične putanje.
- Retryajte samo idempotentne operacije (GET/HEAD sigurno; POST samo s idempotency ključevima).
- Koristite eksponencijalni backoff + jitter kako biste izbjegli sinkronizirane retry oluje.
- Ograničite retry na 2–3 pokušaja za user-facing zahtjeve.
Next.js helper: fetch s retryjem + backoffom#
Retry neka bude kratak i eksplicitan. Ovaj helper retrya samo na mrežne greške, 429 i 5xx.
// app/api/_lib/fetchWithRetry.ts
export async function fetchWithRetry(
url: string,
init: RequestInit,
opts: { retries?: number; timeoutMs?: number } = {}
) {
const retries = opts.retries ?? 2;
const timeoutMs = opts.timeoutMs ?? 8000;
for (let attempt = 0; attempt <= retries; attempt++) {
try {
const res = await fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
if (res.ok) return res;
const retryable = res.status === 429 || (res.status >= 500 && res.status <= 599);
if (!retryable || attempt === retries) return res;
} catch (e) {
if (attempt === retries) throw e;
}
const backoff = Math.round((200 * 2 ** attempt) * (0.7 + Math.random() * 0.6));
await new Promise((r) => setTimeout(r, backoff));
}
throw new Error("unreachable");
}Idempotency ključevi za siguran retry POST-a#
Ako provider podržava idempotency (Stripe-style), generirajte ključ po logičkoj radnji i spremite ga uz order/payment zapis. Ako klijent retrya (refresh, double-click), nećete dvaput naplatiti ili dvaput kreirati resurse.
# Rate limiting: zaštitite aplikaciju i poštujte providere#
Rate limiting ima dvije strane:
- 1Inbound: zaštitite svoje Next.js API rute od zlouporabe i slučajnih burstova.
- 2Outbound: spriječite da “zatrpate” third-party API i dobijete 429.
U 2026. veliki provideri obično nameću per-minute kvote i burst limite. Mnogi također vraćaju Retry-After headere na 429.
Implementirajte inbound rate limiting u Next.js API rutama (jednostavan baseline)#
Za produkciju koristite Redis/KV za dijeljene brojače. Ovaj in-memory primjer je koristan za brzu zaštitu u jednoj instanci.
// app/api/_lib/rateLimit.ts
const buckets = new Map<string, { count: number; resetAt: number }>();
export function rateLimit(key: string, limit: number, windowMs: number) {
const now = Date.now();
const bucket = buckets.get(key);
if (!bucket || bucket.resetAt <= now) {
buckets.set(key, { count: 1, resetAt: now + windowMs });
return { ok: true, remaining: limit - 1, resetAt: now + windowMs };
}
if (bucket.count >= limit) return { ok: false, remaining: 0, resetAt: bucket.resetAt };
bucket.count += 1;
return { ok: true, remaining: limit - bucket.count, resetAt: bucket.resetAt };
}Upotreba u ruti:
// app/api/public/search/route.ts
import { NextResponse } from "next/server";
import { rateLimit } from "../../_lib/rateLimit";
export async function GET(req: Request) {
const ip = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? "unknown";
const rl = rateLimit(`search:${ip}`, 60, 60_000);
if (!rl.ok) {
const retryAfter = Math.max(1, Math.ceil((rl.resetAt - Date.now()) / 1000));
return NextResponse.json(
{ error: { code: "RATE_LIMITED", message: "Too many requests" } },
{ status: 429, headers: { "retry-after": String(retryAfter) } }
);
}
return NextResponse.json({ data: { ok: true }, rateLimit: { remaining: rl.remaining } });
}Outbound throttling: ne dopustite da vaša aplikacija DDOS-a vendora#
Ako zovete providera s poznatim limitom (npr. 10 req/s), nametnite queue ili token bucket na svojoj strani—posebno za batch jobove i webhooks. Ako već koristite n8n za automatizaciju, izrada throttled workflowa često je brža nego ručno “rolanje” queuea.
ℹ️ Napomena: U serverless deploymentima s više instanci, outbound throttling mora biti centraliziran (Redis/KV/queue). Throttling po instanci neće spriječiti prekoračenje agregiranih limita.
# Primjer REST integracije: Next.js API ruta kao stabilna fasada#
Česta najbolja praksa je izložiti stabilan interni endpoint (vaš ugovor) i prilagodbe na promjene third-party providera raditi iza njega. To smanjuje churn u frontendu kad vendor promijeni polja ili formate grešaka.
Primjer: normalizirajte upstream response i sigurno cacheirajte#
Ako se podaci rijetko mijenjaju, dodajte cache na BFF razini. Za user-specific resurse izbjegavajte zajedničke cacheve osim ako ih pravilno ključate.
// app/api/catalog/route.ts
import { NextResponse } from "next/server";
import { fetchWithRetry } from "../_lib/fetchWithRetry";
export async function GET() {
const res = await fetchWithRetry(`${process.env.UPSTREAM_API_URL!}/catalog`, {
headers: { "accept": "application/json", "x-api-key": process.env.UPSTREAM_API_KEY! },
next: { revalidate: 300 },
});
if (!res.ok) {
return NextResponse.json(
{ error: { code: "UPSTREAM_ERROR", message: "Catalog unavailable" } },
{ status: 502 }
);
}
const upstream = await res.json();
const items = (upstream.items ?? []).map((i: any) => ({
id: String(i.id),
title: String(i.name),
priceCents: Number(i.price_cents),
}));
return NextResponse.json({ data: { items } });
}Zašto je ovo važno:
- Vi kontrolirate response contract (
id,title,priceCents) čak i ako provider promijeni nazive polja. revalidate: 300može smanjiti broj upstream poziva i do 95%+ za često posjećene katalog stranice, ovisno o prometnim obrascima.
# Primjer GraphQL integracije: persisted queries i sigurniji fetching#
Ako konzumirate GraphQL API, izbjegavajte slanje proizvoljnih upita s klijenta. Preferirajte server-side integraciju (BFF) i koristite persisted queries gdje je podržano.
Primjer: server-side GraphQL POST s varijablama#
// app/api/profile/route.ts
import { NextResponse } from "next/server";
import { getAccessToken } from "../_lib/oauth";
const query = `
query Profile($id: ID!) {
user(id: $id) { id name email }
}
`;
export async function GET(req: Request) {
const userId = new URL(req.url).searchParams.get("id");
if (!userId) {
return NextResponse.json(
{ error: { code: "VALIDATION_ERROR", message: "Missing id" } },
{ status: 422 }
);
}
const token = await getAccessToken();
const res = await fetch(process.env.GRAPHQL_URL!, {
method: "POST",
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
body: JSON.stringify({ query, variables: { id: userId } }),
signal: AbortSignal.timeout(8_000),
cache: "no-store",
});
if (!res.ok) {
return NextResponse.json(
{ error: { code: "UPSTREAM_ERROR", message: "GraphQL request failed" } },
{ status: 502 }
);
}
const payload = await res.json();
if (payload.errors?.length) {
return NextResponse.json(
{ error: { code: "UPSTREAM_GRAPHQL_ERROR", message: payload.errors[0].message } },
{ status: 502 }
);
}
return NextResponse.json({ data: payload.data.user });
}Produkcijski savjeti za GraphQL potrošače:
- Koristite allowlisted/persisted queries kad god možete.
- Validirajte shape response-a; ne pretpostavljajte da
datapostoji. - Pratite latenciju i error rate po operation nameu.
# Observability: logovi, metrike i tracing koji stvarno pomažu#
Ne treba vam savršen tracing da biste povećali pouzdanost. Trebaju vam konzistentni metapodaci i nekoliko ključnih metrika.
Minimalna observability checklista#
| Signal | Što uhvatiti | Zašto je važno |
|---|---|---|
| Request ID | x-request-id proslijeđen end-to-end | Brza korelacija kroz servise |
| Timing | ukupna latencija + upstream latencija | Prepoznavanje uskih grla i regresija |
| Error kodovi | vaši stabilni kodovi (RATE_LIMITED, TIMEOUT) | Praćenje stvarnih failure modova |
| Upstream status | raspodjela 2xx/4xx/5xx | Vendor probleme vidite odmah |
| Rate limit headeri | remaining, reset | Predviđanje throttlinga prije incidenta |
Praktično pravilo logiranja: logirajte metapodatke, ne osjetljive payloadove. Ako baš morate logirati isječke payloada, redaktirajte PII i tajne.
# Česte zamke (izdanje 2026.)#
- 1Korištenje
fetch()bez timeouta — produkcijski “hangovi” postaju “random sporost.” Uvijek koristiteAbortSignal.timeout. - 2Retry POST-a bez idempotencyja — stvara duplikate i financijske incidente. Koristite idempotency ključeve ili nemojte retryati.
- 3Vraćanje raw upstream grešaka frontendu — otkriva vendor detalje i prisiljava promjene u UI-ju. Normalizirajte greške.
- 4Ignoriranje semantike 429 — tretirajte 429 kao first-class response uz podršku za
Retry-After. - 5Spremanje API ključeva u klijenta — čak i “privremeni” prečaci završe isporučeni. Tajne držite server-side kroz API rute.
- 6Bez contract testova za integracije — provideri mijenjaju stvari. Dodajte osnovne schema/contract asercije u CI za kritične endpoint-e.
# Ključne poruke#
- Držite third-party vjerodajnice na serveru koristeći Next.js API rute kao BFF i izložite stabilan interni contract.
- Birajte REST radi jednostavnosti i cacheiranja; birajte GraphQL kad trebate fleksibilne oblike podataka—i tada uvedite limite kompleksnosti i sigurnije obrasce upita.
- Implementirajte timeoute + retry s backoffom i retryajte samo retryable kvarove; dodajte idempotency ključeve za siguran retry POST-a.
- Normalizirajte greške u konzistentan envelope sa stabilnim error kodovima i prosljeđujte request ID-jeve za brži debugging.
- Rate limiting tretirajte kao zahtjev proizvoda: nametnite inbound kvote i outbound throttling te obradite 429 uz
Retry-After.
# Zaključak#
Pouzdana API integracija u 2026. manje je stvar prvog uspješnog zahtjeva, a više onoga što se događa pod opterećenjem, tijekom vendor incidenata i kad promet naglo skoči. Ako implementirate BFF u Next.js, standardizirate auth, timeoute, retry, error envelope i rate limiting, isporučit ćete integracije koje ostaju stabilne kako proizvod raste.
Ako želite da Samioda dizajnira i implementira vaš integration sloj (web + mobile, uključujući automation workflowe), javite se putem web & mobile development. Za Next.js osnove prije nego krenete, koristite Uvod u Next.js.
FAQ
Više iz kategorije Web razvoj
Sve →Najbolji headless CMS u 2026: Sanity vs Strapi vs Contentful (i još 2)
Praktična usporedba u 2026. top 5 headless CMS opcija—Sanity, Strapi, Contentful, Directus i Storyblok—s fokusom na developer experience, Next.js integraciju, značajke i cijene.
Progresivne web aplikacije (PWA): Potpuni vodič za 2026.
Praktičan vodič za progressive web app (PWA) u 2026.: koncepti, poslovne prednosti u odnosu na nativne aplikacije i implementacija korak‑po‑korak u Next.js-u s manifestom i primjerima koda za service worker.
Optimizacija performansi web stranice: Potpuni checklist (Next.js + Core Web Vitals) za 2026.
Praktičan, production-ready checklist za optimizaciju performansi web stranice u Next.js-u: Core Web Vitals, slike, lazy loading, CDN i caching—uz prije/poslije metrike i copy-paste konfiguraciju.
Trebate pomoć s projektom?
Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.
Povezani članci
Progresivne web aplikacije (PWA): Potpuni vodič za 2026.
Praktičan vodič za progressive web app (PWA) u 2026.: koncepti, poslovne prednosti u odnosu na nativne aplikacije i implementacija korak‑po‑korak u Next.js-u s manifestom i primjerima koda za service worker.
Zašto Je Next.js Najbolji Framework za SEO u 2026
Saznajte zašto Next.js dominira SEO performansama u 2026. Server-side rendering, Core Web Vitals, strukturirani podaci i stvarne usporedbe performansi.
Najbolji headless CMS u 2026: Sanity vs Strapi vs Contentful (i još 2)
Praktična usporedba u 2026. top 5 headless CMS opcija—Sanity, Strapi, Contentful, Directus i Storyblok—s fokusom na developer experience, Next.js integraciju, značajke i cijene.