52

I'm trying to understand how to upload files in Firebase Storage, using Node.js. My first try was to use the Firebase library:

"use strict";

var firebase = require('firebase');

var config = {
    apiKey: "AIz...kBY",
    authDomain: "em....firebaseapp.com",
    databaseURL: "https://em....firebaseio.com",
    storageBucket: "em....appspot.com",
    messagingSenderId: "95...6"
};

firebase.initializeApp(config);

// Error: firebase.storage is undefined, so not a function
var storageRef = firebase.storage().ref();

var uploadTask = storageRef.child('images/octofez.png').put(file);

// Register three observers:
// 1. 'state_changed' observer, called any time the state changes
// 2. Error observer, called on failure
// 3. Completion observer, called on successful completion
uploadTask.on('state_changed', function(snapshot){
    ...
}, function(error) {
    console.error("Something nasty happened", error);
}, function() {
  var downloadURL = uploadTask.snapshot.downloadURL;
  console.log("Done. Enjoy.", downloadURL);
});

But it turns out that Firebase cannot upload files from the server side, as it clearly states in the docs:

Firebase Storage is not included in the server side Firebase npm module. Instead, you can use the gcloud Node.js client.

$ npm install --save gcloud

In your code, you can access your Storage bucket using:

var gcloud = require('gcloud')({ ... }); var gcs = gcloud.storage();
var bucket = gcs.bucket('<your-firebase-storage-bucket>');
  • Can we use gcloud without having an account on Google Cloud Platform? How?

  • If not, how come that uploading files to Firebase Storage from the client side is possible?

  • Can't we just create a library that makes the same requests from the server side?

  • How is Firebase Storage connected with Google Cloud Platform at all? Why Firebase allows us to upload images only from the client side?


My second try was to use the gcloud library, like mentioned in the docs:

var gcloud = require("gcloud");

// The following environment variables are set by app.yaml when running on GAE,
// but will need to be manually set when running locally.
// The storage client is used to communicate with Google Cloud Storage
var storage = gcloud.storage({
  projectId: "em...",
  keyFilename: 'auth.json'
});

storage.createBucket('octocats', function(err, bucket) {

    // Error: 403, accountDisabled
    // The account for the specified project has been disabled.

    // Create a new blob in the bucket and upload the file data.
    var blob = bucket.file("octofez.png");
    var blobStream = blob.createWriteStream();

    blobStream.on('error', function (err) {
        console.error(err);
    });

    blobStream.on('finish', function () {
        var publicUrl = `https://storage.googleapis.com/${bucket.name}/${blob.name}`;
        console.log(publicUrl);
    });

    fs.createReadStream("octofez.png").pipe(blobStream);
});
Community
  • 1
  • 1
Ionică Bizău
  • 109,027
  • 88
  • 289
  • 474

7 Answers7

21

When using the firebase library on a server you would typically authorize using a service account as this will give you admin access to the Realtime database for instance. You can use the same Service Account's credentials file to authorize gcloud.

By the way: A Firebase project is essentially also a Google Cloud Platform project, you can access your Firebase project on both https://console.firebase.google.com and https://console.cloud.google.com and https://console.developers.google.com You can see your Project ID on the Firebase Console > Project Settings or in the Cloud Console Dashboard

When using the gcloud SDK make sure that you use the (already existing) same bucket that Firebase Storage is using. You can find the bucket name in the Firebase web config object or in the Firebase Storage tab. Basically your code should start like this:

var gcloud = require('gcloud');

var storage = gcloud.storage({
  projectId: '<projectID>',
  keyFilename: 'service-account-credentials.json'
});

var bucket = storage.bucket('<projectID>.appspot.com');

