# Lesson 10 - Upload file

Trong bất kỳ những ứng dụng nào, việc có thể tải, lưu trữ các định dạng file là cần thiết và không thể thiếu, ví dụ như các ảnh đại diện, ảnh bìa,... Trong nội dung này, ta sẽ tìm hiểu một số cách để có thể tải, lưu trữ các tệp.

## Lưu trữ

Khi lưu trữ các file vào CSDL, bản chất là hoàn toàn có thể làm được, nhưng tuy nhiên, định dạng file sẽ khó biên dịch, khó xử lý, quan trọng nhất đó là chiếm tài nguyên tương đối lớn của database. Database chỉ nên lưu các CSDL định dạng document (tức với các kiểu dữ liệu cơ bản). Vậy ta sẽ cần phải có một nguồn để có thể lưu trữ các định dạng file.

Trong MongoDB, với một thông số đặc biệt (GridFS), MongoDB sẽ hỗ trợ lưu các định dạng tệp tin một cách dễ dàng, kể cả với các file có dung lượng lớn. Tuy nhiên, về mặt triển khai, vẫn cần phải đảm bảo được rằng, CSDL là để lưu các định dạng data cơ bản.

Lúc này, ta sẽ cần tìm hiểu về các bên thứ 3, cung cấp dịch vụ lưu trữ các định dạng file và để sử dụng được trong database, ta chỉ cần lưu đường dẫn mà bên thứ 3 cung cấp để sử dụng.&#x20;

Trong nội dung này, ta sẽ tìm hiểu về nền tảng cloudinary, một nền tảng cung cấp dịch vụ lưu trữ các định dạng file trên bộ nhớ đám mây. Nhưng trước tiên, ta cần phải biết cách xử lý file phía server trước.

## Multer

Multer là một thư viện cho NodeJs, cung cấp các phương thức đọc, lưu trữ các định dạng file một cách đơn giản.

Multer cung cấp 2 cách thức lưu trữ file: lưu trữ tại ổ cứng (diskStorage) và lưu trên RAM (memoryStorage).

Tuỳ vào mục đích sử dụng lưu trữ file mà ta chọn cách thức lưu tại ổ cứng hoặc RAM

Với các tệp tin mà cần lưu trữ lâu dài, có mục đích sử dụng ngay trên resource server, ta nên sử dụng cách lưu tại ổ cứng, với các trường hợp cần lưu trữ tạm thời, nên lưu trên RAM.

Trong nội dung này, khi xác định lưu trên cloud (nền tảng đám mây hoặc bên thứ 3), ta sẽ lựa chọn lưu với memoryStorage.

Về bản chất, Server sẽ không thể tự hiểu các định dạng tệp tin, multer sẽ đóng vai trò giống như là một middleware, hỗ trợ trong việc xử lý file

**Cài đặt multer**

```
npm i multer
```

```javascript
import express from 'express';
import multer from 'multer';
const app = express();
const port = 8080;

// Khởi tạo tùy chọn lưu trữ memoryStorage
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });

// Xử lý yêu cầu tải lên tệp
app.post('/upload', upload.single('file'), (req, res) => {
  // Truy cập dữ liệu tệp từ req.file
  const file = req.file;

  if (!file) {
    return res.status(400).json({ error: 'Không có tệp được tải lên.' });
  }

  // Trả về phản hồi với thông tin về tệp đã tải lên
  res.json({ message: 'Tệp được tải lên thành công.', data: file });
});

app.listen(port, () => {
  console.log(`Server is running!`);
});

```

Trong đoạn code phía trên, đang được cấu hình với memoryStorage, nên sẽ không có bất kỳ file nào được trả ra, tuy nhiên, thông tin file sẽ có dạng như sau, ví dụ:

```json
{
  fieldname: 'file',
  originalname: 'DS_HS.xlsx',
  encoding: '7bit',
  mimetype: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  buffer: <Buffer 50 4b 02 04 14 00 06 00 08 00 00 00 21 00 41 37 82 cf 6e 01 00 00 04 05 00 00 13 00 08 02 5b 43 6f 6e 74 65 6e 74 5f 54 79 70 65 73 5d 2e 78 6d 6c 20 ... 12519 more bytes>,
  size: 12569
}
```

Trường buffer chính là trường chứa dữ liệu của file, ta hoàn toàn có thể lưu tại thông tin này trong database, tuy nhiên, sẽ tương đối nặng, thậm chí khi cố gắng lưu, trả về cho client và client sẽ cần tự convert sang dạng hiển thị file cũng sẽ khó khăn (không có nghĩa là không làm được).

Lúc này, ta sẽ xử lý bằng cách lưu file trên cloudinary.

> Có thể tìm hiểu thêm về cách lưu trữ file với diskStorage, để có thể đáp ứng cho các nghiệp vụ tương lai.

## Cloudinary

Truy cập cloudinary để thực hiện đăng ký tài khoản: [Link](#cloudinary)

### Giới thiệu

Cloudinary là một dịch vụ quản lý và tối ưu hóa tài nguyên đa phương tiện trực tuyến, được sử dụng rộng rãi cho việc lưu trữ, quản lý, và cung cấp hình ảnh, video, âm thanh, và các tài nguyên đa phương tiện khác cho ứng dụng web và di động. Dưới đây là một số điểm nổi bật về Cloudinary:

1. **Lưu trữ và Quản lý Tài nguyên Đa phương tiện**: Cloudinary cho phép bạn tải lên và lưu trữ các loại tài nguyên đa phương tiện, bao gồm hình ảnh, video, âm thanh, tài liệu, và nhiều loại tệp khác. Tài nguyên này có thể được quản lý và sắp xếp trong các thư mục và nhóm.
2. **Tối ưu hóa Tải ảnh và Video**: Cloudinary tự động tối ưu hóa hình ảnh và video để đảm bảo chất lượng và tải nhanh. Nó cung cấp một loạt các công cụ để điều chỉnh kích thước, định dạng, và chất lượng của tài nguyên.
3. **Cung Cấp và Giao Hình Ảnh Động**: Cloudinary hỗ trợ việc tạo và phục vụ hình ảnh động, chẳng hạn như GIF và WebP. Điều này cho phép bạn tạo hiệu ứng động và hình ảnh chất lượng cao.
4. **Phục vụ Tài nguyên Qua Mạng Content Delivery Network (CDN)**: Cloudinary sử dụng CDN để phục vụ tài nguyên đa phương tiện, giúp cải thiện tải trang web và video nhanh hơn cho người dùng toàn cầu.
5. **Chia sẻ và Quyền Truy Cập**: Cloudinary cho phép bạn quản lý quyền truy cập và chia sẻ tài nguyên với người dùng hoặc ứng dụng khác. Bạn có thể kiểm soát việc truy cập và phân quyền cho các tài nguyên cụ thể.
6. **Hệ Thống Phân Loại Tài nguyên Tự động**: Cloudinary cung cấp khả năng tự động phân loại và gắn nhãn cho tài nguyên dựa trên nội dung, giúp bạn dễ dàng tìm kiếm và quản lý chúng.
7. **Tích hợp dễ dàng**: Cloudinary cung cấp thư viện và các SDK để tích hợp với nhiều ngôn ngữ lập trình và nền tảng phát triển khác nhau, bao gồm JavaScript, Node.js, Python, Ruby, PHP, và nhiều nền tảng khác.

Cloudinary được sử dụng rộng rãi trong các ứng dụng web và di động, đặc biệt là các ứng dụng có tích hợp hình ảnh và video, thương mại điện tử, mạng xã hội, và các dự án có nhu cầu quản lý tài nguyên đa phương tiện lớn. Nó giúp tối ưu hóa hiệu suất và trải nghiệm người dùng bằng cách quản lý và phục vụ tài nguyên đa phương tiện một cách hiệu quả.

### Cài đặt với NodeJs

Để sử dụng Cloudinary, trước tiên ta phải cài đặt thư viện cloudinary:

```
npm i cloudinary
```

Để truy cập cloudinary, ta cần phải có một số config yêu cầu của cloudinary, ví dụ:

```json
{ 
  cloud_name: 'dxo374cj8', 
  api_key: '399236672291454', 
  api_secret: '***************************' 
}
```

Demo config:

```javascript
import { v2 as cloudinary } from 'cloudinary';

cloudinary.config({
    cloud_name: 'dxp374cK8',
    api_key: '399236670491454',
    api_secret: 'qYDv0K5pDyR11eueVbGgSsKGOHQ'
});
```

Để upload file, ta sẽ cần sử dụng đối tượng uploader và phương thức upload được cung cấp trong đối tượng này, ví dụ:

```javascript
cloudinary.uploader.upload("https://upload.wikimedia.org/wikipedia/commons/a/ae/Olympic_flag.jpg",
  { public_id: "olympic_flag" }, 
  function(error, result) {console.log(result); });
```

Trong phương thức upload này, ta sẽ cần quan tâm một số tham số nhận giá trị đầu vào:

```
upload(file, options, callBack)
```

* file: đường dẫn file dạng string,
* options:&#x20;

  là một object chưa các option như:

  * public\_id: tên file, định danh file (giống các trường id)
  * resource\_type:

    &#x20;   là string, thuộc một trong các dạng **auto** , **image**, **raw**, **video,** đây là các định dạng file mà cloudinary cung cấp. (raw sử dụng cho các file định dạng như docx, pdf, ...)
  * folder: folder cần lưu file hiện tại đang cần upload.
* callBack:

  &#x20;một function nhận đầu vào hai tham số lần lượt:&#x20;

  * error: lỗi khi upload
  * result: một object chứa một số thông tin sau khi upload thành công

    Ví dụ:

    <figure><img src="https://content.gitbook.com/content/bApz6rKK01BWUpp5sM0n/blobs/bh44GPcST941JbyEWXEE/image.png" alt=""><figcaption><p>Upload response</p></figcaption></figure>

    Lưu ý: url và secure\_url là hai thông tin cách lưu trữ khác nhau, nên sử dụng secure\_url

Trên đây là một số thông tin cơ bản, cần biết khi sử dụng Cloudinary, tiếp theo ta sẽ hoàn thiện việc sử dụng kết hợp Multer và Cloudinary.

### Kết hợp Multer và Cloudinary

Trước khi cài đặt, ta cần phải hết sức lưu ý, khi sử dụng phương thức upload, tham số file, là string, thường sẽ là đường dẫn file. Nên, khi upload lên cloudinary, ta thường phải có đường dẫn tới file đó (tức phải có file tĩnh sẵn sau đó mới dùng đường dẫn file này được, thư viện sẽ tự xử lý để upload dữ liệu file).

Với Multer, hiện tại ta đang sử dụng với memoryStorage (lưu trên RAM), không thể có đường dẫn file được. Tuy nhiên ta sẽ có cách parse file sang đường dẫn, với cú pháp như sau:

```javascript
data:{định dạng file};base64,{convert dạng buffer về base64 dưới dạng string}
```

#### &#x20;Single Upload

```javascript
import express from 'express';
import multer from 'multer';
import { v2 as cloudinary } from 'cloudinary';

cloudinary.config({
    cloud_name: 'dxo324ch0',
    api_key: '392236692491454',
    api_secret: 'qYDv0H5lDyR81eueVbGoSsKGOHQ'
});

const storage = multer.memoryStorage();
const upload = multer({ storage: storage });

const app = express();

app.use(express.json());

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

    if (!file) {
        return res.status(400).json({ error: 'Không có tệp được tải lên.' });
    }
    const dataUrl = `data:${file.mimetype};base64,${file.buffer.toString('base64')}`;
    const fileName = file.originalname.split('.')[0];

    cloudinary.uploader.upload(dataUrl, {
        public_id: fileName,
        resource_type: 'auto',
        // có thể thêm field folder nếu như muốn tổ chức
    }, (err, result) => {
        if (result) {
            console.log(result.secure_url);
            // lấy secure_url từ đây để lưu vào database.
        }
    });
    res.json({ message: 'Tệp được tải lên thành công.', data: file });
});

app.listen(8080, () => {
    console.log('Server is running!');
});
```

{% hint style="info" %}
Khi đã tồn tại một public\_id, mặc định sẽ bị ghi đề bởi file mới nhất.
{% endhint %}

#### Multi Upload

Để upload nhiều file một lần, như ví dụ phía trên ta sử dụng upload.single, có nghĩa là khai báo rằng chỉ có một file được up trong một lần, để có nhiều file, ta sẽ sử dụng upload.array, và duyệt mảng này để upload từng file một với Cloudinary.

Ví dụ:

```javascript
import express from 'express';
import multer from 'multer';
import { v2 as cloudinary } from 'cloudinary';

cloudinary.config({
    cloud_name: 'dxo324ch0',
    api_key: '392236692491454',
    api_secret: 'qYDv0H5lDyR81eueVbGoSsKGOHQ'
});

const storage = multer.memoryStorage();
const upload = multer({ storage: storage });

const app = express();

app.use(express.json());

app.post('/api/v1/upload', upload.array('files'), (req, res) => {
    const listFile = req.files;
    
    const listResult = [];
    if (!listFile) {
        return res.status(400).json({ error: 'Không có tệp được tải lên.' });
    }
    for (file in listFile){
        const dataUrl = `data:${file.mimetype};base64,${file.buffer.toString('base64')}`;
        const fileName = file.originalname.split('.')[0];

        cloudinary.uploader.upload(dataUrl, {
            public_id: fileName,
            resource_type: 'auto',
            // có thể thêm field folder nếu như muốn tổ chức
        }, (err, result) => {
            if (result) {
               listResult.push(result);
            }
        });
    }
    // code ...
    res.json({ message: 'Tệp được tải lên thành công.', listFile});
});

app.listen(8080, () => {
    console.log('Server is running!');
});
```

#### Single Destroy

Để xoá một file trên Cloudinary, thay vì sử dụng phương thức upload, ta sẽ sử dụng destroy, theo dõi đoạn code sau:

```javascript
cloudinary.uploader.destroy(publicId, (error, result) => {
  if (error) {
    console.error('Error:', error);
  } else {
    console.log('Result:', result);
  }
});
```

{% hint style="info" %}
publicId: chính là trường public\_id khi upload
{% endhint %}

#### Mutil Destroy

Tương tự việc upload, ta sẽ duyệt danh sách publicId đã có và lần lượt xoá. Ví dụ:

```javascript
for (publicId in listPublicI){
  cloudinary.uploader.destroy(publicId, (error, result) => {
    if (error) {
      console.error('Error:', error);
    } else {
      console.log('Result:', result);
    }
  });
}
```

## Thực hành

Hãy thử tạo mỗi bài post có album và các file hình ảnh hoặc video có trong bài post và cho phép user có thể comment vào các hình ảnh hoặc video.

## Kết

Trên đây là nội dung kiến thức về lưu trữ các định dạng tệp tin trong quá trình xây dựng server sử dụng kết hợp multer và cloudinary một cách cơ bản nhất. Có thể có nhiều các thư viện, bên thứ 3 cung cấp dịch vụ nữa, như google firebase, aws, ... nhưng với cloudinary, sẽ là một nền tảng phù hợp cho việc học tập.
