How do I structure Cloud Functions for Firebase to deploy multiple functions from multiple files?
Solution 1
Ah, Cloud Functions for Firebase load node modules normally, so this works
structure:
/functions
|--index.js
|--foo.js
|--bar.js
|--package.json
index.js:
const functions = require('firebase-functions');
const fooModule = require('./foo');
const barModule = require('./bar');
exports.foo = functions.database.ref('/foo').onWrite(fooModule.handler);
exports.bar = functions.database.ref('/bar').onWrite(barModule.handler);
foo.js:
exports.handler = (event) => {
...
};
bar.js:
exports.handler = (event) => {
...
};
Solution 2
The answer by @jasonsirota was very helpful. But it may be useful to see more detailed code, especially in the case of HTTP triggered functions.
Using the same structure as in @jasonsirota's answer, lets say you wish to have two separate HTTP trigger functions in two different files:
directory structure:
/functions
|--index.js
|--foo.js
|--bar.js
|--package.json
index.js:
'use strict';
const fooFunction = require('./foo');
const barFunction = require('./bar');
// Note do below initialization tasks in index.js and
// NOT in child functions:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const database = admin.database();
// Pass database to child functions so they have access to it
exports.fooFunction = functions.https.onRequest((req, res) => {
fooFunction.handler(req, res, database);
});
exports.barFunction = functions.https.onRequest((req, res) => {
barFunction.handler(req, res, database);
});
foo.js:
exports.handler = function(req, res, database) {
// Use database to declare databaseRefs:
usersRef = database.ref('users');
...
res.send('foo ran successfully');
}
bar.js:
exports.handler = function(req, res, database) {
// Use database to declare databaseRefs:
usersRef = database.ref('users');
...
res.send('bar ran successfully');
}
Solution 3
Update: Typescript is now fully supported so no need for the shenanigans below. Just use the firebase cli
Here is how I personnally did it with typescript:
/functions
|--src
|--index.ts
|--http-functions.ts
|--main.js
|--db.ts
|--package.json
|--tsconfig.json
Let me preface this by giving two warnings to make this work:
- the order of import / export matters in index.ts
- the db must be a separate file
For point number 2 I'm not sure why. Secundo you should respect my configuration of index, main and db exactly (at least to try it out).
index.ts : deals with export. I find it cleaner to let the index.ts deal with exports.
// main must be before functions
export * from './main';
export * from "./http-functions";
main.ts: Deals with initialization.
import { config } from 'firebase-functions';
import { initializeApp } from 'firebase-admin';
initializeApp(config().firebase);
export * from "firebase-functions";
db.ts: just reexporting the db so its name is shorter than database()
import { database } from "firebase-admin";
export const db = database();
http-functions.ts
// db must be imported like this
import { db } from './db';
// you can now import everything from index.
import { https } from './index';
// or (both work)
// import { https } from 'firebase-functions';
export let newComment = https.onRequest(createComment);
export async function createComment(req: any, res: any){
db.ref('comments').push(req.body.comment);
res.send(req.body.comment);
}
Solution 4
With Node 8 LTS now available with Cloud/Firebase Functions you can do the following with spread operators:
/package.json
"engines": {
"node": "8"
},
/index.js
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
module.exports = {
...require("./lib/foo.js"),
// ...require("./lib/bar.js") // add as many as you like
};
/lib/foo.js
const functions = require("firebase-functions");
const admin = require("firebase-admin");
exports.fooHandler = functions.database
.ref("/food/{id}")
.onCreate((snap, context) => {
let id = context.params["id"];
return admin
.database()
.ref(`/bar/${id}`)
.set(true);
});
Solution 5
To be kept simple (but does the work), I have personally structured my code like this.
Layout
├── /src/
│ ├── index.ts
│ ├── foo.ts
│ ├── bar.ts
| ├── db.ts
└── package.json
foo.ts
import * as functions from 'firebase-functions';
export const fooFunction = functions.database()......... {
//do your function.
}
export const someOtherFunction = functions.database().......... {
// do the thing.
}
bar.ts
import * as functions from 'firebase-functions';
export const barFunction = functions.database()......... {
//do your function.
}
export const anotherFunction = functions.database().......... {
// do the thing.
}
db.ts
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
export const firestore = admin.firestore();
export const realtimeDb = admin.database();
index.ts
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
admin.initializeApp(functions.config().firebase);
// above codes only needed if you use firebase admin
export * from './foo';
export * from './bar';
Works for directories of any nested levels. Just follow the pattern inside the directories too.
credit to @zaidfazil answer
Related videos on Youtube
jasonsirota
Updated on September 07, 2021Comments
-
jasonsirota over 2 years
I would like to create multiple Cloud Functions for Firebase and deploy them all at the same time from one project. I would also like to separate each function into a separate file. Currently I can create multiple functions if I put them both in index.js such as:
exports.foo = functions.database.ref('/foo').onWrite(event => { ... }); exports.bar = functions.database.ref('/bar').onWrite(event => { ... });
However I would like to put foo and bar in separate files. I tried this:
/functions |--index.js (blank) |--foo.js |--bar.js |--package.json
where foo.js is
exports.foo = functions.database.ref('/foo').onWrite(event => { ... });
and bar.js is
exports.bar = functions.database.ref('/bar').onWrite(event => { ... });
Is there a way to accomplish this without putting all functions in index.js?
-
HuyLe over 6 years@JPVentura. Really don't understand you well. Please explain.
-
tccpg288 about 6 yearsHas this been updated for v1.0? I am having issues: stackoverflow.com/questions/50089807/…
-
AlexGrafe about 5 yearsFYI, this official Firebase functions example contains several
.js
files imported throughrequire
: github.com/firebase/functions-samples/tree/master/… -
Ramesh-X about 4 yearsThis might be helpful: stackoverflow.com/questions/43486278/…
-
-
Alexander Khitev almost 7 yearsCan I for example have several functions in the foo module? If so, how is it better to implement it?
-
jasonsirota almost 7 yearsI suppose you could, and assign different handlers to different exported functions from foo: exports.bar = functions.database.ref('/foo').onWrite(fooModule.barHandler); exports.baz = functions.database.ref('/bar').onWrite(fooModule.bazHandler);
-
bvs over 6 yearsI don't like this solution because it moves information (namely the database paths) from foo.js and bar.js into index.js which kind of defeats the point of having those separate files.
-
Alan over 6 yearsI agree with @bvs, I think Ced has a good approach. I'm going to slightly modify it by explicitly exporting each module to make the index.ts super clear e.g export {newUser} from "./authenticationFunctions"
-
jasonsirota over 6 yearsI think my original question was simply about deploying multiple functions with 1 project without putting the functions in the index.js file, where and how you pass database information is not in scope. Were it me, I would probably create a separate module that controlled the database access and require it in foo.js and bar.js separately, but that is a stylistic decision.
-
Aodh about 6 yearsI can't see how this could possibly work since Firebase supports Node 6.11 currently which doesn't support ES6 import directives?
-
zaidfazil about 6 yearsIf you are using typescript, the problem should never arise. I did port most of my code into typescript lately.
-
PostureOfLearning about 6 yearszaidfazil, you should probably note down any pre-requisites in your answer. @Aodh, it works if you use Babel the same way Konstantin has outlined in an answer. stackoverflow.com/questions/43486278/…
-
bersling over 5 yearswhat does your tsconfig look like? how can I compile into a dist folder and let gcloud functions know where my index.js is? Do you have your code on github? :)
-
Ahmad Moussa over 5 yearsthank you. this worked with typescript and node 6 :)
-
Simon Fakir over 5 yearsI wonder if the growing number of imports slows done the cold start of each function or if there should be many totally seprated modules developed separatly?
-
whatsthatitspat over 5 yearsRather than import and re-export with spread operators, couldn't you just have
export * from './fooFunctions';
andexport * from './barFunctions';
in index.ts? -
Alex Sorokoletov over 5 yearsThis approach is really nice. I was wondering if it is possible to adjust to to allow slashes in the function names so that you can separate different api versions, for example (api v1, api v2, etc)
-
krhitesh over 5 yearsWhy would you want to keep different versions of a cloud function under the same project? Although you can do that by slightly changing the directory structure, by default index.js will deploy all the cloud functions unless you deploy selectively or use if-conditions in your index.js that will eventually end up cluttering up your code
-
Alex Sorokoletov over 5 yearsI'm fine with deploying everything, I just want to version the functions that I put (http triggered ones)
-
krhitesh over 5 yearsI am expecting that every http trigger is in its own
*.f.js
file. The least you can do is renaming the file for every version by prepending the suffix to make it something like*.v1.f.js
or*.v2.f.js
etc. (Assuming all your versions of all of your http trigger are live). Please let me know if you have a better solution. -
zaidfazil over 5 yearsDidn't think about it at the moment. That's really a nice one.
-
elprl over 5 yearsThis is one of the simplest answers for Typescript, thanks. How do you cope with a single instantiation of the firebase database for example?
admin.initializeApp(functions.config().firestore) const db = admin.firestore();
Where do you put this and how do you refer to it in foo and bar? -
Ced over 5 years@choopage-JekBao sorry it's been a long time, I don't have the project anymore. If I recall correctly you can give the firebase config a directory (which is public by default). I could be wrong though since it's been more than a year
-
OK200 about 5 yearsi have exported as you said but the firebase deploy detects the one which is in the end, ex: as per your code it only takes module.exports.stripe = functions.https.onRequest(stripe);
-
ajorquera about 5 years@OK200 what is the command you are using with firebase command line? To help you, I'll need to see some code
-
dcts almost 5 yearsi get an eslint partsing error
unexpected token ...
inside index.js. -
Luke Pighetti almost 5 yearsPerhaps you are not using Node 8
-
Ayyappa over 4 yearsWon't this have overload on boot for every function instance that spins up?
-
Ayyappa over 4 yearswon't this be have on cold start?
-
dsg38 over 4 yearsHey - why can't the contents of
db.ts
go insideindex.ts
(after admin instantiation?). Or have you just split out in this way for clarity/simplicity? -
dsg38 over 4 yearsHey @ced - why can't the contents of
db.ts
go insidemain.ts
(after admin instantiation?). Or have you just split out in this way for clarity/simplicity? -
Ced over 4 years@dsg38 this was posted too long ago, I don't really see why it should be in a separate file looking at the answer now.. I think it was for clarity
-
Reza over 4 years@dsg38 you can mix all together, this makes it clear
-
atereshkov over 4 years@SimonFakir good question. Have you found something about it?
-
Simon Fakir over 4 years@atereshkov yes I found a way to only load the requested function including it's dependencies using "process.env.FUNCTION_NAME" similar to the answer below. I can also share my repo as reference if you are interessted contact me.
-
tonkatata over 4 yearsThe current structure in index.js didn't work out well for me. What I had to do was to first import the firebase modules, then initialize the app and then import the functions from the other folders. That way my app first initializes, authenticates, whatever and then imports the functions which need the app to be initialized beforehand.
-
Douglas Schmidt over 4 yearsI'm also getting unexpedted token ..., running node 8 and eslint 5.12
-
Axes Grinds about 4 yearsfor those getting unexpected token ... add this to .eslintrc generated document "parserOptions": { "ecmaVersion": 6, "ecmaFeatures": { "experimentalObjectRestSpread": true } }, and update ecmaVersion with whatever version was generated.
-
Axes Grinds about 4 years
-
Ruben almost 4 yearsI did something similar for TS thanks this is a simple and good solution
-
Zorayr over 3 yearsI feel like there should be a better way to wire the function files to index.js? The current approach of manually wiring seems like a lot of work.
-
Zorayr over 3 yearsHave you found a better way of importing the exports in index.js, instead of manually wiring each individual file?
-
Lalit Rane over 3 yearsHow can we have typescript and javascript functions in the same folder. I had to create two different folders (one for javascript and one for typescript) and do firebase init, etc etc. Is there any better way to handle this?
-
Lalit Rane over 3 yearsHow can we have typescript and javascript functions in the same folder. I had to create two different folders (one for javascript and one for typescript) and do firebase init, etc etc. Is there any better way to handle this?
-
sarah over 3 yearsthere is an official documentation for this, the grouping function section maybe will also help you, check it here: firebase.google.com/docs/functions/organize-functions
-
Tarik Huber over 2 yearsThx for sharing :). Just came over this question hahah
-
Womprat over 2 yearsI started using index.js and different modules, all in the top-level src directory. This works ok for deployment - I can use "firebase deploy --only functions:user" and it will only deploy the functions in that "user" module. However, it still includes ALL modules in the Cloud Functions Source, even though those modules are not required by my "user" module. So my Source for that module includes a lot of extra code. Does this method of splitting directories (above) prevent the upload of unnecessary code from other modules? (I'm testing this now and I will report back)
-
Womprat over 2 yearsI tested this - even with functions in separate directories, the entire codebase gets uploaded to Cloud Functions when I deploy a function from one directory. The only workaround I've found for this is to create a separate top-level directory for each module with its own "firebase deploy" installation. Has anyone found a better solution for this? For example, if I have 100 cloud functions, I don't need each one to have the full source code for all 99 other functions when I deploy it to Cloud Functions. It WORKS ok, but this seems like overkill and a possible security risk.