Firebase HTTP Functions

HTTP functions respond to web requests just like a REST API endpoint. When someone sends a GET, POST, PUT, or DELETE request to the function's URL, Firebase runs your function code. HTTP functions are the foundation for building custom APIs, webhooks, and server-side form handlers.

Creating an HTTP Function

const { onRequest } = require("firebase-functions/v2/https");

exports.greet = onRequest((req, res) => {
  const name = req.query.name || "stranger";
  res.status(200).json({ message: "Hello, " + name + "!" });
});

The function receives two arguments:

  • req — the HTTP request object (contains query params, body, headers, method)
  • res — the HTTP response object (used to send back data)

Handling Different HTTP Methods

const { onRequest } = require("firebase-functions/v2/https");

exports.handleRequests = onRequest((req, res) => {
  if (req.method === "GET") {
    res.status(200).json({ data: "Here is your data" });
  } else if (req.method === "POST") {
    const body = req.body;
    console.log("Received:", body);
    res.status(201).json({ success: true, received: body });
  } else {
    res.status(405).json({ error: "Method not allowed" });
  }
});

Using Express.js for Routing

For APIs with multiple endpoints, use Express.js inside a single Cloud Function. This creates a full REST API with one function deployment:

const { onRequest } = require("firebase-functions/v2/https");
const express = require("express");
const admin = require("firebase-admin");

admin.initializeApp();
const db = admin.firestore();
const app = express();
app.use(express.json());

// GET all posts
app.get("/posts", async (req, res) => {
  const snapshot = await db.collection("posts").get();
  const posts = snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
  res.json(posts);
});

// GET one post
app.get("/posts/:id", async (req, res) => {
  const doc = await db.collection("posts").doc(req.params.id).get();
  if (!doc.exists) {
    return res.status(404).json({ error: "Post not found" });
  }
  res.json({ id: doc.id, ...doc.data() });
});

// POST create a post
app.post("/posts", async (req, res) => {
  const { title, body, authorId } = req.body;
  const ref = await db.collection("posts").add({
    title,
    body,
    authorId,
    createdAt: admin.firestore.FieldValue.serverTimestamp()
  });
  res.status(201).json({ id: ref.id });
});

// DELETE a post
app.delete("/posts/:id", async (req, res) => {
  await db.collection("posts").doc(req.params.id).delete();
  res.json({ deleted: req.params.id });
});

exports.api = onRequest(app);

This single exported function handles all API routes. The base URL becomes your API root: https://api-abc123-uc.a.run.app/posts

Authenticating HTTP Function Requests

Verify the caller's Firebase ID token in your function to protect endpoints:

const { onRequest } = require("firebase-functions/v2/https");
const admin = require("firebase-admin");
admin.initializeApp();

exports.secureEndpoint = onRequest(async (req, res) => {
  const authHeader = req.headers.authorization || "";
  const token = authHeader.replace("Bearer ", "");

  if (!token) {
    return res.status(401).json({ error: "No token provided" });
  }

  try {
    const decoded = await admin.auth().verifyIdToken(token);
    // Proceed — decoded.uid is the verified user ID
    res.json({ message: "Hello, " + decoded.email });
  } catch (error) {
    res.status(403).json({ error: "Invalid token" });
  }
});

The client sends the Firebase ID token in the Authorization header:

const token = await auth.currentUser.getIdToken();
const response = await fetch("https://your-function-url/secureEndpoint", {
  headers: { Authorization: "Bearer " + token }
});

CORS for Browser Requests

Browsers block requests to different origins by default. Add CORS headers so your web app can call your functions:

const { onRequest } = require("firebase-functions/v2/https");
const cors = require("cors")({ origin: true });

exports.myCorsFunction = onRequest((req, res) => {
  cors(req, res, () => {
    res.json({ message: "CORS enabled!" });
  });
});

Install the CORS package: npm install cors inside the functions/ folder.

Calling Callable Functions from Client Code

For browser-to-function calls that need authentication, use onCall instead of onRequest. Callable functions handle auth token verification automatically:

// functions/index.js
const { onCall } = require("firebase-functions/v2/https");

exports.createPost = onCall(async (request) => {
  if (!request.auth) {
    throw new Error("Must be logged in.");
  }

  const { title, body } = request.data;
  const db = admin.firestore();
  const ref = await db.collection("posts").add({
    title,
    body,
    authorId: request.auth.uid
  });

  return { postId: ref.id };
});
// Client code
import { getFunctions, httpsCallable } from "firebase/functions";
const functions = getFunctions(app);
const createPost = httpsCallable(functions, "createPost");

const result = await createPost({ title: "Hello", body: "World" });
console.log("Created post:", result.data.postId);

Key Takeaway

HTTP functions turn Firebase into a REST API backend. Use onRequest with Express.js for REST APIs and onCall for authenticated browser-to-function calls that Firebase handles end-to-end. Always verify ID tokens in onRequest functions that need authentication. Add CORS handling for browser requests. Deploy with firebase deploy --only functions and test locally with the emulator.

Leave a Comment

Your email address will not be published. Required fields are marked *