HTTPS Functions
HTTP Cloud Functions expose your backend logic as REST endpoints. They auto-scale, integrate with Firebase Auth tokens, and run close to your Firestore/Storage data. onRequest handles plain HTTP; onCall handles authenticated JSON RPCs from the Firebase SDK.
onRequest, onCall, auth, CORS, cold start
EXAMPLE
// 1) Setup
// firebase init functions → choose TypeScript, v2 SDK
import { onRequest, onCall, HttpsError } from 'firebase-functions/v2/https';
import { setGlobalOptions } from 'firebase-functions/v2';
import { defineSecret } from 'firebase-functions/params';
import { initializeApp } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
import { getFirestore } from 'firebase-admin/firestore';
import express from 'express';
import cors from 'cors';
setGlobalOptions({ region: 'australia-southeast1', maxInstances: 10 });
initializeApp();
// 2) onRequest — plain HTTP endpoint
export const hello = onRequest({ cors: true }, async (req, res) => {
res.json({ msg: `hello, ${req.query.name ?? 'world'}` });
});
// URL: https://australia-southeast1-<project>.cloudfunctions.net/hello
// firebase deploy --only functions:hello
// 3) onCall — authenticated JSON RPC from the Firebase SDK
export const createPost = onCall({ enforceAppCheck: true }, async (req) => {
if (!req.auth) throw new HttpsError('unauthenticated', 'Sign in required');
const { title, body } = req.data ?? {};
if (!title?.length) throw new HttpsError('invalid-argument', 'title required');
const docRef = await getFirestore().collection('posts').add({
authorId: req.auth.uid,
title,
body,
createdAt: new Date(),
});
return { id: docRef.id };
});
// Client (web)
import { getFunctions, httpsCallable } from 'firebase/functions';
const fns = getFunctions(app, 'australia-southeast1');
const createPostFn = httpsCallable(fns, 'createPost');
await createPostFn({ title: 'Hi', body: 'World' });
// onCall handles: auth token verification, CORS, JSON parse/serialise, App Check
// 4) onRequest with Express
const app = express();
app.use(cors({ origin: ['https://app.example.com'], credentials: true }));
app.use(express.json());
app.use(async (req, res, next) => {
const idToken = req.headers.authorization?.replace('Bearer ', '');
if (!idToken) return res.sendStatus(401);
try {
req.user = await getAuth().verifyIdToken(idToken);
next();
} catch (e) {
res.sendStatus(401);
}
});
app.get('/me', async (req, res) => {
res.json({ uid: req.user.uid });
});
app.post('/posts', async (req, res) => {
const { title, body } = req.body ?? {};
if (!title) return res.status(400).json({ error: 'title required' });
const doc = await getFirestore().collection('posts').add({
authorId: req.user.uid, title, body, createdAt: new Date(),
});
res.json({ id: doc.id });
});
export const api = onRequest({ region: 'australia-southeast1', cpu: 1, memory: '256MiB' }, app);
// All routes go through one function → https://.../api/me, https://.../api/posts
// 5) Resource configuration
export const heavy = onRequest({
memory: '2GiB', // 256MiB | 512MiB | 1GiB | 2GiB | 4GiB | 8GiB
cpu: 2,
timeoutSeconds: 540, // max 1 hour for v2
concurrency: 50, // one instance can handle 50 in-flight requests
maxInstances: 100,
minInstances: 1, // keep warm; reduces cold starts
ingressSettings: 'ALLOW_ALL',
region: 'us-central1',
}, async (req, res) => { /* … */ });
// 6) Secrets
const STRIPE_KEY = defineSecret('STRIPE_KEY');
export const checkout = onRequest({ secrets: [STRIPE_KEY] }, async (req, res) => {
const key = STRIPE_KEY.value();
// use key to call Stripe
});
// Set: firebase functions:secrets:set STRIPE_KEY
// Rotate: firebase functions:secrets:set STRIPE_KEY (creates new version)
// Old versions remain accessible until you destroy them
// 7) Cold start mitigations
// • minInstances: 1 — keeps one instance warm (costs ~$0.40-1.50/month per warm instance)
// • Slim deps — every MB of node_modules slows cold start
// • Use esbuild bundling for production builds
// • Lazy-load heavy modules INSIDE the handler
// • Concurrency > 1 (default 80 in v2) — fewer instances, lower cold-start ratio
// 8) CORS for onRequest
export const api2 = onRequest({
cors: ['https://app.example.com', 'https://admin.example.com'],
}, app);
// Or DIY:
import cors from 'cors';
const corsHandler = cors({ origin: true, credentials: true });
export const corsApi = onRequest((req, res) => {
corsHandler(req, res, () => app(req, res));
});
// 9) App Check — prevents abuse from unauthorised clients
// onCall: enforceAppCheck: true
// onRequest: verify header manually
import { getAppCheck } from 'firebase-admin/app-check';
app.use(async (req, res, next) => {
const token = req.header('X-Firebase-AppCheck');
if (!token) return res.sendStatus(401);
try { await getAppCheck().verifyToken(token); next(); }
catch { res.sendStatus(401); }
});
// 10) Local development — emulator
// firebase emulators:start --only functions
// Express + onRequest auto-reload on file changes (build:watch in TS)
// 11) Deployment
// firebase deploy --only functions # all functions
// firebase deploy --only functions:api # specific
// firebase functions:delete oldFunc # remove
// firebase functions:log --only api # tail logs
// 12) Monitoring
// • Cloud Logging (filter by function name)
// • Cloud Monitoring metrics: invocations, errors, execution time, memory usage
// • Set alerts on error rate > X%, p95 latency > Y ms
// • Optional: Sentry / Datadog SDKs work inside functions
// 13) Patterns
// • One Express app per logical domain (api, admin, internal)
// • Separate large async jobs into Pub/Sub triggered functions
// • Use onCall for client-facing JSON; onRequest for webhooks + REST
// • For very latency-sensitive endpoints, consider Cloud Run instead of Cloud Functions
// • Tag events with traceId for cross-service correlation
// 14) Common bugs
// • Cold start visible to end user → minInstances or pre-warm
// • Forgot to call initializeApp() → 'app already exists' or 'no app'
// • CORS allowing wildcard + credentials → browsers ignore (good); explicit allowlist
// • Heavy npm dep tree → cold starts >5s; bundle with esbuild
// • Errors in onCall returned as 500 → throw HttpsError with code for client UX
// • Region mismatch between client + function → cross-region latency surprise
// • Long-running task hits 9-minute (background) / 1-hour (HTTP) limit → split into Pub/Sub or use Cloud Run
// • Secrets read before defining → undefined; use defineSecret + secrets: array
Why it matters
Use onRequest for plain HTTP + webhooks, onCall for authenticated client RPCs. Configure region close to your Firestore data, set minInstances: 1 + lazy imports to tame cold starts, lock down secrets with defineSecret, and turn on App Check so only your own apps can hit the API.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { onRequest } from 'firebase-functions/v2/https';
export const hello = onRequest((req, res) => {
res.send('Hello from Firebase!');
});
Try it Yourself »
Discussion
Loading…