iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…