Cloud Functions — a Firebase szerveroldala
- cloud-functionsBevezető
01 — Bevezetés
Mi a Cloud Functions?
Mit jelent a serverless?
Mire jó konkrétan?
- Adatreakciók — egy Firestore dokumentum változásakor egy másik dokumentumot frissít, vagy emailt küld.
- Webhookok fogadása — Stripe, GitHub, Slack, Twilio mind webhook-ot küld, ezeket egy
onRequestCloud Function fogadja. - Időzített feladatok — napi statisztika-számolás, heti emlékeztető emailek a felhasználóknak.
- Privilegizált műveletek — olyan dolgok, amiket a kliens nem futtathat (pl. saját szerepkör beállítása), de egy bejelentkezett felhasználó kérésére szervernek kell elvégeznie.
- Külső API-k hívása — fizetés, email-szolgáltatás, mesterséges intelligencia, térképszolgáltatás. Ezek az API-kulcsok nem szabad hogy a kliensbe kerüljenek.
Mire nem jó?
$0.0000004, egy GB-másodperc memória kb. $0.0000025.- cloud-functionsKözéphaladó
02 — Két generáció
v1 és v2 — melyik, mikor?
A különbség lényege
| Szempont | v1 (eredeti) | v2 (Cloud Run alapú) |
|---|---|---|
| Maximum memória | 8 GB | 32 GB |
| Maximum futási idő | 9 perc (HTTP) / 9 perc (esemény) | 60 perc (HTTP) / 9 perc (esemény) |
| Egy példányon párhuzamos kérések | 1 | akár 1000 |
| Hideg indulás | ~2-5 mp | ~0.5-1.5 mp |
| Min példányszám | — | állítható (mindig fut) |
| Több régió | Igen | Igen |
| VPC kapcsolat | Igen | Igen, jobb integrációval |
| Eventarc események | Korlátozott | Teljes (sokkal több típus) |
Mikor melyik?
v1 vagy v2?
Új projekt, HTTP API, hosszabb futási idő, párhuzamos kérések, nagyobb memória. A Firebase dokumentáció minden új példája v2-vel készül, és Google ezt fejleszti aktívan.
Régi projekt karbantartása. Egyes Auth-trigger események, amik még csak v1-ben támogatottak (onCreate, onDelete a felhasználói rekordokra). Speciális Realtime Database trigger-ek.
v2 importok és szintaxis
// V2: separate imports per type
import { onRequest, onCall } from 'firebase-functions/v2/https';
import { onDocumentCreated } from 'firebase-functions/v2/firestore';
import { onSchedule } from 'firebase-functions/v2/scheduler';
// HTTP endpoint
export const api = onRequest(
{ region: 'europe-west1', memory: '512MiB' },
(req, res) => {
res.json({ ok: true });
}
);
// Firestore trigger
export const onOrderCreated = onDocumentCreated(
{ document: 'orders/{orderId}', region: 'europe-west1' },
async (event) => {
const order = event.data?.data();
// ...
}
);
- cloud-functionsKözéphaladó
03 — Indítók
Indítók — mire reagáljon a függvény?
Az indító-típusok áttekintése
| Indító | Mikor fut | Tipikus felhasználás |
|---|---|---|
onRequest | HTTP-kérés érkezik | REST API, webhook fogadás |
onCall | A kliens-SDK httpsCallable hívása | Privilegizált műveletek, Firebase auth-tal |
onDocumentCreated/Updated/Deleted | Firestore-dokumentum változik | Aggregátum karbantartása, denormalizáció |
onObjectFinalized | Storage-fájl feltöltve | Kép-méretezés, vírus-keresés, metaadat |
onUserCreated (v1) | Új felhasználó regisztrál | Üdvözlő email, alapértelmezett dokumentumok |
onSchedule | Cron-szerű időzítés | Napi statisztika, heti emlékeztető |
onMessagePublished (Pub/Sub) | Üzenet érkezik egy témára | Háttér-feldolgozás, sorok |
onTaskDispatched (Cloud Tasks) | Időzített / sorba állított feladat | Késleltetett munkavégzés |
onRequest vs. onCall — a leggyakoribb választás
onRequest vagy onCall?
Bárhonnan hívható, plain HTTP. Webhookok fogadásához, vagy ha külső rendszerből jön a kérés. Az auth-ot magadnak kell ellenőrizni.
Csak a Firebase kliens-SDK-ból hívható. A Firebase Auth tokent magától ellenőrzi és átadja, és az App Check is automatikusan érvényesül. Egyszerűbb biztonság, de zártabb.
Egy onCall példa
import { onCall, HttpsError } from 'firebase-functions/v2/https';
import { getAuth } from 'firebase-admin/auth';
export const setUserRole = onCall(
{ region: 'europe-west1', enforceAppCheck: true },
async (request) => {
// request.auth is automatically read from the JWT
if (request.auth?.token.role !== 'admin') {
throw new HttpsError('permission-denied', 'Admin only');
}
const { uid, role } = request.data;
await getAuth().setCustomUserClaims(uid, { role });
return { success: true };
}
);
import { getFunctions, httpsCallable } from 'firebase/functions';
const functions = getFunctions(app, 'europe-west1');
const setUserRole = httpsCallable(functions, 'setUserRole');
await setUserRole({ uid: 'usr_abc', role: 'editor' });
- cloud-functionsKözéphaladó
04 — Hideg indulás
Hideg indulás — az eltűnő szerver ára
Mi történik egy hideg indulásnál?
- Példány készítés — a Google létrehoz egy új konténert.
- Node.js indítás — a futtatókörnyezet betöltődik.
- A te kódod betöltődik — minden import, ami a fájl tetején van, lefut.
- Globális inicializáció — Firebase Admin SDK, adatbázis-kapcsolatok, külső kliens-könyvtárak.
- A függvény végre lefut — a kérés feldolgozása megkezdődik.
Mit tehetsz ellene?
1) Min példányszám beállítása
minInstances: 1, akkor mindig fut legalább egy példány — soha nincs hideg indulás. Ára: ezt a futó példányt is fizetni kell, akkor is, ha senki sem használja:export const api = onRequest(
{
region: 'europe-west1',
memory: '512MiB',
minInstances: 1, // always 1 instance running
},
(req, res) => { /* ... */ }
);
$10-15 24/7 futtatás esetén. Egy felhasználó-szembesülő API-nál ez gyakran megéri a látható UX-javulásért. Egy belső admin-funkciónál (amit napi 5-ször használnak) felesleges.2) Karcsú importok
- Specifikus importok —
import { onCall } from 'firebase-functions/v2/https'a teljesfirebase-functionsbehúzása helyett. - Lusta betöltés — egy nagy könyvtár (pl. PDF-generáló, képfeldolgozó), amit csak ritkán használsz, dynamic import-tal töltsd be (
const { generatePdf } = await import('./pdf')). - Ne tartsd a teljes Lodash-t — ha csak
_.chunkkell, importáld direkt:import chunk from 'lodash/chunk'.
3) Globális változók újrahasznosítása
import { initializeApp } from 'firebase-admin/app';
import { getFirestore } from 'firebase-admin/firestore';
import Stripe from 'stripe';
// Initialised once, reused across all warm calls
initializeApp();
const db = getFirestore();
const stripe = new Stripe(process.env.STRIPE_KEY!);
export const api = onRequest(async (req, res) => {
// db and stripe are ready — no per-call initialisation
await db.collection('logs').add({ ts: new Date() });
res.json({ ok: true });
});
- cloud-functionsKözéphaladó
05 — Beállítások
Memória, CPU, időtúllépés
A három fő beállítás
| Beállítás | Alapérték | Maximum | Mire befolyásol |
|---|---|---|---|
memory | 256 MiB | 32 GiB (v2) | Memória + CPU együtt — nagyobb memória, gyorsabb CPU |
timeoutSeconds | 60 mp | 3600 mp (v2 HTTP) | Mennyi ideig futhat a függvény, mielőtt elhal |
cpu | auto (memóriához kötve) | 8 vCPU | Kifejezett CPU-kérés (csak v2) |
Mennyi memória elég?
- 128-256 MiB — egyszerű API-hívás, kis Firestore-művelet, webhook fogadás. Ez a legtöbb függvény.
- 512 MiB — közepes méretű JSON-feldolgozás, néhány külső API-hívás.
- 1-2 GiB — kép-méretezés (Sharp), kis PDF-generálás, közepes ML-modell.
- 4 GiB+ — nagy fájlok feldolgozása (videó, archívum), nehéz ML.
Időtúllépés
- Egy nagy adatbázis-feltöltés (több ezer Firestore-művelet) — 540 mp.
- Egy hosszú külső API-hívás (pl. videó-feldolgozás) — 60 perc (csak v2 HTTP).
- Egy időzített napi statisztika sok millió rekorddal — 540 mp.
- cloud-functionsKözéphaladó
06 — Párhuzamosság
Párhuzamosság — egy példány, sok kérés
Mit jelent a concurrency?
- Kevesebb hideg indulás — a meglévő példány felveszi a szomszédos kéréseket, nem indít újat.
- Olcsóbb futás — a memória és a CPU időt csak a tényleges aktivitás alatt fizeted, és egy példányon több kérés osztozik rajta.
export const api = onRequest(
{
region: 'europe-west1',
memory: '512MiB',
concurrency: 80, // default
},
(req, res) => { /* ... */ }
);
Mire kell figyelni?
// BAD — the variable is shared across requests!
let currentUserId: string | null = null;
export const api = onCall(async (request) => {
currentUserId = request.auth?.uid; // ← race condition
await someAsync();
return { user: currentUserId }; // ← may have flipped to another user
});
// GOOD — each request has its own local variable
export const api = onCall(async (request) => {
const userId = request.auth?.uid;
await someAsync();
return { user: userId };
});
concurrency: 10 vagy akár 1. Ha minden kérés egyenként 50 MB-ot fogyaszt, akkor 80 párhuzamossal együtt 4 GB kell — már a 32 GiB-os tetőtől sem messze.- cloud-functionsKözéphaladó
07 — Régiók
Régiók és telepítés
A régió-választás szempontjai
- A felhasználók közelsége — minden 1000 km extra távolság kb. 10 ms késleltetés. EU-s felhasználóknál
europe-west1(Belgium) vagyeurope-west3(Frankfurt) az alap. - A többi Firebase-szolgáltatás régiója — ha a Firestore
europe-west3-ban van, a Cloud Functions is legyen ott. Az inter-régiós forgalom drágább és lassabb. - Adat-szuverenitás — GDPR alatt az EU-állampolgárok adata nem szabad, hogy az EU-n kívülre menjen feldolgozásra. Ez gyakran csak EU-s régiókat enged.
- Régió-eltérések — egyes régiók nem támogatnak minden szolgáltatást vagy minden Firebase-funkciót. A
us-central1a legteljesebb, de EU-s ügyfelekhez nem ideális.
Több régiós telepítés
export const api = onRequest(
{ region: ['europe-west1', 'us-central1', 'asia-east1'] },
(req, res) => { /* ... */ }
);
A telepítés folyamata
# Deploy every function
$ firebase deploy --only functions
# Just one specific function (faster)
$ firebase deploy --only functions:setUserRole
# Just one "group" (if you've defined groups)
$ firebase deploy --only functions:webhooks
firebase functions:secrets:set STRIPE_KEY. A függvény belsejében pedig process.env.STRIPE_KEY alatt elérhető. A secret titkosítva tárolódik, és csak a futási idő alatt férhető hozzá.- cloud-functionsKözéphaladó
08 — Trigger-láncok
Trigger-láncok és körkörösség
A klasszikus végtelen ciklus
// BAD — every update fires the trigger again
export const updateTimestamp = onDocumentUpdated(
'orders/{orderId}',
async (event) => {
await event.data?.after.ref.update({
lastModified: new Date(),
}); // ← re-triggers itself!
}
);
Megoldás 1: változás-figyelés
export const updateTimestamp = onDocumentUpdated(
'orders/{orderId}',
async (event) => {
const before = event.data?.before.data();
const after = event.data?.after.data();
// Only update when something "real" actually changed
if (before?.status === after?.status &&
before?.total === after?.total) {
return; // do nothing
}
await event.data?.after.ref.update({
lastModified: new Date(),
});
}
);
Megoldás 2: különálló kollekció
/orders-ben, a számlált aggregátumok a /orderStats-ban:export const updateOrderStats = onDocumentWritten(
'orders/{orderId}',
async (event) => {
// Writes to a different collection — won't re-trigger
await db.doc('orderStats/global').set({
lastChangeAt: new Date(),
total: FieldValue.increment(1),
}, { merge: true });
}
);
Trigger-lánc: hosszú láncok
| Lépés | Indító | Mit csinál |
|---|---|---|
| 1 | onDocumentCreated('orders/{id}') | Visszaigazoló email küldése |
| 2 | onDocumentCreated('orders/{id}') | Admin értesítés (push, Slack) |
| 3 | onDocumentWritten('orders/{id}') | Aggregátum frissítése |
| 4 | onSchedule('every 24 hours') | Napi statisztika |
- cloud-functionsKözéphaladó
09 — Hibakezelés
Hibakezelés és újrapróbálkozás
A function típus határozza meg a viselkedést
| Típus | Hibakezelés alapból | Újrapróba |
|---|---|---|
HTTP (onRequest, onCall) | A kliens kapja meg a hibát | Nincs (a kliensnek kell újrapróbálni) |
| Esemény-trigger (Firestore, Storage, Auth) | Visszaadod a hibát, az event érkezik újra | Igen, max 7 napon át |
| Pub/Sub | Visszaadott hiba esetén újra | Igen, beállítható ack-deadline |
| Időzített (Scheduler) | Hiba → következő futás várható | Csak a következő ütemben |
Idempotencia — kötelező esemény-triggereknél
// BAD — every run bumps the counter
export const countOrder = onDocumentCreated(
'orders/{id}',
async (event) => {
await db.doc('stats/orders').update({
count: FieldValue.increment(1), // ← runs twice → counts twice
});
}
);
// GOOD — uses event.id to prevent duplication
export const countOrder = onDocumentCreated(
'orders/{id}',
async (event) => {
const processedRef = db.doc(`processed/${event.id}`);
const alreadyDone = (await processedRef.get()).exists;
if (alreadyDone) return; // already handled
await db.runTransaction(async (tx) => {
tx.update(db.doc('stats/orders'), {
count: FieldValue.increment(1),
});
tx.set(processedRef, { at: new Date() });
});
}
);
Strukturált logok
console.log kimenete csak szöveges, nehezen kereshető. A firebase-functions/logger strukturált JSON-t ír, amiben szűrhetsz mezőkre:import { logger } from 'firebase-functions/v2';
logger.info('New order received', {
orderId: order.id,
userId: order.userId,
total: order.total,
});
logger.error('Stripe API error', {
err: error.message,
orderId: order.id,
});
jsonPayload.orderId="ord_123"-ra, és pontosan azt a hívást kapod meg.- emulatorKözéphaladó
10 — Tesztelés
Tesztelés helyben emulátorral
Az emulátor indítása
# One-time setup
$ firebase init emulators
# Start (Functions + Firestore + Auth)
$ firebase emulators:start
# Just one specific emulator
$ firebase emulators:start --only functions,firestore
http://localhost:4000), ahol látod a futó függvényeket, a Firestore tartalmát, az Auth-felhasználókat. A logok valós időben jönnek a terminálban.Mire jó és mire nem?
- Jó: üzleti logika tesztelése, trigger-láncok kipróbálása, hibakezelés, idempotencia ellenőrzése. A tesztek gyorsak, és nem kerül semmibe.
- Kevésbé jó: a teljesítmény-mérés (a helyi gép másképp mér), és néhány Google Cloud-szolgáltatás (mint az Eventarc) nem 100%-ban emulált.
Egységtesztek a függvényekhez
firebase-functions-test SDK-val unit-tesztet is írhatsz, ami nem indít emulátort, csak a függvényt magát teszteli, mock kontextussal:import { initializeApp } from 'firebase-functions-test';
import { setUserRole } from './index';
const testEnv = initializeApp();
describe('setUserRole', () => {
it('an admin user can set the role', async () => {
const wrapped = testEnv.wrap(setUserRole);
const result = await wrapped({
data: { uid: 'usr_abc', role: 'editor' },
auth: { uid: 'admin_uid', token: { role: 'admin' } },
});
expect(result.success).toBe(true);
});
});
firebase emulators:exec "npm test" parancs elindítja az emulátort, lefuttatja a teszteket, majd leállítja. Egy átlagos projektnél a CI 1-2 perc alatt végez.- cloud-functionsKözéphaladó
11 — Anti-minták
Hat klasszikus hiba
1) A „mindenes" függvény
onRequest, ami az egész app backend-jét egyetlen függvényben kezeli (Express-es route-okkal belül). Hideg indulása lassú, hibakeresés nehéz, és a partikuláris szolgáltatás-szintű kontroll (memória, régió) elveszik. Bontsd külön logikus egységekre.2) Túl sok kis függvény
3) Szinkron várakozás külső API-ra
// BAD — the user waits, the function pays
export const placeOrder = onCall(async (request) => {
const order = await createOrder();
await sendConfirmationEmail(order); // 5 sec
await notifySlack(order); // 3 sec
await syncToCRM(order); // 8 sec
return order;
});
// GOOD — returns fast; the rest runs in the background via a Firestore trigger
export const placeOrder = onCall(async (request) => {
const order = await createOrder();
return order; // 200 ms
});
4) Nincs idempotencia esemény-triggereknél
5) Globális állapot a párhuzamos kérések közt
6) Élesben tesztelés
A Cloud Functions akkor jó, ha minden függvény egy fókuszált feladatot végez, és a hibák utáni tisztítást automatikusan megoldja. Aki erre nem figyel, az fél év múlva 200 különböző függvény és kiszámíthatatlan számla között találja magát.
12 — Konklúzió
Mire való a Cloud Functions, és mire nem?
Amire jó
- Adat-reakciók (Firestore, Storage, Auth eseményekre).
- Webhookok fogadása külső szolgáltatásoktól.
- Privilegizált műveletek, amiket a kliens nem futtathat.
- Időzített háttér-feladatok (napi statisztika, heti emlékeztető).
- Kis-közepes API-réteg a frontend és a backend-szolgáltatások közt.
Amire nem
- Tartós háttér-feladatok (1 órán túli) — Cloud Run jobbnak való.
- Hosszú életű kapcsolatok (WebSocket, server-sent events) — más szolgáltatás kell.
- Magas fenntartható forgalom alacsony válaszidővel — Cloud Run + min példányszám olcsóbb lehet.
- Egy nagy „backend monolit" — több, de logikus függvénybe bontva érdemes.
Tervezési checklist élesítés előtt
- ☐v2 generációt használok, kivéve ahol kifejezetten v1 trigger kell (pl. Auth onCreate).
- ☐Minden függvénynek be van állítva a régiója (EU-s ügyfeleknél
europe-west1vagyeurope-west3). - ☐A globális inicializációk (Firebase Admin, külső kliensek) a fájl tetején vannak — nem a függvényben.
- ☐Az érzékeny adatok (API-kulcsok, jelszavak) a Secret Manager-ben vannak, nem a kódban.
- ☐A felhasználó-szembesülő API-knak van
minInstances: 1beállítása, ha a hideg indulás zavaró. - ☐A memória és időtúllépés a tényleges használatra van mérve, nem találgatva.
- ☐Az esemény-triggerek idempotensek — kezeli az event.id-t.
- ☐A trigger-láncok nem önmagukat indítják (változás-figyelés, vagy különálló kollekció).
- ☐Strukturált logokat használok, ahol a fontos azonosítók (orderId, userId) JSON-mezőként vannak.
- ☐Az App Check be van kapcsolva minden kliens-szembesülő
onCallfüggvényen. - ☐A hosszú folyamatok Pub/Sub-bal vagy Cloud Tasks-szal vannak feldarabolva, nem egyetlen függvényben futnak.
- ☐Az emulátor-tesztek a CI-ben minden push-on lefutnak, és a deploy csak zöld CI mellett történik.
A Firebase tudásbázis-cikkek sora itt egy szakaszhoz ér — az adatkezelés (Firestore, SQL Connect), a biztonság (Security Rules, Auth), a kiszolgálás (Hosting), és a szerveroldali logika (Cloud Functions) mind át lett tekintve. A következő cikkekben a kiegészítő szolgáltatások jönnek: a Storage a fájlokra, az FCM a push-értesítésekre, az App Check a csaló-védelemre, és néhány konkrét architektúra-példa egy teljes SaaS-projekten keresztül.