1. Origin of the article

In the gold white piao for so long, always feel that there must be a output, otherwise white piao strange embarrassed 🤣 see a variety of online attachment upload, feel for the entry of people may be some difficulty, today brought about the Node+Mongodb attachment upload and download of the article. The project is relatively simple, so the directory is simple, you can quickly build a small network disk map bed what. Thank you nuggets everybody big article let me promotion raise 😁 (an is a boy)

2 hands

2.1 concept

First we need to understand 👉Mongodb file storage (GridFS) is what, because we are based on GridFS for file storage

2.2 What do we need

Now that we know the general concept, we can start installing the necessary plug-ins

  • 👉 Express (this is what I don’t need to say more)
  • 👉body-parser (middleware for Node to parse body)
  • 👉 EJS (Vue/React)
  • 👉 Gridfs-stream (Easily transfer files between MongoDB Gridfs.)
  • 👉method-override (form form does not support put/delete requests, so it is not necessary to use Ajax.)
  • 👉 Mongoose (essential plug-in for connecting to mongodb)
  • 👉multer (Multer is node.js middleware for processing multi-part/form data, mainly for uploading files. It was written on top of Busboy for maximum efficiency.)
  • 👉 multer-Gridfs-storage (Multer’s GridFS storage engine can store uploaded files directly to MongoDb.)
  • 👉 Nodemon (Hot Update)

So that’s all we need to prepare

// Lazy copy area



npm install express body-parser ejs gridfs-stream method-override mongoose multer multer-gridfs-storage

// or

yarn add express body-parser ejs gridfs-stream method-override mongoose multer multer-gridfs-storage

Copy the code

2.3 Initializing a Project

// You can add your own information

// npm init

Copy the code

Then create a new entry file app.js and page views/index.ejs in the root directory

3 Now the project begins

3.1 Finish the basic part first

Bring in our installed package and have a look around

const express = require('express')

const path = require('path')

const crypto = require('crypto')

const mongoose = require('mongoose')

const multer = require('multer')

const GridFsStorage = require('multer-gridfs-storage')

const GridFsStream = require('gridfs-stream')

const methodOverride = require('method-override')

const bodyParser = require('body-parser')



const app = express()



app.set('view engine'.'ejs'// Set up the template engine



app.use(bodyParser.json()) 

app.use(methodOverride('_method'))



app.get('/', (req, res) => {

        res.render('index')

    })

})



const port = 5000

app.listen(port, () => {

    console.log(`App listering on port ${port}`)

})

Copy the code

Generally speaking, if app.js is started, you can see the interface in views/index.ejs by visiting http://localhost:5000 in the browser. If not, you can check the console to see whether there is an error

3.2 Connecting to our Mongodb database

We can use NoSQL Manager for Mongdb to view the data in our database. We will create a new set called grid_uploads. So if you connect, you also connect this set

// Database link

const mongoURL = 'mongodb://localhost:27017/grid_uploads'



const connect = mongoose.createConnection(mongoURL, {

    useNewUrlParser: true.

    useUnifiedTopology: true

})

Copy the code

Try writing some data to NoSQL. For details, see 👉 [MongoDB] NoSQL Manager for MongoDB

3.3 Beautify the interface (views/index.ejs)

As a front-end engineer with two years of experience, we can’t make the interface ugly, so we will simply use Bootstrap4 to make the interface

<! DOCTYPE html>

<html lang="en">



<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">

<title> File upload </title>

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">

    <style>

        img {

            width: 100%;

        }

    </style>

</head>



<body>

    <div class="container">

        <div class="row">

            <div class="col-md-6 m-auto">

                <h2 class="text-center display-4 my-4"> </h2>

                <form action="/upload" method="POST" enctype="multipart/form-data">

                    <div class="custom-file mb-3">

                        <input type="file" name="file" id="file" class="custom-file-input">

                        <label for="file" class="custom-file-label"> Select file </label>

                    </div>

                    <input class="btn btn-primary btn-block" type="submit" value="Submit">

                </form>

                <hr>

            </div>

        </div>

    </div>

</body>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>



</html>

Copy the code

So this is what we should see if we request http://localhost:5000

3.4 Do what is necessary

// Define the GFS variable

let gfs;

connect.once('open', () = > {

// Enable file access control through gridFS-stream middleware and database

    gfs = GridFsStream(connect.db, mongoose.mongo)

    gfs.collection('upload')

Upload. files(record file information) upload.chunks(store file chunks)

})



// Use multer-gridfs-storage multer middleware to store attachments directly to MongoDb

const storage = new GridFsStorage({

    url: mongoURL,

    file: (req, file) => {

        return new Promise((resolve, reject) => {

Originalname = file.originalName = file.originalName = file.originalName = file.originalName

// You are advised to save two files, one for the original file name and the other for the encrypted file name, and then return the Chinese name to the page

            

            // crypto.randomBytes(16, (err, buf) => {

            //     if (err) {

            //         return reject(err)

            //     }

            //     const filename = buf.toString('hex') + path.extname(file.originalname)

            //     const fileinfo = {

            //         filename,

            //         bucketName: 'upload'

            //     }

            //     resolve(fileinfo)

            // })

            const fileinfo = {

                filename: new Date() + The '-' + file.originalname,

                bucketName: 'upload'

            }

            resolve(fileinfo)

        })

    }

})



