Here is an example of how to implement a file-based router (similar to Next.js API router), that works with Cloud Functions and URL parameters.
Assuming you want to handle a bunch of API endpoints looking as follows:
GET https://example.com/api/v1/posts
POST https://example.com/api/v1/posts
GET https://example.com/api/v1/posts/123
PATCH https://example.com/api/v1/posts/123
Create a new file for each URL route:
/v1/posts/[id].ts
/v1/posts/index.ts
/v1/authors/[username].ts
/v1/authors/index.ts
Where each route matches the following signature (example):
import { HttpFunction } from "@google-cloud/functions-framework";
/**
* Fetches a post by ID.
*
* @example GET /api/v1/posts/1
*/
export const GET: HttpFunction = async (req, res) => {
const url = `https://jsonplaceholder.typicode.com/posts/${req.params.id}`;
const fetchRes = await fetch(url);
const post = await fetchRes.json();
res.send(post);
};
/**
* Updates an existing post.
*
* @example PATCH /api/v1/posts/1
*/
export const PATCH: HttpFunction = async (req, res) => {
throw new Error("Not implemented");
};
At the top level, in the entry point, just load the list of files from the "routes" folder, e.g.
// https://vitejs.dev/guide/features.html#glob-import
const routeFiles = import.meta.glob(["./v1/**/*.ts", "!./**/*.test.ts"]);
Append a RegExp for each route derived from the filename using path-to-regexp, then iterate through the files and execute the matching handler function:
import { HttpFunction, http } from "@google-cloud/functions-framework";
import { HttpError, NotFound } from "http-errors";
import { match } from "path-to-regexp";
// https://vitejs.dev/guide/features.html#glob-import
const routeFiles = import.meta.glob(["./v1/**/*.ts", "!./**/*.test.ts"]);
const routes = Object.entries(routeFiles).map(...);
http("api", async (req, res) => {
try {
for await (const route of routes) {
const match = route.match(req.path);
if (match) {
Object.assign(req.params, match.params);
const module = await route.import();
await module?.[req.method]?.(req, res);
if (res.headersSent) return;
}
}
throw new NotFound();
} catch (err) {
// TODO: Send an error response
}
});
A unit test for each route would look similar to this (e.g. v1/posts/[id].test.ts):
import { getTestServer } from "@google-cloud/functions-framework/testing";
import supertest from "supertest";
import { expect, test } from "vitest";
import { functionName } from "../../index";
test("GET /api/v1/posts/1", async () => {
const server = getTestServer(functionName);
const res = await supertest(server)
.get("/api/v1/posts/1")
.set("Accept", "application/json");
expect({ statusCode: res.statusCode body: res.body })
.toEqual({/* ... */});
});
https://github.com/koistya/cloud-functions-routing