Declare separate Firebase Cloud Functions and still use Express.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:
- /payment/someaction (RPC)
- /order (get,put,post, etc.)
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.
Interesting discussion.
I choose the same approach : one "endpoint" (aka a root route like "/posts", "/users") == a dedicated cloud function (for the reason already evoked + it's more "µservice like" and it's what are "lambda functions" for me).
To be "DRY" all my function import an "express" generator. I configure my express instance in one place.
const express = () => {
const express = require("express");
const cors = require("cors")({ origin: true });
const morgan = require("morgan");
const helmet = require("helmet");
const app = express();
app.use(helmet());
app.use(helmet.noCache());
app.use(cors);
app.use(morgan("combined"));
return app;
};
module.exports = express;
My "hello" endpoint :
const app = require("./../../express")();
/**
* /hello
*/
app.get("/", (req, res) => {
return res.send("Hello World");
});
module.exports = app;
My index.js (main export) :
const helloApi = require("./api/hello");
const https = functions.region("europe-west1").https;
module.exports.hello = https.onRequest(helloApi);
Seems to work well for us :)