186

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?

chainicko
  • 461
  • 2
  • 13
jasonsirota
  • 3,459
  • 3
  • 15
  • 14
  • 1
    @JPVentura. Really don't understand you well. Please explain. – HuyLe Jan 09 '18 at 00:06
  • Has this been updated for v1.0? I am having issues: https://stackoverflow.com/questions/50089807/firebase-cloud-functions-functions-predeploy-error-when-structuring-multiple-f – tccpg288 Apr 29 '18 at 23:09
  • 3
    FYI, this official Firebase functions example contains several `.js` files imported through `require`: https://github.com/firebase/functions-samples/tree/master/isomorphic-react-app/functions – AlexGrafe Apr 06 '19 at 10:45
  • This might be helpful: https://stackoverflow.com/questions/43486278/how-do-i-structure-cloud-functions-for-firebase-to-deploy-multiple-functions-fro – Ramesh-X May 12 '20 at 12:43

16 Answers16

137

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) => {
    ...
};
jasonsirota
  • 3,459
  • 3
  • 15
  • 14
  • 1
    Can I for example have several functions in the foo module? If so, how is it better to implement it? – Alexander Khitev May 18 '17 at 03:29
  • 1
    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); – jasonsirota May 19 '17 at 00:14
  • 49
    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. – bvs Sep 27 '17 at 15:18
  • 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" – Alan Oct 02 '17 at 17:47
  • 2
    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. – jasonsirota Oct 03 '17 at 18:32
  • there is an official documentation for this, the grouping function section maybe will also help you, check it here: https://firebase.google.com/docs/functions/organize-functions – sarah Jan 23 '21 at 08:20
88

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'); 
}
anothermh
  • 6,482
  • 3
  • 20
  • 39
College Student
  • 1,460
  • 13
  • 15
  • 1
    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. – tonkatata Sep 22 '19 at 10:26
  • 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 Sep 11 '20 at 20:38
55

Update: This doc should help, my answer is older than this doc.


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);
}
Ced
  • 12,657
  • 11
  • 62
  • 124
  • 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? :) – bersling Aug 25 '18 at 12:02
  • @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 – Ced Feb 11 '19 at 13:12
  • 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? – dsg38 Sep 04 '19 at 22:42
  • 1
    @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 – Ced Sep 05 '19 at 01:28
  • 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 Jan 12 '21 at 05:21
27

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);
  });
Luke Pighetti
  • 2,785
  • 4
  • 25
  • 40
  • 2
    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? – Simon Fakir Oct 23 '18 at 16:12
  • 3
    i get an eslint partsing error `unexpected token ...` inside index.js. – thomas Jul 15 '19 at 14:27
  • Perhaps you are not using Node 8 – Luke Pighetti Jul 15 '19 at 15:46
  • @SimonFakir good question. Have you found something about it? – atereshkov Sep 09 '19 at 10:02
  • @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. – Simon Fakir Sep 11 '19 at 12:41
  • I'm also getting unexpedted token ..., running node 8 and eslint 5.12 – Douglas Schmidt Jan 20 '20 at 17:39
  • 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 Mar 21 '20 at 15:08
  • https://stackoverflow.com/questions/36001552/eslint-parsing-error-unexpected-token – Axes Grinds Mar 21 '20 at 15:08
19

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

Reza
  • 15,335
  • 4
  • 62
  • 120
  • 1
    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? – elprl Dec 29 '18 at 16:47
  • 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 Sep 04 '19 at 22:40
  • 1
    @dsg38 you can mix all together, this makes it clear – Reza Sep 05 '19 at 13:30
  • I did something similar for TS thanks this is a simple and good solution – Ruben Jul 24 '20 at 22:08
12

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
Konstantin Tarkus
  • 35,208
  • 14
  • 127
  • 117
12

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.

user1032613
  • 17,258
  • 13
  • 73
  • 91
Adam Hurwitz
  • 6,459
  • 4
  • 48
  • 92
8

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.

zaidfazil
  • 8,041
  • 2
  • 16
  • 39
  • I can't see how this could possibly work since Firebase supports Node 6.11 currently which doesn't support ES6 import directives? – Aodh Apr 21 '18 at 13:30
  • If you are using typescript, the problem should never arise. I did port most of my code into typescript lately. – zaidfazil Apr 22 '18 at 10:56
  • 2
    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. https://stackoverflow.com/questions/43486278/how-do-i-structure-cloud-functions-for-firebase-to-deploy-multiple-functions-fro#48825037 – PostureOfLearning May 15 '18 at 12:35
  • 1
    thank you. this worked with typescript and node 6 :) – Ahmad Moussa Sep 23 '18 at 15:24
  • 4
    Rather than import and re-export with spread operators, couldn't you just have `export * from './fooFunctions';` and `export * from './barFunctions';` in index.ts? – whatsthatitspat Nov 29 '18 at 14:43
  • Didn't think about it at the moment. That's really a nice one. – zaidfazil Dec 05 '18 at 04:54
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];
}
davidverweij
  • 151
  • 14
Matthew Rideout
  • 4,311
  • 1
  • 24
  • 46
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
ajorquera
  • 952
  • 16
  • 22
  • 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); – OK200 Apr 30 '19 at 05:13
  • @OK200 what is the command you are using with firebase command line? To help you, I'll need to see some code – ajorquera May 06 '19 at 14:15
5

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;
nhe
  • 115
  • 1
  • 7
  • Have you found a better way of importing the exports in index.js, instead of manually wiring each individual file? – Zorayr Sep 11 '20 at 20:43
3

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.

krhitesh
  • 727
  • 7
  • 14
  • 1
    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) – Alex Sorokoletov Dec 02 '18 at 21:45
  • 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 – krhitesh Dec 03 '18 at 04:59
  • 1
    I'm fine with deploying everything, I just want to version the functions that I put (http triggered ones) – Alex Sorokoletov Dec 03 '18 at 05:12
  • 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. – krhitesh Dec 03 '18 at 05:30
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';

Markymark
  • 1,724
  • 22
  • 29
  • 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 Jan 12 '21 at 05:18
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.

Corey
  • 4,266
  • 2
  • 21
  • 20
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 firebase@7.3.0):

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

No sweat ;)

Sebastián Lara
  • 4,397
  • 2
  • 23
  • 21
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