const upload = multer({ storage })

Copy the code

3.5 Write the interface to upload our first file

app.post('/upload', upload.single('file'), (req, res) => {

    res.redirect('/')

})

Copy the code

It seems simple, but keep a few things in mind

  • In views/index.ejs (Input type=file specifies the same name as the interface upload.single(‘file’)
  • After uploading the files we redirect back to our home page and now we can see in NoSql that our two documents have data

This is the upload. Chunks

This is the upload files

3.6 Access to all of our file information

Get access to all our files

app.get('/files', (req, res) => {

// Return an array object by lookup

    gfs.files.find().toArray((err, files) => {

        if(! files || files.length === 0) {

            return res.status(404).json({

                err: 'File does not exist! '

            })

        }

        return res.json(files)

    })

})

Copy the code

We can beautify the interface of views/index.ejs (EJS syntax is really troublesome to use). For example, we can change the interface of views/index.ejs to beautify the interface of ejS. Reformat and add delete buttons

<! DOCTYPE html>

<html lang="en">



<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">

<title> File upload </title>

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"

        integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">

    <style>

        img {

            width: 100%;

        }

    </style>

</head>



<body>

    <div class="container">

        <div class="row">

            <div class="col-md-6 m-auto">

                <h2 class="text-center display-4 my-4"> </h2>

                <form action="/upload" method="POST" enctype="multipart/form-data">

                    <div class="custom-file mb-3">

                        <input type="file" name="file" id="file" class="custom-file-input">

                        <label for="file" class="custom-file-label"> Select file </label>

                    </div>

                    <input class="btn btn-primary btn-block" type="submit" value="Submit">

                </form>

                <hr>

            </div>

        </div>

        <div class="row">

The < %if(files){ %>

            <% files.forEach(function(file){ %>

            <div class="col-sm card card-body m-3 col-md-2">

The < %if(file.isImage){ %>

                <img src="image/<%= file.filename %>" />

The < %}else { %>

                <a href="download/<%= file.filename %>"><%= file.filename %></a>

The < %} % >

                    <form action="/files/<%= file._id%>? _method=DELETE" method="POST">

                <button class="btn btn-danger btn-block mt-4"> delete < / button >

                </form>

            </div>

The < %}) % >

The < %}else { %>

            <p class="card card-body text-center display-4 my-4"> File does not exist </p>

The < %} % >

        </div>

    </div>

</body>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.slim.min.js"

    integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"

    crossorigin="anonymous"></script>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js"

    integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"

    crossorigin="anonymous"></script>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"

    integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"

    crossorigin="anonymous"></script>



</html>

Copy the code

To retrieve the files variable from ejS, we should override the get(‘/’) interface to read the database file information and output it to the page when accessing localhost:5000

app.get('/', (req, res) => {

    gfs.files.find().toArray((err, files) => {

        if(! files || files.length === 0) {

            res.render('index', { files: false })

            return

        }

        files.map(file => {

// If the following image types are displayed in the front end, the rest will be processed as attachments, using isImage to distinguish images from non-images

            const imageType = ['image/png'.'image/jpg'.'image/gif'.'image/jpeg']

            if (imageType.includes(file.contentType)) {

                file.isImage = true

            } else {

                file.isImage = false

            }

        })

        res.render('index', { files: files })

    })

})



Copy the code

To complete the above situation we visit the home page should be the following situation

3.7 Downloading a Single File

Here we use the a tag to access the /download/:filename interface, filename is the filename, of course you can use other names such as _id. When the attachment is found, merge it into a readable file and return it through the pipe. In this way, click the file title in the front interface to download it directly

app.get('/download/:filename', (req, res) => {

    gfs.files.findOne({ filename: req.params.filename }, (err, file) => {

        if(! file) {

            return res.status(404).json({

                err: 'File does not exist! '

            })

        }

        const readstream = gfs.createReadStream(file.filename)

        readstream.pipe(res)

    })

})

Copy the code

3.8 Deleting a Single File

Here we access /files/: ID interface through a tag, id corresponds, click delete button, delete directly, and redirect to the home page

app.delete('/files/:id', (req, res) => {

    gfs.remove({ _id: req.params.id, root: 'upload' }, (err) => {

        if (err) {

            return res.status(404).json({

                err: 'Deleted file does not exist! '

            })

        }

        res.redirect('/')

    })

})

Copy the code

We always use form to make requests, but the form does not have a delete request, so we use the method-Override plugin. Of course, it does not matter if we use Ajax. After all, our project can be achieved quickly, mainly depending on the effect and process

4 The end is broken

Simply come to an end, learned friends can try more in-depth operation, so we can use this project to do a map bed or small network disk, or simply drop. Of course, the attachment upload also has certain limitations, such as large files may be uploaded for a longer time, we need to use the file sharding way to upload, you can see the gold 👉 file sharding case.

Finally, thank you for your taste. If there is something new and interesting, I will share it again

  • 👉The project address
  • 👉 online experience address my potato server, you do not start too malicious ha 😅, experience experience is good

Reading a good book is talking with noble people. — Goethe (I did say that)