├── .eslintignore
├── .npmrc
├── playground
├── .env.example
├── tsconfig.json
├── server
│ ├── tsconfig.json
│ └── api
│ │ └── files.ts
├── public
│ ├── specificFolder
│ │ └── JEAdxnaN15ef.png
│ ├── nuxt-storage-logo.svg
│ └── nuxt-file-storage-banner.svg
├── nuxt.config.ts
├── package.json
└── app.vue
├── tsconfig.json
├── bun.lockb
├── test
├── fixtures
│ └── basic
│ │ ├── package.json
│ │ ├── nuxt.config.ts
│ │ ├── app.vue
│ │ └── server
│ │ └── api
│ │ └── files.ts
└── basic.test.ts
├── .vscode
├── settings.json
└── launch.json
├── src
├── runtime
│ ├── plugins
│ │ └── plugin.ts
│ ├── composables
│ │ └── useFileStorage.ts
│ └── server
│ │ └── utils
│ │ └── storage.ts
├── types.d.ts
└── module.ts
├── .editorconfig
├── .eslintrc
├── .prettierrc
├── .github
├── FUNDING.yml
└── workflows
│ └── npm-publish.yml
├── .gitignore
├── LICENSE
├── package.json
├── CHANGELOG.md
└── README.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | shamefully-hoist=true
2 | strict-peer-dependencies=false
3 |
--------------------------------------------------------------------------------
/playground/.env.example:
--------------------------------------------------------------------------------
1 | mount="" # Provide a value for mount
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.nuxt/tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NyllRE/nuxt-file-storage/HEAD/bun.lockb
--------------------------------------------------------------------------------
/playground/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.nuxt/tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------
/playground/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../.nuxt/tsconfig.server.json"
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/basic/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "basic",
4 | "type": "module"
5 | }
6 |
--------------------------------------------------------------------------------
/test/fixtures/basic/nuxt.config.ts:
--------------------------------------------------------------------------------
1 | export default defineNuxtConfig({
2 | ssr: true,
3 | modules: ['../../../src/module'],
4 | })
5 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.codeActionsOnSave": {
3 | "source.fixAll.eslint": "explicit"
4 | },
5 | "eslint.validate": ["javascript"]
6 | }
7 |
--------------------------------------------------------------------------------
/playground/public/specificFolder/JEAdxnaN15ef.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NyllRE/nuxt-file-storage/HEAD/playground/public/specificFolder/JEAdxnaN15ef.png
--------------------------------------------------------------------------------
/src/runtime/plugins/plugin.ts:
--------------------------------------------------------------------------------
1 | import { defineNuxtPlugin } from '#app'
2 |
3 | export default defineNuxtPlugin((nuxtApp) => {
4 | console.log('nuxt-file-storage initialized successfully')
5 | })
6 |
--------------------------------------------------------------------------------
/test/fixtures/basic/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/playground/nuxt.config.ts:
--------------------------------------------------------------------------------
1 | export default defineNuxtConfig({
2 | modules: ['../src/module'],
3 |
4 | fileStorage: {
5 | mount: process.env.mount,
6 | },
7 |
8 | devtools: { enabled: true },
9 | compatibilityDate: '2025-02-27',
10 | })
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_size = 3
5 | indent_style = tab
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/playground/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "nuxt-file-storage-playground",
4 | "type": "module",
5 | "scripts": {
6 | "dev": "nuxi dev",
7 | "build": "nuxi build",
8 | "generate": "nuxi generate"
9 | },
10 | "devDependencies": {
11 | "nuxt": "latest"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": ["@nuxt/eslint-config"],
4 | "rules": {
5 | "vue/multi-word-component-names": 0,
6 | "vue/html-indent": 0,
7 | "vue/html-self-closing": 0,
8 | "vue/max-attributes-per-line": 0,
9 | "vue/singleline-html-element-content-newline": 0,
10 | "@typescript-eslint/no-unused-vars": 0
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "tabWidth": 3,
4 | "semi": false,
5 | "singleQuote": true,
6 | "trailingComma": "all",
7 | "printWidth": 100,
8 | "overrides": [
9 | {
10 | "files": "*.vue",
11 | "options": {
12 | "singleQuote": true,
13 | "maxAttributesPerLine": 2,
14 | "singleTagSingleLine": false
15 | }
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/playground/server/api/files.ts:
--------------------------------------------------------------------------------
1 | import type { ServerFile } from '../../../src/types'
2 |
3 | export default defineEventHandler(async (event) => {
4 | const { files } = await readBody<{ files: ServerFile[] }>(event)
5 | const fileNames: string[] = []
6 | for (const file of files) {
7 | fileNames.push(await storeFileLocally(file, 12, '/specificFolder'))
8 | }
9 | return fileNames
10 | })
11 |
--------------------------------------------------------------------------------
/src/types.d.ts:
--------------------------------------------------------------------------------
1 | export interface ServerFile {
2 | name: string
3 | content: string
4 | size: string
5 | type: string
6 | lastModified: string
7 | }
8 |
9 | export interface ClientFile extends Blob {
10 | content: string | ArrayBuffer | null | undefined
11 | name: string
12 | lastModified: number
13 | }
14 |
15 | export interface ModuleOptions {
16 | mount: string
17 | version: string
18 | }
19 |
--------------------------------------------------------------------------------
/test/fixtures/basic/server/api/files.ts:
--------------------------------------------------------------------------------
1 | import { storeFileLocally } from '../../../../../src/runtime/server/utils/storage'
2 |
3 | export default defineEventHandler(async (event) => {
4 | const body = await readBody<{ files: File[] }>(event)
5 | console.dir(body)
6 |
7 | const fileNames: string[] = []
8 | for (const file of body.files) {
9 | fileNames.push(await storeFileLocally(file.content, file.name))
10 | }
11 | return fileNames
12 | })
13 |
14 | interface File {
15 | name: string
16 | content: string
17 | }
18 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "Launch Program",
11 | "skipFiles": [
12 | "/**"
13 | ],
14 | "program": "${workspaceFolder}/dist/module.cjs",
15 | "outFiles": [
16 | "${workspaceFolder}/**/*.js"
17 | ]
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [nyllre]
4 | patreon: nyll
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | node_modules
3 |
4 | # Logs
5 | *.log*
6 |
7 | # Temp directories
8 | .temp
9 | .tmp
10 | .cache
11 |
12 | # Yarn
13 | **/.yarn/cache
14 | **/.yarn/*state*
15 |
16 | # Generated dirs
17 | dist
18 |
19 | # Nuxt
20 | .nuxt
21 | .output
22 | .data
23 | .vercel_build_output
24 | .build-*
25 | .netlify
26 |
27 | # Env
28 | .env
29 |
30 | # Testing
31 | reports
32 | coverage
33 | *.lcov
34 | .nyc_output
35 |
36 | # VSCode
37 | .vscode/*
38 | !.vscode/settings.json
39 | !.vscode/tasks.json
40 | !.vscode/launch.json
41 | !.vscode/extensions.json
42 | !.vscode/*.code-snippets
43 |
44 | # Intellij idea
45 | *.iml
46 | .idea
47 |
48 | # OSX
49 | .DS_Store
50 | .AppleDouble
51 | .LSOverride
52 | .AppleDB
53 | .AppleDesktop
54 | Network Trash Folder
55 | Temporary Items
56 | .apdisk
57 |
58 | playground/public/userFiles/*
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3 |
4 | name: Node.js Package
5 |
6 | on:
7 | release:
8 | types: [created]
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v3
15 | - uses: actions/setup-node@v3
16 | with:
17 | node-version: 20
18 | - run: npm i
19 | - run: npm ci
20 | - run: npm test
21 |
22 | publish-npm:
23 | needs: build
24 | runs-on: ubuntu-latest
25 | steps:
26 | - uses: actions/checkout@v3
27 | - uses: actions/setup-node@v3
28 | with:
29 | node-version: 20
30 | registry-url: https://registry.npmjs.org/
31 | - run: npm ci
32 | - run: npm publish
33 | env:
34 | NODE_AUTH_TOKEN: ${{secrets.npm_token}}
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024-Present Nuxt File Storage Project
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/test/basic.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from 'vitest'
2 | import { fileURLToPath } from 'node:url'
3 | import { setup, $fetch } from '@nuxt/test-utils/e2e'
4 |
5 | describe('ssr', async () => {
6 | await setup({
7 | rootDir: fileURLToPath(new URL('./fixtures/basic', import.meta.url)),
8 | })
9 |
10 | it('renders the index page', async () => {
11 | // Get response to a server-rendered page with `$fetch`.
12 | const html = await $fetch('/')
13 | expect(html).toContain(' ')
14 | })
15 | })
16 |
17 | //! Look into this: https://runthatline.com/how-to-mock-fetch-api-with-vitest/
18 | // describe('Server API', async () => {
19 | // await setup({
20 | // rootDir: fileURLToPath(new URL('./fixtures/basic', import.meta.url)),
21 | // })
22 |
23 | // it('sends a file to the server', async () => {
24 | // const fileName = await fetch('/api/files', {
25 | // method: 'POST',
26 | // body: JSON.stringify({
27 | // files: [
28 | // {
29 | // name: 'ExampleFile',
30 | // content: 'data:text/plain;base64,dGhpcyBpcyBhbiBleGFtcGxlIGZpbGUK',
31 | // },
32 | // ],
33 | // }),
34 | // })
35 | // console.log(`API Request Result: ${fileName}`)
36 |
37 | // expect(fileName).toContain('ExampleFile')
38 | // })
39 | // })
40 |
--------------------------------------------------------------------------------
/src/runtime/composables/useFileStorage.ts:
--------------------------------------------------------------------------------
1 | import { ref } from 'vue'
2 | import type { ClientFile } from '../../types'
3 |
4 | type Options = {
5 | clearOldFiles: boolean
6 | }
7 |
8 | export default function (options: Options = { clearOldFiles: true }) {
9 | const files = ref([])
10 | const serializeFile = (file: ClientFile): Promise => {
11 | return new Promise((resolve, reject) => {
12 | const reader = new FileReader()
13 | reader.onload = (e: ProgressEvent) => {
14 | files.value.push({
15 | ...file,
16 | name: file.name,
17 | size: file.size,
18 | type: file.type,
19 | lastModified: file.lastModified,
20 | content: e.target?.result,
21 | })
22 | resolve()
23 | }
24 | reader.onerror = (error) => {
25 | reject(error)
26 | }
27 | reader.readAsDataURL(file)
28 | })
29 | }
30 |
31 | const clearFiles = () => {
32 | files.value.splice(0, files.value.length)
33 | }
34 |
35 |
36 | const handleFileInput = async (event: any) => {
37 | if (options.clearOldFiles) {
38 | clearFiles()
39 | }
40 |
41 | const promises = []
42 | for (const file of event.target.files) {
43 | promises.push(serializeFile(file))
44 | }
45 |
46 | await Promise.all(promises)
47 | }
48 |
49 | return {
50 | files,
51 | handleFileInput,
52 | clearFiles,
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nuxt-file-storage",
3 | "version": "0.3.0",
4 | "description": "Easy solution to store files in your nuxt apps. Be able to upload files from the frontend and recieve them from the backend to then save the files in your project.",
5 | "repository": "NyllRE/nuxt-file-storage",
6 | "license": "MIT",
7 | "type": "module",
8 | "exports": {
9 | ".": {
10 | "types": "./dist/types.d.ts",
11 | "import": "./dist/module.mjs",
12 | "require": "./dist/module.cjs"
13 | }
14 | },
15 | "main": "./dist/module.cjs",
16 | "types": "./dist/types.d.ts",
17 | "files": [
18 | "dist"
19 | ],
20 | "scripts": {
21 | "prepack": "nuxt-module-build build",
22 | "dev": "nuxi dev playground",
23 | "dev:build": "nuxi build playground",
24 | "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
25 | "release": "npm run lint && npm run prepack && changelogen && npm publish && git push --follow-tags",
26 | "lint": "eslint .",
27 | "test": "vitest run",
28 | "test:watch": "vitest watch"
29 | },
30 | "dependencies": {
31 | "@nuxt/kit": "^3.15.4",
32 | "defu": "^6.1.4"
33 | },
34 | "devDependencies": {
35 | "@nuxt/devtools": "latest",
36 | "@nuxt/eslint-config": "^0.2.0",
37 | "@nuxt/module-builder": "^0.8.4",
38 | "@nuxt/schema": "^3.15.4",
39 | "@nuxt/test-utils": "^3.17.0",
40 | "@types/node": "^20.17.19",
41 | "changelogen": "^0.5.7",
42 | "eslint": "^8.57.1",
43 | "nuxt": "^3.15.4",
44 | "vitest": "^1.6.1"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/module.ts:
--------------------------------------------------------------------------------
1 | import {
2 | defineNuxtModule,
3 | createResolver,
4 | addImportsDir,
5 | addServerScanDir,
6 | logger,
7 | } from '@nuxt/kit'
8 | // import { $fetch } from 'ofetch'
9 | import defu from 'defu'
10 | // import { version } from '../package.json'
11 |
12 | import type { ModuleOptions } from './types'
13 | export type * from './types'
14 |
15 | export default defineNuxtModule({
16 | meta: {
17 | name: 'nuxt-file-storage',
18 | configKey: 'fileStorage',
19 | },
20 | //? Default configuration options of the Nuxt module
21 | //! no defaults for now
22 | // defaults: {
23 | // version: '0.0.0',
24 | // },
25 | setup(options, nuxt) {
26 | const config = nuxt.options.runtimeConfig as any
27 | config.public.fileStorage = defu(config.public.fileStorage, {
28 | ...options,
29 | })
30 |
31 | if (!config.public.fileStorage.mount) {
32 | logger.error(
33 | 'Please provide a mount path for the file storage module in your nuxt.config.js',
34 | )
35 | } else {
36 | logger.ready(
37 | `Nuxt File Storage has mounted successfully`,
38 | )
39 | }
40 |
41 | // if (nuxt.options.dev) {
42 | // // $fetch('https://registry.npmjs.org/nuxt-file-storage/latest')
43 | // // .then((release: any) => {
44 | // // if (release.version > version)
45 | // // logger.info(
46 | // // `A new version of Nuxt File Storage (v${release.version}) is available: https://github.com/nyllre/nuxt-file-storage/releases/latest`,
47 | // // )
48 | // // })
49 | // // .catch(() => {})
50 | // }
51 |
52 | const resolve = createResolver(import.meta.url).resolve
53 |
54 | addImportsDir(resolve('runtime/composables'))
55 | addServerScanDir(resolve('./runtime/server'))
56 | },
57 | })
58 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 |
4 | ## v0.3.1
5 |
6 | [compare changes](https://github.com/NyllRE/nuxt-file-storage/compare/v0.3.0...v0.3.1)
7 |
8 | ## v0.2.9
9 |
10 | [compare changes](https://github.com/NyllRE/nuxt-file-storage/compare/v0.2.8...v0.2.9)
11 |
12 | ## v0.2.8
13 |
14 | [compare changes](https://github.com/NyllRE/nuxt-file-storage/compare/v0.2.7...v0.2.8)
15 |
16 | ### 🩹 Fixes
17 |
18 | - Update to latest `@nuxt/module-builder` ([2d0c20c](https://github.com/NyllRE/nuxt-file-storage/commit/2d0c20c))
19 |
20 | ### ❤️ Contributors
21 |
22 | - Daniel Roe ([@danielroe](http://github.com/danielroe))
23 |
24 | ## v0.2.7
25 |
26 | [compare changes](https://github.com/NyllRE/nuxt-file-storage/compare/v0.2.6...v0.2.7)
27 |
28 | ## v0.2.6
29 |
30 | [compare changes](https://github.com/NyllRE/nuxt-file-storage/compare/v0.2.5...v0.2.6)
31 |
32 | ### 🏡 Chore
33 |
34 | - **release:** V0.2.3 ([fc8cffa](https://github.com/NyllRE/nuxt-file-storage/commit/fc8cffa))
35 |
36 | ### ❤️ Contributors
37 |
38 | - NyllRE
39 |
40 | ## v0.2.5
41 |
42 | [compare changes](https://github.com/NyllRE/nuxt-file-storage/compare/v0.2.4...v0.2.5)
43 |
44 | ## v0.2.2
45 |
46 | [compare changes](https://github.com/NyllRE/nuxt-file-storage/compare/v0.2.1...v0.2.2)
47 |
48 | ## v0.2.1
49 |
50 | [compare changes](https://github.com/NyllRE/nuxt-file-storage/compare/v0.1.4...v0.2.1)
51 |
52 | ## v0.1.4
53 |
54 | [compare changes](https://github.com/NyllRE/nuxt-file-storage/compare/v0.1.3...v0.1.4)
55 |
56 | ## v0.1.3
57 |
58 | [compare changes](https://github.com/NyllRE/nuxt-file-storage/compare/v0.1.2...v0.1.3)
59 |
60 | ## v0.1.2
61 |
62 | [compare changes](https://github.com/NyllRE/nuxt-file-storage/compare/v0.1.1...v0.1.2)
63 |
64 | ## v0.1.1
65 |
66 |
--------------------------------------------------------------------------------
/playground/public/nuxt-storage-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/runtime/server/utils/storage.ts:
--------------------------------------------------------------------------------
1 | import { writeFile, rm, mkdir, readdir } from 'fs/promises'
2 | import { useRuntimeConfig } from '#imports'
3 | import type { ServerFile } from '../../../types'
4 | import { join } from 'path'
5 |
6 | /**
7 | * @description Will store the file in the specified directory
8 | * @param file provide the file object
9 | * @param fileNameOrIdLength you can pass a string or a number, if you enter a string it will be the file name, if you enter a number it will generate a unique ID
10 | * @param filelocation provide the folder you wish to locate the file in
11 | * @returns file name: `${filename}`.`${fileExtension}`
12 | *
13 | *
14 | * [Documentation](https://github.com/NyllRE/nuxt-file-storage#handling-files-in-the-backend)
15 | *
16 | *
17 | * @example
18 | * ```ts
19 | * import { ServerFile } from "nuxt-file-storage";
20 | *
21 | * const { file } = await readBody<{ files: ServerFile }>(event)
22 |
23 | * await storeFileLocally( file, 8, '/userFiles' )
24 | * ```
25 | */
26 | export const storeFileLocally = async (
27 | file: ServerFile,
28 | fileNameOrIdLength: string | number,
29 | filelocation: string = '',
30 | ): Promise => {
31 | const { binaryString, ext } = parseDataUrl(file.content)
32 | const location = useRuntimeConfig().public.fileStorage.mount
33 |
34 | //? Extract the file extension from the original filename
35 | const originalExt = file.name.toString().split('.').pop() || ext
36 |
37 | const filename =
38 | typeof fileNameOrIdLength == 'number'
39 | ? `${generateRandomId(fileNameOrIdLength)}.${originalExt}`
40 | : `${fileNameOrIdLength}.${originalExt}`
41 |
42 | await mkdir(join(location, filelocation), { recursive: true })
43 |
44 | await writeFile(join(location, filelocation, filename), binaryString as any, {
45 | flag: 'w',
46 | })
47 |
48 | return filename
49 | }
50 |
51 | /**
52 | * @description Get file path in the specified directory
53 | * @param filename provide the file name (return of storeFileLocally)
54 | * @param filelocation provide the folder you wish to locate the file in
55 | * @returns file path: `${config.fileStorage.mount}/${filelocation}/${filename}`
56 | */
57 | export const getFileLocally = (filename: string, filelocation: string = ''): string => {
58 | const location = useRuntimeConfig().public.fileStorage.mount
59 | const normalizedFilelocation = filelocation.startsWith('/') ? filelocation.slice(1) : filelocation;
60 | return join(location, normalizedFilelocation, filename)
61 | }
62 |
63 |
64 | /**
65 | * @description Get all files in the specified directory
66 | * @param filelocation provide the folder you wish to locate the file in
67 | * @returns all files in filelocation: `${config.fileStorage.mount}/${filelocation}`
68 | */
69 | export const getFilesLocally = async (filelocation: string = ''): Promise => {
70 | const location = useRuntimeConfig().public.fileStorage.mount
71 | const normalizedFilelocation = filelocation.startsWith('/') ? filelocation.slice(1) : filelocation;
72 | return await readdir(join(location, normalizedFilelocation)).catch(() => [])
73 | }
74 |
75 |
76 | /**
77 | * @param filename the name of the file you want to delete
78 | * @param filelocation the folder where the file is located, if it is in the root folder you can leave it empty, if it is in a subfolder you can pass the name of the subfolder with a preceding slash: `/subfolder`
79 | * @example
80 | * ```ts
81 | * await deleteFile('/userFiles', 'requiredFile.txt')
82 | * ```
83 | */
84 | export const deleteFile = async (filename: string, filelocation: string = '') => {
85 | const location = useRuntimeConfig().public.fileStorage.mount
86 | const normalizedFilelocation = filelocation.startsWith('/') ? filelocation.slice(1) : filelocation;
87 | await rm(join(location, normalizedFilelocation, filename))
88 | }
89 |
90 |
91 | /**
92 | * @description generates a random ID with the specified length
93 | * @param length the length of the random ID
94 | * @returns a random ID with the specified length
95 | */
96 | const generateRandomId = (length: number) => {
97 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
98 | let randomId = ''
99 | for (let i = 0; i < length; i++) {
100 | randomId += characters.charAt(Math.floor(Math.random() * characters.length))
101 | }
102 | return randomId
103 | }
104 |
105 | /**
106 | * @description Parses a data URL and returns an object with the binary data and the file extension.
107 | * @param {string} file - The data URL
108 | * @returns {{binaryString: Buffer, ext: string}} An object with the binary data - file extension
109 | *
110 | * @example
111 | * ```ts
112 | * const { binaryString, ext } = parseDataUrl(file.content)
113 | * ```
114 | */
115 | export const parseDataUrl = (file: string):
116 | {binaryString: Buffer, ext: string} => {
117 | const arr: string[] = file.split(',')
118 | const mimeMatch = arr[0].match(/:(.*?);/)
119 | if (!mimeMatch) {
120 | throw new Error('Invalid data URL')
121 | }
122 | const mime: string = mimeMatch[1]
123 | const base64String: string = arr[1]
124 | const binaryString: Buffer = Buffer.from(base64String, 'base64')
125 |
126 | const ext = mime.split('/')[1]
127 |
128 | return { binaryString, ext }
129 | }
130 |
--------------------------------------------------------------------------------
/playground/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Nuxt
7 | Storage
8 |
9 |
10 |
11 |
12 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
78 |
79 |
80 |
248 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Nuxt File Storage
4 |
5 | [](https://badges.pufler.dev)
6 | [![npm version][npm-version-src]][npm-version-href]
7 | [![npm downloads][npm-downloads-src]][npm-downloads-href]
8 | [![License][license-src]][license-href]
9 | [![Nuxt][nuxt-src]][nuxt-href]
10 |
11 | Easy solution to store files in your nuxt apps. Be able to upload files from the frontend and receive them from the backend to then save the files in your project.
12 |
13 | - [✨ Release Notes](/CHANGELOG.md)
14 | - [🏀 Online playground](https://stackblitz.com/github/NyllRE/nuxt-file-storage?file=playground%2Fapp.vue)
15 |
16 |
17 | ## Features
18 |
19 |
20 |
21 | - 📁 Get files from file input and make them ready to send to backend
22 | - ⚗️ Serialize files in the backend to be able to use them appropriately
23 | - 🖴 Store files in a specified location in your Nuxt backend with Nitro Engine
24 |
25 | ## Quick Setup
26 |
27 | 1. Add `nuxt-file-storage` dependency to your project
28 |
29 | ```bash
30 | # Using pnpm
31 | pnpm add -D nuxt-file-storage
32 |
33 | # Using yarn
34 | yarn add --dev nuxt-file-storage
35 |
36 | # Using npm
37 | npm install --save-dev nuxt-file-storage
38 | ```
39 |
40 | 2. Add `nuxt-file-storage` to the `modules` section of `nuxt.config.ts`
41 |
42 | ```js
43 | export default defineNuxtConfig({
44 | modules: ['nuxt-file-storage'],
45 | })
46 | ```
47 |
48 | That's it! You can now use Nuxt Storage in your Nuxt app ✨
49 |
50 | ## Configuration
51 |
52 | You can currently configure a single setting of the `nuxt-file-storage` module. Here is the config interface:
53 |
54 | ```js
55 | export default defineNuxtConfig({
56 | modules: ['nuxt-file-storage'],
57 | fileStorage: {
58 | // enter the absolute path to the location of your storage
59 | mount: '/home/$USR/development/nuxt-file-storage/server/files',
60 |
61 | // {OR} use environment variables (recommended)
62 | mount: process.env.mount
63 | // you need to set the mount in your .env file at the root of your project
64 | },
65 | })
66 | ```
67 |
68 | ## Usage
69 |
70 | ### Handling Files in the frontend
71 | You can use Nuxt Storage to get the files from the ` ` tag:
72 |
73 | ```html
74 |
75 |
76 |
77 |
78 |
83 | ```
84 | The `files` return a ref object that contains the files
85 |
86 | > `handleFileInput` returns a promise in case you need to check if the file input has concluded
87 |
88 |
89 |
90 | Here's an example of using files to send them to the backend
91 | ```html
92 |
93 |
94 | submit
95 |
96 |
97 |
109 | ```
110 |
111 |
112 | #### Handling multiple file input fields
113 | You have to create a new instance of `useFileStorage` for each input field
114 |
115 |
116 | ```html
117 |
118 | ← | 1 |
119 | ← | 2 |
120 |
121 |
122 |
130 | ```
131 | by calling a new `useFileStorage` instance you separate the internal logic between the inputs
132 |
133 | ### Handling files in the backend
134 | using Nitro Server Engine, we will make an api route that receives the files and stores them in the folder `userFiles`
135 | ```ts
136 | import { ServerFile } from "nuxt-file-storage";
137 |
138 | export default defineEventHandler(async (event) => {
139 | const { files } = await readBody<{ files: ServerFile[] }>(event)
140 |
141 | for ( const file of files ) {
142 | await storeFileLocally(
143 | file, // the file object
144 | 8, // you can add a name for the file or length of Unique ID that will be automatically generated!
145 | '/userFiles' // the folder the file will be stored in
146 | )
147 |
148 | // {OR}
149 |
150 | // Parses a data URL and returns an object with the binary data and the file extension.
151 | const { binaryString, ext } = parseDataUrl(file.content)
152 | }
153 |
154 | // Deleting Files
155 | await deleteFile('requiredFile.txt', '/userFiles')
156 |
157 | // Get file path
158 | await getFileLocally('requiredFile.txt', '/userFiles')
159 | // returns: {AbsolutePath}/userFiles/requiredFile.txt
160 |
161 | // Get all files in a folder
162 | await getFilesLocally('/userFiles')
163 | })
164 | ```
165 |
166 | And that's it! Now you can store any file in your nuxt project from the user ✨
167 |
168 | ## Contribution
169 | Run into a problem? Open a [new issue](https://github.com/NyllRE/nuxt-file-storage/issues/new). I'll try my best to include all the features requested if it is fitting to the scope of the project.
170 |
171 | Want to add some feature? PRs are welcome!
172 | - Clone this repository
173 | - install the dependencies
174 | - prepare the project
175 | - run dev server
176 | ```bash
177 | git clone https://github.com/NyllRE/nuxt-file-storage && cd nuxt-file-storage
178 | npm i
179 | npm run dev:prepare
180 | npm run dev
181 | ```
182 |
183 |
184 |
185 |
186 | [npm-version-src]: https://img.shields.io/npm/v/nuxt-file-storage/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
187 | [npm-version-href]: https://npmjs.com/package/nuxt-file-storage
188 | [npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-file-storage.svg?style=flat&colorA=18181B&colorB=28CF8D
189 | [npm-downloads-href]: https://npmjs.com/package/nuxt-file-storage
190 | [license-src]: https://img.shields.io/npm/l/nuxt-file-storage.svg?style=flat&colorA=18181B&colorB=28CF8D
191 | [license-href]: https://npmjs.com/package/nuxt-file-storage
192 | [nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js
193 | [nuxt-href]: https://nuxt.com/modules/nuxt-file-storage
194 |
--------------------------------------------------------------------------------
/playground/public/nuxt-file-storage-banner.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------