How to Upload Files to Firebase Cloud Storage With React and Node.js
By: Claire Chabas
Tags: JavaScript, React, Programming, Nodejs, Firebase

API: Node.js + Express.js + Multer

To provide you with a fully functional repo, I’ve included the API in the same project folder.

However, this is not ideal since having all your eggs in the same basket never is. So, keep in mind that in real life, you’ll either use a serverless function or have the upload API run with the rest of your back end.

Setting up the Express server

1. We install the necessary dependencies:

$ npm i express body-parser cors dotenv

2. At the root of the project, we create an api/ folder and within it an index.js file in which we set up the Express server:

Now we want both our front end and our API to run simultaneously while still having a single command to run. For this, inside our package.json file, we update our start script as such:

"scripts": {

"start": "(cd api && node index.js) & react-scripts start",

...

}

By the way, if you want to read more about how to run multiple commands simultaneously, I found this article useful.

Adding the upload endpoint

We create an /api/upload endpoint that is able to receive POST requests. We’ll use Multer, which is a Node.js middleware that allows us to handle multipart/form-data :

$ npm i multer

Multer adds a file (or files if you send multiples files) to the request object, which contains the file(s) uploaded. It also adds a body object to request , which contains any other text field you’d want to add to your request.

So, thanks to Multer, we’ll be able to access our uploaded file via req.file and our remaining data from req.body .

In index.js , we create a Multer instance:

const multer = require('multer');

To send our file to Firebase, we’ll need to make a Buffer object out of it. We’ll do that using the memory storage engine provided by Multer:

When using memory storage, our req.file will contain a field called buffer that contains the entire file.

Here’s the starter code for this endpoint:

We’re halfway there. Now all that is left is connecting to Firebase Cloud Storage and processing our files as the service needs to upload it properly.

Connecting to Firebase Cloud Storage

First, we need to add the @google-cloud/storage dependency to connect to Firebase Cloud Storage:

$ npm i @google-cloud/storage

2. Going on, we’ll need three environment variables. This is where the dotenv module we installed at the beginning comes into play. It loads environment variables from a .env file into process.env .

Create a .env file at the root of your project and add two environment variables inside it:

GCLOUD_PROJECT_ID : This is your Firebase project ID. You can find it in your Firebase account > Settings > General settings.

: This is your Firebase project ID. You can find it in your Firebase account > Settings > General settings. GCLOUD_APPLICATION_CREDENTIALS : Remember the private key you stored somewhere at the beginning? This is where we need it. You need to indicate the path to your private key JSON file here. So, it could be something like /api/services/myprivatekey.json .

: Remember the private key you stored somewhere at the beginning? This is where we need it. You need to indicate the path to your private key JSON file here. So, it could be something like . GCLOUD_STORAGE_BUCKET_URL : Finally, you need the URL of your storage bucket which is [YOUR_GCLOUD_PROJECT_ID].appspot.com . If you’re not sure, you can see it in your Firebase account > Storage > Files tab (which is the default one) > Just above the stored files list on the left, you have a URL like gs://you-project-id.appspot.com . This is it.

Finally, make sure you add your new .env file to your .gitignore .

3. Now we can initiate a Storage instance with our Firebase credentials:

const { Storage } = require('@google-cloud/storage'); const storage = new Storage({

projectId: process.env.GCLOUD_PROJECT_ID,

keyFilename: process.env.GCLOUD_APPLICATION_CREDENTIALS,

});

4. And create a bucket , which is a container for objects (files), which we associate to our Firebase storage bucket. We’ll use it below in our endpoint to process our file.

const bucket =

storage.bucket(process.env.GCLOUD_STORAGE_BUCKET_URL);

5. Next, we continue inside our app.post('api/upload') endpoint. After checking that we do have an existing req.file . If you log it, you’ll see it contains the following:

{

fieldname: 'image',

originalname: 'Medium Post Cover 3.png',

encoding: '7bit',

mimetype: 'image/png',

buffer: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 0b 22 00 00 07 6c 08 06 00 00 00 e9 93 d9 14 00 00 00 04 67 41 4d 41 00 00 b1 8f 0b fc 61 05 00 ... 1834232 more bytes>,

size: 1834282

}

Firebase uses Blobs (Binary Large Objects), which is a data type that can store binary data in a database.

So, we create a new blob in our bucket with the file() method, passing it our file name as reference:

const blob = bucket.file(req.file.originalname);

Then we initiate a writable stream. FreeCodeCamp wrote an awesome guide about Node.js streams in which they answer all the questions you might have about them (and I had a lot).

Streams are a collection of data (like arrays or strings) that might not be available all at once. They allow us to work with large amounts of data (like images or videos) that we need to process one chunk at a time.

And a writable stream is one type of stream that is an abstraction for a destination to which data can be written, so basically, we use a writable stream when we want to write data, which is what we want here.

const blobStream = blob.createWriteStream( {

metadata: {

contentType: req.file.mimetype,

},

} );

Important: You need to pass the file mimetype as metadata to createWriteStream() otherwise your file won’t be stored in the proper format and won’t be readable.

This returns a WriteStream object on which we can check for events. On the finish event, we assemble the public URL of our newly stored file and send it in the response for the user to either display the file on the front or store the location string in a database.

Note that we use encodeURI on the blob.name to cover cases where the file name had whitespaces or other characters needing to be encoded.

// If there's an error

blobStream.on('error', (err) => next(err)); // If all is good and done

blobStream.on('finish', () => { // Assemble the file public URL

const publicUrl =

`https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/${encodeURI(blob.name)}?alt=media`; // Return the file name and its public URL

// for you to store in your own database

res.status(200).send({

fileName: req.file.originalname,

fileLocation: publicUrl

});

}); // When there is no more data to be consumed from the stream the end event gets emitted

blobStream.end(req.file.buffer);

And there we have it! Our API is ready for use. Here is the gist with the full index.js file:

index.js | File upload API endpoint handling uploads to Firebase Cloud Storage

All that’s left is a simple front end with a form handling a file input. We’ll use React for this.