Firebase Hosting with dynamic cloud functions rewrites
Solution 1
What seems to be the main issue is that this:
{
"source": "/api",
"function": "api"
}
is actually rewriting to https://my-firebase-app.cloudfunctions.net/api/api
instead of https://my-firebase-app.cloudfunctions.net/api
like you'd expect. Notice how api
is repeated.
My solution to this is to create a main
function which hosts all other top-level functions:
const functions = require('firebase-functions');
const express = require('express');
const app = express();
app.get('/users/:userId/:userData/json', (req, res) => {
// Do App stuff here
}
// A couple more app.get in the same format
// Create "main" function to host all other top-level functions
const main = express();
main.use('/api', app);
exports.main = functions.https.onRequest(main);
You can now use this main
function to delegate to all other functions without breaking your URL structure:
{
"source": "/api/**", // "**" ensures we include paths such as "/api/users/:userId"
"function": "main"
}
And voila! You can now access all api
function calls via https://my-app.firebaseapp.com/api/users/:userId/:userData
just like you'd expect.
Calling this endpoint, now rewrites to https://my-firebase-app.cloudfunctions.net/main/api
which is technically correct. You can then add more top-level functions by simply adding them to your main
function if you wish:
const hooks = express();
main.use('/hooks/, hooks);
Solution 2
You can use a single Firebase hosting rewrite rule with a complementing rewrite middleware in Express.
-
Add a rewrite in your
firebase.json
file.{ "source": "/api/**", "function": "api" }
-
Include an app.use() middleware to rewrite the request url.
const functions = require('firebase-functions'); const express = require('express'); const API_PREFIX = 'api'; const app = express(); // Rewrite Firebase hosting requests: /api/:path => /:path app.use((req, res, next) => { if (req.url.indexOf(`/${API_PREFIX}/`) === 0) { req.url = req.url.substring(API_PREFIX.length + 1); } next(); }); app.get('/users/:userId/:userData/json', (req, res) => { // Do App stuff here }); exports[API_PREFIX] = functions.https.onRequest(app);
Related videos on Youtube
Aditya Aggarwal
A DevOps Engineer and cloud developer at India's biggest hospitality company, from one of the most competitive countries. I have an avid Interest in Android Mobile Development, Application Development as well as Cloud Development Hoping to interact with programmers and learn!
Updated on June 04, 2022Comments
-
Aditya Aggarwal almost 2 years
I have an express.js based cloud functions app on firebase in a function named
api
. To use a custom domain, I'm trying to use Firebase Hosting rewrites to route the specific URL to the function. I'm following the official documentation about cloud functions and Firebase hosting here, https://firebase.google.com/docs/hosting/functions, and have tried many combinations including the following:"rewrites": [ { "source": "/api/**", "function": "api" } ] "rewrites": [ { "source": "/api/:path1/:dat1/dat", "function": "api/:path1/:dat1/dat" } ] "rewrites": [ { "source": "/api/path1/dat1/dat", "function": "api" } ] "rewrites": [ { "source": "/api/*/*/*", "function": "api" } ]
Sadly, It doesn't seem to work for any possible combination. My express app has the following GET paths I plan on using:
'/api/users/:userId/:userData' '/api/users/:userId/:userData/json' '/api/users/:userId/'
and others similar to these. the :userId and :userData are the params in my request, as it works with express.js
The required functions work as expected in
https://my-firebase-app.cloudfunctions.net
but they do not work with
https://my-app.firebaseapp.com
Please tell me how these are supposed to work and what I'm doing wrong.
EDIT: Here is a sample of what my cloud functions export looks like
const functions = require('firebase-functions'); const express = require('express'); const app = express(); app.get('/users/:userId/:userData/json', (req, res) => { // Do App stuff here } // A couple more app.get in the same format exports.api = functions.https.onRequest(app);
EDIT 2: After @DougStevenson's suggestion, I tried the following configuration
I tried the following in my firebase.json ,
{ "hosting": { "rewrites": [ { "source": "/api", "function": "api" } ], "public": "public" } }
But I got the same problem, the function is never called. I read about how the rewrites are last resort options and if there are files present in the hosting, it will not go to the specified function.(I tried looking for the SO post where this ws mentioned but I can't find it) So I removed the 404.html and index.html files from hosting's public directory, since I don't need them anyway. But the problem still remained.
EDIT 2: Okay, so after a lot of trial and error, I just had to hard code the paths in the following format:
rewrites : [ { "source": "/users/**/**/json", "function": "api" }, { "source": "/api/users/**/**/json", "function": "api" } ]
After this, the express app is configured something like this:
app.get('/users/:userId/:userData/json', Foo)
I still would prefer if someone could suggest a better way to accomplish this rather than manually putting in each required Uri in the hosting rewrites.
-
Doug Stevenson almost 7 yearsCan you edit your question to briefly show the Cloud Functions side of your express app and https function export? I need to see how you're setting things up.
-
Aditya Aggarwal almost 7 years@DougStevenson done sir, Thank you for your reply. I'm fairly inexperienced with a JS based backend, so I've kept most of it as shown in the firebase-examples.
-
Pkmmte almost 7 yearsWere you ever able to find a solution for this? I've also been struggling all day trying to solve this...
-
Aditya Aggarwal almost 7 years@Pkmmte Not exactly a solution, a workaround. Please look at edit 2 for details.
-
Pkmmte almost 7 years@AdityaAggarwal I took a slightly different approach to resolving this issue, which basically involves using a
main
function wrapper. See my answer below for more details. :)
-
-
Pkmmte almost 7 yearsIn my case, I redirect everything to the
main
function so I never have to touch firebase.json ever again:"source": "**"
-
Muhammad Hassan Nasr almost 7 years@Pkmmte I don't understand why { "source": "/api", "function": "api" } is causing repeating /api/api in the url
-
Pkmmte almost 7 years@MuhammadHassan That's the way Firebase works internally for some reason. I don't agree with it either. It says "Hey, you wanna redirect
/api
to yourapi
function? Alright, I'll pass it there asapi + /api
". Maybe they did this so you can redirect something like/cat
to a function calledanimals
like so:animals/cat
. -
DevShadow about 6 yearsThis has saved me a lot of time and money trying to host my api on app engine. Many thanks!
-
triple almost 6 yearsdoes this /api rewrite still happen - seems totally nonsensical?
-
Pkmmte almost 6 years@triple As far as I'm aware, yeah. At least in my projects.
-
inorganik over 5 yearsThanks, this works great when deployed, however locally I still have to hit the endpoint with the repeated
api
for it to work. How can I make it work both locally and deployed? -
galki over 5 years@inorganik set GOOGLE_APPLICATION_CREDENTIALS
-
Kartik Watwani over 5 yearsNot working with Angular Single page application. Everything is being redirected to project-id.firebaseapp.com
-
PvDev almost 5 years@Pkmmte i have deployment issue on vuejs/node js in firebase deploy. can you check this question stackoverflow.com/questions/56523054/… may i know whether i structured correctly and calling right firebase function.
-
optilude about 4 yearsThis had me confused for ages. Thank you!
-
dinkydani almost 4 yearsWorks great thank you! I think this is a better solution than the second express server the accepted answer gives but both solve the problem
-
secretshardul over 3 years
"source": "/api/**"
will catch requests made to/api/xyz
and/api/
but not to/api
. To make it inclusive, use"source": "/api{,/**}"
. Source: firebase.google.com/docs/hosting/full-config#rewrites -
jean d'arme over 3 yearsThis actually worked. Firebase should be ashamed of their unintuitive documentation and need for making gimmicks like this to make it work the same way locally and on the production. Thanks!
-
Ashish almost 3 yearsI don't get where you have provided params
-
Ashish almost 3 years@Pkmmte why it does not work with me ? i tried same way as your stackoverflow.com/questions/67931862/…
-
Gavin Chebor almost 3 yearsthe request provides access to the params req.query.{{params}}
-
fthdgn over 2 yearsI tried this and it works. I have a secondary website for api subdomain.
api.mycustomdomain
` "rewrites": [ { "source": "**", "function": "/" } ] `api.mycustomdomain/function-name
runs just likehttps://us-central1-project-name.cloudfunctions.net/function-name
-
Volodymyr Rudov-Tsymbalist about 2 yearsMany years later, I didn't find this Question and Answer after 2 days of fight with this issue. Only when drafting my own question - this one popped up and I finally solved. Thank you sir! And someone likes FromSoftware games for their hardcore... Please :)