Published on
NodeJS

ทำระบบอัพโหลดไฟล์ด้วย Node.js + Multer

file-upload-nodejs-multer
Discord

วิธีการทำระบบไฟล์อัพโหลดด้วยการใช้ Node.js + Express และ Multer ซึ่งบทความนี้เป็นบทความอัพเดท จากที่เคยเขียนไว้ก่อนหน้านี้ 7-8 แล้วครับ ทำระบบอัพโหลดไฟล์ด้วย Node.js ซึ่งตัว Library มันก็มีการอัพเดทไปพอสมควร

Step 1 - Create Project

เริ่มต้นด้วยการสร้างโปรเจ็คขึ้นมาโดยใช้ npm init หรือ yarn init ก็ได้

yarn init -y

จากนั้นก็ทำการติดตั้ง express และ multer

yarn add express multer

Step 2 - Frontend (index.html)

ต่อมาสร้างไฟล์ index.html ขึ้นมา โดยส่วนนี้จะเป็นหน้าเว็บธรรมดา ที่มีแค่การอัพโหลดไฟล์ โดยใช้ <input type="file" />

index.hmlt
<!DOCTYPE html>
<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>Hello Multer</title>
</head>
<body>
  <h1>Hello Multer</h1>
  <form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="photo" />
    <button type="submit" value="Upload" />
  </form>
</body>
</html>

โดยที่ตัว form ต้องกำหนด enctype เป็น multipart/form-data

Step 3 - Create Server

สร้าง app.js ขึ้นมา เพื่อเป็น Server ง่ายๆ โดยให้ index ทำการอ่านจากไฟล์ index.html ที่เราสร้างไว้้ และ POST /upload เพื่อไว้รับค่า จากไฟล์ที่เราต้องการอัพโหลด

app.js
const express = require('express')
const app = express()

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html')
})

app.post('/upload', (req, res) => {
  res.json(req.body)
})

app.listen(9999, () => console.log("Running on port 9999"))

ทดสอบ โดยการ start server

node app.js

ทดสอบเรียก request ไปที่ POST /upload เพื่อดูผลลัพธ์

curl -L -X POST 'http://localhost:9999/upload' \
-H 'Content-Type: application/json' \
-D '{"message": "Hello"}'

หรือทดสอบด้วย Postman

Postman

จะเห็นว่าเราจะไม่ได้ผลลัพธ์อะไร แม้ว่าเราจะให้มัน res.json(req.body) ก็ตาม เพราะเนื่องจาก โดย default ตัว Express ไม่สามารถอ่าน req.body ได้ ถ้าอยากให้อ่านได้ คือใช้ body-parser หรือใช้ตัว express.json() แบบนี้

const express = require('express')
const app = express()

app.use(express.json())

Restart และทดสอบ POST Request ไปใหม่ จะเห็นว่าเราสามารถอ่านค่า req.body ได้แล้ว

Step 4- Multer

ตัว multer จะมาช่วยจัดการเรื่องของไฟล์ โดยที่เราจะสามารถเข้าถึง object ไฟล์ client ส่งมา ด้วย req.file และตัว req.body ตัว multer ก็ทำการ handle ให้เราแล้ว ฉะนั้น ก็ไม่จำเป็นต้องใช้ body-parser เราก็สามารถเข้าถึงได้ทั้ง req.file และ req.body

app.js
const multer = require('multer')
const upload = multer({ dest: 'uploads' })

โค๊ดด้านบนเป็นการกำหนด multer และให้โฟลเดอร์ที่เราจะเก็บไฟล์คือ uploads สิ่งที่ต้องรู้คือ

  • upload.single : จะอัพโหลดได้ทีละไฟล์
  • ตัว upload.single('photo') ชื่อที่เรากำหนด ต้องเป็นชื่อเดียวกับ form ของเรา <input name="photo" />
app.js
const express = require('express')
const multer = require('multer')

const app = express()

const upload = multer({ dest: 'uploads' })

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html')
})

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

app.listen(9999, () => console.log('Running on port 9999'))

ทดลองเปิดหน้าเว็บ http://localhost:9999 จากนั้นเลือกไฟล์แล้วคลิ๊ก Upload ดูครับ จะได้ผลลัพธ์ประมาณนี้

{
  "fieldname": "photo",
  "originalname": "Screen Shot 2565-07-14 at 22.26.37.png",
  "encoding": "7bit",
  "mimetype": "image/png",
  "destination": "uploads",
  "filename": "801ea9c5a8ee00fba92f0589fb8230e0",
  "path": "uploads/801ea9c5a8ee00fba92f0589fb8230e0",
  "size": 4243
}

จะเห็นได้ว่า ไฟล์ที่เราอัพโหลด เราสามารถรู้ค่า properties ได้ อย่างเช่น

  • originalname : ชื่อไฟล์ที่เราอัพโหลด
  • filename : ชื่อไฟล์หลังจากที่อัพโหลด
  • mimetype : ชนิดของไฟล์
  • path : location ที่เก็บไฟล์หลักจากถูกอัพโหลด
  • size : ขนาดของไฟล์

