Securely upload and view image in Node JS and Mongo DB

Learn how to securely upload and view image in Node JS and Mongo DB. Most of the time when we create a website where we allow users to upload images, we face a problem to secure them from unauthorized access. For example, you are creating a website where you are allowing users to upload pictures and share them with only selected people. Now you do not want any other person to view that picture.

This can be done easily with Node JS. First, we are going to create an empty project. So run the following commands in your terminal.

> npm init
> npm install express express-formidable ejs fs mongodb
> npm install -g nodemon
> nodemon server.js

Upload image

After that, create a file named server.js and write the following code in it.

// server.js

const express = require("express")
const app = express()
app.set("view engine", "ejs")

const port = process.env.PORT || 3000
app.listen(port, function () {
    console.log("Server started.")

    app.get("/", function (request, result) {
      result.render("index")
    })
})

Now, we will create a folder named views and inside this folder, create a file named index.ejs. Following will be the content of this file.

<!-- views/index.ejs -->

<form method="POST" action="/upload" enctype="multipart/form-data">
  <input type="file" name="file" />
  <input type="submit" value="Upload" />
</form>

This will create a form where user can select image. You can access this from the URL:

http://localhost:3000

Now in our server.js file, we will include the file system module, express formidable module and connect Mongo DB.

// server.js

const fs = require("fs")
const mongodb = require("mongodb")
const ObjectId = mongodb.ObjectId
const mongoClient = new mongodb.MongoClient("mongodb://localhost:27017")

const expressFormidable = require("express-formidable")
app.use(expressFormidable())

// Connect the client to the server (optional starting in v4.7)
await mongoClient.connect()

// Establish and verify connection
const db = await mongoClient.db("upload_view")
db.command({ ping: 1 })
console.log("Database connected")

After that, we will create a route that will handle this request.

// server.js

app.post("/upload", async function (request, result) {
  const file = request.files.file

  const fileData = await fs.readFileSync(file.path)
  if (!fileData) {
    console.error(fileData)
    return
  }

  const filePath = "uploads/" + (new Date().getTime()) + "-" + file.name
  fs.writeFileSync(filePath, fileData)

  await db.collection("files").insertOne({
    path: filePath,
    name: file.name,
    size: file.size
  })

  result.send("File has been uploaded.")
})

This will upload the file in your uploads folder. If you do not have that folder, you need to create it. This will also save the uploaded file data in Mongo DB.

You will not be able to view the images directly from the URL in the browser. So we need to create an API allowing users to view the image.

View image

First, we will fetch all the images from Mongo DB. So change your main route to the following:

// server.js

app.get("/", async function (request, result) {
  const files = await db.collection("files").find({}).toArray()
  result.render("index", {
    files: files
  })
})

Now, in your views/index.ejs, we will loop through all images and display their name and a link to view.

<!-- views/index.ejs -->

<% files.forEach(function (file) { %>
  <p>
    <a href="image/<%= file._id %>">
      <%= file.name %>
    </a>

    <img src="image/<%= file._id %>" style="width: 300px;" />
  </p>
<% }) %>

Finally, we need to create an API that will return the content of image. This will allow you to view the image on user side.

// server.js

app.get("/image/:_id", async function (request, result) {
  const _id = request.params._id
  const file = await db.collection("files").findOne({
    _id: new ObjectId(_id)
  })

  if (file == null) {
    result.json({
      status: "error",
      message: "File not found."
    })
    return
  }

  const fileData = await fs.readFileSync(file.path)
  if (!fileData) {
    console.error(fileData)
    return
  }
  result.writeHead(200, {
    "Content-Type": "image/png",
    "Content-Length": fileData.length
  })
  result.end(fileData)
})

Now you will be able to view the image as well.