Blob Storage
Getting Started
Enable the blob storage in your NuxtHub project by adding the blob
property to the hub
object in your nuxt.config.ts
file.
export default defineNuxtConfig({
hub: {
blob: true
}
})
hubBlob()
Server composable that returns a set of methods to manipulate the blob storage.
list()
Returns a paginated list of blobs.
export default eventHandler(async () => {
const { blobs } = await hubBlob().list({ limit: 10 })
return blobs
})
Params
The list options.
Return
Returns BlobListResult
.
serve()
Returns a blob's data.
export default eventHandler(async (event) => {
const { pathname } = getRouterParams(event)
return hubBlob().serve(event, pathname)
})
Params
Handler's event, needed to set headers.
The name of the blob to serve.
Return
Returns the blob's raw data and sets Content-Type
and Content-Length
headers.
head()
Returns a blob's metadata.
const blob = await hubBlob().head(pathname)
Params
The name of the blob to serve.
Return
Returns a BlobObject
.
put()
Uploads a blob to the storage.
export default eventHandler(async (event) => {
const form = await readFormData(event)
const file = form.get('file') as File
if (!file || !file.size) {
throw createError({ statusCode: 400, message: 'No file provided' })
}
ensureBlob(file, {
maxSize: '1MB',
types: ['image']
})
return hubBlob().put(file.name, file, {
addRandomSuffix: false,
prefix: 'images'
})
})
See an example on the Vue side:
<script setup lang="ts">
async function uploadImage (e: Event) {
const form = e.target as HTMLFormElement
await $fetch('/api/files', {
method: 'POST',
body: new FormData(form)
}).catch((err) => alert('Failed to upload image:\n'+ err.data?.message))
form.reset()
}
</script>
<template>
<form @submit.prevent="uploadImage">
<label>Upload an image: <input type="file" name="image"></label>
<button type="submit">
Upload
</button>
</form>
</template>
Params
The name of the blob to serve.
The blob's data.
The put options. Any other provided field will be stored in the blob's metadata.
Return
Returns a BlobObject
.
del()
Delete a blob with its pathname.
export default eventHandler(async (event) => {
const { pathname } = getRouterParams(event)
await hubBlob().del(pathname)
return sendNoContent(event)
})
You can also delete multiple blobs at once by providing an array of pathnames:
await hubBlob().del(['images/1.jpg', 'images/2.jpg'])
delete()
method as alias of del()
.Params
The name of the blob to serve.
Return
Returns nothing.
handleUpload()
This is an "all in one" function to validate a Blob
by checking its size and type and upload it to the storage.
useUpload()
Vue composable.It can be used to handle file uploads in API routes.
export default eventHandler(async (event) => {
return hubBlob().handleUpload(event, {
formKey: 'files', // read file or files form the `formKey` field of request body (body should be a `FormData` object)
multiple: true, // when `true`, the `formKey` field will be an array of `Blob` objects
ensure: {
contentType: ['image/jpeg', 'images/png'], // allowed types of the file
},
put: {
addRandomSuffix: true
}
})
})
Params
The form key to read the file from. Defaults to 'files'
.
When true
, the formKey
field will be an array of Blob
objects.
See ensureBlob()
options for more details.
See put()
options for more details.
Return
Returns a BlobObject
or an array of BlobObject
if multiple
is true
.
Throws an error if file
doesn't meet the requirements.
handleMultipartUpload()
Handle the request to support multipart upload.
export default eventHandler(async (event) => {
return await hubBlob().handleMultipartUpload(event)
})
[action]
and [...pathname]
params.On the client side, you can use the useMultipartUpload()
composable to upload a file in parts.
<script setup lang="ts">
async function uploadFile(file: File) {
const upload = useMultipartUpload('/api/files/multipart')
const { progress, completed, abort } = upload(file)
}
</script>
useMultipartUpload()
on usage details.Params
The content type of the blob.
The content length of the blob.
If true
, a random suffix will be added to the blob's name. Defaults to false
.
createMultipartUpload()
handleMultipartUpload()
method to handle the multipart upload request.Start a new multipart upload.
export default eventHandler(async (event) => {
const { pathname } = getRouterParams(event)
const mpu = await hubBlob().createMultipartUpload(pathname)
return {
uploadId: mpu.uploadId,
pathname: mpu.pathname,
}
})
Params
The name of the blob to serve.
The put options. Any other provided field will be stored in the blob's metadata.
Return
Returns a BlobMultipartUpload
resumeMultipartUpload()
handleMultipartUpload()
method to handle the multipart upload request.Continue processing of unfinished multipart upload.
To upload a part of the multipart upload, you can use the uploadPart()
method:
export default eventHandler(async (event) => {
const { pathname } = getRouterParams(event)
const { uploadId, partNumber } = getQuery(event)
const stream = getRequestWebStream(event)!
const body = await streamToArrayBuffer(stream, contentLength)
const mpu = hubBlob().resumeMultipartUpload(pathname, uploadId)
return await mpu.uploadPart(partNumber, body)
})
Complete the upload by calling complete()
method:
export default eventHandler(async (event) => {
const { pathname, uploadId } = getQuery(event)
const parts = await readBody(event)
const mpu = hubBlob().resumeMultipartUpload(pathname, uploadId)
return await mpu.complete(parts)
})
If you want to cancel the upload, you need to call abort()
method:
export default eventHandler(async (event) => {
const { pathname } = getRouterParams(event)
const { uploadId } = getQuery(event)
const mpu = hubBlob().resumeMultipartUpload(pathname, uploadId)
await mpu.abort()
return sendNoContent(event)
})
A simple example of multipart upload in client with above routes:
async function uploadLargeFile(file: File) {
const chunkSize = 10 * 1024 * 1024 // 10MB
const count = Math.ceil(file.size / chunkSize)
const { pathname, uploadId } = await $fetch(
`/api/files/multipart/${file.name}`,
{ method: 'POST' },
)
const uploaded = []
for (let i = 0; i < count; i++) {
const start = i * chunkSize
const end = Math.min(start + chunkSize, file.size)
const partNumber = i + 1
const chunk = file.slice(start, end)
const part = await $fetch(
`/api/files/multipart/${pathname}`,
{
method: 'PUT',
query: { uploadId, partNumber },
body: chunk,
},
)
uploaded.push(part)
}
return await $fetch(
'/api/files/multipart/complete',
{
method: 'POST',
query: { pathname, uploadId },
body: { parts: uploaded },
},
)
}
Params
The name of the blob to serve.
The upload ID of the multipart upload.
Return
Returns a BlobMultipartUpload
Params
The event to handle.
ensureBlob()
ensureBlob()
is a handy util to validate a Blob
by checking its size and type:
// Will throw an error if the file is not an image or is larger than 1MB
ensureBlob(file, { maxSize: '1MB', types: ['image' ]})
Params
The file to validate.
Note that at least maxSize
or types
should be provided.
Return
Returns nothing.
Throws an error if file
doesn't meet the requirements.
Vue Composables
server/
directory).useUpload()
useUpload
is to handle file uploads in your Nuxt application.
<script setup lang="ts">
const upload = useUpload('/api/blob', { method: 'PUT' })
async function onFileSelect({ target }: Event) {
const uploadedFiles = await upload(target as HTMLInputElement)
// file uploaded successfully
}
</script>
<template>
<input
accept="jpeg, png"
type="file"
name="file"
multiple
@change="onFileSelect"
>
</template>
Params
The base URL of the upload API.
Optionally, you can pass Fetch options to the request. Read more about Fetch API here.
Return
Return a MultipartUpload
function that can be used to upload a file in parts.
const { completed, progress, abort } = upload(file)
const data = await completed
useMultipartUpload()
Application composable that creates a multipart upload helper.
export const mpu = useMultipartUpload('/api/files/multipart')
Params
The base URL of the multipart upload API handled by handleMultipartUpload()
.
The options for the multipart upload helper.
Return
Return a MultipartUpload
function that can be used to upload a file in parts.
const { completed, progress, abort } = mpu(file)
const data = await completed
Types
BlobObject
interface BlobObject {
pathname: string
contentType: string | undefined
size: number
uploadedAt: Date,
customMetadata?: Record<string, string> | undefined
}
BlobMultipartUpload
export interface BlobMultipartUpload {
pathname: string
uploadId: string
uploadPart(
partNumber: number,
value: string | ReadableStream<any> | ArrayBuffer | ArrayBufferView | Blob
): Promise<BlobUploadedPart>
abort(): Promise<void>
complete(uploadedParts: BlobUploadedPart[]): Promise<BlobObject>
}
BlobUploadedPart
export interface BlobUploadedPart {
partNumber: number;
etag: string;
}
MultipartUploader
export type MultipartUploader = (file: File) => {
completed: Promise<SerializeObject<BlobObject> | undefined>
progress: Readonly<Ref<number>>
abort: () => Promise<void>
}
BlobListResult
interface BlobListResult {
blobs: BlobObject[]
hasMore: boolean
cursor?: string
folders?: string[]
}
Examples
List blobs with pagination
export default eventHandler(async (event) => {
const { limit, cursor } = await getQuery(event)
return hubBlob().list({
limit: limit ? Number.parseInt(limit) : 10,
cursor: cursor ? cursor : undefined
})
})