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

19

230

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?

Bangka answered 19/4, 2017 at 4:17 Comment(4)
@JPVentura. Really don't understand you well. Please explain.Purdah
Has this been updated for v1.0? I am having issues: #50090307Juliusjullundur
FYI, this official Firebase functions example contains several .js files imported through require: github.com/firebase/functions-samples/tree/master/…Ronda
This might be helpful: #43486778Lanlana
B
145

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) => {
    ...
};
Bangka answered 19/4, 2017 at 5:22 Comment(6)
Can I for example have several functions in the foo module? If so, how is it better to implement it?Sessions
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);Bangka
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.Greenockite
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"Somewhat
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.Bangka
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-functionsQuadriplegic
T
101

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'); 
}
Thrift answered 1/7, 2017 at 20:56 Comment(2)
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.Jepum
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.Phonetist
S
71

Note: This is an old answer to and old question at an older time. In 2023 lots of things have changed by now.

Here is how I personally did it with typescript:

    /functions
       |--src
          |--index.ts
          |--http-functions.ts
          |--main.ts
          |--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);
}
Sinotibetan answered 24/8, 2017 at 12:28 Comment(5)
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? :)Gibbs
@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 yearSinotibetan
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?Wolfort
@Wolfort 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 claritySinotibetan
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?Christie
R
32

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);
  });
Revoice answered 28/8, 2018 at 11:29 Comment(8)
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?Culberson
i get an eslint partsing error unexpected token ... inside index.js.Superincumbent
Perhaps you are not using Node 8Revoice
@SimonFakir good question. Have you found something about it?Residue
@Residue 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.Culberson
I'm also getting unexpedted token ..., running node 8 and eslint 5.12Di
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.Ladle
#36002052Ladle
L
24

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

Latreshia answered 29/12, 2018 at 0:15 Comment(4)
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?Waterscape
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?Wolfort
@Wolfort you can mix all together, this makes it clearLatreshia
I did something similar for TS thanks this is a simple and good solutionThundercloud
P
15

bigcodenerd.org outline's a simpler architecture pattern in order to have methods separated into different files and exported in one line within the index.js file.

The architecture for the project in this sample is the following:

projectDirectory

  • index.js
  • podcast.js
  • profile.js

index.js

const admin = require('firebase-admin');
const podcast = require('./podcast');
const profile = require('./profile');
admin.initializeApp();

exports.getPodcast = podcast.getPodcast();
exports.removeProfile = profile.removeProfile();

podcast.js

const functions = require('firebase-functions');

exports.getPodcast = () => functions.https.onCall(async (data, context) => {
      ...
      return { ... }
  });

The same pattern would be used for the removeProfile method in the profile file.

Pericranium answered 27/2, 2019 at 18:32 Comment(0)
W
14

In case with Babel/Flow it would look like this:

Directory Layout

.
├── /build/                     # Compiled output for Node.js 6.x
├── /src/                       # Application source files
│   ├── db.js                   # Cloud SQL client for Postgres
│   ├── index.js                # Main export(s)
│   ├── someFuncA.js            # Function A
│   ├── someFuncA.test.js       # Function A unit tests
│   ├── someFuncB.js            # Function B
│   ├── someFuncB.test.js       # Function B unit tests
│   └── store.js                # Firebase Firestore client
├── .babelrc                    # Babel configuration
├── firebase.json               # Firebase configuration
└── package.json                # List of project dependencies and NPM scripts


src/index.js - Main export(s)

export * from './someFuncA.js';
export * from './someFuncB.js';


src/db.js - Cloud SQL Client for Postgres

import { Pool } from 'pg';
import { config } from 'firebase-functions';

export default new Pool({
  max: 1,
  user: '<username>',
  database: '<database>',
  password: config().db.password,
  host: `/cloudsql/${process.env.GCP_PROJECT}:<region>:<instance>`,
});


src/store.js - Firebase Firestore Client

import firebase from 'firebase-admin';
import { config } from 'firebase-functions';

firebase.initializeApp(config().firebase);

export default firebase.firestore();


src/someFuncA.js - Function A

import { https } from 'firebase-functions';
import db from './db';

export const someFuncA = https.onRequest(async (req, res) => {
  const { rows: regions } = await db.query(`
    SELECT * FROM regions WHERE country_code = $1
  `, ['US']);
  res.send(regions);
});


src/someFuncB.js - Function B