Step 5 - Customize Multer

ต่อมาเรามาปรับแต่ง Multer เพิ่มเติม เนื่องจากตัว default แค่กำหนด folder เท่านั้น ยังไม่ได้ตั้งชื่อไฟล์เลย โดยเราจะใช้ DiskStorage และกำหนด options ด้วย destination และ filename แบบนี้

const storage = multer.diskStorage({
  destination: function (req, file, callback) {
    callback(null, './my-folders')
  },
  filename: function (req, file, callback) {
    callback(null, file.originalname)
  },
})

const upload = multer({ storage })
  • destination - เป็นการบอกว่าให้อัพโหลดไปที่โฟลเดอร์ไหน (เราต้องสร้างโฟลเดอร์เองนะครับ)
  • filename - function ที่ให้เรากำหนดชื่อไฟล์ได้ โดย return เป็น callback กลับไป ตัวอย่างผมใช้ file.originalname ครับ

กำหนดขนาดไฟล์

เราสามารถกำหนดขนาดไฟล์ด้วยการใช้ limits เช่น กำหนดให้ไฟล์มีขนาดไม่เกิน 1000 (bytes)

multer({ storage, limits: { fileSize: 1024 * 1024 }})

ทดลองอัพโหลไฟล์ใหม่ จะได้ error (เนื่องจากเรากำหนดขนาดแค่ 1000 bytes หรือ ~1kb เอง)

MulterError: File too large

เปลี่ยนใหม่ ให้เป็น use case จริงๆ เช่น ไฟล์ไม่เกิน 1MB (1M ถ้านับแบบ decimal ก็ 1ล้าน ถ้านับแบบ binary ก็คือ 1024x1024)

{ fileSize: 1024 * 1024 }

รองรับการอัพโหลดหลายๆไฟล์

เราจะใช้ upload.array('images') เพื่อทำการอัพโหลดหลายๆไฟล์ครับ และเข้าถึงด้วย req.files เช่น

app.post('/images', upload.array('images'), (req, res) => {
  res.send(req.files)
})

โดยที่ฝั่ง Client (HTML) ก็แค่เพิ่ม attribute multiple ลงไปแบบนี้

<input type="file" name="images" multiple />

ตัว index.html แบบที่รองรับทั้ง ไฟล์เดียว และหลายไฟล์

index.html
<!DOCTYPE html>
<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>Hello Multer</title>
</head>
<body>
  <h1>Hello Multer</h1>
  <form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="photo" />
    <button type="submit">Upload</button>
  </form>

  <form action="/images" method="post" enctype="multipart/form-data">
    <input type="file" name="images" multiple />
    <button type="submit">Upload Multiple</button>
  </form> 
</body>
</html>
HTML File input

อื่นๆ

นอกจากนี้ ทางฝั่ง Client เรายังสามารถใช้ FormData เพื่อทำการสร้าง multipart/form-data ด้วย key value ได้อีกด้วย ประมาณนี้

const form = new FormData()
form.append('message', 'Hello')
form.append('photo', <FILE>)

เดี๋ยวบทความถัดๆไป จะมีตัวอย่างการทำ File Upload ฝั่ง Client ออกมานะครับ เช่น ใช้ Vanilla JavaScript ธรรมดาๆ ร่วมกับ FileReader API กับ FormData หรือ React.js หรือ Dropzone, Uppy, FilePond เป็นต้นครับ

ปัญหาอื่นๆ

MulterError: Unexpected field

เกิดขึ้น กรณีที่เรากำหนด .single('file') หรือ .array('files') และชื่อไม่ตรง input

MulterError: File too large

ไฟล์มีขนาดใหญ่เกิดกว่าที่เรากำหนด

Error: ENOENT: no such file or directory

ไม่มีโฟลเดอร์ที่เรากำหนดให้ multer อัพโหลด (ให้เราสร้างโฟลเดอร์เอง) นอกจากใช้ multer({ dest: 'uploads'}) multer ถึงจะสร้าง folder ให้เราอัตโนมัติ

สรุป

ตัวอย่างนี้ก็เป็นตัวอย่างง่ายๆ ในการทำระบบอัพโหลดไฟล์ด้วย Node.js นะครับ ตัว multer ก็สามารถรองรับทั้งอัพโหลดทีละไฟล์ อัพโหลดหลายๆไฟล์ รวมถึงเรายังสามารถต่อยอด ไปใช้การเก็บข้อมูลลง AWS S3 ได้อีกด้วย ก็หวังว่าบทความนี้จะเป็นประโยชน์ไม่มากก็น้อย ลองเล่น ลองฝึกและลงมือทำกันดูนะครับ

Happy Coding ❤️

Authors
Discord