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.