import { https } from 'firebase-functions';
import store from './store';

export const someFuncB = https.onRequest(async (req, res) => {
  const { docs: regions } = await store
    .collection('regions')
    .where('countryCode', '==', 'US')
    .get();
  res.send(regions);
});


.babelrc

{
  "presets": [["env", { "targets": { "node": "6.11" } }]],
}


firebase.json

{
  "functions": {
    "source": ".",
    "ignore": [
      "**/node_modules/**"
    ]
  }
}


package.json

{
  "name": "functions",
  "verson": "0.0.0",
  "private": true,
  "main": "build/index.js",
  "dependencies": {
    "firebase-admin": "^5.9.0",
    "firebase-functions": "^0.8.1",
    "pg": "^7.4.1"
  },
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-core": "^6.26.0",
    "babel-jest": "^22.2.2",
    "babel-preset-env": "^1.6.1",
    "jest": "^22.2.2"
  },
  "scripts": {
    "test": "jest --env=node",
    "predeploy": "rm -rf ./build && babel --out-dir ./build src",
    "deploy": "firebase deploy --only functions"
  }
}


$ yarn install                  # Install project dependencies
$ yarn test                     # Run unit tests
$ yarn deploy                   # Deploy to Firebase
Wether answered 16/2, 2018 at 10:43 Comment(0)
R
11

To be kept simple (but does the work), I have personally structured my code like this.

Layout

├── /src/                      
│   ├── index.ts               
│   ├── foo.ts           
│   ├── bar.ts           
└── package.json  

foo.ts

export const fooFunction = functions.database()......... {
    //do your function.
}

export const someOtherFunction = functions.database().......... {
    // do the thing.
}

bar.ts

export const barFunction = functions.database()......... {
    //do your function.
}

export const anotherFunction = functions.database().......... {
    // do the thing.
}

index.ts

import * as fooFunctions from './foo';
import * as barFunctions from './bar';

module.exports = {
    ...fooFunctions,
    ...barFunctions,
};

Works for directories of any nested levels. Just follow the pattern inside the directories too.

Rile answered 22/3, 2018 at 8:20 Comment(6)
I can't see how this could possibly work since Firebase supports Node 6.11 currently which doesn't support ES6 import directives?Cheekpiece
If you are using typescript, the problem should never arise. I did port most of my code into typescript lately.Rile
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. #43486778Claudie
thank you. this worked with typescript and node 6 :)Jayson
Rather than import and re-export with spread operators, couldn't you just have export * from './fooFunctions'; and export * from './barFunctions'; in index.ts?Faunie
Didn't think about it at the moment. That's really a nice one.Rile
D
9

The Firebase docs have now been updated with a good guide to multi-file code organization:

Docs > Cloud Functions > Write functions > Organize functions

To summarize:

foo.js

const functions = require('firebase-functions');
exports.foo = functions.https.onRequest((request, response) => {
  // ...
});

bar.js

const functions = require('firebase-functions');
exports.bar = functions.https.onRequest((request, response) => {
  // ...
});

index.js

const foo = require('./foo');
const bar = require('./bar');
exports.foo = foo.foo;
exports.bar = bar.bar;
Delineation answered 18/8, 2020 at 0:58 Comment(1)
Have you found a better way of importing the exports in index.js, instead of manually wiring each individual file?Phonetist
V
6

This format allows your entry-point to find additional function files, and export each function within each file, automatically.

Main Entry Point Script

Finds all .js files inside of the functions folder, and exports each function exported from each file.

const fs = require('fs');
const path = require('path');

// Folder where all your individual Cloud Functions files are located.
const FUNCTIONS_FOLDER = './scFunctions';

fs.readdirSync(path.resolve(__dirname, FUNCTIONS_FOLDER)).forEach(file => { // list files in the folder.
  if(file.endsWith('.js')) {
    const fileBaseName = file.slice(0, -3); // Remove the '.js' extension
    const thisFunction = require(`${FUNCTIONS_FOLDER}/${fileBaseName}`);
    for(var i in thisFunction) {
        exports[i] = thisFunction[i];
    }
  }
});

Example Export of Multiple Functions from One File

const functions = require('firebase-functions');

const query = functions.https.onRequest((req, res) => {
    let query = req.query.q;

    res.send({
        "You Searched For": query
    });
});

const searchTest = functions.https.onRequest((req, res) => {
    res.send({
        "searchTest": "Hi There!"
    });
});

