How to save images to MongoDB in Node.js
Tags: node, mongodb
Published: 2023-04-07 07:32:38

In this article, we will see how to create APIs to upload an image and store them in MongoDB, list the images and download an image.

## Project setup

Create a directory called `node-mongo-image-upload` and run the command `npm run init` inside it.
This will initiate a Node.js project.

Install the following dependencies:

```bash
npm i express multer dotenv multer-gridfs-storage
```

- `express` to create APIs is Node.js.
- `multer` to upload files.
- `dotenv` - to store configurations like database URL.
- `multer-gridfs-storage` to save the file uploaded via multer to MongoDB.

Create `.env` file and add the MongoDB URL in it:

```
MONGO_DB_URL=mongodb://localhost:27017/images
```

## Uploading file

Create a file named `index.js` with the following code:

```js
const express = require("express")
const multer = require("multer")
const { GridFsStorage } = require("multer-gridfs-storage")
require("dotenv").config()

const url = process.env.MONGO_DB_URL

// Create a storage object with a given configuration
const storage = new GridFsStorage({
  url,
  file: (req, file) => {
    //If it is an image, save to photos bucket
    if (file.mimetype === "image/jpeg" || file.mimetype === "image/png") {
      return {
        bucketName: "photos",
        filename: `${Date.now()}_${file.originalname}`,
      }
    } else {
      //Otherwise save to default bucket
      return `${Date.now()}_${file.originalname}`
    }
  },
})

// Set multer storage engine to the newly created object
const upload = multer({ storage })

const app = express()

app.post("/upload/image", upload.single("avatar"), (req, res) => {
  const file = req.file
  // Respond with the file details
  res.send({
    message: "Uploaded",
    id: file.id,
    name: file.filename,
    contentType: file.contentType,
  })
})

const server = app.listen(process.env.PORT || 8765, function () {
  const port = server.address().port

  console.log("App started at port:", port)
})
```

In the above code,

- We are initializing the GridFs with the bucket name `photos`, whenever an image is uploaded, it will be saved to this bucket.
- We are prefixing the file name with the date so that, each time a unique name is generated.
- If it is not an image, then it will be saved to the default bucket called `fs`.
- We have written a post route `/upload/image`, which will call the upload middleware.
- The upload middleware will save the file sent at the parameter `avatar`.
- Finally, we are responding with the uploaded file details.

Now start the application by running `node index.js` or `nodemon`.