...
Nicolas Garnier
  • 12,134
  • 2
  • 42
  • 39
  • That was really helpful, but still, the root problem is still there: I cannot upload stuff using `gcloud` (for instance, creating a bucket) because, according to [this answer](http://stackoverflow.com/a/23136508/1420197), I didn't enable billing. Again, why accessing Storage via `gcloud` would require billing while accessing it via client side firebase would not? Thanks! – Ionică Bizău Oct 04 '16 at 10:08
  • I added another snippet to my question. – Ionică Bizău Oct 04 '16 at 10:10
  • It seems you might need to enable billing this is true. I'll report to the team internally. There are always debates whether if we should create a Firebase specific Server SDK for Storage and for now the consensus is "no". For the billing issue: as a workaround you can enable billing and set a budget with a limit of Zero USD to avoid getting billed anything. The API itself should be free to use and the bucket that Firebase Storage uses has a free quota so things should be working. – Nicolas Garnier Oct 04 '16 at 12:54
  • Looks like an issue to me—to enable billing, I need a billing account. For a billing account I need to sign up on Google Cloud Platform and to sign up there I need a business. All these just to upload something in Firebase Storage from the server side. I'm curious what they will say. Thanks! – Ionică Bizău Oct 04 '16 at 13:00
  • Can't you create the billing account as an individual? In my case I can (I am in Switzerland). If not could you tell me which country you are located in? (This will help for the internal discussions) – Nicolas Garnier Oct 04 '16 at 13:24
  • I'm in Romania—after accepting the terms, this page is opened `https://console.cloud.google.com/freetrial` where clearly states: *This service can only be used for business or commercial reasons. You are responsible for assessing and reporting VAT.*. At least I don't see any other way to skip that. While the VAT id is optional, the business name is mandatory. – Ionică Bizău Oct 04 '16 at 14:47
  • What makes me think at is: why we cannot do the same POST (or PUT etc) request from the server, in the same way like we do it on the client? Probably we can. Would I break the rules if I made one? :-) – Ionică Bizău Oct 04 '16 at 14:50
  • OK I see that for Romania. For Switzerland and USA there is an option to sign up as an individual, thanks. You can always try to send your own requests but just be aware since these are undocumented they could change in non-backward compatible ways (although that's unlikely IMO). – Nicolas Garnier Oct 04 '16 at 15:19
  • I see—probably it's better to wait for feedback from you. I wrote you an email. Thanks! – Ionică Bizău Oct 04 '16 at 16:05
  • Hey Ionica, I initially missed that you are trying to create a new bucket with `storage.createBucket('octocats', function(err, bucket) {` you can't do that without billing (only the Firebase/App Engine bucket are free, other buckets are not). Can you try not creating a bucket and instead using the already created Firebase bucket? The bucket name is in your Firebase config `storageBucket: "em....appspot.com",` – Nicolas Garnier Oct 04 '16 at 16:40
  • Basically instead you should use this: `var bucket = storage.bucket('em....appspot.com')` – Nicolas Garnier Oct 04 '16 at 16:59
  • Yay! It works. I posted [here](https://github.com/firebase/quickstart-nodejs/issues/6#issuecomment-251478326) my code. You can include it in the post if you like. Thanks! – Ionică Bizău Oct 04 '16 at 18:51
  • Can you please take a look [at this related question](http://stackoverflow.com/q/40177087/1420197)? – Ionică Bizău Oct 21 '16 at 12:57
  • So how do I upload a nodejs app to Firebase? Because I just logged into my Firebase account which I havent used in a while and noticed that there are links to 2 recently created Google Actions projects (which is precisely what I want to move from Heroku to Firebase) that have been added automatically. But the link takes me to the console. Can I connect deploy it from my github or upload it from a terminal? Does anyone have a video link tutorial for this? – marciokoko May 15 '17 at 17:28
  • Where is `service-account-credentials.json`? – Aero Wang Aug 10 '17 at 06:34
  • 1
    `gloud` is depreicated now – ishandutta2007 Jun 30 '18 at 11:15
19

Firebase Storage is now supported by the admin SDK with NodeJS:

https://firebase.google.com/docs/reference/admin/node/admin.storage

// Get the Storage service for the default app
var defaultStorage = firebaseAdmin.storage();
var bucket = defaultStorage.bucket('bucketName');
...
Laurent
  • 14,122
  • 13
  • 57
  • 89
  • When I try this, I get `TypeError: defaultStorage is not a function`. With the latest `firebase-admin` npm library. – Pat Needham Jan 16 '18 at 20:54
  • `var bucket = firebaseAdmin.storage().bucket('bucketName');` - that does the trick, following this as a guide: https://firebase.google.com/docs/storage/admin/start#use_a_custom_firebase_app – Pat Needham Jan 16 '18 at 21:30
  • What comes after `bucket()` to get a ref()? These seem to work: bucket.ref() or bucket.child('path') – Baz Feb 15 '19 at 20:21
14

Firebase Admin SDK allows you to directly access your Google Cloud Storage.

For more detail visit Introduction to the Admin Cloud Storage API

var admin = require("firebase-admin");
var serviceAccount = require("path/to/serviceAccountKey.json");

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    storageBucket: "<BUCKET_NAME>.appspot.com"
});

var bucket = admin.storage().bucket();

bucket.upload('Local file to upload, e.g. ./local/path/to/file.txt')
cnnr
  • 1,267
  • 4
  • 18
  • 23
Madhav Kumar
  • 161
  • 2
  • 7
  • How do i download a file on Storage ? – Learn2Code Jul 01 '19 at 02:06
  • 1
    By using the method [getDownloadURL()](https://firebase.google.com/docs/reference/js/firebase.storage.Reference.html#getdownloadurl) – Madhav Kumar Jul 01 '19 at 05:11
  • Thanks for the answer! But can you please share an example of how to use the getDownloadURL() ? Thanks again. – Mohammed Alawneh Jul 19 '19 at 08:17
  • `const destFilename = 'Local destination for file, e.g. ./local/path/to/file.txt';` `const options = {` `// The path to which the file should be downloaded, e.g. "./file.txt"` `destination: destFilename,` `};` `bucket.file('Remote file to download, e.g. file.txt').download(options)` ref [Downloading objects](https://cloud.google.com/storage/docs/downloading-objects) – Madhav Kumar Aug 29 '19 at 11:21
  • @Learn2Code the above code will download the file from storage to local. – Madhav Kumar Aug 29 '19 at 11:48
8

I hope It will useful for you. I uploaded one file from locally and then I added access Token using UUID after that I uploaded into firebase storage.There after I am generating download url. If we hitting that generate url it will automatically downloaded a file.

    const keyFilename="./xxxxx.json"; //replace this with api key file
    const projectId = "xxxx" //replace with your project id
    const bucketName = "xx.xx.appspot.com"; //Add your bucket name
    var mime=require('mime-types');
    const { Storage } = require('@google-cloud/storage');
    const uuidv1 = require('uuid/v1');//this for unique id generation

   const gcs = new Storage({
    projectId: projectId,
    keyFilename: './xxxx.json'
     });
    const bucket = gcs.bucket(bucketName);

    const filePath = "./sample.odp";
    const remotePath = "/test/sample.odp";
    const fileMime = mime.lookup(filePath);

//we need to pass those parameters for this function
    var upload = (filePath, remoteFile, fileMime) => {

      let uuid = uuidv1();

      return bucket.upload(filePath, {
            destination: remoteFile,
            uploadType: "media",
            metadata: {
              contentType: fileMime,
              metadata: {
                firebaseStorageDownloadTokens: uuid
              }
            }
          })
          .then((data) => {

              let file = data[0];

              return Promise.resolve("https://firebasestorage.googleapis.com/v0/b/" + bucket.name + "/o/" + encodeURIComponent(file.name) + "?alt=media&token=" + uuid);
          });
    }
//This function is for generation download url    
 upload(filePath, remotePath, fileMime).then( downloadURL => {
        console.log(downloadURL);

      });
4

Note that gcloud is deprecated, use google-cloud instead. You can find SERVICE_ACCOUNT_KEY_FILE_PATH at project settings->Service Accounts.

var storage = require('@google-cloud/storage');

var gcs = storage({
    projectId: PROJECT_ID,
    keyFilename: SERVICE_ACCOUNT_KEY_FILE_PATH
  });

// Reference an existing bucket.
var bucket = gcs.bucket(PROJECT_ID + '.appspot.com');

...
MrRhoads
  • 183
  • 1
  • 10
2

Or you could simply polyfill XmlHttpRequest like so -

const XMLHttpRequest = require("xhr2");
global.XMLHttpRequest = XMLHttpRequest

and import

require('firebase/storage');

That's it. All firebase.storage() methods should now work.

Shubham Kanodia
  • 6,036
  • 3
  • 32
  • 46
0

I have implemented the API to upload the file on the firebase storage:

const fs = require('firebase-admin');
const app = require('express')();
const serviceAccount = require('./serviceAccountKey_DEV.json');

fs.initializeApp({
    credential: fs.credential.cert(serviceAccount),
    storageBucket: `${serviceAccount.project_id}.appspot.com`
});

// Upload the file on firebase storage
app.get("/upload-on-firebase", async (req, res, next) => {
    var bucket = fs.storage().bucket();
    const result = await bucket.upload('data.json');
    console.log('result :', result);
    return res.send(result);
});

Call API to upload:

curl --location --request GET 'http://localhost:3000/upload-on-firebase'

Response:

[
    {
        "_events": {},
        "_eventsCount": 0,
        "metadata": {
            "kind": "storage#object",
            "id": "project_id.appspot.com/data.json/1678222222",
            "selfLink": "https://www.googleapis.com/storage/v1/b/project_id.appspot.com/o/data.json",
            "mediaLink": "https://storage.googleapis.com/download/storage/v1/b/project_id.appspot.com/o/data.json",
            "name": "data.json",
            "bucket": "project_id.appspot.com",
            "generation": "16787922222222",
            "metageneration": "1",
            "contentType": "application/json",
            "storageClass": "STANDARD",
            ....
            ....
            ....
        },
        "baseUrl": "/o",
    }
]
Shubham Verma
  • 8,783
  • 6
  • 58
  • 79