Storage ClientVue / Nuxt
Vue / Nuxt
Use the storage client with Vue 3 and Nuxt
Last updated:
Vue / Nuxt
Use @visulima/storage-client with Vue 3 and Nuxt 3 for file uploads with composables and TanStack Vue Query integration.
Installation
Vue 3
npm install @visulima/storage-client @tanstack/vue-query vueNuxt 3
npm install @visulima/storage-client @tanstack/vue-querySetup QueryClient
Vue 3
import { createApp } from "vue";
import { VueQueryPlugin } from "@tanstack/vue-query";
import App from "./App.vue";
const app = createApp(App);
app.use(VueQueryPlugin);
app.mount("#app");Nuxt 3
Create a plugin file:
// plugins/vue-query.client.ts
import { VueQueryPlugin } from "@tanstack/vue-query";
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(VueQueryPlugin);
});Basic Upload
Use the useUpload composable for automatic method selection:
<template>
<div class="app">
<h1>Storage Client - Vue Example</h1>
<div class="upload-section">
<input type="file" @change="handleFileChange" :disabled="isUploading" ref="fileInputRef" />
<button @click="handleUpload" :disabled="!file || isUploading">
{{ isUploading ? "Uploading..." : "Upload" }}
</button>
</div>
<div v-if="isUploading" class="progress-section">
<div>Progress: {{ progress }}%</div>
<progress :value="progress" max="100" />
</div>
<div v-if="error" class="error">Error: {{ error.message }}</div>
<div v-if="result" class="success">Upload complete! File: {{ result.filename }}</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { useUpload } from "@visulima/storage-client/vue";
const file = ref<File | null>(null);
const fileInputRef = ref<HTMLInputElement | null>(null);
const { upload, progress, isUploading, error, result } = useUpload({
endpointMultipart: "/api/upload/multipart",
endpointTus: "/api/upload/tus",
onSuccess: (result) => {
console.log("Upload successful:", result);
file.value = null;
if (fileInputRef.value) {
fileInputRef.value.value = "";
}
},
onError: (error_) => {
console.error("Upload error:", error_);
},
});
const handleFileChange = (e: Event) => {
const target = e.target as HTMLInputElement;
const selectedFile = target.files?.[0];
file.value = selectedFile || null;
};
const handleUpload = async () => {
if (file.value) {
try {
await upload(file.value);
} catch (error_) {
console.error("Upload failed:", error_);
}
}
};
</script>Multipart Upload
For traditional form-based uploads:
<script setup lang="ts">
import { useMultipartUpload } from "@visulima/storage-client/vue";
const { upload, progress, isUploading } = useMultipartUpload({
endpoint: "/api/upload/multipart",
});
const handleFileChange = async (e: Event) => {
const target = e.target as HTMLInputElement;
const file = target.files?.[0];
if (file) {
await upload(file);
}
};
</script>
<template>
<div>
<input type="file" @change="handleFileChange" />
<div v-if="isUploading">
<progress :value="progress" max="100" />
</div>
</div>
</template>TUS Resumable Upload
For large files with pause/resume support:
<script setup lang="ts">
import { useTusUpload } from "@visulima/storage-client/vue";
const { upload, pause, resume, progress, isUploading, isPaused } = useTusUpload({
endpoint: "/api/upload/tus",
});
const handleFileChange = async (e: Event) => {
const target = e.target as HTMLInputElement;
const file = target.files?.[0];
if (file) {
await upload(file);
}
};
</script>
<template>
<div>
<input type="file" @change="handleFileChange" />
<div v-if="isUploading">
<progress :value="progress" max="100" />
<button v-if="isPaused" @click="resume">Resume</button>
<button v-else @click="pause">Pause</button>
</div>
</div>
</template>Batch Upload
Upload multiple files simultaneously:
<script setup lang="ts">
import { useBatchUpload } from "@visulima/storage-client/vue";
const { uploadBatch, progress, isUploading, items, completedCount, errorCount } = useBatchUpload({
endpoint: "/api/upload/multipart",
onSuccess: (results) => {
console.log("Batch upload complete:", results);
},
});
const handleFilesChange = (e: Event) => {
const target = e.target as HTMLInputElement;
const files = Array.from(target.files || []);
if (files.length > 0) {
uploadBatch(files);
}
};
</script>
<template>
<div>
<input type="file" multiple @change="handleFilesChange" />
<div v-if="isUploading">
<div>Progress: {{ progress }}%</div>
<div>Completed: {{ completedCount }} / {{ items.length }}</div>
<div>Errors: {{ errorCount }}</div>
</div>
</div>
</template>File Input Composable
Use the useFileInput composable for drag & drop support:
<script setup lang="ts">
import { ref } from "vue";
import { useFileInput } from "@visulima/storage-client/vue";
import { useBatchUpload } from "@visulima/storage-client/vue";
const { files, inputRef, handleFileChange, handleDrop, handleDragOver, openFileDialog } = useFileInput({
multiple: true,
});
const { uploadBatch } = useBatchUpload({
endpoint: "/api/upload/multipart",
});
const handleUpload = () => {
if (files.value.length > 0) {
uploadBatch(files.value);
}
};
</script>
<template>
<div @drop="handleDrop" @dragover="handleDragOver" style="border: 2px dashed #ccc; padding: 2rem; text-align: center">
<input ref="inputRef" type="file" multiple @change="handleFileChange" style="display: none" />
<p>Drag and drop files here or</p>
<button @click="openFileDialog">Select Files</button>
<div v-if="files.length > 0">
<p>Selected: {{ files.length }} files</p>
<button @click="handleUpload">Upload</button>
</div>
</div>
</template>File Operations
Get File
<script setup lang="ts">
import { ref, watch } from "vue";
import { useGetFile } from "@visulima/storage-client/vue";
const props = defineProps<{ fileId: string }>();
const { data, isLoading, error, meta } = useGetFile({
endpoint: "/api/files",
id: () => props.fileId,
});
const url = ref<string | null>(null);
watch(data, (blob) => {
if (blob) {
url.value = URL.createObjectURL(blob);
}
});
</script>
<template>
<div v-if="isLoading">Loading...</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<div v-else-if="data && url">
<img :src="url" :alt="meta?.filename || 'File'" />
<p v-if="meta">Filename: {{ meta.filename }}</p>
</div>
</template>Delete File
<script setup lang="ts">
import { useDeleteFile } from "@visulima/storage-client/vue";
const props = defineProps<{ fileId: string }>();
const { deleteFile, isLoading } = useDeleteFile({
endpoint: "/api/files",
});
</script>
<template>
<button @click="() => deleteFile(props.fileId)" :disabled="isLoading">
{{ isLoading ? "Deleting..." : "Delete" }}
</button>
</template>Get File Metadata
<script setup lang="ts">
import { useGetFileMeta } from "@visulima/storage-client/vue";
const props = defineProps<{ fileId: string }>();
const {
data: meta,
isLoading,
error,
} = useGetFileMeta({
endpoint: "/api/files",
id: () => props.fileId,
});
</script>
<template>
<div v-if="isLoading">Loading...</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<div v-else-if="meta">
<p>Filename: {{ meta.filename }}</p>
<p>Size: {{ meta.size }} bytes</p>
<p>Content Type: {{ meta.contentType }}</p>
<p v-if="meta.url">URL: {{ meta.url }}</p>
</div>
</template>Head File (Check Upload Status)
<script setup lang="ts">
import { useHeadFile } from "@visulima/storage-client/vue";
const props = defineProps<{ fileId: string }>();
const { data, isLoading } = useHeadFile({
endpoint: "/api/files",
id: () => props.fileId,
});
</script>
<template>
<div v-if="isLoading">Checking status...</div>
<div v-else-if="data">
<p v-if="data.uploadComplete">Upload complete</p>
<p v-else>Upload in progress: {{ data.uploadOffset }} / {{ data.contentLength }} bytes</p>
<p v-if="data.receivedChunks">Received chunks: {{ data.receivedChunks.length }}</p>
</div>
</template>Put File (Create/Update)
<script setup lang="ts">
import { ref } from "vue";
import { usePutFile } from "@visulima/storage-client/vue";
const props = defineProps<{ fileId: string }>();
const file = ref<File | null>(null);
const { putFile, progress, isLoading, error, data } = usePutFile({
endpoint: "/api/files",
onSuccess: (result) => {
console.log("File uploaded:", result);
},
});
const handleFileChange = (e: Event) => {
const target = e.target as HTMLInputElement;
file.value = target.files?.[0] || null;
};
const handleUpload = async () => {
if (file.value) {
await putFile(props.fileId, file.value);
}
};
</script>
<template>
<div>
<input type="file" @change="handleFileChange" />
<button @click="handleUpload" :disabled="!file || isLoading">
{{ isLoading ? `Uploading... ${progress}%` : "Upload" }}
</button>
<div v-if="error">Error: {{ error.message }}</div>
<div v-if="data">Upload complete! ID: {{ data.id }}</div>
</div>
</template>Batch Delete Files
<script setup lang="ts">
import { useBatchDeleteFiles } from "@visulima/storage-client/vue";
const { batchDeleteFiles, isLoading } = useBatchDeleteFiles({
endpoint: "/api/files",
onSuccess: (result) => {
console.log(`Deleted ${result.successful} files`);
if (result.failed) {
console.log(`${result.failed} files failed to delete`);
}
},
});
const handleDelete = async () => {
const fileIds = ["file1", "file2", "file3"];
await batchDeleteFiles(fileIds);
};
</script>
<template>
<button @click="handleDelete" :disabled="isLoading">
{{ isLoading ? "Deleting..." : "Delete Selected Files" }}
</button>
</template>Transform Operations
Transform File
<script setup lang="ts">
import { ref, watch } from "vue";
import { useTransformFile } from "@visulima/storage-client/vue";
const props = defineProps<{ fileId: string }>();
const transform = ref({ width: 800, height: 600, quality: 85 });
const { data, isLoading, error, meta } = useTransformFile({
endpoint: "/api/files",
id: () => props.fileId,
transform: () => transform.value,
});
const url = ref<string | null>(null);
watch(data, (blob) => {
if (blob) {
url.value = URL.createObjectURL(blob);
}
});
</script>
<template>
<div v-if="isLoading">Transforming...</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<div v-else-if="data && url">
<img :src="url" alt="Transformed" />
<div>
<label>
Width:
<input type="number" :value="transform.width" @input="transform.width = Number(($event.target as HTMLInputElement).value)" />
</label>
<label>
Height:
<input type="number" :value="transform.height" @input="transform.height = Number(($event.target as HTMLInputElement).value)" />
</label>
<label>
Quality:
<input
type="number"
min="1"
max="100"
:value="transform.quality"
@input="transform.quality = Number(($event.target as HTMLInputElement).value)"
/>
</label>
</div>
</div>
</template>Get Transform Metadata
<script setup lang="ts">
import { useTransformMetadata } from "@visulima/storage-client/vue";
const { data, isLoading, error } = useTransformMetadata({
endpoint: "/api/files",
});
</script>
<template>
<div v-if="isLoading">Loading...</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<div v-else-if="data">
<h3>Available Formats</h3>
<ul>
<li v-for="format in data.formats" :key="format">{{ format }}</li>
</ul>
<h3>Available Parameters</h3>
<ul>
<li v-for="param in data.parameters" :key="param">{{ param }}</li>
</ul>
</div>
</template>Abort Operations
Abort All Uploads
<script setup lang="ts">
import { useAbortAll } from "@visulima/storage-client/vue";
const { abortAll } = useAbortAll({
endpoint: "/api/upload/multipart",
});
</script>
<template>
<button @click="abortAll">Abort All Uploads</button>
</template>Abort Batch
<script setup lang="ts">
import { useAbortBatch } from "@visulima/storage-client/vue";
const props = defineProps<{ batchId: string }>();
const { abortBatch } = useAbortBatch({
endpoint: "/api/upload/multipart",
});
</script>
<template>
<button @click="() => abortBatch(props.batchId)">Abort Batch</button>
</template>Abort Item
<script setup lang="ts">
import { useAbortItem } from "@visulima/storage-client/vue";
const props = defineProps<{ itemId: string }>();
const { abortItem } = useAbortItem({
endpoint: "/api/upload/multipart",
});
</script>
<template>
<button @click="() => abortItem(props.itemId)">Abort Upload</button>
</template>Retry Operations
Retry Failed Upload
<script setup lang="ts">
import { useRetry } from "@visulima/storage-client/vue";
const props = defineProps<{ itemId: string }>();
const { retryItem } = useRetry({
endpoint: "/api/upload/multipart",
});
</script>
<template>
<button @click="() => retryItem(props.itemId)">Retry Upload</button>
</template>Batch Retry
<script setup lang="ts">
import { useBatchRetry } from "@visulima/storage-client/vue";
const props = defineProps<{ batchId: string }>();
const { retryBatch } = useBatchRetry({
endpoint: "/api/upload/multipart",
});
</script>
<template>
<button @click="() => retryBatch(props.batchId)">Retry Failed Items in Batch</button>
</template>Nuxt-Specific Features
Server-Side Setup
In Nuxt, you can set up upload endpoints using server routes:
// server/api/upload/multipart.post.ts
import { DiskStorage } from "@visulima/storage";
import { Multipart } from "@visulima/storage/handler/http/node";
const storage = new DiskStorage({ directory: "./uploads" });
const multipart = new Multipart({ storage });
export default defineEventHandler(async (event) => {
event.node.res.setHeader("Access-Control-Allow-Origin", "*");
await multipart.handle(event.node.req, event.node.res);
});Using with Nuxt Module
If you're using the @visulima/storage Nuxt module, endpoints are automatically registered:
// nuxt.config.ts
import { DiskStorage } from "@visulima/storage";
import storageModule from "@visulima/storage/adapter/nuxt";
export default defineNuxtConfig({
modules: [
[
storageModule,
{
storage: new DiskStorage({
directory: "./uploads",
}),
},
],
],
});Then use the client in your components:
<script setup lang="ts">
import { useUpload } from "@visulima/storage-client/vue";
const { upload, progress, isUploading } = useUpload({
endpointMultipart: "/api/upload/multipart",
endpointTus: "/api/upload/tus",
});
</script>Event Listeners
Batch Event Listeners
Listen to batch upload events:
<script setup lang="ts">
import { useBatchStartListener, useBatchProgressListener, useBatchFinishListener, useBatchErrorListener } from "@visulima/storage-client/vue";
useBatchStartListener({
endpoint: "/api/upload/multipart",
onBatchStart: (batchId) => {
console.log("Batch started:", batchId);
},
});
useBatchProgressListener({
endpoint: "/api/upload/multipart",
onBatchProgress: (progress, batchId) => {
console.log(`Batch ${batchId} progress: ${progress}%`);
},
});
useBatchFinishListener({
endpoint: "/api/upload/multipart",
onBatchFinish: (results, batchId) => {
console.log(`Batch ${batchId} finished:`, results);
},
});
useBatchErrorListener({
endpoint: "/api/upload/multipart",
onBatchError: (error, batchId) => {
console.error(`Batch ${batchId} error:`, error);
},
});
</script>
<template>
<div>Upload component with listeners</div>
</template>Retry Listener
<script setup lang="ts">
import { useRetryListener } from "@visulima/storage-client/vue";
useRetryListener({
endpoint: "/api/upload/multipart",
onRetry: (itemId) => {
console.log("Retrying item:", itemId);
},
});
</script>
<template>
<!-- This is just a listener -->
</template>All Abort Listener
<script setup lang="ts">
import { useAllAbortListener } from "@visulima/storage-client/vue";
useAllAbortListener({
endpoint: "/api/upload/multipart",
onAbortAll: () => {
console.log("All uploads aborted");
},
});
</script>
<template>
<!-- This is just a listener -->
</template>Next Steps
- React Guide - Compare with React implementation
- API Reference - Complete API documentation