module.exports = {
    query,
    searchTest
}

http accessible endpoints are appropriately named

✔ functions: query: http://localhost:5001/PROJECT-NAME/us-central1/query
✔ functions: helloWorlds: http://localhost:5001/PROJECT-NAME/us-central1/helloWorlds
✔ functions: searchTest: http://localhost:5001/PROJECT-NAME/us-central1/searchTest

One file

If you only have a few additional files (e.g. just one), you can use:

const your_functions = require('./path_to_your_functions');

for (var i in your_functions) {
  exports[i] = your_functions[i];
}
Vermifuge answered 19/1, 2018 at 16:2 Comment(1)
Won't this have overload on boot for every function instance that spins up?Unaccomplished
I
5

So I have this project which has background functions and http functions. I also have tests for unit testing. CI/CD will make your life much easier when deploying cloud functions

Folder structure

|-- package.json
|-- cloudbuild.yaml
|-- functions
    |-- index.js
    |-- background
    |   |-- onCreate
    |       |-- index.js
            |-- create.js
    |
    |-- http
    |   |-- stripe
    |       |-- index.js
    |       |-- payment.js
    |-- utils
        |-- firebaseHelpers.js
    |-- test
        |-- ...
    |-- package.json

Note: utils/ folder is for share code between functions

functions/index.js

Here you can just import all the functions you need and declare them. No need to have logic here. It makes it cleaner in my opinion.

require('module-alias/register');
const functions = require('firebase-functions');

const onCreate = require('@background/onCreate');
const onDelete = require('@background/onDelete');
const onUpdate = require('@background/onUpdate');

const tours  = require('@http/tours');
const stripe = require('@http/stripe');

const docPath = 'tours/{tourId}';

module.exports.onCreate = functions.firestore.document(docPath).onCreate(onCreate);
module.exports.onDelete = functions.firestore.document(docPath).onDelete(onDelete);
module.exports.onUpdate = functions.firestore.document(docPath).onUpdate(onUpdate);

module.exports.tours  = functions.https.onRequest(tours);
module.exports.stripe = functions.https.onRequest(stripe);

CI/CD

How about having continuos integration and deployment every time you push your changes to the repo? You can have it by using google google cloud build. It's free until certain point :) Check this link.

./cloudbuild.yaml

steps:
  - name: "gcr.io/cloud-builders/npm"
    args: ["run", "install:functions"]
  - name: "gcr.io/cloud-builders/npm"
    args: ["test"]
  - name: "gcr.io/${PROJECT_ID}/firebase"
    args:
      [
        "deploy",
        "--only",
        "functions",
        "-P",
        "${PROJECT_ID}",
        "--token",
        "${_FIREBASE_TOKEN}"
      ]

substitutions:
    _FIREBASE_TOKEN: nothing
Inapprehensive answered 4/4, 2019 at 18:2 Comment(2)
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);Peroration
@Peroration what is the command you are using with firebase command line? To help you, I'll need to see some codeInapprehensive
O
4

There is a pretty good way to organize all of your cloud functions for the long term. I did this recently and it is working flawlessly.

What I did was organize each cloud function in separate folders based on their trigger endpoint. Every cloud function filename ends with *.f.js. For example, if you had onCreate and onUpdate triggers on user/{userId}/document/{documentId} then create two files onCreate.f.js and onUpdate.f.js in directory functions/user/document/ and your function will be named userDocumentOnCreate and userDocumentOnUpdate respectively. (1)

Here is a sample directory stucture:

functions/
|----package.json
|----index.js
/----user/
|-------onCreate.f.js
|-------onWrite.f.js
/-------document/
|------------onCreate.f.js
|------------onUpdate.f.js
/----books/
|-------onCreate.f.js
|-------onUpdate.f.js
|-------onDelete.f.js

Sample Function

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const db = admin.database();
const documentsOnCreate = functions.database
    .ref('user/{userId}/document/{documentId}')
    .onCreate((snap, context) => {
        // your code goes here
    });
exports = module.exports = documentsOnCreate;

Index.js

const glob = require("glob");
const camelCase = require('camelcase');
const admin = require('firebase-admin');
const serviceAccount = require('./path/to/ServiceAccountKey.json');
try {
    admin.initializeApp({ credential: admin.credential.cert(serviceAccount),
    databaseURL: "Your database URL" });
} catch (e) {
    console.log(e);
}

