nodejs-upload-multiple-files-trungquandev

Upload multiple files trong NodeJS

Xin chào tất cả các bạn, mình là Quân, tiếp tục ở bài trước, mình đã hướng dẫn các bạn upload file trong Nodejs nhưng mới chỉ là upload single mỗi file một lần, nên hôm nay chúng ta sẽ cùng nhau đi xử lý xem việc upload nhiều file cùng một lúc nó như thế nào nhé.

“Bài này nằm trong loạt bài Lập Trình Nodejs từ cơ bản đến nâng cao trên trang blog chính thức trungquandev.com

Những nội dung có trong bài:

  1. Tản mạn phân tích bài toán
  2. Dán mắt vào màn hình để code…
  3. Demo ứng dụng upload multiple files.
  4. Full source code trên github.

1. Tản mạn phân tích bài toán

Như ở bài trước, mình đã hướng dẫn cho các bạn xử lý upload file trong NodeJS bằng 2 module là FormidableMulter. Nhưng nội dung ở bài đó mới chỉ dừng lại ở việc upload file đơn lẻ, có nghĩa là upload mỗi lần mỗi file.

Nên trong bài viết ngày hôm nay, chúng ta sẽ cùng nhau đi xử lý việc upload nhiều file trong NodeJS nhé.

Hôm nay để không lan man và để “focus on the point” thì mình sẽ chỉ hướng dẫn cho các bạn làm việc với thằng Multer thôi nhé, đây là module mà mình khá thích vì tính dễ dàng sử dụng của nó. Bạn nào chưa rõ thì có thể xem lại bài upload file trong nodejs hôm trước của mình, mình có phân tích khá rõ về module này trước khi code rồi nha.

Ok, tản mạn đủ rồi, chúng ta bắt đầu đi vào code nhé.

2. Dán mắt vào màn hình để code

Đầu tiên là cấu trúc thư mục, phần này rất quan trọng, mình sẽ làm rất rõ ràng, chia nhỏ các thành phần code nên các bạn chịu khó học nhé, sẽ rất bổ ích cho những bạn mới học đấy:

src

controllers

homeController.js

multipleUploadController.js

middleware

multipleUploadMiddleware.js

routes

web.js

views

master.html

uploadResults

server.js

package.json

Việc khởi tạo ứng dụng nodejs thì mình không làm lại, các bạn có thể xem cách làm ở bài viết trước của mình tại link bên dưới đây: (Ngoài lề: Mấy bạn admin của mấy trang TopDev, TechBlog…vv gì đấy đã đi copy bài không phải của các bạn thì hãy tôn trọng người viết, đừng có xóa đoạn quan trọng này cũng như tự ý xóa linh tinh các câu thoại của mình trong bài viết này, lần này nếu mình phát hiện ra nữa thì chắc chắn sẽ ăn report DMCA nhé.)
https://trungquandev.com/series-lap-trinh-nodejs/

Trước khi đi vào code, chúng ta sẽ cần phải cài 2 module cho bài hôm nay đó là:
express và multer

npm install --save express multer

Chúng ta bắt đầu code lần lượt như sau:

Đầu tiên là file views/master.html

File này các bạn cần lưu ý một vài điểm:

  • Thứ nhất là cái form upload <form action="/multiple-upload"method="POST"enctype="multipart/form-data">
  • Thứ hai là thẻ <input type="file"name="many-files" multipleid="input-many-files"class="form-control-file border">
  • Còn lại có cái đoạn script phía dưới cùng mình viết để khi chọn ảnh xong nó sẽ preview lên trình duyệt cho chúng ta xem trước, mình không giải thích trong bài vì sợ lan man, bạn nào muốn hiểu rõ phần này có thể comment dưới bài viết, mình sẽ giải đáp sau nhé.
