There are many examples of using Express for Firebase Cloud Functions.
In every example of I have found the code exposes the Express app as a single Cloud Function:
exports.app = functions.https.onRequest(app);
For one's Firebase Project Functions that means they will see one entry called "app" and all logs for all Express.js HTTP listeners will go to one place in Firebase. This also means, no matter how large one's Express.js app is, Firebase will deploy a single function in production for the app.
Alternatively, when using firebase-functions.https.onRequest
you get separate functions for each export, for example, in Typescript:
export const hello = functions.https.onRequest(async (req, res) => {
res.status(200).send('hello world');
});
And in Firebase Console, I have my hello function and also another function in my index.js:
This also means Firebase will create different nodes/instances for each function: hello
and emphemeralKey
.
And I will get separate logging for each function in the Firebase Console.
I would like to use middleware to ensure that valid auth tokens are being passed to my endpoint Cloud Functions like this Firebase example but I would prefer not to use a single "app" single Cloud Function, I would prefer a dedicated function for function export in my index.js.
Thanks to Doug Stevenson for his answer and help. I wanted to provide my own answer though.
So the answer to my question is, generally speaking: no you can't.
As Doug was pointing out, this is not a problem for many people's scaling needs. Firebase will create up to 1,000 instances of your function to scale.
I wanted to provide a slightly different answer then Doug's to how I would write an Express app and have different Firebase Cloud Functions for a project:
const payment = express()
const order = express()
payment.get('/route', ...)
order.get('/route', ...)
export const payment = functions.https.onRequest(payment)
export const order = functions.https.onRequest(order)
The advantage here is that I can start to express REST or RPC routes like:
Another benefit is that I can provide a "test" API and a "live" API for things like credit card payments/processing:
// [START Express LIVE App]
// [START get user]
app.get('/user', async (req, res) => {
await handleGetUser(req, res, paymentServiceLive);
});
// [END get user]
// [START claim]
app.post('/claim', async (req, res) => {
await handleClaim(req, res, claimEmailTo);
});
// [END claim]
// [START user]
app.post('/user', async (req, res) => {
await handleUserPost(req, res, paymentServiceLive);
});
// [END user]
// [START ephemeralKey]
app.post('/ephemeralKey', async (req, res) => {
await handleEphemeralKey(req, res, paymentServiceLive);
});
// [END ephemeralKey]
// [START charge]
app.post('/charge', async (req, res) => {
await handleCharge(req, res, paymentServiceLive);
});
// [END charge]
// [START purchase]
app.post('/purchase', async (req, res) => {
await handlePurchase(req, res, paymentServiceLive);
});
// [END purchase]
//Expose Express API as a single Cloud Function:
exports.app = functions.https.onRequest(app);
// [END Express LIVE App]
// [START Express TEST App]
// [START get user]
appTest.get('/user', async (req, res) => {
console.log('appTest /user get', req);
await handleGetUser(req, res, paymentServiceTest);
});
// [END get user]
// [START claim]
appTest.post('/claim', async (req, res) => {
await handleClaim(req, res, claimEmailToTest, true);
});
// [END claim]
// [START user]
appTest.post('/user', async (req, res) => {
console.log('appTest /user post', req);
await handleUserPost(req, res, paymentServiceTest);
});
// [END user]
// [START ephemeralKey]
appTest.post('/ephemeralKey', async (req, res) => {
await handleEphemeralKey(req, res, paymentServiceTest)
});
// [END ephemeralKey]
// [START charge]
appTest.post('/charge', async (req, res) => {
await handleCharge(req, res, stripeTest);
});
// [END charge]
// [START purchase]
appTest.post('/purchase', async (req, res) => {
await handlePurchase(req, res, paymentServiceTest);
});
// [END purchase]
//Expose Express API as a single Cloud Function:np
exports.apptest = functions.https.onRequest(appTest);
// [END Express TEST App]
This allows me to have a development environment and a live environment. in my app config files I just have a different API url:
/us-central1/apptest
or
/us-central1/app
If you have a single express app, you can't split its routes between different logical functions in a project. If you must try to split the load between more than one function, you can deploy the same express app as to as many individual functions as you want.
const app = express()
app.get('/route1', ...)
app.get('/route2', ...)
export const f1 = functions.https.onRequest(app)
export const f2 = functions.https.onRequest(app)
// etc
Now you can try to address that different routes between the different functions (and their different resulting URLs). But you have not inherently restricted certain routes from being invoked in different functions. It's up to you to make sure clients are using the function you want.
If you're trying to perform this split for performance reasons, I would consider this premature optimization. Cloud Functions will scale up your app seamlessly beyond just a single server instance, on demand. Splitting up your functions like this may help scalability, if you're expecting to exceed the documented limits, but I would expect that to be uncommon. Without understanding the actual, observed performance characteristics if your app, it's impossible to say. If you are exceeding the limits, contact support to help explain what's not happening the way you expect with your deployment.
If you find the basic logging for Cloud Functions to be unhelpful when it comes to monitoring each of your routes, you should look into custom StackDriver logging, which will help you better organize and monitor the different types of logs your functions generate.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With