const files = glob.sync('./**/*.f.js', { cwd: __dirname });
for (let f = 0, fl = files.length; f < fl; f++) {
    const file = files[f];
    const functionName = camelCase(file.slice(0, -5).split('/')); 
    if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === functionName) {
        exports[functionName] = require(file);
      }
}

(1): You can use any name you want. To me, onCreate.f.js, onUpdate.f.js etc. seem more relevant to the kind of trigger they are.

Or answered 11/11, 2018 at 21:25 Comment(4)
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)Gospodin
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 codeOr
I'm fine with deploying everything, I just want to version the functions that I put (http triggered ones)Gospodin
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.Or
G
4

I am also in the jouney of finding the best folder structure for Cloud Functions, so I decided to share what I've come up with:

+  /src
|    - index.ts
|    + /events
|    |    - moduleA_events.ts
|    |    - moduleB_events.ts
|    + /service
|    |    - moduleA_services.ts
|    |    - moduleB_services.ts
|    + /model
|    |    - objectA.ts
|    |    - objectB.ts
|    |    - objectC.ts
  • /src/index.ts this file works as the entry point for all events (functions) available in your app, such as database events, https requests, scheduled functions. However, functions are not directly declared in index.js, but in the events folder indead. Code sample:

    exports.user = require("./events/userEvents")
    exports.order = require("./events/orderEvents")
    exports.product = require("./events/productEvents")

Note: according to GCF official documentation, this approach will automatically rename all your functions to the "module-function" pattern. Example: if you have "userCreated" function inside userEvents.ts, firebase will rename this function to "user-userCreated"

  • /src/events this folder should only contain cloud functions declarations and should not handle business logic directly. For the actual business, you should call custom functions from your /service folder (which maps the same modules as in the events folder). Code sample for userEvents.ts:

    exports.userCreated = functions.firestore.document("/users/{documentId}").onCreate(async (snapshot) => { userServices.sendWelcomeEmail() }

  • /src/service the actual busienss logic that will connect with other firebase services such as firestore, storage, auth. You can also import your /model layer here (typescript only).

  • /src/model the interfaces used in typescript to ensure strong typed functions and objects.

As you would notice, this approach is mainly based on MVC and OOP principles. There is a lot of good debates on whether we should go with functional programming in serverless environment instead. Since my backend background is Java & C#, the folder structure I presented here seems more natural to me, however, I would be very interested in knowing how different this folder structure would be when moving to a functional programming approach.

Glutelin answered 6/9, 2021 at 14:14 Comment(2)
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)Periphery
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.Periphery
L
3

Here's a simple answer if you're creating cloud functions with typescript.

/functions
|--index.ts
|--foo.ts

Near all your regular imports at the top just export all the functions from foo.ts.

export * from './foo';

Liber answered 21/11, 2019 at 4:46 Comment(1)
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?Christie
F
1

I use a vanilla JS bootloader to auto-include all of the functions I want to use.

├── /functions
│   ├── /test/
│   │   ├── testA.js
│   │   └── testB.js
│   ├── index.js
│   └── package.json

index.js (bootloader)

/**
 * The bootloader reads all directories (single level, NOT recursively)
 * to include all known functions.
 */
const functions = require('firebase-functions');
const fs = require('fs')
const path = require('path')

fs.readdirSync(process.cwd()).forEach(location => {
  if (!location.startsWith('.')) {
    location = path.resolve(location)

    if (fs.statSync(location).isDirectory() && path.dirname(location).toLowerCase() !== 'node_modules') {
      fs.readdirSync(location).forEach(filepath => {
        filepath = path.join(location, filepath)

        if (fs.statSync(filepath).isFile() && path.extname(filepath).toLowerCase() === '.js') {
          Object.assign(exports, require(filepath))
        }
      })
    }
  }
})

This example index.js file only auto-includes directories within the root. It could be expanded to walk directories, honor .gitignore, etc. This was enough for me though.

With the index file in place, adding new functions is trivial.

/test/testA.js

const functions = require('firebase-functions');

exports.helloWorld = functions.https.onRequest((request, response) => {
 response.send("Hello from Firebase!");
});

/test/testB.js

const functions = require('firebase-functions');

exports.helloWorld2 = functions.https.onRequest((request, response) => {
 response.send("Hello again, from Firebase!");
});

npm run serve yields:

λ ~/Workspace/Ventures/Author.io/Firebase/functions/ npm run serve

