Blob Storage
Getting Started
Enable blob storage in your project by setting blob: true
in the NuxtHub config.
export default defineNuxtConfig({
hub: {
blob: true
}
})
Automatic Configuration
When building the Nuxt app, NuxtHub automatically configures the blob storage driver on many providers.
When deploying to Vercel, Nitro Storage blob
is configured for Vercel Blob Storage.
- Install the
@vercel/blob
package
pnpm add @vercel/blob
yarn add @vercel/blob
npm install @vercel/blob
bun add @vercel/blob
deno add npm:@vercel/blob
npx nypm add @vercel/blob
- Assign a Vercel Blob Store to your project from the Vercel dashboard -> Project -> Storage
When deploying to Cloudflare, Nitro Storage blob
is configured for Cloudflare R2.
Add a BLOB
binding to a Cloudflare R2 bucket in your wrangler.jsonc
config.
{
"$schema": "node_modules/wrangler/config-schema.json",
// ...
"r2_buckets": [
{
"binding": "BLOB",
"bucket_name": "<bucket_name>"
}
]
}
Learn more about adding bindings on Cloudflare's documentation.
When deploying to Netlify, Nitro Storage blob
is configured for Netlify Blobs.
- Install the
@netlify/blobs
package
pnpm add @netlify/blobs
yarn add @netlify/blobs
npm install @netlify/blobs
bun add @netlify/blobs
deno add npm:@netlify/blobs
npx nypm add @netlify/blobs
- Set the
NETLIFY_BLOB_STORE_NAME
environment variable to configure your blob store name
When deploying to Azure Functions, Nitro Storage blob
is configured for Azure Blob Storage.
- Install the
@azure/app-configuration
and@azure/identity
packages
pnpm add @azure/app-configuration @azure/identity
yarn add @azure/app-configuration @azure/identity
npm install @azure/app-configuration @azure/identity
bun add @azure/app-configuration @azure/identity
deno add npm:@azure/app-configuration @azure/identity
npx nypm add @azure/app-configuration @azure/identity
- Set the
AZURE_BLOB_ACCOUNT_NAME
environment variable to configure your storage account. - Create a new Managed Identity with the Storage Blob Data Contributor role assigned to it.
When deploying to AWS Lambda or AWS Amplify, Nitro Storage blob
is configured for Amazon S3.
- Install the
aws4fetch
package
pnpm add aws4fetch
yarn add aws4fetch
npm install aws4fetch
bun add aws4fetch
deno add npm:aws4fetch
npx nypm add aws4fetch
- Set the following environment variables:
S3_ACCESS_KEY_ID
S3_SECRET_ACCESS_KEY
S3_BUCKET
S3_REGION
S3_ENDPOINT
(optional)
When deploying to DigitalOcean, Nitro Storage blob
is configured for DigitalOcean Spaces.
- Install the
aws4fetch
package
pnpm add aws4fetch
yarn add aws4fetch
npm install aws4fetch
bun add aws4fetch
deno add npm:aws4fetch
npx nypm add aws4fetch
- Set the following environment variables:
SPACES_KEY
SPACES_SECRET
SPACES_BUCKET
SPACES_REGION
When deploying to other providers, Nitro Storage blob
is configured to use the filesystem.
Manual Configuration
If you need to apply changes to automatic configuration, or would like to use a different storage driver, you can manually configure the blob
mount within your Nitro Storage configuration.
blob
mount in Nitro Storage overrides automatic configuration.export default defineNuxtConfig({
nitro: {
storage: {
blob: {
driver: 's3',
accessKeyId: 'your-access-key-id',
secretAccessKey: 'your-secret-access-key',
bucket: 'your-bucket-name',
region: 'your-region'
/* any additional driver options */
}
}
},
hub: {
blob: true,
},
})
Local Development
NuxtHub uses the filesystem during local development. You can modify this behaviour by specifying a different development storage driver.
export default defineNuxtConfig({
nitro: {
devStorage: {
blob: {
driver: 's3',
accessKeyId: 'your-access-key-id',
secretAccessKey: 'your-secret-access-key',
bucket: 'your-bucket-name',
region: 'your-region'
}
}
},
})
hubBlob()
Server composable that returns a set of methods to manipulate the blob storage.
list()
Returns a paginated list of blobs (metadata only).
export default eventHandler(async () => {
const { blobs } = await hubBlob().list({ limit: 10 })
return blobs
})
Params
1000
.true
, the list will be folded using /
separator and list of folders will be returned.Return
Returns BlobListResult
.
Return all blobs
To fetch all blobs, you can use a while
loop to fetch the next page until the cursor
is null
.
let blobs = []
let cursor = null
do {
const res = await hubBlob().list({ cursor })
blobs.push(...res.blobs)
cursor = res.cursor
} while (cursor)
serve()
Returns a blob's data and sets Content-Type
, Content-Length
and ETag
headers.
export default eventHandler(async (event) => {
const { pathname } = getRouterParams(event)
return hubBlob().serve(event, pathname)
})
<template>
<img src="/images/my-image.jpg">
</template>
You can also set a Content-Security-Policy
header to add an additional layer of security:
export default eventHandler(async (event) => {
const { pathname } = getRouterParams(event)
setHeader(event, 'Content-Security-Policy', 'default-src \'none\';')
return hubBlob().serve(event, pathname)
})
Params
Return
Returns the blob's raw data and sets Content-Type
and Content-Length
headers.
head()
Returns a blob's metadata.
const metadata = await hubBlob().head(pathname)
Params
Return
Returns a BlobObject
.
get()
Returns a blob body.
const blob = await hubBlob().get(pathname)
Params
Return
Returns a Blob
or null
if not found.
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
true
, a random suffix will be added to the blob's name. Defaults to false
.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
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: {
types: ['image/jpeg', 'image/png'], // allowed types of the file
},
put: {
addRandomSuffix: true
}
})
})
<script setup lang="ts">
const upload = useUpload('/api/blob', { method: 'PUT' })
async function onFileSelect(event: Event) {
const uploadedFiles = await upload(event.target as HTMLInputElement)
// file uploaded successfully
}
</script>
<template>
<input type="file" name="file" @change="onFileSelect" multiple accept="image/jpeg, image/png" />
</template>
Params
'files'
.true
, the formKey
field will be an array of Blob
objects.ensureBlob()
options for more details.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
true
, a random suffix will be added to the blob's name. Defaults to false
.createMultipartUpload()
handleMultipartUpload()
to handle the multipart upload requests.If you want to handle multipart uploads manually using this utility, keep in mind that you cannot use this utility with Vercel Blob due to payload size limits on Vercel functions. Consider using Vercel Blob Client SDK.
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
true
, a random suffix will be added to the blob's name. Defaults to true
.Return
Returns a BlobMultipartUpload
resumeMultipartUpload()
handleMultipartUpload()
to handle the multipart upload requests.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
Return
Returns a BlobMultipartUpload
Params
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
maxSize
or types
should be provided.(
1
| 2
| 4
| 8
| 16
| 32
| 64
| 128
| 256
| 512
| 1024
) + (B
| KB
| MB
| GB
) e.g.
'512KB'
, '1MB'
, '2GB'
, etc.['image/jpeg']
.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="image/jpeg, image/png"
type="file"
name="file"
multiple
@change="onFileSelect"
>
</template>
Params
'files'
.true
.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
handleMultipartUpload()
.10MB
.1
.3
.query
and headers
will be merged with the options provided by the uploader.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
httpEtag: string
uploadedAt: Date
httpMetadata: Record<string, string>
customMetadata: Record<string, string>
url: 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[]
}