nuxt-file-save
v1.2.5
Published
A nuxt plugin for file uploading to local disk
Readme
Nuxt File Save
A nuxt plugin for file uploading to local disk
Features
- ⛰ Configurable, support for multi-file upload
- 🚠 The type and size of the file are checked on the back end
- 🌲 Save to the local disk
Quick Setup
Install the module to your Nuxt application with one command:
npx nuxi module add nuxt-file-saveOr add it manually to your nuxt.config.ts:
# Using pnpm
pnpm add -D nuxt-file-save
# Using yarn
yarn add --dev nuxt-file-save
# Using npm
npm install --save-dev nuxt-file-saveexport default defineNuxtConfig({
modules: ['nuxt-file-save'],
fileSave: {
// The location where the file is saved to disk, public by default, in the public folder at the root of the current project
mount:'public',
// Verify the configuration item associated with the upload file, overwriting it in the 'useFileVerify' method
// options?: BlobUploadOptions
}
})That's it! You can now use Nuxt File Save in your Nuxt app ✨
Usage
Handling Files in the frontend
Just send the formData format data to the backend
<template>
<input type="file" multiple @change="handleFileChange" />
</template>
<script setup>
const handleFileChange = (e: any) => {
const files = e.target.files[0]
if (!files) return
const formData = new FormData()
formData.append('files', files.value)
// You can also verify before uploading the file
// try {
// await useFileVerify(formData, {
// ensure: {
// maxSize: '100MB',
// types: ['audio', 'image', 'video', 'pdf', 'zip'],
// },
// })
// } catch (error) {
// alert(error)
// return
// }
const res = await $fetch<{code: number, data: string, msg: string}>('/api/upload', {
method: 'POST',
body: formData,
})
console.log(res)
if(res.code !== 200){ // fail
return alert(res.msg)
}
}
</script>Handling files in the backend
On the server side, the useFileVerify method is used to verify the file, and the useFileSave method is used to save it
// server/api/upload.ts
export default defineEventHandler(async (event) => {
try {
const form = await readFormData(event)
// multiple
// const files = await useFileVerify(form, {
// multiple: 3, // Max 3 files at a time for now
// ensure: {
// maxSize: '50MB', // Max 50 MB each file
// types: ['audio', 'csv', 'image', 'video', 'pdf', 'text'], // Only allow these file types
// },
// });
// for (const file of files) {
// await useFileSave(file);
// }
// single
const [file] = await useFileVerify(form, {
formKey: 'files', // The key of the form data
multiple: false, // Only allow one file at a time
ensure: {
maxSize: '256MB',
types: ['audio', 'csv', 'image', 'video', 'pdf', 'text', 'zip', 'exe'],
},
lang: 'en', // Language for error messages
})
const date = new Date()
// Generate folders based on time
const dateDir = date.toLocaleDateString('zh-cn') // 2023/01/02
// const dateDir = `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`
const randomStr = Math.random().toString(36).substring(2, 6 + 2) // Random String 6 bits
const fileDir = `/upload/${dateDir}`
const url = await useFileSave(file, `${Date.now()}-${randomStr}`, fileDir)
if (!url) return { code: 500, msg: 'error' }
return { code: 200, msg: 'success', data: url }
} catch (error: any) { // eslint-disable-line
return { code: error.statusCode, msg: error.message }
}
})If you don't want to check the file, just save the file
const form = await readFormData(event)
const [file] = form.getAll('files') as File[]
if (file) {
const url = await useFileSave(file)
}Method
useFileVerify(form: FormData, options?: BlobUploadOptions)
A method of receiving and validating files
form:FormDataobjectoptions:BlobUploadOptionsobject
Options:
formKey: The key of the form data, default isfilesmultiple: Whether to allow multiple files, The type isnumber | boolean, default istrueensure: The file validation rules, File size and file typelang: The language for error messages, default isen
Returns:
- An array of
Fileobjects, If validation fails and an error message is thrown, you can use try.catch
Example:
// multiple
const files = await useFileVerify(form, {
multiple: 3, // Max 3 files at a time for now
ensure: {
maxSize: '50MB', // Max 50 MB each file
types: ['audio', 'csv', 'image', 'video', 'pdf', 'text'], // Only allow these file types
},
});
try{
// single
const [file] = await useFileVerify(form, {
formKey: 'files', // The key of the form data
multiple: false, // Only allow one file at a time
ensure: {
maxSize: '256MB',
types: ['audio', 'csv', 'image', 'video', 'pdf', 'text', 'zip', 'exe'],
},
lang: 'en', // Language for error messages
})
}catch(e){
// error info
}useFileSave(file: File, fileName = file.name, fileDir?: string)
A method of uploading files.
fileis the file object data.fileNameis the fileName, which defaults to the original fileName, or you can use a string with no suffix.fileDiris the directory where the file is stored.
Returns:
- The file url, Also using try.catch gets the error message
Example:
const url = await useFileSave(file, `${Date.now()}`, 'upload')Type Declarations
export type FileSizeUnit = 'B' | 'KB' | 'MB' | 'GB' | 'TB'
export type BlobSize = `${number}${FileSizeUnit}`
export type BlobType =
| 'image'
| 'video'
| 'audio'
| 'pdf'
| 'csv'
| 'text'
| 'blob'
| (string & Record<never, never>)
export type MessageLangType = 'en'|'zh'
export interface BlobUploadOptions {
/**
* The key to get the file/files from the request form.
* @default 'files'
*/
formKey?: string
/**
* Whether to allow multiple files to be uploaded.
* @default true
*/
multiple?: boolean | number
/**
* Options used for the ensure() method.
*/
ensure?: BlobEnsureOptions
/**
* The language in which the text message corresponds
* @default 'en'
*/
lang?: MessageLangType
}
export interface BlobEnsureOptions {
/**
* The maximum size of the blob (e.g. '1MB')
*/
maxSize?: BlobSize
/**
* The allowed types of the blob (e.g. ['image/png', 'application/json', 'video'])
*/
types?: BlobType[]
}Error message code
| code | message |
| ------ | ------------------------------------------------------- |
| 1000 | Received invalid file |
| 1001 | Invalid file type. Only allowed: {{types}} |
| 1002 | File too heavy. Max size is: {{maxSize}} |
| 1003 | No files received |
| 1004 | Multiple files are not allowed |
| 1005 | Number of files exceeded, Maximum allowed: {{multiple}} |
| 1006 | Invalid file size format: {{blobSize}} |
| 1007 | Invalid file size unit: {{sizeUnit}} |
| 1008 | Error uploading file |
And that's it! Now you can use it happily in your project ✨
Cautions (production environment related)
(node:24872) ExperimentalWarning: buffer.File is an experimental feature and might change at any time
- The message you are receiving isn't an issue, it's a warning emitted from the use of an experimental feature.
This library saves files to the public folder, so we can access them directly in the project.for example:
<img src="/upload/1678438436123.png" alt="image" />But in a production environment, when we use nuxt build to build a project, the /public file will be copied to .output/public(which will result in an increase in disk space), but in reality, the file will only be saved to /public.
For production environment problem, I suggest using nginx to configure proxies to solve them
Recommended: store uploads outside public
Rather than relying on the public folder (which nuxt build copies into .output/public), point mount to a dedicated directory on the server in production and let nginx serve it directly. The uploaded files then live entirely outside the build output, so the copy issue above no longer applies and re-deploying never overwrites them:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['nuxt-file-save'],
fileSave: {
// 'public' in dev; an absolute, nginx-served directory in production
mount: process.env.NODE_ENV === 'production' ? '/home/project/uploads' : 'public',
},
})Then point nginx at that directory — just set the alias in the config below to your mount path (e.g. alias /home/project/uploads/upload;).
Run the project using PM2
// ecosystem.config.js
// or
// ecosystem.config.cjs
module.exports = {
apps: [
{
name: 'nuxt-web', // Sets the name of the PM2 process
port: '8000', // The port on which the project runs
exec_mode: 'cluster',
instances: 'max',
script: './.output/server/index.mjs',
env: {
},
},
],
}
Run the project, if filename is ecosystem.config.js, ecosystem.config.js can be omitted
pm2 start ecosystem.config.js
# or
pm2 start ecosystem.config.cjsNginx configuration
# nuxt-web.conf
server {
listen 80;
server_name your-domain.com www.your-domain.com;
# Set variables, project root directory
set $base_path /home/project/nuxt-web;
location / {
root $base_path;
proxy_pass http://127.0.0.1:8000;
}
#############################################################
# Change the directory accessed by `/upload`, Example of `useFileSave(file, '', '/upload')` with the third parameter '/upload'
location ^~/upload {
alias $base_path/public/upload; # Set access to file directory
autoindex on;
autoindex_exact_size off; # Display file size in MB, GB format instead of KB
autoindex_localtime on; # The file time is the time of the server
if ($request_filename ~* ^.*?\.(txt|doc|pdf|rar|gz|zip|docx|exe|xlsx|ppt|pptx|png|jpg|gif|webp)$){
add_header Content-Disposition attachment;
add_header Content-Type application/octet-stream;
} # These files with suffixes can be downloaded
}
#############################################################
}At this point, the access path of /upload has been changed, and files uploaded in the production environment can still be accessed normally. The .output/public/upload folder can also be deleted or not
Contribution
Run into a problem? Open a new issue. I'll try my best to include all the features requested if it is fitting to the scope of the project.
Want to add some feature? PRs are welcome!
- Clone this repository
- install the dependencies
- prepare the project
- run dev server
git clone https://github.com/nowo/nuxt-file-save && cd nuxt-file-save
pnpm i
pnpm run dev:prepare
pnpm run devLicense
MIT - Copyright (c) 2024 nowo
