How do I structure Cloud Functions for Firebase to deploy multiple functions from multiple files?

84,300

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:

  1. the order of import / export matters in index.ts
  2. 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

Share:
84,300

Related videos on Youtube

jasonsirota
Author by

jasonsirota

Updated on September 07, 2021

Comments

  • jasonsirota
    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?

  • Alexander Khitev
    Alexander Khitev almost 7 years
    Can I for example have several functions in the foo module? If so, how is it better to implement it?
  • jasonsirota
    jasonsirota almost 7 years
    I 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
    bvs over 6 years
    I 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
    Alan over 6 years
    I 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
    jasonsirota over 6 years
    I 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
    Aodh about 6 years
    I can't see how this could possibly work since Firebase supports Node 6.11 currently which doesn't support ES6 import directives?
  • zaidfazil
    zaidfazil about 6 years
    If you are using typescript, the problem should never arise. I did port most of my code into typescript lately.
  • PostureOfLearning
    PostureOfLearning about 6 years
    zaidfazil, 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
    bersling over 5 years
    what 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
    Ahmad Moussa over 5 years
    thank you. this worked with typescript and node 6 :)
  • Simon Fakir
    Simon Fakir over 5 years
    I 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
    whatsthatitspat over 5 years
    Rather than import and re-export with spread operators, couldn't you just have export * from './fooFunctions'; and export * from './barFunctions'; in index.ts?
  • Alex Sorokoletov
    Alex Sorokoletov over 5 years
    This 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
    krhitesh over 5 years
    Why 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
    Alex Sorokoletov over 5 years
    I'm fine with deploying everything, I just want to version the functions that I put (http triggered ones)
  • krhitesh
    krhitesh over 5 years
    I 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
    zaidfazil over 5 years
    Didn't think about it at the moment. That's really a nice one.
  • elprl
    elprl over 5 years
    This 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
    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
    OK200 about 5 years
    i 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
    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
    dcts almost 5 years
    i get an eslint partsing error unexpected token ... inside index.js.
  • Luke Pighetti
    Luke Pighetti almost 5 years
    Perhaps you are not using Node 8
  • Ayyappa
    Ayyappa over 4 years
    Won't this have overload on boot for every function instance that spins up?
  • Ayyappa
    Ayyappa over 4 years
    won't this be have on cold start?
  • dsg38
    dsg38 over 4 years
    Hey - why can't the contents of db.ts go inside index.ts (after admin instantiation?). Or have you just split out in this way for clarity/simplicity?
  • dsg38
    dsg38 over 4 years
    Hey @ced - why can't the contents of db.ts go inside main.ts (after admin instantiation?). Or have you just split out in this way for clarity/simplicity?
  • Ced
    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
    Reza over 4 years
    @dsg38 you can mix all together, this makes it clear
  • atereshkov
    atereshkov over 4 years
    @SimonFakir good question. Have you found something about it?
  • Simon Fakir
    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
    tonkatata over 4 years
    The 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
    Douglas Schmidt over 4 years
    I'm also getting unexpedted token ..., running node 8 and eslint 5.12
  • Axes Grinds
    Axes Grinds about 4 years
    for 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
    Axes Grinds about 4 years
  • Ruben
    Ruben almost 4 years
    I did something similar for TS thanks this is a simple and good solution
  • Zorayr
    Zorayr over 3 years
    I 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
    Zorayr over 3 years
    Have you found a better way of importing the exports in index.js, instead of manually wiring each individual file?
  • Lalit Rane
    Lalit Rane over 3 years
    How 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
    Lalit Rane over 3 years
    How 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
    sarah over 3 years
    there 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
    Tarik Huber over 2 years
    Thx for sharing :). Just came over this question hahah
  • Womprat
    Womprat over 2 years
    I 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
    Womprat over 2 years
    I 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.