Now in [Postman](https://www.postman.com/), post an image to the endpoint `http://localhost:8765/upload/image`

![Uploading a file in postman](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0c5f7lh48ns27ro3w2bq.png)

If you click 'Send', you will receive uploaded image details in the response:

![upload image postman](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/udiq8mixz42z77f1apqh.png)

Now if you go and check the MongoDB, you will see the following collections created there:

![mongo collections](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/igw21i0tn7la3x0ixfp4.png)

> `fs.files` and `fs.chucks` will be created when you upload files that are not images.

The `photos.files` collection will have the information about the uploaded images:

![photos files](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gzpurosxyzw33u8bdebd.png)

The `photos.chunks` collection will have the actual image data:

![photos chunks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5s44l0szk1p8qmfik2b0.png)

> If the file size is larger, it will break into multiple chunks.

## Listing uploaded files

You can list all the images by querying `photos.files` collection:

```js
const express = require("express")
const multer = require("multer")
const { GridFsStorage } = require("multer-gridfs-storage")
require("dotenv").config()
const MongoClient = require("mongodb").MongoClient

const url = process.env.MONGO_DB_URL

const mongoClient = new MongoClient(url)

// Create a storage object with a given configuration
const storage = new GridFsStorage({
  url,
  file: (req, file) => {
    //If it is an image, save to photos bucket
    if (file.mimetype === "image/jpeg" || file.mimetype === "image/png") {
      return {
        bucketName: "photos",
        filename: `${Date.now()}_${file.originalname}`,
      }
    } else {
      //Otherwise save to default bucket
      return `${Date.now()}_${file.originalname}`
    }
  },
})

// Set multer storage engine to the newly created object
const upload = multer({ storage })

const app = express()

app.post("/upload/image", upload.single("avatar"), (req, res) => {
  const file = req.file
  // Respond with the file details
  res.send({
    message: "Uploaded",
    id: file.id,
    name: file.filename,
    contentType: file.contentType,
  })
})

app.get("/images", async (req, res) => {
  try {
    await mongoClient.connect()

    const database = mongoClient.db("images")
    const images = database.collection("photos.files")
    const cursor = images.find({})
    const count = await cursor.count()
    if (count === 0) {
      return res.status(404).send({
        message: "Error: No Images found",
      })
    }

    const allImages = []

    await cursor.forEach(item => {
      allImages.push(item)
    })

    res.send({ files: allImages })
  } catch (error) {
    console.log(error)
    res.status(500).send({
      message: "Error Something went wrong",
      error,
    })
  }
})

const server = app.listen(process.env.PORT || 8765, function () {
  const port = server.address().port

  console.log("App started at port:", port)
})
```

If you access `http://localhost:8765/images` via postman or browser, you should be able to see the list of image details.

![list all images](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4lie2dfxqu6qwa4198yc.png)

## Downloading an image

You can download an image by providing its name using the below code:

```js
const express = require("express")
const multer = require("multer")
const { GridFsStorage } = require("multer-gridfs-storage")
require("dotenv").config()
const MongoClient = require("mongodb").MongoClient
const GridFSBucket = require("mongodb").GridFSBucket

const url = process.env.MONGO_DB_URL

const mongoClient = new MongoClient(url)

// Create a storage object with a given configuration
const storage = new GridFsStorage({
  url,
  file: (req, file) => {
    //If it is an image, save to photos bucket
    if (file.mimetype === "image/jpeg" || file.mimetype === "image/png") {
      return {
        bucketName: "photos",
        filename: `${Date.now()}_${file.originalname}`,
      }
    } else {
      //Otherwise save to default bucket
      return `${Date.now()}_${file.originalname}`
    }
  },
})

// Set multer storage engine to the newly created object
const upload = multer({ storage })

const app = express()

app.post("/upload/image", upload.single("avatar"), (req, res) => {
  const file = req.file
  // Respond with the file details
  res.send({
    message: "Uploaded",
    id: file.id,
    name: file.filename,
    contentType: file.contentType,
  })
})

app.get("/images", async (req, res) => {
  try {
    await mongoClient.connect()

    const database = mongoClient.db("images")
    const images = database.collection("photos.files")
    const cursor = images.find({})
    const count = await cursor.count()
    if (count === 0) {
      return res.status(404).send({
        message: "Error: No Images found",
      })
    }

    const allImages = []

    await cursor.forEach(item => {
      allImages.push(item)
    })

    res.send({ files: allImages })
  } catch (error) {
    console.log(error)
    res.status(500).send({
      message: "Error Something went wrong",
      error,
    })
  }
})

app.get("/download/:filename", async (req, res) => {
  try {
    await mongoClient.connect()

    const database = mongoClient.db("images")

    const imageBucket = new GridFSBucket(database, {
      bucketName: "photos",
    })

    let downloadStream = imageBucket.openDownloadStreamByName(
      req.params.filename
    )

    downloadStream.on("data", function (data) {
      return res.status(200).write(data)
    })

    downloadStream.on("error", function (data) {
      return res.status(404).send({ error: "Image not found" })
    })

    downloadStream.on("end", () => {
      return res.end()
    })
  } catch (error) {
    console.log(error)
    res.status(500).send({
      message: "Error Something went wrong",
      error,
    })
  }
})

const server = app.listen(process.env.PORT || 8765, function () {
  const port = server.address().port

  console.log("App started at port:", port)
})
```

If you access the URL `http://localhost:8765/download/<image_name>`, you should be able to see the image.

![download image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mqvmaq5bv7fg1b0kmtcz.png)

## Source code

You can download the complete source code from [here](https://github.com/collegewap/node-mongo-image-upload).