Firebase Hosting with dynamic cloud functions rewrites

14,585

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.

  1. Add a rewrite in your firebase.json file.

    {
      "source": "/api/**",
      "function": "api"
    }
    
  2. 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);
    
Share:
14,585

Related videos on Youtube

Aditya Aggarwal
Author by

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, 2022

Comments

  • Aditya Aggarwal
    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
      Doug Stevenson almost 7 years
      Can 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
      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
      Pkmmte almost 7 years
      Were you ever able to find a solution for this? I've also been struggling all day trying to solve this...
    • Aditya Aggarwal
      Aditya Aggarwal almost 7 years
      @Pkmmte Not exactly a solution, a workaround. Please look at edit 2 for details.
    • Pkmmte
      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
    Pkmmte almost 7 years
    In my case, I redirect everything to the main function so I never have to touch firebase.json ever again: "source": "**"
  • Muhammad Hassan Nasr
    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
    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 your api function? Alright, I'll pass it there as api + /api". Maybe they did this so you can redirect something like /cat to a function called animals like so: animals/cat.
  • DevShadow
    DevShadow about 6 years
    This has saved me a lot of time and money trying to host my api on app engine. Many thanks!
  • triple
    triple almost 6 years
    does this /api rewrite still happen - seems totally nonsensical?
  • Pkmmte
    Pkmmte almost 6 years
    @triple As far as I'm aware, yeah. At least in my projects.
  • inorganik
    inorganik over 5 years
    Thanks, 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
    galki over 5 years
  • Kartik Watwani
    Kartik Watwani over 5 years
    Not working with Angular Single page application. Everything is being redirected to project-id.firebaseapp.com
  • PvDev
    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
    optilude about 4 years
    This had me confused for ages. Thank you!
  • dinkydani
    dinkydani almost 4 years
    Works 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
    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
    jean d'arme over 3 years
    This 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
    Ashish almost 3 years
    I don't get where you have provided params
  • Ashish
    Ashish almost 3 years
    @Pkmmte why it does not work with me ? i tried same way as your stackoverflow.com/questions/67931862/…
  • Gavin Chebor
    Gavin Chebor almost 3 years
    the request provides access to the params req.query.{{params}}
  • fthdgn
    fthdgn over 2 years
    I tried this and it works. I have a secondary website for api subdomain. api.mycustomdomain ` "rewrites": [ { "source": "**", "function": "/" } ] ` api.mycustomdomain/function-name runs just like https://us-central1-project-name.cloudfunctions.net/function‌​-name
  • Volodymyr Rudov-Tsymbalist
    Volodymyr Rudov-Tsymbalist about 2 years
    Many 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 :)