如何在Hapi.js中上传并保存文件/图片?求推荐适用工具包
Hey there! Let's break down how to handle file uploads and saving in Hapi.js based on your code and questions.
1. Your Upload Configuration is Already Correct
First off, the payload setup in your POST /company endpoint is spot-on for handling file uploads:
maxBytes: 20715200: Limits uploads to 20MB (perfect for most logos/files)output: "stream": Treats uploaded files as Node.js streams, which is ideal for efficient savingparse: true: Automatically parses the multipart form data, so you can access text fields likecompanyNameand file streams likelogodirectly fromrequest.payloadallow: "multipart/form-data": Allows the multipart form format required for file uploads
No changes needed here—you’ve got the upload part covered!
2. Saving Files: Use Node.js Native fs Module (No Extra Dependencies!)
You don’t need any fancy third-party packages for saving files. Node.js built-in fs (file system) module is more than enough. Here’s how to modify your handler to save the logo:
First, add these imports at the top of your file:
const fs = require('fs').promises; // Promise-based fs for async/await const path = require('path');
Then update your handler to an async function (since file operations are asynchronous):
handler: async (request, h) => { const { companyName, logo } = request.payload; try { // Define where you want to save the logo (e.g., ./uploads/logos) const uploadDirectory = path.join(__dirname, 'uploads', 'logos'); // Create the directory if it doesn't exist (recursive: true makes parent folders too) await fs.mkdir(uploadDirectory, { recursive: true }); // Generate a unique filename to avoid overwriting existing files const uniqueFileName = `${Date.now()}-${logo.hapi.filename}`; const fullSavePath = path.join(uploadDirectory, uniqueFileName); // Pipe the uploaded file stream to a write stream await new Promise((resolve, reject) => { const writeStream = fs.createWriteStream(fullSavePath); logo.pipe(writeStream); // Resolve when the file finishes writing writeStream.on('finish', resolve); // Reject if there's an error writing the file writeStream.on('error', reject); }); // Optional: Save the file path to your database alongside companyName // Example: await db.companies.create({ name: companyName, logoPath: fullSavePath }); return h.response({ message: 'Company created successfully!', company: { name: companyName, logoUrl: `/uploads/logos/${uniqueFileName}` // For frontend access } }).code(201); // 201 = Created status code } catch (error) { console.error('Error saving file or creating company:', error); return h.response({ error: 'Failed to create company' }).code(500); } }
Key Details:
logo.hapi.filename: Hapi adds this property to the file stream, giving you the original name of the uploaded fileDate.now()prefix: Ensures filenames are unique, so you don’t accidentally overwrite existing logosfs.mkdir({ recursive: true }): Automatically creates all parent directories if they don’t exist (no need to manually check each folder)- Promise wrapper around
pipe: Since streaming is asynchronous, we wait for the write operation to finish before sending a response
3. Optional: Restrict File Types
If you want to limit uploads to specific file types (e.g., only images), add an allowed array to your payload config:
payload: { maxBytes: 20715200, output: "stream", parse: true, allow: "multipart/form-data", allowed: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'] // Only allow image files }
Hapi will automatically reject files that don’t match these MIME types and return a 415 Unsupported Media Type error.
4. Bonus: Better Error Handling with @hapi/boom (Optional)
For more consistent error responses, you can use the @hapi/boom package to create standard HTTP errors:
- Install it first:
npm install @hapi/boom
- Update your catch block:
const Boom = require('@hapi/boom'); // ... catch (error) { console.error('Error:', error); throw Boom.internal('Failed to create company', error); }
Hapi will automatically convert Boom errors into properly formatted HTTP responses with the correct status codes.
内容的提问来源于stack exchange,提问作者Shringiraj Dewangan

