This is continuation of the File Share Application using MEAN Stack.
If you have not read it, its Important to understand the structure:
https://dev.to/varun21vaidya/mean-stack-project-create-quick-file-share-application-878
Here we will use NodeJS and ExpressJS on server side.
lets start by running this command
npm init
This will be our Project Structure:
now first install all the required dependencies, we will see their use along the way.
npm i cors dotenv ejs express mongoose multer nodemon uuid
Now Start with Server.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
const path = require('path');
app.use(express.json());
app.listen(PORT, console.log(`Listening on port ${PORT}.`));
follow this to create your Mongo Atlas Cluster:
https://www.mongodb.com/docs/drivers/node/current/quick-start/
and Put the MONGO_CONNECTION_URL in .env file.
and now create db.js in config folder.
require("dotenv").config();
const mongoose = require("mongoose");
function connectDB() {
mongoose
.connect(process.env.MONGO_CONNECTION_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log('DB connection successful!')).catch((e) => {
console.log('error while connencting database');
console.log(e);
});
}
// mIAY0a6u1ByJsWWZ
module.exports = connectDB;
now use this db in server.js
const dotenv = require('dotenv')
dotenv.config({ path: './.env' })
const connectDB = require('./config/db');
connectDB();
Now we need to create a Schema for our filesystem, which will contain
- filename
- path: file stored location
- size: filesize that should be shown on view
- uuid: encrypted file name
- sender
- reciever
file.js in Models Folder
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const fileSchema = new Schema({
filename: { type: String, required: true },
path: { type: String, required: true },
size: { type: Number, required: true },
uuid: { type: String, required: true },
sender: { type: String, required: false },
receiver: { type: String, required: false },
}, { timestamps: true });
module.exports = mongoose.model('File', fileSchema);
Now in the routes folder, create *files.js
*
this will be controller, and will contain post method to upload the file.
Now we are going to use uuid which stores unique uuid for file to store in database.
Because uuid is so unique that no one with one download link would be able to change the id and download other previous documents.
Otherwise if it was just 1,2,3.. others could be able to download by entering previous index.
const { v4: uuidv4 } = require('uuid');
Now to store and upload the file we will use multer.
Multer is a node.js middleware for handling multipart/form-data , which is primarily used for uploading files.
// diskStorage Returns a StorageEngine implementation configured
// to store files on the local file system.
let storage = multer.diskStorage({
// destination stores file at given folder
destination: (req, file, cb) => cb(null, 'uploads/'),
// gives filename
filename: (req, file, cb) => {
// create a unique file name
// path.extname takes extension name from file and saves it as same
// 38974612841-129834729432.png
const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1E9)}${path.extname(file.originalname)}`;
cb(null, uniqueName)
},
});
First with multer we will set the file limits to be uploaded, which will handle uploading of file and store it in uploads section.
Storage returns multer instance providing methods for generating middleware that process uploaded files.
let upload = multer({ storage, limits: { fileSize: 1000000000 * 100 }, }).single('myfile'); //100mb
Now its time to create POST request API.
we will create post request and create an upload method, which will create and make db model through schema with the filename, created uuid, path and size and returns a downloadable link which will be fetched by the client side in Angular.
which will look like this http://localhost:3000/files/234dfhsebrg-2342dvsdvsdk
router.post('/', (req, res) => {
upload(req, res, async (err) => {
if (err) {
return res.status(500).send({ error: err.message });
}
const file = new File({
filename: req.file.filename,
// gets unique uuid
uuid: uuidv4(),
// gets path from destination + filename recieved from multer
path: req.file.path,
size: req.file.size
});
// response will give download link
// save this with await while upload is async
const response = await file.save();
res.json({ file: `${process.env.APP_BASE_URL}/files/${response.uuid}` });
// download link will look like
// http://localhost:3000/files/234dfhsebrg-2342dvsdvsdk
});
});
This is the full code for controller.
file.js in router.
const router = require('express').Router();
const multer = require('multer');
// path gives extension of the uploaded file
const path = require('path');
const File = require('../models/file');
// stores unique uuid for file to store in db
// WHY UUID: cz its so unique that no one with one download link
// would be able to change the id and download other previous documents
// if it was just 1,2,3.. others could be able to download by entering previous index
const { v4: uuidv4 } = require('uuid');
// diskStorage Returns a StorageEngine implementation configured
// to store files on the local file system.
let storage = multer.diskStorage({
// destination stores file at given folder
destination: (req, file, cb) => cb(null, 'uploads/'),
// gives filename
filename: (req, file, cb) => {
// create a unique file name
// path.extname takes extension name from file and saves it as same
// 38974612841-129834729432.png
const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1E9)}${path.extname(file.originalname)}`;
cb(null, uniqueName)
},
});
// handle uploading of file and store it in uploads section with multer
// middleware for handling form-data, ie used for uploading files
// storage returns multer instance providing
// methods for generating middleware that process uploaded files
// storage: storage,
let upload = multer({ storage, limits: { fileSize: 1000000000 * 100 }, }).single('myfile'); //100mb
router.post('/', (req, res) => {
upload(req, res, async (err) => {
if (err) {
return res.status(500).send({ error: err.message });
}
const file = new File({
filename: req.file.filename,
// gets unique uuid
uuid: uuidv4(),
// gets path from destination + filename recieved from multer
path: req.file.path,
size: req.file.size
});
// response will give download link
// save this with await while upload is async
const response = await file.save();
res.json({ file: `${process.env.APP_BASE_URL}/files/${response.uuid}` });
// download link will look like
// http://localhost:3000/files/234dfhsebrg-2342dvsdvsdk
});
});
module.exports = router;
Now we need to create a server side rendering view to show the filesystem and download button.
Show.js in routes
This will create a rendering view when the user takes download link after completing the upload button.
Here we need to create GET API which will show the view showing the filename, size, and a download button to download the actual file.
IT will take uuid and find the file data in database.
and if file is present will generate the view.
This will also send the download link to the frontend to show when the user completes file upload.
// router
const router = require('express').Router()
// model import
const File = require('../models/file')
// function to try and get data from database with dynamic parameter uuid
// and render download page else in catch return err
router.get('/:uuid', async (req, res) => {
try {
const file = await File.findOne({ uuid: req.params.uuid })
// if file not present return error
// in download section
if (!file) {
return res.render('download', { error: 'link has expired' })
}
// generate a download page with the details to download the file
return res.render('download', {
uuid: file.uuid,
fileName: file.filename,
fileSize: file.size,
// download link
downloadLink: `${process.env.APP_BASE_URL}/files/download/${file.uuid}`
})
}
catch (err) {
return res.render('download', { err: 'something went wrong' })
}
});
module.exports = router;
But first create GET API to download the file by giving the uuid.
download.js in routes
const router = require('express').Router();
const File = require('../models/file');
router.get('/:uuid', async (req, res) => {
const file = await File.findOne({ uuid: req.params.uuid })
// if file not present return error
// in download section
if (!file) {
return res.render('download', { error: 'link has expired' })
}
// to download file we need filepath
// if you check database you will find each file has mentioned file.path
const filePath = `${__dirname}/../${file.path}`;
// downloading in express just require res.download(filepath)
res.download(filePath);
})
module.exports = router;
Now we have to create a view to show all the data and donwload button.We can create it with ejs
and css
first create ejs in Views folder as shown in file structure.
<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>Download Page</title>
<link href="../public/css/style.css" rel="stylesheet" type="text/css">
</head>
<body>
<section class="container">
<div class="drop-zone">
<!-- if there is error in link or uuid has expired or invalid
show.js will give error and this error is printed here-->
<% if(locals.error) { %>
<h4>
<%= locals.error %>
</h4>
<% } else { %>
<!-- else provide the link to download file -->
<div class="icon-container">
<img src="../public/img/download-icon.png" alt="download-icon">
</div>
<div class="content">
<h3>Your File is Ready to Download</h3>
<p>Link Exprires in 24 Hours</p>
<!-- file name and filesize are in show.js -->
<h4>
<%- fileName -%>
</h4>
<!-- filesize is available in bytes so convert it using /1000
and to remove decimal places use parseInt -->
<small>
<%- parseInt(fileSize/1000) -%>KB
</small>
<div class="send-btn-container">
<a href="<%- downloadLink %>">Download File</a>
</div>
</div>
<% } %>
</div>
</section>
</html>
And Inside the public folder, create css file, which will give styles to our ejs view.
body,
html {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
background-color: rgb(189, 255, 255);
height: 100%;
width: auto;
padding: 0;
margin: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
.container {
background-color: white;
border-radius: 30px;
box-shadow: 0px 15px 30px 5px rgba(0, 0, 0, 0.2);
}
.drop-zone {
width: 300px;
height: 300px;
margin: 30px;
border-radius: 15px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.content {
line-height: 30%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.send-btn-container {
background-color: DodgerBlue;
border: none;
margin-top: 20px;
padding: 12px 30px;
cursor: pointer;
border-radius: 20px;
}
.send-btn-container a {
text-decoration: none;
color: white;
}
.icon-container {
width: 75px;
height: 75px;
position: relative;
}
.icon-container img {
position: absolute;
height: 60px;
width: 60px;
}
Now we need to set ejs in our server.js file
and also need to configure cors
So finally our server.js will have all the routes and cors and is ready to be tested.
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
const path = require('path');
const dotenv = require('dotenv')
dotenv.config({ path: './.env' })
const connectDB = require('./config/db');
connectDB();
var cors = require('cors');
// use it before all route definitions
// app.use(cors({ origin: ['https://share-now-backend-gi2ea360d-varun21vaidya.vercel.app/', 'https://share-now-file-sharing-app.vercel.app/', 'http://localhost:4200'] }));
// cors policy
app.use(cors({
origin: ['https://share-now-backend.vercel.app/https://share-now-file-sharing-app.vercel.app/', 'https://share-now-backend.vercel.app/http://localhost:4200'], // use your actual domain name (or localhost), using * is not recommended
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Origin', 'X-Requested-With', 'Accept', 'x-client-key', 'x-client-token', 'x-client-secret', 'Authorization'],
credentials: true
}))
app.use(express.json());
// setup static files
// using app.use to serve up static CSS files in public/css/ folder
// when /public link is called in ejs files
// app.use("/route", express.static("foldername"));
app.use('/public', express.static('public'));
//template engine
app.set('views', path.join(__dirname, '/views'));
app.set('view engine', 'ejs');
// Routes
// to upload file use this route
app.use('/api/files', require('./routes/files'));
// when file is uploaded it returns this route
app.use('/files', require('./routes/show'));
// to download file use this route
app.use('/files/download', require('./routes/download'));
app.listen(PORT, console.log(`Listening on port ${PORT}.`));
So we will test our APIs in POSTMAN:
So as you can see it will return a download link.
So our Final output will look like this !! And we have completed our application.
run both frontend and backend to get working Application.
as like this: https://share-now-file-sharing-app.vercel.app/
You can also get the code from github.
Github link (Don't forget to leave a star🌟):
Frontend Repo: https://github.com/varun21vaidya/ShareNow
Backend Repo: https://github.com/varun21vaidya/ShareNow-Backend
Hope you Enjoyed and learned from this useful project !! Happy Coding !!
Top comments (0)