Welcome folks today in this blog post we will be building a password protected
file sharing web app in node.js and express using mongodb
and ejs. All the full source code of the application is shown below.
Get Started
In order to get started you need to make a new node.js
project using the below command as shown below
npm init -y
npm i express
npm i mongoose
npm i multer
npm i dotenv
npm i bcrypt
And after that you will see the below directory structure of the final
project as shown below
And now first of all you need to create the index.js
file and copy paste the below code
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
require("dotenv").config() const multer = require("multer") const mongoose = require("mongoose") const bcrypt = require("bcrypt") const File = require("./models/File") const express = require("express") const app = express() app.use(express.urlencoded({ extended: true })) const upload = multer({ dest: "uploads" }) mongoose.connect(process.env.DATABASE_URL) app.set("view engine", "ejs") app.get("/", (req, res) => { res.render("index") }) app.listen(process.env.PORT) |
As you can see in the above code we are starting out a basic express
app at the port number that we define inside the .env
file and also we are importing the File
model file that we need to define for the mongodb schema. And here we are making the directory
where we will be storing all the uploaded files using multer. And also we are setting the ejs
view engine. Now we need to create the .env
file and define the constants for the database url
for the connection using mongoose and also the port number
.env
1 2 |
DATABASE_URL=mongodb://localhost:27017/nodefilesharing PORT=4000 |
Now we need to create the views
folder and inside it we will be creating the index.ejs
file which will be shown to the user when he or she visits the home page
views/index.ejs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <form action="/upload" method="post" style="display: grid; gap: .5rem; grid-template-columns: auto 1fr; max-width: 500px; margin: 0 auto;" enctype="multipart/form-data"> <label for="file">File:</label> <input type="file" id="file" name="file" required /> <label for="password">Password:</label> <input type="password" id="password" name="password" /> <button style="grid-column: span 2;" type="submit">Share</button> </form> </body> </html> |
As you can see guys we are having a simple html
form where we have the input file element where we allow the users to select any file to share
and then we have the field to password protect the file by entering the password. This is optional totally upto the user. And then we are submitting the form to the /upload
route and we are making the POST
request.
Making the Models
Now guys before we upload the file we need to create the model
inside the mongodb database for this you need to create the models
folder and inside it we will create the File.js
as shown below
models/File.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
const mongoose = require("mongoose") const File = new mongoose.Schema({ path: { type: String, required: true, }, originalName: { type: String, required: true, }, password: String, downloadCount: { type: Number, required: true, default: 0, }, }) module.exports = mongoose.model("File", File) |
As you can see we are defining the schema
and it contains various fields such as the originalName
of the file and the password
written by the user and then we have also the downloadCount property where we keep track of how many downloads
of the file has happened and also we have the path
property to keep track of the path of the file.
Also guys we need to create the uploads
folder where we will be storing all the uploaded files by the user with the help of multer
library.
Uploading Files Using Multer
Now guys we need to add the code inside the index.js
file to write the post
request where we will be adding the multer
middleware to actually upload the file and then we also update the table
inside the mongodb database as shown below
1 2 3 4 5 6 7 8 9 10 11 12 13 |
app.post("/upload", upload.single("file"), async (req, res) => { const fileData = { path: req.file.path, originalName: req.file.originalname, } if (req.body.password != null && req.body.password !== "") { fileData.password = await bcrypt.hash(req.body.password, 10) } const file = await File.create(fileData) res.render("index", { fileLink: `${req.headers.origin}/file/${file.id}` }) }) |
As you can see we are have the fileData
object and we are storing all the information and for the password we are checking if the password is entered by the user then only insert that in database
and for inserting the information we are using the create()
method and also we are hashing
the plain password which is submitted by the user using the bcrypt
library. And then we are re-rendering the ejs
template and then we are passing the fileLink
of the uploaded file.
Now we need to add the ejs
code inside the views/index.ejs
file as shown below
views/index.ejs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <% if (locals.fileLink != null) { %> <div style="margin-bottom: 1rem;"> Your file is upload <a href="<%= locals.fileLink %>"><%= locals.fileLink %></a> </div> <% } %> <form action="/upload" method="post" style="display: grid; gap: .5rem; grid-template-columns: auto 1fr; max-width: 500px; margin: 0 auto;" enctype="multipart/form-data"> <label for="file">File:</label> <input type="file" id="file" name="file" required /> <label for="password">Password:</label> <input type="password" id="password" name="password" /> <button style="grid-column: span 2;" type="submit">Share</button> </form> </body> </html> |
As you can see we have the simple if
condition in ejs to check if the property fileLink exists or not and then we are displaying the path inside the anchor
element as shown below
And now we need to write the get
and post
route to actually show the password
page whenever user does write the password before uploading it. And also to download
the file whenever user tries to click the above
link of the uploaded file.
1 |
app.route("/file/:id").get(handleDownload).post(handleDownload) |
As you can see we have define both the get
and the post routes for the same endpoint here we will be getting the id
of the file inside the params. Now we need to define this handleDownload
method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
async function handleDownload(req, res) { const file = await File.findById(req.params.id) if (file.password != null) { if (req.body.password == null) { res.render("password") return } if (!(await bcrypt.compare(req.body.password, file.password))) { res.render("password", { error: true }) return } } file.downloadCount++ await file.save() console.log(file.downloadCount) res.download(file.path, file.originalName) } |
As you can see inside the above method we are checking if the password
is present for the above file which is requested then we are showing the password.ejs
template where user must enter the password
to unlock the file. And if the password is not correct in that case we are showing the error message inside the ejs
template and if the password is correct by using the bcrypt
library or if the password is not present then we simply download
the file as an attachment and also we are increasing the count
number of how many downloads has happened for that particular file and then saving it.