DEV Community

Cover image for MEAN Stack Project: Create Quick File Share Application: Part 2 Backend
Varun
Varun

Posted on

MEAN Stack Project: Create Quick File Share Application: Part 2 Backend

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
Enter fullscreen mode Exit fullscreen mode

This will be our Project Structure:

Image description

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
Enter fullscreen mode Exit fullscreen mode

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}.`));
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

now use this db in server.js

const dotenv = require('dotenv')
dotenv.config({ path: './.env' })
const connectDB = require('./config/db');
connectDB();

Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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)
    },
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

    });
});
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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;
}

Enter fullscreen mode Exit fullscreen mode

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}.`));
Enter fullscreen mode Exit fullscreen mode

So we will test our APIs in POSTMAN:

Image description

So as you can see it will return a download link.

Image description

So our Final output will look like this !! And we have completed our application.

Image description

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)