Skip to main content

Quickstart Guide

This guide will walk you through creating a complete file upload system with Nuxt File Storage. You’ll build a simple page that allows users to select files and upload them to your server.

Complete Example

Follow these steps to implement file uploads in your Nuxt application:
1

Create the Frontend Component

Create a Vue component with a file input and upload button. Use the useFileStorage composable to handle file selection.
app.vue
<template>
  <div>
    <h1>Upload Files</h1>
    
    <input 
      type="file" 
      multiple 
      @input="handleFileInput" 
    />
    
    <button @click="submit">Upload</button>
    
    <p v-if="uploadStatus">{{ uploadStatus }}</p>
    
    <!-- Preview selected files -->
    <div v-if="files.length > 0">
      <h2>Selected Files:</h2>
      <div v-for="file in files" :key="file.name">
        <img 
          v-if="file.type.startsWith('image/')" 
          :src="file.content" 
          :alt="file.name" 
          style="max-width: 200px; margin: 10px;"
        />
        <p>{{ file.name }} ({{ (file.size / 1024).toFixed(2) }} KB)</p>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
// Initialize the file storage composable
// clearOldFiles: true (default) clears previous files when new ones are selected
const { handleFileInput, files } = useFileStorage({ clearOldFiles: true })

const uploadStatus = ref('')

const submit = async () => {
  try {
    const response = await $fetch('/api/files', {
      method: 'POST',
      body: {
        files: files.value,
      },
    })
    
    uploadStatus.value = `Successfully uploaded ${response.length} file(s)!`
  } catch (error) {
    uploadStatus.value = 'Upload failed. Please try again.'
  }
}
</script>
The handleFileInput function automatically serializes files to base64 data URLs with metadata. The files ref contains an array of ClientFile objects ready to send to your backend.
2

Create the API Route

Create a server API route to receive and store the uploaded files.
server/api/files.ts
import { ServerFile } from "#file-storage/types"

export default defineEventHandler(async (event) => {
  // Read the uploaded files from the request body
  const { files } = await readBody<{ files: ServerFile[] }>(event)
  
  const fileNames: string[] = []
  
  // Store each file
  for (const file of files) {
    // Store the file with a custom name in a specific folder
    const fileName = await storeFileLocally(
      file,                // The file object
      8,                   // Generate a random 8-character ID as filename
      '/uploads'           // Store in the 'uploads' folder within your mount point
    )
    
    fileNames.push(fileName)
  }
  
  // Return the stored file names
  return fileNames
})
The second parameter of storeFileLocally can be either:
  • A number to generate a random ID of that length (e.g., 8aB3dE7fG.jpg)
  • A string to use as the filename (e.g., 'profile-picture'profile-picture.jpg)
The file extension is automatically added based on the file’s MIME type.
3

Configure Storage Location

Make sure you’ve configured the storage mount point in your nuxt.config.ts:
nuxt.config.ts
export default defineNuxtConfig({
  modules: ['nuxt-file-storage'],
  fileStorage: {
    mount: process.env.FILE_STORAGE_MOUNT || './server/files',
  },
})
Files will be stored at: {mount}/uploads/{filename}
4

Test Your Implementation

  1. Start your development server: npm run dev
  2. Navigate to your page
  3. Select one or more files
  4. Click the upload button
  5. Files will be stored in your configured directory!

Understanding the Flow

Here’s what happens when a user uploads a file:
  1. User selects files - The <input type="file"> triggers the @input event
  2. Serialization - handleFileInput reads each file and converts it to a base64 data URL
  3. Client state - The files ref is updated with ClientFile objects containing:
    • name - Original filename
    • content - Base64 data URL
    • size - File size in bytes
    • type - MIME type
    • lastModified - Last modified timestamp
  4. Transmission - Files are sent to the API route via $fetch
  5. Backend processing - The API route receives ServerFile objects (same structure as ClientFile)
  6. Storage - storeFileLocally decodes the base64 content and writes it to disk
  7. Path validation - Built-in security checks prevent path traversal attacks

Handling Multiple File Inputs

If you have multiple file input fields on the same page, create a separate instance of useFileStorage for each:
<template>
  <div>
    <!-- Profile picture input -->
    <input type="file" @input="handleProfileInput" />
    
    <!-- Document upload input -->
    <input type="file" multiple @input="handleDocumentInput" />
  </div>
</template>

<script setup lang="ts">
// Separate instance for profile picture
const { 
  handleFileInput: handleProfileInput, 
  files: profileFiles 
} = useFileStorage()

// Separate instance for documents
const { 
  handleFileInput: handleDocumentInput, 
  files: documentFiles 
} = useFileStorage()
</script>

Advanced Storage Options

You can customize how files are stored:
server/api/files.ts
import { ServerFile } from "#file-storage/types"

export default defineEventHandler(async (event) => {
  const { files } = await readBody<{ files: ServerFile[] }>(event)
  
  for (const file of files) {
    // Option 1: Use a specific filename
    await storeFileLocally(file, 'profile-avatar', '/users/avatars')
    // Result: {mount}/users/avatars/profile-avatar.jpg
    
    // Option 2: Generate a random ID
    await storeFileLocally(file, 12, '/documents')
    // Result: {mount}/documents/aB3dE7fG9hIj.pdf
    
    // Option 3: Parse the data URL manually
    const { binaryString, ext } = parseDataUrl(file.content)
    // Use binaryString and ext for custom processing
  }
})
All file paths are validated to prevent directory traversal attacks. Attempting to use paths like ../ will throw an error.

Next Steps

Now that you have file uploads working, explore more features:

Build docs developers (and LLMs) love