Created
March 20, 2025 14:14
-
-
Save danielkellyio/3f2baee05fa908e7c13f2b83ea967e0b to your computer and use it in GitHub Desktop.
Nuxt File Uploads with useStorage()
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// server/api/upload/post.ts | |
// Endpoint to upload the files | |
import { H3Error } from "h3"; | |
export default defineEventHandler(async (event) => { | |
// Parse multipart form data | |
const formData = await readMultipartFormData(event); | |
if (!formData || formData.length === 0) { | |
throw createError({ | |
statusCode: 400, | |
statusMessage: "No files uploaded", | |
}); | |
} | |
// Get storage instance | |
const storage = useStorage("uploads"); | |
const uploadedFiles = []; | |
try { | |
// Process each file in the form data | |
for (const file of formData) { | |
// Validate file size (5MB limit) | |
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB in bytes | |
if (file.data.length > MAX_FILE_SIZE) { | |
throw createError({ | |
statusCode: 400, | |
statusMessage: `File ${file.filename} exceeds maximum size of 5MB`, | |
}); | |
} | |
// Validate file type | |
const allowedTypes = [ | |
"image/jpeg", | |
"image/png", | |
"image/gif", | |
"application/pdf", | |
"application/msword", | |
"text/plain", | |
]; | |
if (!file.type || !allowedTypes.includes(file.type)) { | |
throw createError({ | |
statusCode: 400, | |
statusMessage: `File type ${ | |
file.type || "unknown" | |
} not allowed. Allowed types: ${allowedTypes.join(", ")}`, | |
}); | |
} | |
if (!file || !file.filename) { | |
console.warn("Skipping invalid file entry"); | |
continue; | |
} | |
// Store file using useStorage | |
const fileName = `${Date.now()}-${file.filename}`; | |
await storage.setItemRaw(`${fileName}`, file.data); | |
uploadedFiles.push({ | |
filename: fileName, | |
url: `/uploads/${fileName}`, | |
}); | |
} | |
return { | |
files: uploadedFiles, | |
}; | |
} catch (error) { | |
if (error instanceof H3Error) { | |
throw error; | |
} | |
throw createError({ | |
statusCode: 500, | |
statusMessage: "Error uploading files", | |
}); | |
} | |
}); | |
// ------------------------------------------------------------------------------------------------- | |
// server/routes/uploads/[...path].ts | |
// Endpoint to serve the files | |
// (we use the routes folder just so the path doesn't include `/api` which feels cleaner) | |
export default defineEventHandler(async (event) => { | |
const storage = useStorage("uploads"); | |
const path = await getRouterParam(event, "path"); | |
// you could also put in logic here | |
// to restrict access to the file based on | |
// the user, file type, or any other criteria | |
// but this implementation makes the file public | |
if (!path) { | |
throw createError({ | |
statusCode: 400, | |
statusMessage: "Path is required", | |
}); | |
} | |
return await storage.getItemRaw(path); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Looking to add a file upload component to compliment this approach on the front-end? Checkout the Vue School course File Uploads in Vue.js
file-upload-course.mp4