File Uploads with Hapi.js
Hapi.js offers server-side development with its configuration-focused approach and a powerful plugin system. When it comes to file uploads, many developers face challenges with security vulnerabilities and poor user experiences. Hapi.js provides reliable tools to manage file uploads safely and efficiently.
File upload functionality is crucial for modern web applications; however, many developers implement it poorly. They risk security issues or create frustrating user experiences. Hapi.js gives you the framework to build it correctly from the beginning.
This detailed guide walks you through creating production-ready file upload systems with Hapi.js. You will learn everything from simple single-file uploads to advanced features like progress tracking and batch processing.
Prerequisites
You'll need Node.js 18 or later installed on your system. This tutorial expects familiarity with JavaScript ES modules, async/await patterns, and basic web server concepts.
Understanding HTTP multipart encoding is also helpful since file uploads rely on this protocol to transmit binary data alongside form information.
Setting up your Hapi.js file upload server
Building a reliable file upload system demands careful planning and solid project organization. You need a foundation that scales as your application expands.
Start by creating a new project directory and establishing a clean development workspace:
Configure your project to use ES modules by updating your package.json:
Install the essential packages for handling file operations:
Each package serves a specific purpose in your file upload system:
@hapi/hapi: The core framework that manages requests and file handling.@hapi/inert: Official plugin for serving static files and handling file uploads.@hapi/boom: Error handling utilities for consistent API responses.uuid: Generates unique filenames to prevent naming conflicts.
Begin with a minimal Hapi.js server to ensure your environment is properly configured. Create your server.js file:
Test this basic setup to verify your environment works correctly. Launch the server:
Navigate to http://localhost:3000 in your browser. You should see:
Your Hapi.js server is operational and ready for file upload functionality.
Getting started and testing your setup
Now you'll add file upload capabilities to your server. Hapi.js requires the @hapi/inert plugin to handle multipart form data and file uploads effectively.
Update your server.js with file upload functionality:
This code adds file upload functionality to your basic server. The @hapi/inert plugin enables multipart form handling. The payload configuration sets streaming mode with multipart: true for efficient file processing. The Promise wrapper handles the async file streaming properly.
Start your enhanced server:
Test your file upload endpoint using curl. Create a simple test file first:
Upload the file:
You should receive a successful response:
Check your project directory — you'll find an uploads folder containing your test file. This confirms your basic file upload system functions correctly.
Building comprehensive file validation systems
Your current upload endpoint works but lacks security measures for production use. Users can upload any file type or size, which could crash your system or lead to security issues.
You need validation that checks file types and sizes before accepting uploads. Let's add this to your existing upload endpoint.
Create a simple validators.js file:
This validator class creates a reusable system for checking files. The constructor sets maximum file size and allowed extensions. The validateFile method runs three checks: filename existence, extension validation using path.extname(), and size limits from the HTTP headers. It returns an object with validation results and any error messages.
Now update your server.js file to add validation:
The new imports add essential functionality: @hapi/boom provides standardized HTTP error responses, uuid generates unique identifiers, and your custom validator handles file checking.
The validator instance gets created with a 20MB size limit. Inside the handler, we first run validation using fileValidator.validateFile(), which returns success status and any errors. If validation fails, Boom.badRequest() sends a proper HTTP 400 response with error details.
For successful uploads, uuid.v4() creates a unique filename by combining a UUID with the original file extension. This prevents filename conflicts when multiple users upload files with the same name. The response now includes both original and stored filenames, plus a timestamp for tracking.
Your upload endpoint now validates files before saving them and uses unique filenames to prevent conflicts.
Restart your server:
Test with a valid file:
You should see a successful response.
Now test with an invalid file type. Create a fake image file:
You should get an error response:
Your validation system protects your server while providing clear feedback when uploads fail.
Handling multiple file uploads
Single-file uploads work great, but users often need to upload multiple files simultaneously. Think document batches, photo collections, or backup files. Hapi.js handles this with a small change to your endpoint configuration.
You need to process multiple files while keeping the same validation and error handling. Let's add a new endpoint for batch uploads.
Add this new route to your server.js file:
The new endpoint changes the payload field from file to files and handles both single files and arrays. The Array.isArray() check ensures compatibility with different client implementations. Each file gets validated individually using your existing validator, and the system continues processing even if some files fail.
The for...of loop processes files sequentially to avoid overwhelming the server. Each file operation is wrapped in a try-catch block to handle individual file errors without stopping the entire batch. The response provides detailed feedback showing which files succeeded and which failed.
Restart your server:
Create multiple test files:
Upload multiple files using curl:
You should see a response showing results for each file:
Your multiple file upload system processes each file individually and gives you clear feedback about the entire batch operation.
The system gracefully handles mixed results, allowing valid files to be saved while rejecting invalid ones with helpful error messages.
Final thoughts
You've built a complete file upload system with Hapi.js that manages single and multiple file uploads while validating everything before saving. Your system now guards against unsafe uploads and provides users with clear feedback when issues occur.
Next, you could add features like file deletion endpoints, image processing, or cloud storage integration. The foundation you've created makes these enhancements easy to implement.
For more advanced features and deployment tips, check out the Hapi.js documentation and consider adding a database to track file metadata.