Seamless Image Handling: Uploading to Firebase Storage and Storing Image URLs in MongoDB Using Node.js

#Nodejs #FirebaseStorage #MongoDB #ExpressJs

Seamless Image Handling: Uploading to Firebase Storage and Storing Image URLs in MongoDB Using Node.js

Welcome Coders! 💻️

In this guide, we'll utilize Firebase Storage to securely store images for our application while storing image URLs within our database model.

Let's get started! 👍️

Prerequisites

  • Basic knowledge of Node.js

  • Installed Node.js and npm

  • Access to a MongoDB database

  • Set up Firebase Storage for image upload

Setting up Firebase Admin SDK with Nodejs

Create a Firebase Account:

  1. Sign in to Firebase.

  2. Click Go to console.

  3. Click + Add Project and follow the prompts to create a project. You can name your project anything you want.

  4. Once the project is created, the project configuration page is open.

Generate a new private key for Admin SDK (node js)

  1. In the Firebase console click on the Setting icon: Project settings -> Service accounts.

  2. Click on Generate new private key.

  3. Rename the downloaded JSON file to Firebaseservicekey.json and move it to the project containing the main nodejs server file.

Setting up the Nodejs Server

Familiarity with Node.js and API integration will be beneficial when following this guide.

Setup and Configuration:

Initialize Node.js project: npm init

Install necessary packages:

npm install express mongoose firebase-admin multer uuid-v4 dotenv cors

Project folder structure:

create files according to folder structure.

  • Server.js: main server file containing express server, routes, MongoDB connection.
require('dotenv').config()
const express=require('express')
const app=express()
const mongoose=require('mongoose')
const cors=require('cors')

//server port
const port=process.env.PORT||4000
//mongodb database url
const url=process.env.url
//artist route
const artistRoute=require('./routes/artistRoute')
app.use(express.urlencoded({extended:true}))
app.use(express.json())
app.use(cors())
//Routers
app.use('/artist',artistRoute)
//starting server  
app.listen(port,()=>{
    console.log(`Listening on port:${port}`)
})
//Database connection mongoDB
mongoose.connect(url)
const db=mongoose.connection
db.on('error',(error)=>{
    console.error('MongoDB connection error:',error)
})
db.once('open',()=>{
    console.log('Connected to MongoDB')
})
  • artistModel.js: Artist schema for storing artist name, cover_img (URL & file path)
const mongoose=require('mongoose')

const artistSchema=new mongoose.Schema({
    name:{
        type:String,
    },
    picture:{
        url:{
            type:String,
        },
        filepath:{
            type:String,
        }
    },
    type:{
        type:String,
        default:'artist'
    }

})

module.exports=mongoose.model('Artist',artistSchema)