<!--
* Created by trungquandev.com's author on 17/08/2019.
*/
// views/master.html
-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="shortcut icon" href="https://trungquandev.com/wp-content/uploads/2016/11/LOGO.png" />
    <title>Node.js multiple upload files</title>

    <!-- Get bootstrap from CDN-->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">

    <style>
        div.preview-images>img {
            width: 30%;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col-sm-8">
                <br>
                <h4>
                    Node.js multiple upload files - <a href="https://trungquandev.com" target="blank">trungquandev.com</a>
                </h4>
                <div class=""></div>
                <form action="/multiple-upload" method="POST" enctype="multipart/form-data">
                    <div class="form-group">
                        <label for="example-input-file"> </label>
                        <input type="file" name="many-files" multiple id="input-many-files" class="form-control-file border">
                    </div>
                    <button type="submit" class="btn btn-primary">Submit</button>
                </form>
            </div>
        </div>
        <hr>
        <div class="row">
            <div class="col-sm-12">
                <div class="preview-images"></div>
            </div>
        </div>
    </div>

    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.bundle.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script>
        $(document).ready(function() {
            // Multiple images preview in browser - trungquandev.com
            let imagesPreview = function(input, placeToInsertImagePreview) {
                if (input.files) {
                    let filesAmount = input.files.length;
                    for (i = 0; i < filesAmount; i++) {
                        let reader = new FileReader();
                        reader.onload = function(event) {
                            $($.parseHTML("<img>")).attr("src", event.target.result).appendTo(placeToInsertImagePreview);
                        }
                        reader.readAsDataURL(input.files[i]);
                    }
                }
            };

            $("#input-many-files").on("change", function() {
                imagesPreview(this, "div.preview-images");
            });
        });
    </script>
</body>
</html>

Tiếp theo tới file middleware/multipleUploadMiddleware.js

File này gói gọn lại chức năng upload filesexport ra để bên controller lát nữa mình sử dụng
Trong file này thì các bạn để ý cho mình là ngoài multer ra thì mình có dùng thêm util để convert lại thằng multer cho nó khả dụng với Promise, và thằng path để lấy đường dẫn gốc của file hiện tại.

/**
 * Created by trungquandev.com's author on 17/08/2019.
 * multipleUploadMiddleware.js
 */
const util = require("util");
const path = require("path");
const multer = require("multer");

// Khởi tạo biến cấu hình cho việc lưu trữ file upload
let storage = multer.diskStorage({
  // Định nghĩa nơi file upload sẽ được lưu lại
  destination: (req, file, callback) => {
    callback(null, path.join(`${__dirname}/../../uploadResults`));
  },
  filename: (req, file, callback) => {
    // ở đây các bạn có thể làm bất kỳ điều gì với cái file nhé.
    // Mình ví dụ chỉ cho phép tải lên các loại ảnh png & jpg
    let math = ["image/png", "image/jpeg"];
    if (math.indexOf(file.mimetype) === -1) {
      let errorMess = `The file <strong>${file.originalname}</strong> is invalid. Only allowed to upload image jpeg or png.`;
      return callback(errorMess, null);
    }

    // Tên của file thì mình nối thêm một cái nhãn thời gian để tránh bị trùng tên file.
    let filename = `${Date.now()}-trungquandev-${file.originalname}`;
    callback(null, filename);
  }
});

// Khởi tạo middleware uploadManyFiles với cấu hình như ở trên,
// Bên trong hàm .array() truyền vào name của thẻ input, ở đây mình đặt là "many-files", và tham số thứ hai là giới hạn số file được phép upload mỗi lần, mình sẽ để là 17 (con số mà mình yêu thích). Các bạn thích để bao nhiêu cũng được.
let uploadManyFiles = multer({storage: storage}).array("many-files", 17);

// Mục đích của util.promisify() là để bên controller có thể dùng async-await để gọi tới middleware này
let multipleUploadMiddleware = util.promisify(uploadManyFiles);

module.exports = multipleUploadMiddleware;

File controller/homeController.js

File này không có gì khó hiểu cả, trong nó có một function trả về cái màn hình cho chúng ta chọn file để upload, chính là trả về file master.html ở trên kia:

/**
 * Created by trungquandev.com's author on 17/08/2019.
 * homeController.js
 */

const path = require("path");

let getHome = (req, res) => {
  return res.sendFile(path.join(`${__dirname}/../views/master.html`));
};

module.exports = {
  getHome: getHome
};

File controller/multipleUploadController.js

Ở controller này, chúng ta sẽ import cái multipleUploadMiddleware vừa viết lúc nãy và sử dụng nó với async-await, nếu có lỗi thì trả về thông báo lỗi tương ứng, ngược lại nếu thành công thì trả về dòng thông báo “Your files has been uploaded.”

/**
 * Created by trungquandev.com's author on 17/08/2019.
 * multipleUploadController.js
 */
const multipleUploadMiddleware = require("../middleware/multipleUploadMiddleware");

let debug = console.log.bind(console);

let multipleUpload = async (req, res) => {
  try {
    // thực hiện upload
    await multipleUploadMiddleware(req, res);

    // Nếu upload thành công, không lỗi thì tất cả các file của bạn sẽ được lưu trong biến req.files
    debug(req.files);

    // Mình kiểm tra thêm một bước nữa, nếu như không có file nào được gửi lên thì trả về thông báo cho client
    if (req.files.length <= 0) {
      return res.send(`You must select at least 1 file or more.`);
    }

    // trả về cho người dùng cái thông báo đơn giản.
    return res.send(`Your files has been uploaded.`);
  } catch (error) {
    // Nếu có lỗi thì debug lỗi xem là gì ở đây
    debug(error);

    // Bắt luôn lỗi vượt quá số lượng file cho phép tải lên trong 1 lần
    if (error.code === "LIMIT_UNEXPECTED_FILE") {
      return res.send(`Exceeds the number of files allowed to upload.`);
    }

    return res.send(`Error when trying upload many files: ${error}}`);
  }
};

module.exports = {
  multipleUpload: multipleUpload
};

File routes/web.js

File này là nơi chúng ta cấu hình routers cho ứng dụng, sẽ có 2 route chính, một là để gọi ra màn hình chọn file upload, có method là GET, route còn lại là gọi đến controller upload file ở ngay phía trên, method là POST.

/**
 * Created by trungquandev.com's author on 17/08/2019.
 * routes/web.js
 */
const express = require("express");
const router = express.Router();
const homeController = require("../controllers/homeController");
const multipleUploadController = require("../controllers/multipleUploadController");

let initRoutes = (app) => {
  // Gọi ra trang home cho việc upload
  router.get("/", homeController.getHome);
  
  // Upload nhiều file với phương thức post
  router.post("/multiple-upload", multipleUploadController.multipleUpload);

  return app.use("/", router);
};

module.exports = initRoutes;

File server.js

Và cuối cùng, “last but not least”, file server.js của chúng ta, sử dụng để chạy ứng dụng nodejs:

/**
 * Created by trungquandev.com's author on 17/08/2019.
 * server.js
 */
const express = require("express");
const app = express();
const initRoutes = require("./routes/web");

// Cho phép lý dữ liệu từ form method POST
app.use(express.urlencoded({extended: true}));

// Khởi tạo các routes cho ứng dụng
initRoutes(app);

// chọn một port mà bạn muốn và sử dụng để chạy ứng dụng tại local
let port = 8017;
app.listen(port, () => {
  console.log(`Hello trungquandev.com, I'm running at localhost:${port}/`);
});

3. Demo ứng dụng upload multiple files

Sau khi đã xử lý xong tất cả các file ở trên, chúng ta sẽ chạy ứng dụng bằng lệnh:
node src/server.js

Kết quả đầu tiên sẽ hiện lên màn hình cho chúng ta chọn files:

Mình sẽ chọn upload 3 file ảnh như hình bên dưới:

Nếu upload thành công thì trả về cho phía client:

Ngoài ra thì các bạn có thể vào phần code, mình có log ra file sau khi upload và cả 3 file đều đã nằm trong thư mục uploadResults:

Một số lỗi mà mình bắt ra sau đây đó là không chọn file nào mà nhấn submit, vượt quá số lượng file cho phép uploadupload kiểu file không hợp lệ:

4. Full source code trên github

Vậy là bài hôm nay chúng ta đã cùng nhau hoàn thiện về ý tưởng và cách triển khai cho việc upload nhiều file trong Nodejs rồi.

Mình có để full source code của bài hôm nay ở repo này cho các bạn tham khảo nhé, nếu thấy bài viết bổ ích, hãy ủng hộ bằng cách cho mình 1 star trên repo này để mình có động lực tiếp tục viết những bài viết chất lượng nha, chân thành cảm ơn các bạn.
https://github.com/trungquan17/nodejs-upload-multiple-files


Cảm ơn các bạn đã dành thời gian đọc bài viết.

Xin chào và hẹn gặp lại các bạn ở những bài viết tiếp theo.

Best Regards – Trung Quân – Green Cat


Tham khảo kiến thức:

https://www.npmjs.com/package/multer

“Thanks for awesome knowledges.”

trungquandev-img-modal

Khóa học lập trình làm việc thực tế:

Nếu các bạn thấy bài viết của mình có ích thì hãy ủng hộ mình bằng cách tham khảo bài viết giới thiệu khóa học cực kỳ chất lượng và chính chủ dưới đây của mình nhé, cảm ơn các bạn ^^

nodejs-mongodb-messenger-realtime-course-trungquandev
Node.js và MongoDB - Xây dựng một ứng dụng Messenger trò chuyện trực tuyến.