H3 Integration
This guide explains how to integrate FileZen with H3 applications using the @filezen/js package. This package provides a ZenStorage class that simplifies file upload and management operations with comprehensive error handling and automatic multipart upload support.
H3 is a lightweight and composable server framework that works universally across Node.js, Bun, Deno, and edge runtimes.
📁 Code Example: See the complete H3 server example for a working implementation with interactive test interface.
Installation
Install the FileZen JavaScript SDK:
Quick Start
1. Environment Configuration
Set your FileZen API key as an environment variable. This can be done in your local .env file during development or as a system environment variable in production.
FILEZEN_API_KEY=your_api_key_hereThe FileZen SDK automatically detects the FILEZEN_API_KEY environment variable for server-side operations, so no additional configuration is needed in your server setup. The keepUploads option defaults to false for server-side usage, so temporary uploads are automatically cleaned up.
2. Initialize ZenStorage
Create a ZenStorage instance with your configuration. The SDK will automatically detect the FILEZEN_API_KEY from your environment variables.
import { ZenStorage } from '@filezen/js';
// Initialize storage with automatic environment variable detection
const zenStorage = new ZenStorage({
apiKey: "your_api_key_here", // Optional: can use FILEZEN_API_KEY env var
});3. Upload Files
You can now use the ZenStorage instance to upload files with various methods.
import fs from 'fs';
// Upload a single file from file path
const fileBuffer = fs.readFileSync(filePath);
const upload = await zenStorage.upload(fileBuffer, {
name: 'my-file.jpg',
mimeType: 'image/jpeg',
});
if (upload.error) {
console.error('Upload failed:', upload.error);
} else {
console.log('File uploaded:', upload.file.url);
}FileZen Functions
Single File Upload
Upload individual files with error handling.
import fs from 'fs';
async function uploadSingleFile(filePath, filename, mimeType) {
const fileBuffer = fs.readFileSync(filePath);
const upload = await zenStorage.upload(fileBuffer, {
name: filename,
mimeType: mimeType,
});
if (upload.error) {
throw new Error(`Upload failed: ${upload.error.message}`);
}
return {
url: upload.file.url,
name: upload.file.name,
size: upload.file.size,
mimeType: upload.file.mimeType,
};
}Bulk Upload
Upload multiple files concurrently for better performance.
async function uploadMultipleFiles(filesData) {
const uploadItems = filesData.map(file => ({
source: file.buffer,
options: {
name: file.filename,
mimeType: file.mimetype,
},
}));
const uploads = await zenStorage.bulkUpload(...uploadItems);
// Check for any upload errors
const failedUploads = uploads.filter(upload => upload.error);
if (failedUploads.length > 0) {
const failures = failedUploads.map(upload => ({
name: upload.name || 'unknown',
error: upload.error?.message || 'Unknown error'
}));
throw new Error(`${failedUploads.length} uploads failed`);
}
return uploads.map(upload => ({
url: upload.file.url,
name: upload.file.name,
size: upload.file.size,
mimeType: upload.file.mimeType,
}));
}Upload from URL
Upload files directly from external URLs.
async function uploadFromUrl(sourceUrl, fileName) {
const upload = await zenStorage.uploadFromUrl(sourceUrl, {
name: fileName || 'downloaded-file',
});
if (upload.error) {
throw new Error(`URL upload failed: ${upload.error.message}`);
}
return {
url: upload.file.url,
name: upload.file.name,
size: upload.file.size,
mimeType: upload.file.mimeType,
};
}Upload from Base64
Upload files from base64 encoded data.
async function uploadFromBase64(base64Data, fileName, mimeType) {
const upload = await zenStorage.uploadFromBase64(base64Data, {
name: fileName || 'base64-file',
mimeType: mimeType,
});
if (upload.error) {
throw new Error(`Base64 upload failed: ${upload.error.message}`);
}
return {
url: upload.file.url,
name: upload.file.name,
size: upload.file.size,
mimeType: upload.file.mimeType,
};
}Manual Multipart Upload Control
For fine-grained control over multipart uploads, especially for large files or when you need to track progress:
async function manualMultipartUpload(file, fileName, mimeType) {
// Start multipart upload session
const { id: sessionId } = await zenStorage.multipart.start({
fileName: fileName,
mimeType: mimeType,
totalSize: file.size,
uploadMode: 'STREAMING',
metadata: { type: 'manual_upload' }
});
let uploadedSize = 0;
const chunkSize = 5 * 1024 * 1024; // 5MB chunks
// Upload file in chunks
while (uploadedSize < file.size) {
// Create a chunk from the current position
const chunk = file.slice(
uploadedSize,
Math.min(file.size, uploadedSize + chunkSize)
);
// Upload this specific chunk
await zenStorage.multipart.uploadPart({
sessionId: sessionId,
chunk: chunk
});
// Update progress
uploadedSize = Math.min(file.size, uploadedSize + chunkSize);
console.log(`Uploaded ${uploadedSize}/${file.size} bytes`);
}
// Finalize the multipart upload
const result = await zenStorage.multipart.finish({
sessionId: sessionId
});
return result;
}Generate Signed URL
Generate secure signed URLs for direct uploads from client applications.
function generateUploadUrl(fileKey, expirationTime = 3600) {
const signedUrl = zenStorage.generateSignedUrl({
path: '/files/upload',
fileKey: fileKey,
expiresIn: expirationTime, // 1 hour by default
});
return {
signedUrl: signedUrl,
expiresIn: expirationTime,
};
}Delete File
Delete files by their URL.
async function deleteFile(fileUrl) {
try {
await zenStorage.deleteByUrl(fileUrl);
return { success: true, message: 'File deleted successfully' };
} catch (error) {
throw new Error(`Failed to delete file: ${error.message}`);
}
}Error Handling
H3 provides excellent error handling capabilities with structured error system. All endpoints return standardized error responses:
Standard Error Response
{
"statusCode": 400,
"statusMessage": "Error description",
"data": {
"message": "Detailed error message"
}
}Upload-Specific Error Handling
The server implements comprehensive error checking for FileZen SDK upload operations:
- Single File Upload Errors: Checks
upload.errorbefore returning success - Bulk Upload Errors: Validates each upload in the batch and provides detailed failure information
- Network Errors: Handles fetch failures for URL uploads
- Validation Errors: Returns 400 status for missing required parameters
Bulk Upload Error Response
For bulk uploads, if some files fail while others succeed, the server returns detailed error information:
{
"statusCode": 500,
"statusMessage": "Some uploads failed",
"data": {
"message": "2 out of 5 uploads failed",
"failures": [
{
"name": "failed-file1.jpg",
"error": "File too large"
},
{
"name": "failed-file2.pdf",
"error": "Invalid file type"
}
]
}
}Complete H3 Server Example
Here’s a complete H3 server implementation with all FileZen endpoints. This example demonstrates how to integrate FileZen with H3’s event-driven architecture, built-in utilities, and error handling.
Why H3?
H3 offers several advantages for FileZen integration:
- Lightweight: Minimal overhead and fast startup
- Universal: Works with Node.js, Bun, Deno, and edge runtimes
- Composable: Event-driven architecture with reusable handlers
- Modern: Built-in TypeScript support and ESM-first
- Cross-platform: Designed for multiple JavaScript runtimes
- Performance: Optimized for speed and efficiency
Framework Dependencies: This example uses H3 and listhen for development. Install them with: npm install h3 listhen. These are just for the example - you can use any server setup method with FileZen.
import { createApp, createRouter, defineEventHandler, readMultipartFormData, readBody, createError, serveStatic } from 'h3';
import { listen } from 'listhen';
import { ZenStorage } from '@filezen/js';
const app = createApp();
const router = createRouter();
// Initialize FileZen storage
const zenStorage = new ZenStorage();
// Serve static files
app.use('/public', serveStatic('./public'));
// Health check
router.get('/', defineEventHandler(async (event) => {
return { message: 'FileZen H3 Server Running' };
}));
// Single file upload
router.post('/api/files/upload', defineEventHandler(async (event) => {
try {
const formData = await readMultipartFormData(event);
if (!formData || formData.length === 0) {
throw createError({
statusCode: 400,
statusMessage: 'No file provided'
});
}
const fileItem = formData.find(item => item.name === 'file');
if (!fileItem) {
throw createError({
statusCode: 400,
statusMessage: 'No file found in form data'
});
}
const upload = await zenStorage.upload(fileItem.data, {
name: fileItem.filename || 'uploaded-file',
mimeType: fileItem.type || 'application/octet-stream',
});
if (upload.error) {
throw createError({
statusCode: 400,
statusMessage: upload.error.message
});
}
return {
success: true,
file: {
url: upload.file.url,
name: upload.file.name,
size: upload.file.size,
mimeType: upload.file.mimeType,
},
};
} catch (error) {
if (error.statusCode) {
throw error;
}
throw createError({
statusCode: 500,
statusMessage: 'Internal server error'
});
}
}));
// Bulk file upload
router.post('/api/files/bulk-upload', defineEventHandler(async (event) => {
try {
const formData = await readMultipartFormData(event);
if (!formData || formData.length === 0) {
throw createError({
statusCode: 400,
statusMessage: 'No files provided'
});
}
const fileItems = formData.filter(item => item.name === 'files');
if (fileItems.length === 0) {
throw createError({
statusCode: 400,
statusMessage: 'No files found in form data'
});
}
const uploadItems = fileItems.map(file => ({
source: file.data,
options: {
name: file.filename || 'uploaded-file',
mimeType: file.type || 'application/octet-stream',
},
}));
const uploads = await zenStorage.bulkUpload(...uploadItems);
// Check for any upload errors
const failedUploads = uploads.filter(upload => upload.error);
if (failedUploads.length > 0) {
const failures = failedUploads.map(upload => ({
name: upload.name || 'unknown',
error: upload.error?.message || 'Unknown error'
}));
throw createError({
statusCode: 500,
statusMessage: 'Some uploads failed',
data: {
message: `${failedUploads.length} out of ${uploads.length} uploads failed`,
failures: failures
}
});
}
const results = uploads.map(upload => ({
url: upload.file.url,
name: upload.file.name,
size: upload.file.size,
mimeType: upload.file.mimeType,
}));
return {
success: true,
files: results,
};
} catch (error) {
if (error.statusCode) {
throw error;
}
throw createError({
statusCode: 500,
statusMessage: 'Internal server error'
});
}
}));
// Upload from URL
router.post('/api/files/upload-from-url', defineEventHandler(async (event) => {
try {
const { url, name } = await readBody(event);
if (!url) {
throw createError({
statusCode: 400,
statusMessage: 'URL is required'
});
}
const upload = await zenStorage.uploadFromUrl(url, {
name: name || 'downloaded-file',
});
if (upload.error) {
throw createError({
statusCode: 400,
statusMessage: upload.error.message
});
}
return {
success: true,
file: {
url: upload.file.url,
name: upload.file.name,
size: upload.file.size,
mimeType: upload.file.mimeType,
},
};
} catch (error) {
if (error.statusCode) {
throw error;
}
throw createError({
statusCode: 500,
statusMessage: 'Internal server error'
});
}
}));
// Upload from Base64
router.post('/api/files/upload-from-base64', defineEventHandler(async (event) => {
try {
const { base64Data, name, mimeType } = await readBody(event);
if (!base64Data) {
throw createError({
statusCode: 400,
statusMessage: 'Base64 data is required'
});
}
const upload = await zenStorage.uploadFromBase64(base64Data, {
name: name || 'base64-file',
mimeType: mimeType,
});
if (upload.error) {
throw createError({
statusCode: 400,
statusMessage: upload.error.message
});
}
return {
success: true,
file: {
url: upload.file.url,
name: upload.file.name,
size: upload.file.size,
mimeType: upload.file.mimeType,
},
};
} catch (error) {
if (error.statusCode) {
throw error;
}
throw createError({
statusCode: 500,
statusMessage: 'Internal server error'
});
}
}));
// Generate signed URL
router.post('/api/files/generate-signed-url', defineEventHandler(async (event) => {
try {
const { fileKey, expiresIn = 3600 } = await readBody(event);
if (!fileKey) {
throw createError({
statusCode: 400,
statusMessage: 'fileKey is required'
});
}
const signedUrl = zenStorage.generateSignedUrl({
path: '/files/upload',
fileKey: fileKey,
expiresIn: expiresIn,
});
return {
success: true,
signedUrl: signedUrl,
expiresIn: expiresIn,
};
} catch (error) {
if (error.statusCode) {
throw error;
}
throw createError({
statusCode: 500,
statusMessage: 'Internal server error'
});
}
}));
// Delete file
router.delete('/api/files/delete', defineEventHandler(async (event) => {
try {
const { url } = await readBody(event);
if (!url) {
throw createError({
statusCode: 400,
statusMessage: 'File URL is required'
});
}
await zenStorage.deleteByUrl(url);
return {
success: true,
message: 'File deleted successfully',
};
} catch (error) {
if (error.statusCode) {
throw error;
}
throw createError({
statusCode: 500,
statusMessage: 'Internal server error'
});
}
}));
// Register router
app.use(router);
// Start server
const port = process.env.PORT || 3003;
await listen(app, { port });
console.log(`Server running on http://localhost:${port}`);