點燈坊

失くすものさえない今が強くなるチャンスよ

Upload File with Original File Name and Downloadable Link

Sam Xiao's Avatar 2022-08-19

We can upload file with original file name and provide downloadable link by static web server.

Version

Node 16.16.0
Express 4.17.1
Alpine 3.10.3

Alpine

original000

Upload a single file by Alpine and Express with the original file name and provide downloadable link.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/alpinejs" defer></script>
    <title>Upload</title>
  </head>
  <body x-data="fileUpload">
    <input x-ref="upload" type="file" />
    <button @click="onClick">Upload</button>
    <div x-text="resMsg"></div>
  </body>
  <script>
    let fileUpload = {
      resMsg: '',
      async onClick() {
        let formData = new FormData()
        formData.append('file', this.$refs.upload.files[0])

        try {
          let res = await fetch(`http://localhost:8080/upload`, {
            method: 'POST',
            body: formData,
          })

          let data = await res.json()
          this.resMsg = data.message
        } catch (e) {
          console.error(e)
        }
      },
    }
  </script>
</html>

Line 8

<body x-data="fileUpload">
  <input x-ref="upload" type="file" />
  <button @click="onClick">Upload</button>
  <div x-text="resMsg"></div>
</body>
  • x-data:with fileUpload Object to control the component
  • x-ref:to set the name for the DOM element
  • @click:upload the file to the server
  • x-text:display the result message

Line 17

let formData = new FormData();
formData.append("file", this.$refs.upload.files[0]);
  • fetch() accepts FormData as an argument for body
  • Append the file key for the uploaded file

Line 20

try {
  let res = await fetch(`http://localhost:8080/upload`, {
    method: 'POST',
    body: formData,
  })

  let data = await res.json()
  this.resMsg = data.message
} catch (e) {
  console.error(e)
}
  • Use fetch() API with method POST and body formData
  • response.json()fetch() returns Promise with Response Object, use json() to convert Response Object to JSON Object

Express

import express, { json, static as web } from 'express'
import cors from 'cors'
import multer, { diskStorage } from 'multer'
import { dirname } from 'path'
import { fileURLToPath } from 'url'

let __filename = fileURLToPath(import.meta.url)
let __dirname = dirname(__filename)

let app = express()
app.use(cors())
app.use(json())
app.use(web(__dirname))

let storage = diskStorage({
  destination(req, file, cb) {
    cb(null, 'uploads')
  },
  filename(req, file, cb) {
    cb(null, file.originalname)
  },
})

let opts = multer({ storage }).single('file')

app.post('/upload', opts, (req, res) => {
  res.json({ message: 'Successfully uploaded files' })
})

app.listen(8080, () => {
  console.log('Node listen on port: 8080')
})

Line 1

import express, { json, static as web } from 'express'
import cors from 'cors'
import multer, { diskStorage } from 'multer'
import { dirname } from 'path'
import { fileURLToPath } from 'url'
  • Import expresscors and multer
  • Import path for dirname()
  • Import url for fileURLToPath

Line 7

let __filename = fileURLToPath(import.meta.url)
  • import.meta.url:get the file URL of the current module. For example: file:///Users/oomusou/code/express/express-lab/app.js
  • fileURLToPath():return the fully-resolved platform-specific Node file path. For example: /Users/oomusou/code/express/express-lab/app.js

Line 8

let __dirname = dirname(__filename)
  • dirname():return the directories from a file path

Line 10

let app = express()
  • Create app Object for Express

Line 11

app.use(cors())
  • Create cors middleware by cors()
  • Use cors middleware by Express

Line 12

app.use(json())
  • Create json middleware by json(). This is a built-in middleware by Express
  • Use json middleware by Express

Line 13

app.use(web(__dirname))
  • Use project root directory as the home directory of the Web

Line 15

let storage = diskStorage({
  destination(req, file, cb) {
    cb(null, 'uploads')
  },
  filename(req, file, cb) {
    cb(null, file.originalname)
  },
})
  • Use diskStorage() to create storage Object for Multer options
  • destination:determine within which folder the uploaded files should be stored
  • filename:determine what the file should be named inside the folder

Line 24

let opts = multer({ storage }).single('file')
  • Create opts Object for Multer
  • storage:use the storage key for options
  • single:upload single file by Multer, the argument for single() must depend on the name for the input specified in formData in Alpine

Line 26

app.post('/upload', opts, (req, res) => {
  res.json({ message: 'Successfully uploaded files' })
})
  • Create /upload POST with Multer options
  • Return JSON Object with successful message

Line 30

app.listen(8080, () => {
  console.log('Node listen on port: 8080')
})
  • Express listens on port 8080

Conclusion

  • If we want to control the file name of the uploaded file, the disk storage engine gives us full control on storing files to disk
  • If we want to provide downloadable link with uploaded files, we can use Express to provide static web server