XyPriss File Upload API - Best Practices Guide #
Overview #
The XyPriss File Upload API provides a robust, multer-based file upload system. This guide covers best practices for uploading files to XyPriss servers, with special attention to runtime compatibility.
⚠️ Important: Runtime Compatibility #
Bun Runtime (RECOMMENDED APPROACHES) #
When using Bun, you MUST use one of these approaches:
✅ Option 1: Native FormData with Blob (RECOMMENDED)
import fs from "fs";
const fileBuffer = fs.readFileSync(filePath);
const blob = new Blob([fileBuffer], { type: "image/jpeg" });
const form = new FormData();
form.append("file", blob, "filename.jpg");
const response = await fetch("http://localhost:8085/upload", {
method: "POST",
body: form,
});
Why this works: Bun's native FormData and Blob APIs properly handle the multipart/form-data encoding and streaming.
✅ Option 2: File API (Browser-Compatible)
import fs from "fs";
const fileBuffer = fs.readFileSync(filePath);
const file = new File([fileBuffer], "filename.jpg", {
type: "image/jpeg",
});
const form = new FormData();
form.append("file", file);
const response = await fetch("http://localhost:8085/upload", {
method: "POST",
body: form,
});
Why this works: The File API is a web standard that Bun implements correctly.
❌ AVOID: form-data npm package with Bun
// ❌ DO NOT USE THIS WITH BUN
import FormData from "form-data";
const form = new FormData();
form.append("file", fs.createReadStream(filePath));
// This will fail with "Unexpected end of form" error
const response = await fetch(url, {
method: "POST",
body: form,
headers: form.getHeaders(),
});
Why this fails: The form-data npm package creates Node.js streams that are incompatible with Bun's fetch implementation. The stream is not properly consumed, resulting in truncated data (typically only 17 bytes sent).
Node.js Runtime #
When using Node.js, you have more flexibility:
✅ Option 1: form-data package with node-fetch
import FormData from "form-data";
import fetch from "node-fetch";
import fs from "fs";
const form = new FormData();
form.append("file", fs.createReadStream(filePath), {
filename: "file.jpg",
contentType: "image/jpeg",
});
const response = await fetch("http://localhost:8085/upload", {
method: "POST",
body: form,
headers: form.getHeaders(),
});
✅ Option 2: Native FormData (Node.js 18+)
import fs from "fs";
import { Blob } from "buffer";
const fileBuffer = fs.readFileSync(filePath);
const blob = new Blob([fileBuffer], { type: "image/jpeg" });
const form = new FormData();
form.append("file", blob, "filename.jpg");
const response = await fetch("http://localhost:8085/upload", {
method: "POST",
body: form,
});
Server Configuration #
XyPriss provides two ways to use the file upload API:
✅ Option 1: Using the Uploader Singleton (NEW - Recommended) #
The Uploader is a pre-initialized singleton that automatically uses the Configs system. This is the easiest and recommended approach.
import { createServer, Upload } from "xypriss";
const app = createServer({
fileUpload: {
enabled: true,
maxFileSize: 5 * 1024 * 1024, // 5MB
storage: "memory", // or "disk"
allowedMimeTypes: [
"image/jpeg",
"image/png",
"image/gif",
"application/pdf",
],
allowedExtensions: [".jpg", ".jpeg", ".png", ".gif", ".pdf"],
},
});
// Use the Uploader singleton directly - no initialization needed!
app.post("/upload", upload.single("file"), (req, res) => {
console.log("Uploaded file:", req.file);
res.xJson({ success: true, file: req.file });
});
// Multiple files upload
app.post("/upload-multiple", upload.array("files", 5), (req, res) => {
console.log("Uploaded files:", req.files);
res.xJson({ success: true, files: req.files });
});
app.start();
Why use Uploader?
- ✅ No manual initialization required
- ✅ Automatically uses
Configssystem - ✅ Single source of truth for configuration
- ✅ Less boilerplate code
- ✅ Perfect for simple use cases
✅ Option 2: Using the FileUploadAPI Class (OLD - For Advanced Use Cases) #
Use this approach when you need multiple upload instances with different configurations or more control.
import { createServer, FileUploadAPI, Configs } from "xypriss";
const app = createServer({
fileUpload: {
enabled: true,
maxFileSize: 5 * 1024 * 1024, // 5MB
storage: "memory", // or "disk"
allowedMimeTypes: [
"image/jpeg",
"image/png",
"image/gif",
"application/pdf",
],
allowedExtensions: [".jpg", ".jpeg", ".png", ".gif", ".pdf"],
},
});
// Create file upload instance
const upload = new FileUploadAPI();
await upload.initialize(Configs); // Pass Configs class for single source of truth
// Single file upload
app.post("/upload", upload.single("file"), (req, res) => {
console.log("Uploaded file:", req.file);
res.xJson({ success: true, file: req.file });
});
// Multiple files upload
app.post("/upload-multiple", upload.array("files", 5), (req, res) => {
console.log("Uploaded files:", req.files);
res.xJson({ success: true, files: req.files });
});
app.start();
When to use FileUploadAPI?
- ✅ Need multiple upload instances with different configs
- ✅ Need custom logger instance
- ✅ Advanced use cases requiring more control
- ✅ Testing scenarios
Comparison #
| Feature | Uploader (Singleton) | FileUploadAPI (Class) |
|---|---|---|
| Initialization | ✅ Automatic | ⚠️ Manual required |
| Boilerplate | ✅ Minimal | ⚠️ More code |
| Multiple Instances | ❌ Single instance | ✅ Multiple instances |
| Custom Logger | ❌ Uses default | ✅ Custom logger support |
| Use Case | Simple, standard uploads | Advanced, custom setups |
| Recommended For | Most applications | Complex scenarios |
Configuration Options #
interface FileUploadConfig {
enabled: boolean;
maxFileSize?: number; // in bytes
maxFiles?: number; // max number of files
storage?: "memory" | "disk";
destination?: string; // for disk storage
allowedMimeTypes?: string[];
allowedExtensions?: string[];
limits?: {
fieldNameSize?: number;
fieldSize?: number;
fields?: number;
fileSize?: number;
files?: number;
parts?: number;
headerPairs?: number;
};
}
Client-Side Examples #
Browser (Vanilla JavaScript) #
<input type="file" id="fileInput" />
<button onclick="uploadFile()">Upload</button>
<script>
async function uploadFile() {
const fileInput = document.getElementById("fileInput");
const file = fileInput.files[0];
const form = new FormData();
form.append("file", file);
const response = await fetch("http://localhost:8085/upload", {
method: "POST",
body: form,
});
const result = await response.json();
console.log(result);
}
</script>
React #
import { useState } from "react";
function FileUpload() {
const [file, setFile] = useState<File | null>(null);
const handleUpload = async () => {
if (!file) return;
const form = new FormData();
form.append("file", file);
const response = await fetch("http://localhost:8085/upload", {
method: "POST",
body: form,
});
const result = await response.json();
console.log(result);
};
return (
<div>
<input
type="file"
onChange={(e) => setFile(e.target.files?.[0] || null)}
/>
<button onClick={handleUpload}>Upload</button>
</div>
);
}
Bun/Node.js Script #
import fs from "fs";
async function uploadFile(filePath: string) {
// Read file
const fileBuffer = fs.readFileSync(filePath);
const blob = new Blob([fileBuffer], { type: "image/jpeg" });
// Create form
const form = new FormData();
form.append("file", blob, "myfile.jpg");
// Upload
const response = await fetch("http://localhost:8085/upload", {
method: "POST",
body: form,
});
return response.json();
}
// Usage
const result = await uploadFile("./image.jpg");
console.log(result);
Error Handling #
Common Errors #
1. "Unexpected end of form"
Cause: Using form-data package with Bun's fetch.
Solution: Use native FormData with Blob or File API.
2. "File too large"
Cause: File exceeds maxFileSize limit.
Solution: Increase maxFileSize in server config or compress the file.
{
success: false,
error: "File too large",
message: "File size exceeds the maximum limit of 5.00MB"
}
3. "File type not allowed"
Cause: File MIME type or extension not in allowed list.
Solution: Add the MIME type/extension to server config or convert the file.
{
success: false,
error: "File type not allowed",
message: "File type 'application/zip' not allowed. Allowed types: image/jpeg, image/png"
}
Proper Error Handling #
async function uploadWithErrorHandling(file: File) {
try {
const form = new FormData();
form.append("file", file);
const response = await fetch("http://localhost:8085/upload", {
method: "POST",
body: form,
});
const result = await response.json();
if (!response.ok || !result.success) {
throw new Error(result.message || "Upload failed");
}
return result;
} catch (error) {
console.error("Upload error:", error);
throw error;
}
}
Testing #
Test Script Example #
See .private/test_upload_comprehensive.ts for a complete test suite that validates:
- Native FormData with Blob
- File API
- form-data package compatibility
Run tests:
bun .private/test_upload_comprehensive.ts
Performance Tips #
- Use memory storage for small files (< 1MB) - faster processing
- Use disk storage for large files - prevents memory issues
- Set appropriate file size limits - prevents abuse
- Validate file types - security best practice
- Use streaming for very large files - better memory management
Security Considerations #
- Always validate file types on the server
- Set reasonable file size limits
- Sanitize file names before saving to disk
- Scan uploaded files for malware (if applicable)
- Use HTTPS in production
- Implement rate limiting to prevent abuse
Troubleshooting #
Enable Debug Logging #
The server logs detailed information about file uploads. Check the console for:
- Content-Type headers
- File size validation
- MIME type checking
- Extension validation
Verify Server Configuration #
console.log("File upload enabled:", upload.isEnabled());
console.log("Max file size:", app.configs?.fileUpload?.maxFileSize);
Test with curl #
curl -X POST http://localhost:8085/upload \
-F "file=@./test.jpg" \
-v
Summary #
| Runtime | Recommended Approach | Avoid |
|---|---|---|
| Bun | Native FormData + Blob/File | form-data package |
| Node.js | form-data + node-fetch OR Native API | Mixing fetch implementations |
| Browser | Native FormData + File | Server-side streaming packages |
Golden Rule: When in doubt, use native FormData with Blob or File - it works everywhere! 🎯