web-dev-qa-db-fra.com

Comment structurer Cloud Functions for Firebase pour déployer plusieurs fonctions à partir de plusieurs fichiers?

J'aimerais créer plusieurs fonctions de cloud pour Firebase et les déployer en même temps à partir d'un projet. Je voudrais aussi séparer chaque fonction dans un fichier séparé. Actuellement, je peux créer plusieurs fonctions si je les mets toutes les deux dans index.js, telles que:

exports.foo = functions.database.ref('/foo').onWrite(event => {
    ...
});

exports.bar = functions.database.ref('/bar').onWrite(event => {
    ...
});

Cependant, j'aimerais mettre foo et bar dans des fichiers séparés. J'ai essayé ceci:

/functions
|--index.js (blank)
|--foo.js
|--bar.js
|--package.json

où foo.js est

exports.foo = functions.database.ref('/foo').onWrite(event => {
    ...
});

et bar.js est

exports.bar = functions.database.ref('/bar').onWrite(event => {
    ...
});

Y at-il un moyen d'accomplir cela sans mettre toutes les fonctions dans index.js?

85
jasonsirota

Ah, les fonctions de nuage pour les modules de nœud de chargement Firebase normalement, donc cela fonctionne

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) => {
    ...
};
89
jasonsirota

La réponse de @jasonsirota a été très utile. Mais il peut être utile de voir un code plus détaillé, en particulier dans le cas de fonctions déclenchées par HTTP. 

En utilisant la même structure que dans la réponse de @ jasonsirota, disons que vous souhaitez avoir deux fonctions de déclencheur HTTP distinctes dans deux fichiers différents:

structure de répertoire:

    /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'); 
}
51
College Student

Voici comment je l'ai fait personnellement avec TypeScript:

/functions
   |--src
      |--index.ts
      |--http-functions.ts
      |--main.js
      |--db.ts
   |--package.json
   |--tsconfig.json

Permettez-moi de commencer par donner deux avertissements pour que cela fonctionne:

  1. l'ordre d'importation/exportation dans index.ts
  2. la base de données doit être un fichier séparé

Pour le point numéro 2, je ne sais pas pourquoi. Secundo, vous devez respecter ma configuration de index, main et db exactement (au moins pour l'essayer).

index.ts : traite de l'exportation. Je trouve plus propre de laisser l’index.ts s’occuper des exportations.

// main must be before functions
export * from './main';
export * from "./http-functions";

main.ts : Traite l'initialisation.

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

initializeApp(config().firebase);
export * from "firebase-functions";

db.ts : il suffit de réexporter la base de données pour que son nom soit plus court que database()

import { database } from "firebase-admin";

export const db = database();

http-functions.ts

// de 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);
}
29
Ced

Dans le cas où Babel / Flow cela ressemblerait à ceci:

Structure du répertoire

.
├── /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 - Principale (s) exportation (s)

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


src/db.js - Client Cloud SQL pour 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 - Client Firebase Firestore

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

firebase.initializeApp(config().firebase);

export default firebase.firestore();


src/someFuncA.js - Fonction 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 - Fonction 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
8
Konstantin Tarkus

Avec Node 8 LTS maintenant disponible avec les fonctions Cloud/Firebase, vous pouvez effectuer les opérations suivantes avec des opérateurs étendus:

/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);
  });
6
Luke Pighetti

Pour rester simple (mais fait le travail), j'ai personnellement structuré mon code comme ça.

Disposition

├── /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,
};

Fonctionne pour les répertoires de tous niveaux imbriqués. Suivez simplement le modèle à l'intérieur des répertoires aussi.

5
zaidfazil

Pour rester simple (mais fait le travail), j'ai personnellement structuré mon code comme ça.

Disposition

├── /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';

Fonctionne pour les répertoires de tous niveaux imbriqués. Suivez simplement le modèle à l'intérieur des répertoires aussi.

crédit à @zaidfazil answer

4
RezaRahmati

Ce format permet à votre point d’entrée de rechercher des fichiers de fonction supplémentaires et d’exporter automatiquement chaque fonction de chaque fichier.

Script de point d'entrée principal

Recherche tous les fichiers .js dans le dossier des fonctions et exporte chaque fonction exportée à partir de chaque fichier.

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];
    }
  }
});

Exemple d'exportation de plusieurs fonctions à partir d'un fichier

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
}

les points d'accès http accessibles sont nommés de manière appropriée

✔ 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

Un fichier

Si vous ne disposez que de quelques fichiers supplémentaires (un seul par exemple), vous pouvez utiliser:

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

for (var i in your_functions) {
  exports[i] = your_functions[i];
}

4
Matthew Rideout

Il existe un très bon moyen d'organiser toutes vos fonctions cloud à long terme. Je l'ai fait récemment et cela fonctionne parfaitement.

Ce que j'ai fait est d'organiser chaque fonction de nuage dans des dossiers distincts en fonction de leur point de terminaison de déclenchement. Chaque nom de fichier de fonction cloud se termine par *.f.js. Par exemple, si vous avez des déclencheurs onCreate et onUpdate sur user/{userId}/document/{documentId}, créez deux fichiers onCreate.f.js et onUpdate.f.js dans le répertoire functions/user/document/ et votre fonction sera nommée userDocumentOnCreate et userDocumentOnUpdate. (1)

Voici un exemple de structure de répertoire:

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

Exemple de fonction

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): Vous pouvez utiliser le nom de votre choix. Pour moi, onCreate.f.js, onUpdate.f.js, etc. semblent plus pertinents pour le type de déclencheur qu’ils sont.

1
Kumar Hitesh

J'ai donc ce projet qui a des fonctions d'arrière-plan et des fonctions http. J'ai aussi des tests pour les tests unitaires. CI/CD vous simplifiera la vie lors du déploiement de fonctions cloud

Structure de dossier

|-- 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

Remarque: le dossier utils/ est destiné au code de partage entre les fonctions.

fonctions/index.js

Ici, vous pouvez simplement importer toutes les fonctions dont vous avez besoin et les déclarer. Pas besoin d'avoir de logique ici. Cela le rend plus propre à mon avis.

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

Pourquoi ne pas avoir une intégration et un déploiement continus à chaque fois que vous appliquez vos modifications au référentiel? Vous pouvez l'avoir en utilisant google google cloud build . C'est gratuit jusqu'à un certain point :) Cochez cette 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
1
ajorquera

bigcodenerd.org outline est un modèle d'architecture plus simple permettant de séparer les méthodes dans différents fichiers et de les exporter dans une ligne dans le fichier index.js.

L'architecture du projet dans cet exemple est la suivante:

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 = user.removeProfile();

podcast.js

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

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

Le même modèle serait utilisé pour la méthode removeProfile dans le fichier profil.

0
Adam Hurwitz

J'utilise un chargeur de démarrage Vanilla JS pour inclure automatiquement toutes les fonctions que je souhaite utiliser.

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

index.js (chargeur de démarrage)

/**
 * 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))
        }
      })
    }
  }
})

Cet exemple de fichier index.js n'inclut que automatiquement les répertoires de la racine. Il pourrait être étendu pour marcher dans les répertoires, honorer .gitignore, etc. Cela me suffisait cependant.

Avec le fichier d'index en place, l'ajout de nouvelles fonctions est simple.

/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 donne:

λ ~/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

Ce flux de travail consiste en gros à "écrire et exécuter", sans avoir à modifier le fichier index.js chaque fois qu'une nouvelle fonction/un nouveau fichier est ajouté/modifié/supprimé.

0
Corey