> functions@ serve /Users/cbutler/Workspace/Ventures/Author.io/Firebase/functions
> firebase serve --only functions


=== Serving from '/Users/cbutler/Workspace/Ventures/Author.io/Firebase'...

i  functions: Preparing to emulate functions.
Warning: You're using Node.js v9.3.0 but Google Cloud Functions only supports v6.11.5.
✔  functions: helloWorld: http://localhost:5000/authorio-ecorventures/us-central1/helloWorld
✔  functions: helloWorld2: http://localhost:5000/authorio-ecorventures/us-central1/helloWorld2

This workflow is pretty much just "write and run", without having to modify the index.js file each time a new function/file is added/modified/removed.

Fagen answered 23/7, 2018 at 23:36 Comment(1)
won't this be have on cold start?Unaccomplished
E
1

On my effort to implement the solution of @zaidfazil, I came up with the following (using JavaScript, not TypeScript).

multi.js

exports.onQuestionMultiCreate = functions.database
  .ref("/questions-multi/{questionId}")
  .onCreate(async (snapshot, context) => {
   ...
    }
  });

trueFalse.js

exports.onQuestionTrueFalseCreate = functions.database
  .ref("/questions-truefalse/{questionId}")
  .onCreate(async (snapshot, context) => {
   ...
    }
  });

index.js


const multi = require("./multi");
const trueFalse = require("./trueFalse");

module.exports = {
  ...multi,
  ...trueFalse
Ethnomusicology answered 15/7, 2021 at 18:50 Comment(0)
O
1

For Latest Fireabse V9 and Node 18 (my stack Next.Js 13 + Fireabse sdk v9)

When getting started with Cloud Functions you might put your first few functions in a single file:

index.js

const functions = require('firebase-functions');
exports.foo = functions.https.onRequest((request, response) => {
  // ...
});
exports.bar = functions.https.onRequest((request, response) => {
  // ...
});

This can become hard to manage with more than a few functions. Instead, you can put all of your logic for each function in its own file and use your index.js file as a simple list of exports:

foo.js

const functions = require('firebase-functions');
exports.fooFunc = functions.https.onRequest((request, response) => {
  // ...
});

bar.js

const functions = require('firebase-functions');
exports.barFunc = functions.https.onRequest((request, response) => {
  // ...
});

index.js

const foo = require('./foo');
const bar = require('./bar');
exports.foo = foo.fooFunc;
exports.bar = bar.barFunc;

Ref: Firebase Docs

Don't forget to deploy:

firebase deploy --only functions:fooFunc

Remember if you follow this structure you might see an error about initializeApp() called multiple times. Remember you should just include initialisation on your index file like this:

const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
Opus answered 6/6, 2023 at 12:55 Comment(0)
H
0

I spent lot of time looking for the same, and there is what I think is the best way to achieve it (I'm using [email protected]):

https://codeburst.io/organizing-your-firebase-cloud-functions-67dc17b3b0da

No sweat ;)

Hesperian answered 7/10, 2019 at 23:37 Comment(1)
Thx for sharing :). Just came over this question hahahFolk
D
0

The above answers pointed me to the right direction, just that none really worked for me. Below is a working prototype, an example of onCall, onRequest and a Database trigger

foo.js - onCall

exports.handler = async function(data, context, admin) {
    // const database = admin.database();
    // const firestore = admin.firestore();
    //...
};

bar.js - onRequest

exports.handler = async function(req, res, admin) {
    // const database = admin.database();
    // const firestore = admin.firestore();
    //...
};

jar.js - trigger/document/onCreate

exports.handler = async function(snapshot, context, admin) {
    // const database = admin.database();
    // const firestore = admin.firestore();
    //...
};

index.js

// import firebase admin SDK dependencies

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase); 

// import functions
const foo = require("./foo");
const bar = require("./bar");
const jar = require("./jar");

// onCall for foo.js
exports.foo = functions.https.onCall((data, context) => {
    return foo.handler(data, context, admin);
});

// onRequest for bar.js
exports.bar = functions.https.onRequest((req, res) => {
    return bar.handler(req, res, admin);
});

// document trigger for jar.js
exports.jar = functions.firestore
  .document("parentCollection/{parentCollectionId}")
  .onCreate((snapshot, context) => {
    return jar.handler(snapshot, context, admin);
});

NOTE: You can also make a sub folder to house your individual functions

Digenesis answered 20/8, 2020 at 13:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.