artistRoute.js: routes to create, update, delete artist.

 const express=require('express')
 const router=express.Router()
 //unique id
 const UUID = require("uuid-v4");
 //firebase bucket
 const bucket=require('../firebaseadmin')
 //upload file in firebase
 const uploadFile=require('../firebasestorage/upload')

 // Configure multer to store uploaded files in memory
 const multer = require('multer');
 const upload = multer({ storage: multer.memoryStorage() });

 //artist model 
 const artistModel=require('../models/artistModel')


 //Create Artist 
 router.post('/',upload.single('file'),async(req,res)=>{
    try{
        //file data containing :buffer,mimetype,name,etc
        const file = req.file;
        //filepath of image in firebase bucket. 
        const filename = `aura/artist_img/${Date.now()}_${file.originalname}`;
        //unique id
        const uuid=UUID()

        //after successfull upload of image a url is returned. 
        const fileURL = await uploadFile(file.buffer, filename,file.mimetype,uuid);
        //creating artist model
        const artist=new artistModel({
        name:req.body.name,
        picture:{ 
            //url to access image            
            url:fileURL,
            //filepath can be used to delete image in future.
            filepath:filename
        }
        })
        //saving artist model
        await artist.save()
        res.status(200).json({message:'Artist Saved successfully'})

    }catch(err){
        console.log(err)
        res.status(500).send('Internal Server Error')
    }
 })

 //Get all artist
 router.get('/',async(req,res)=>{
    try{
        const artist=await artistModel.find()
        res.status(200).json({data:artist,message:'success'})
    }catch(err){
        console.log(err)
        res.status(500).json({data:err,message:'Internal Server Error!'})
    } 
 })

 //Update Artist 
 router.put('/update',upload.single('file'),async(req,res)=>{
       try{
          //if image file present then replace image otherwise just update req.body data. 
          if(!req.file){
            //update artist model without image
            const artist=await artistModel.findByIdAndUpdate(req.body.id,req.body,{new:true})

          }else{
            //update artist model with image
            const artist=await artistModel.findById(req.body.id)
            //delete previous image present in bucket
            await bucket.file(artist.filepath).delete()
            //upload new one
            const filename = `aura/artist_pic/${Date.now()}_${req.file.originalname}`;
            const uuid=UUID()
            const fileURL = await uploadFile(req.file.buffer, filename, req.file.mimetype,uuid);
            //updating url and filepath
            artist.picture=fileURL
            artist.filepath=filename
            //update artist model
            const updateartist=await artistModel.findByIdAndUpdate(artist._id,artist,{new:true})
          }
          res.status(200).json({message:'Artist Updated Successfully'})

       }catch(err){
        console.log(err)
        res.status(500).send('Internal Server Error')
       }
 })

 //Delete Artist
 router.delete('/delete/:id',async(req,res)=>{
  try{
      //find artist in database using id
      const artist=await artistModel.findById(req.params.id)
      //delete image object in firebase bucket
      await bucket.file(artist.filepath).delete()
      //delete artist model in database
      const artistdelete=await artistModel.findByIdAndDelete(artist._id)    
      res.status(200).json('Artist deleted successfully')
  }catch(err){
    console.log(err)
    res.status(500).json('Internal Server Error')
  }
 })

 //Get Single Artist by Id
 router.get('/single/:id',async(req,res)=>{
  try{
      const artist=await artistModel.findById(req.params.id)
      res.status(200).json({data:artist,message:'success'})

  }catch(err){
    console.log(err)
    res.status(500).json({data:err,message:'Internal Server Error'})
  }
 }) 

 module.exports=router
  • firebaseadmin.js: firebase bucket config file

 require('dotenv').config()
 var admin = require("firebase-admin");

 //firebase Accountkey
 var serviceAccount = require("./Firebaseservicekey.json");


 admin.initializeApp({
   credential: admin.credential.cert(serviceAccount),
   storageBucket: process.env.BUCKET_URL,
 });

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

 module.exports=bucket
  • upload.js: to handle file upload to firebase bucket using createWriteStream method and return a signed URL of the file(image) to store it in the database.
const bucket=require('../firebaseadmin')
const { promisify } = require('util');

const uploadFile = promisify((buffer, filename, mimetype,uuid, cb) => {
    const fileUpload = bucket.file(filename);
    const blobStream = fileUpload.createWriteStream({
      metadata: {
        contentType: mimetype,
        metadata: {
            firebaseStorageDownloadTokens: uuid
          }
      },
    });

    blobStream.on('error', (error) => {
      console.error(error);
      cb({ error: 'An error occurred while uploading the file' });
    });

    blobStream.on('finish', () => {
      const fileURL = `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/${encodeURIComponent(filename)}?alt=media&token=${uuid}`;
      cb(null, fileURL);
    });

    blobStream.end(buffer);
  });
module.exports=uploadFile
  • .env: MongoDB connection URL and firebase bucket URL.
url=mongodb_database_url
//eg: mongodb://localhost:27017
BUCKET_URL=firebase_bucket_url
//eg: gs://firebase_project_name.appspot.com/

In Firebase Console ->Storage menu - copy the bucket URL and store it in the .env file

  • package.json: npm package details and scripts to run the server.js file
{
  "name": "project",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "node server.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cors": "^2.8.5",
    "dotenv": "^16.3.1",
    "express": "^4.18.2",
    "firebase-admin": "^11.10.1",
    "mongoose": "^7.4.3",
    "multer": "^1.4.5-lts.1",
    "uuid-v4": "^0.1.0"
  }
}
  • Run the node server
npm run dev

  • The server starts running on port:4000

After successfully running our node server we can test the route

For this guide, we will use THUNDER CLIENT to send HTTP/ requests to our server.

Thunder Client can be downloaded from the VS. code extension tab.

Send HTTP Post request to create a new artist:

localhost:4000/artist

After a successful post request, you will get a response: Status:200 OK and a success message!

The cover image URL and file path are saved in the Artist model.

That's it!

I hope you find the information valuable.

For any questions, you can reach me on Linkedin

Feel free to checkout my Music streaming application AURA created using Nodejs, Vitejs, MongoDB and Firebase storage.