19

I get 'Cannot find module 'firebase' when I try to run this in Lambda (Node.js 4.3)

var Firebase = require('firebase');

Same thing happens when I try to upload a zipped package that includes node_modules/firebase

Does anybody have a working 'write from lambda to firebase' implementation?

starball
  • 20,030
  • 7
  • 43
  • 238
user2676299
  • 617
  • 1
  • 5
  • 14
  • I am getting same error.I wrote below answer in my aws lamda function but how to add firebase module or library.Please give me hint – Narender Reddy May 10 '18 at 04:57

7 Answers7

24

To safely use firebase npm package (version 3.3.0) in AWS Lambda (Nodejs 4.3), Please do the following:

'use strict';

var firebase = require("firebase");

exports.handler = (event, context, callback) => {
    context.callbackWaitsForEmptyEventLoop = false;  //<---Important

    var config = {
        apiKey: "<<apikey>>",
        authDomain: "<<app_id>>.firebaseapp.com",
        databaseURL: "https://<<app_id>>.firebaseio.com",
        storageBucket: "<<app_id>>.appspot.com",
    };

    if(firebase.apps.length == 0) {   // <---Important!!! In lambda, it will cause double initialization.
        firebase.initializeApp(config);
    }

    ...
    <Your Logic here...>
    ...
};
Josiah Choi
  • 1,827
  • 1
  • 12
  • 9
  • This has solved a problem I was wrestling with. How on earth did you find those 2 gems to get firebase working? And I still don't really understand the second one (`fireabase.apps.length == 0`) - according to my logs, it is only ever 0, but if I _don't_ do that test, it times out. So I know I _need_ to do it, but really want to know _why_. – dsl101 Oct 12 '16 at 14:13
  • OK - some speculation (I'm new to Lambda): `context.callbackWaitsForEmptyEventLoop = false` means the function will be suspended (but not 'termindated') when the callback is called. But it may hang around in Lambda-world for quite a long time (at least 25 minutes from my testing). If you call the function again during this time, it is effectively brought back to life with all the variables holding their original values, and this is when `firebase.apps.length == 1`, so no need to initialise again. Am I close? – dsl101 Oct 12 '16 at 14:34
  • Sorry for the flood. One last thought. Does moving the call to `firebase.initializeApp()` outside the handler function (as per @paul-richter's answer) make this simpler? No need to test `firebase.apps.length`, and my testing indicates it is still using the 'frozen' version - the first call takes ~2300ms to get a result from firebase, but subsequent calls are taking around 100ms, suggesting they are already authenticated. – dsl101 Oct 12 '16 at 14:41
  • Some observations for future readers. I appreciate both this solution (which solves the actual question) and the REST API answers (which are likely the better choice for this use case). I tested both in a node6.10 Lambda, and I found the admin SDK spooled in 0.6-4 s and executed in 28-90ms when hot. The REST API spooled in 0.2-0.6s and executed in 75-200ms when hot. It seems that the SDK may be faster (and thus cheaper) in the simple use case of getting a scalar database value once. YMMV, but do test both methods. – Pflugs Apr 10 '18 at 14:20
  • You can move `firebase.initializeApp` to outside of the handler. – Jordizle Jan 10 '22 at 13:36
12

I solved my problem by using firebase REST api

var https = require('https');

exports.handler = function(event, context, callback) {

    var body = JSON.stringify({
        foo: "bar"
    })

   var https = require('https');

var options = {
  host: 'project-XXXXX.firebaseio.com',
  port: 443,
  path: '/.json',
  method: 'POST'
};

var req = https.request(options, function(res) {
  console.log(res.statusCode);
  res.on('data', function(d) {
    process.stdout.write(d);
  });
});
req.end(body);

req.on('error', function(e) {
  console.error(e);
});

    callback(null, "some success message");

}
user2676299
  • 617
  • 1
  • 5
  • 14
11

This is late, but in case someone else is looking:

Zipping your project folder instead of the contents of the project folder can cause this. The zipped folder, when extracted, should not contain a folder with the lambda files in it, but should have the index.js file and the node_modules folder at root level.

A working example of a lambda function is (using latest shiny firebase stuff *sigh*):

var firebase = require('firebase');

// Your service account details
var credentials = {
  "type": "service_account",
  "project_id": "project-123451234512345123",
  "private_key_id": "my1private2key3id",
  "private_key": "-----BEGIN PRIVATE KEY-----InsertKeyHere-----END PRIVATE KEY-----\n",
  "client_email": "projectname@project-123451234512345123.iam.gserviceaccount.com",
  "client_id": "1111222223333344444",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://accounts.google.com/o/oauth2/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/projectname%40project-123451234512345123.iam.gserviceaccount.com"
};

firebase.initializeApp({
  serviceAccount: credentials,
  databaseURL: "https://project-123451234512345123.firebaseio.com"
});

exports.handler = function (event, context, callback) {

  // I use some data passed in from AWS API Gateway:
  if (!event.firebaseUid) {
    callback('Missing param for id');
  }

  firebase.database().ref().child('users').child(firebaseUid).child('at').set(newTokens.access_token).then(function (data) {
    console.log('Firebase data: ', data);
    firebase.database().goOffline();
    callback(null, 'Firebase data: ', data);
  }).catch(function (error) {
    callback('Database set error ' + error);
  });
 };

Now for the caveat. I have experienced this causing the lambda function to timeout even after the firebase callback has happened, ie. the set function seems to create a listener that holds the lambda function open despite return of correct data.

Update: Calling firebase.database().goOffline() fixes the Lambda function timeout issue i was experiencing.

The usual cautions about security not being verified or appropriate, and the possibilities of halting space and time by using this apply.

richter
  • 441
  • 4
  • 8
  • i'm working on my local machine at the moment (in java script), but my function also got stuck in the end. goOffline() is not working in my case. How did you fix that? – Fayza Nawaz Jul 13 '16 at 07:05
  • i'm working on my local machine at the moment (in java script), but my function also got stuck in the end. goOffline() is not working in my case. How did you fix that? – Fayza Nawaz Jul 18 '16 at 11:50
  • 2
    @josiah-choi's answer below solved the problem for me - see `context.callbackWaitsForEmptyEventLoop = false` – dsl101 Oct 12 '16 at 14:48
3

2017-03-22 edit: google just announced firebase cloud functions, which is a much better way to do this. Cloud functions work just like lambda, and can trigger from firebase events.


Here's my solution using the REST api (so you don't need to require anything):

var https = require('https');
var firebaseHost = "yourapp.firebaseio.com";
function fbGet(key){
  return new Promise((resolve, reject) => {
    var options = {
      hostname: firebaseHost,
      port: 443,
      path: key + ".json",
      method: 'GET'
    };
    var req = https.request(options, function (res) {
      res.setEncoding('utf8');
      var body = '';
      res.on('data', function(chunk) {
        body += chunk;
      });
      res.on('end', function() {
        resolve(JSON.parse(body))
      });
    });
    req.end();
    req.on('error', reject);
  });
}

function fbPut(key, value){
  return new Promise((resolve, reject) => {
    var options = {
      hostname: firebaseHost,
      port: 443,
      path: key + ".json",
      method: 'PUT'
    };

    var req = https.request(options, function (res) {
      console.log("request made")
      res.setEncoding('utf8');
      var body = '';
      res.on('data', function(chunk) {
        body += chunk;
      });
      res.on('end', function() {
        resolve(body)
      });
    });
    req.end(JSON.stringify(value));
    req.on('error', reject);
  });
}

You can use it like this:

fbPut("/foo/bar", "lol").then(res => {
  console.log("wrote data")
})

And then:

fbGet("/foo/bar").then(data => {
  console.log(data); // prints "lol"
}).catch(e => {
  console.log("error saving to firebase: ");
  console.log(e);
})
cgenco
  • 3,370
  • 2
  • 31
  • 36
  • 3
    I want to use the REST API, it's the best solution but I'm having trouble with Authentication. I need to get an access token and I'm find it very difficult to find good documentation on this. I've followed a lot of their own docs and haven't been able to get anything working yet – Francisc0 Sep 07 '17 at 20:07
2

Another alternative if you're using a node-based development setup is to use the node-lambda package from here. Essentially it provides wrappers to set up, test and deploy to lambda. node-lambda deploy will package up any modules you've installed (e.g. with npm i --save firebase) and make sure they're available on Lambda itself. I've found it really helpful for managing external modules.

dsl101
  • 1,715
  • 16
  • 36
2

For me firebase-admin should do the trick. https://firebase.google.com/docs/admin/setup

Thanks for Josiah Choi for suggesting context.callbackWaitsForEmptyEventLoop though. So lambda doesn't need to initializeFirebase everytimes. My first run was really slow.

  var firebase = require('firebase-admin');
  module.exports.Test = (event, context, callback) => {

  context.callbackWaitsForEmptyEventLoop = false;  //<---Important

  if(firebase.apps.length == 0) {   // <---Important!!! In lambda, it will cause double initialization.
    firebase.initializeApp({
      credential: firebase.credential.cert("serviceAccount.json"),
      databaseURL: <YOUR FIREBASE URL>
    });

  }


 firebase.database().ref('conversation').once('value').then(function(snapshot) {
    console.log (snapshot.val()) ;
   var bodyReturn = {
     input: snapshot.val()
   } ;

   callback(null,bodyReturn);
   context.succeed() ;
});

};
-1

After trying a few things, this seems to work for me (v 3.10.8) :

for(var i=0;i<5;i++)
{

    var firebase = require('firebase');
    var config = {
        apiKey: "",
        authDomain: "",
        databaseURL: "",
        storageBucket: "",
        messagingSenderId: ""
      };
        if(firebase.apps)
        if(firebase.apps.length==0)
      firebase.initializeApp(config)

        firebase.database().ref().child("test").once('value').
          then(function(snapshot) {
            console.log(snapshot.val());

                                 });

  }
Nick
  • 136
  • 1
  • 10