File Upload Guide
Overview
XyPriss provides comprehensive file upload support with automatic error handling, configuration-based setup, and both class-based and functional APIs. This guide covers the file upload system, configuration options, and usage examples.
Key Features
- ✅ Automatic Error Handling: File upload errors are automatically converted to user-friendly JSON responses
- ✅ Class-Based API: Modern
FileUploadAPIclass for better organization - ✅ Legacy Compatibility: Function-based API still available for backward compatibility
- ✅ Multipart Support: Fixed multipart/form-data handling (no more "Unexpected end of form" errors)
- ✅ Flexible Configuration: Extensive configuration options for security and performance
- ✅ Type Safety: Full TypeScript support with proper type definitions
Bug Fix: Multipart Form-Data Support
Issue
Previously, XyPriss automatically consumed request body streams for all POST/PUT/PATCH requests before middleware execution. This prevented multer and other multipart parsers from reading the stream, causing "Unexpected end of form" errors.
Solution
The parseBody method in HttpServer now skips body parsing for multipart/form-data content types, allowing middleware to access the raw request stream.
Quick Start
Class-Based API (Recommended)
import { createServer } from "xypriss";
import { FileUploadAPI, Configs } from "xypriss";
const app = createServer({
fileUpload: {
enabled: true,
maxFileSize: 5 * 1024 * 1024, // 5MB
storage: "memory",
},
});
// Create file upload instance
const fileUpload = new FileUploadAPI();
await fileUpload.initialize(Configs.get("fileUpload"));
app.post("/upload", fileUpload.single("file"), (req, res) => {
console.log(req.file); // File object available
res.json({ success: true });
});
Functional API (Legacy)
import { createServer } from "xypriss";
import { uploadSingle } from "xypriss";
const app = createServer({
fileUpload: {
enabled: true,
maxFileSize: 5 * 1024 * 1024, // 5MB
storage: "memory",
},
});
app.post("/upload", uploadSingle("file"), (req, res) => {
console.log(req.file); // File object available
res.json({ success: true });
});
Automatic Error Handling
XyPriss automatically handles file upload errors and converts them to user-friendly JSON responses. No manual error handling required!
Error Response Examples
File Too Large:
{
"success": false,
"error": "File too large",
"message": "File size exceeds the maximum limit of 1.00MB",
"details": {
"maxSize": 1048576,
"maxSizeMB": "1.00",
"fileSize": "unknown"
}
}
File Type Not Allowed:
{
"success": false,
"error": "File type not allowed",
"message": "File type 'application/exe' not allowed. Allowed types: image/jpeg, image/png"
}
Configuration Error:
{
"success": false,
"error": "Configuration Error",
"message": "File upload not enabled. Set fileUpload.enabled to true in server options."
}
Configuration Options
Core Options
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable file upload functionality |
maxFileSize | number | 1048576 (1MB) | Maximum file size in bytes |
maxFiles | number | 1 | Maximum number of files per request |
storage | 'memory' | 'disk' | 'memory' | Storage backend |
destination | string | './uploads' | Upload directory (disk storage only) |
filename | function | Auto-generated | Custom filename function |
Type Filtering
| Option | Type | Default | Description |
|---|---|---|---|
allowedMimeTypes | string[] | Common types | Allowed MIME types |
allowedExtensions | string[] | Common extensions | Allowed file extensions |
Advanced Options
| Option | Type | Description |
|---|---|---|
limits | object | Detailed multer limits |
fileFilter | function | Custom file filter function |
preservePath | boolean | Preserve full file paths |
createParentPath | boolean | Create destination directory |
multerOptions | object | Raw multer configuration |
Default Configuration
{
enabled: false, // Disabled by default for security
maxFileSize: 1024 * 1024, // 1MB
maxFiles: 1,
storage: 'memory',
allowedMimeTypes: [
'image/jpeg', 'image/png', 'image/gif', 'image/webp',
'application/pdf', 'text/plain', 'text/csv'
],
allowedExtensions: [
'.jpg', '.jpeg', '.png', '.gif', '.webp',
'.pdf', '.txt', '.csv'
],
createParentPath: true,
preservePath: false,
limits: {
fieldNameSize: 100,
fieldSize: 1024 * 1024,
fields: 10,
headerPairs: 20
}
}
Upload Methods
Single File Upload
app.post("/upload", app.uploadSingle("fieldname"), (req, res) => {
// req.file contains the uploaded file
const file = req.file;
console.log(file.originalname, file.size, file.mimetype);
});
Multiple Files (Array)
app.post("/upload", app.uploadArray("files", 5), (req, res) => {
// req.files contains array of uploaded files
console.log(`Uploaded ${req.files.length} files`);
});
Multiple Fields
app.post(
"/upload",
app.uploadFields([
{ name: "avatar", maxCount: 1 },
{ name: "documents", maxCount: 3 },
]),
(req, res) => {
// req.files contains files organized by field name
console.log(req.files);
}
);
Accept Any Files
app.post("/upload", app.uploadAny(), (req, res) => {
// req.files contains all uploaded files
console.log(req.files);
});
Manual Multer Integration
For advanced use cases, you can still use multer directly:
import multer from "multer";
const manualUpload = multer({
storage: multer.diskStorage({
destination: "./uploads",
filename: (req, file, cb) => {
cb(null, Date.now() + "-" + file.originalname);
},
}),
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
});
app.post("/upload-manual", manualUpload.single("file"), (req, res) => {
// Traditional multer usage
console.log(req.file);
});
File Object Structure
Memory Storage
{
fieldname: 'file',
originalname: 'example.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
buffer: <Buffer>, // File data
size: 12345
}
Disk Storage
{
fieldname: 'file',
originalname: 'example.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
destination: './uploads',
filename: '123456789-example.jpg',
path: './uploads/123456789-example.jpg',
size: 12345
}
Error Handling
Automatic Error Handling (Recommended)
With the new FileUploadAPI, errors are automatically handled and converted to JSON responses. No manual error handling needed!
import { FileUploadAPI } from "xypriss";
const fileUpload = new FileUploadAPI();
await fileUpload.initialize({ enabled: true, maxFileSize: 1024 * 1024 });
app.post("/upload", fileUpload.single("file"), (req, res) => {
// Errors are automatically handled - this code only runs on success
res.json({
success: true,
file: {
name: req.file.originalname,
size: req.file.size,
type: req.file.mimetype,
},
});
});
Manual Error Handling (Legacy)
For the legacy functional API, you can still handle errors manually:
import { uploadSingle } from "xypriss";
app.post(
"/upload",
(req, res, next) => {
const uploadMiddleware = uploadSingle("file");
uploadMiddleware(req, res, (err) => {
if (err) {
// Manual error handling
if (err.code === "LIMIT_FILE_SIZE") {
return res.status(400).json({
error: "File too large",
message: `Maximum size: ${err.field || "1MB"}`,
});
}
return res.status(400).json({ error: err.message });
}
// Success - continue to handler
next();
});
},
(req, res) => {
res.json({ success: true, file: req.file });
}
);
Custom File Filters
const fileUploadConfig = {
enabled: true,
fileFilter: (req, file, cb) => {
// Custom validation logic
if (file.originalname.includes("virus")) {
return cb(new Error("Suspicious filename detected"), false);
}
if (!["image/jpeg", "image/png"].includes(file.mimetype)) {
return cb(new Error("Only JPEG and PNG files allowed"), false);
}
cb(null, true);
},
};
Security Considerations
File Upload Security
- Validate File Types: Always check MIME types and file extensions
- Limit File Sizes: Set reasonable size limits to prevent DoS attacks
- Use Memory Storage: For untrusted uploads, use memory storage to avoid disk I/O
- Sanitize Filenames: Avoid user-provided filenames for security
- Scan for Malware: Consider integrating virus scanning for uploaded files
Example Security Configuration
const secureConfig = {
fileUpload: {
enabled: true,
maxFileSize: 2 * 1024 * 1024, // 2MB
maxFiles: 1,
storage: "memory", // Safer than disk
allowedMimeTypes: ["image/jpeg", "image/png"],
allowedExtensions: [".jpg", ".jpeg", ".png"],
fileFilter: (req, file, cb) => {
// Additional security checks
if (file.originalname.includes("..")) {
return cb(new Error("Invalid filename"), false);
}
cb(null, true);
},
},
};
API Comparison
Class-Based API (Recommended)
import { FileUploadAPI } from "xypriss";
const fileUpload = new FileUploadAPI();
await fileUpload.initialize({
enabled: true,
maxFileSize: 5 * 1024 * 1024,
storage: "memory",
});
// Automatic error handling
app.post("/upload", fileUpload.single("file"), (req, res) => {
res.json({ success: true, file: req.file });
});
Functional API (Legacy)
import { uploadSingle } from "xypriss";
// Automatic error handling
app.post("/upload", uploadSingle("file"), (req, res) => {
res.json({ success: true, file: req.file });
});
Manual Multer (Advanced)
import multer from "multer";
const upload = multer({
dest: "uploads/",
limits: { fileSize: 5 * 1024 * 1024 },
});
// Manual error handling required
app.post(
"/upload",
(req, res, next) => {
upload.single("file")(req, res, (err) => {
if (err) {
return res.status(400).json({ error: err.message });
}
next();
});
},
(req, res) => {
res.json({ success: true, file: req.file });
}
);
Migration Guide
From Manual Multer
Before:
import multer from "multer";
const upload = multer({ dest: "uploads/" });
app.post("/upload", upload.single("file"), (req, res) => {
// Manual error handling
if (!req.file) {
return res.status(400).json({ error: "Upload failed" });
}
res.json({ success: true });
});
After (Class-Based - Recommended):
import { FileUploadAPI } from "xypriss";
const fileUpload = new FileUploadAPI();
await fileUpload.initialize({
enabled: true,
storage: "disk",
destination: "uploads/",
});
app.post("/upload", fileUpload.single("file"), (req, res) => {
// Automatic error handling - only success code here
res.json({ success: true, file: req.file });
});
After (Functional - Simple):
import { uploadSingle } from "xypriss";
app.post("/upload", uploadSingle("file"), (req, res) => {
// Automatic error handling - only success code here
res.json({ success: true, file: req.file });
});
After (Keep Manual - Advanced):
// Manual multer still works exactly the same
import multer from "multer";
const upload = multer({ dest: "uploads/" });
app.post("/upload", upload.single("file"), (req, res) => {
// Still need manual error handling
res.json({ success: true, file: req.file });
});
Performance Tips
- Use Memory Storage for small files and trusted uploads
- Use Disk Storage for large files to avoid memory pressure
- Set Appropriate Limits to prevent resource exhaustion
- Enable Compression for large file transfers
- Use Streaming for very large files when possible
Troubleshooting
Common Issues
"File too large" error:
- Check
maxFileSizeconfiguration - Ensure client is not sending files larger than the limit
"Unexpected end of form" error:
- This was the original bug - ensure you're using XyPriss with file upload support
- Check that
multipart/form-datacontent type is being used
Files not uploading:
- Verify
fileUpload.enabledistrue - Check that the correct field name is used in
uploadSingle()
Type errors:
- Ensure you're using the correct field names
- Check that multer types are properly imported
Debug Mode
Enable debug logging to troubleshoot issues:
const app = createServer({
fileUpload: {
enabled: true,
debug: true, // Enable debug logging
// ... other options
},
});
API Reference
FileUploadConfig Interface
interface FileUploadConfig {
enabled?: boolean;
maxFileSize?: number;
maxFiles?: number;
allowedMimeTypes?: string[];
allowedExtensions?: string[];
destination?: string;
filename?: (req: any, file: any, callback: Function) => void;
limits?: {
fieldNameSize?: number;
fieldSize?: number;
fields?: number;
fileSize?: number;
files?: number;
headerPairs?: number;
};
fileFilter?: (req: any, file: any, callback: Function) => void;
storage?: "memory" | "disk" | "custom";
preservePath?: boolean;
createParentPath?: boolean;
multerOptions?: any;
}
App Methods
interface UltraFastApp {
uploadSingle(fieldname: string): RequestHandler;
uploadArray(fieldname: string, maxCount?: number): RequestHandler;
uploadFields(fields: any[]): RequestHandler;
uploadAny(): RequestHandler;
}
Examples
Image Upload with Validation
const app = createServer({
fileUpload: {
enabled: true,
maxFileSize: 5 * 1024 * 1024, // 5MB
allowedMimeTypes: ["image/jpeg", "image/png", "image/webp"],
allowedExtensions: [".jpg", ".jpeg", ".png", ".webp"],
storage: "disk",
destination: "./uploads/images",
},
});
app.post("/upload-image", app.uploadSingle("image"), (req, res) => {
if (!req.file) {
return res.status(400).json({ error: "No image uploaded" });
}
// Process the uploaded image
const imagePath = req.file.path;
// ... image processing logic ...
res.json({
success: true,
filename: req.file.filename,
size: req.file.size,
type: req.file.mimetype,
});
});
Multiple File Upload
const app = createServer({
fileUpload: {
enabled: true,
maxFileSize: 10 * 1024 * 1024, // 10MB per file
maxFiles: 5,
storage: "memory",
},
});
app.post("/upload-documents", app.uploadArray("documents", 5), (req, res) => {
if (!req.files || req.files.length === 0) {
return res.status(400).json({ error: "No documents uploaded" });
}
const uploadedFiles = req.files.map((file) => ({
name: file.originalname,
size: file.size,
type: file.mimetype,
}));
res.json({
success: true,
count: req.files.length,
files: uploadedFiles,
});
});
Form Data with Files
app.post(
"/submit-form",
app.uploadFields([
{ name: "avatar", maxCount: 1 },
{ name: "resume", maxCount: 1 },
]),
(req, res) => {
const formData = req.body; // Other form fields
const avatar = req.files.avatar?.[0];
const resume = req.files.resume?.[0];
// Process form data and files
res.json({
formData,
avatar: avatar
? { name: avatar.originalname, size: avatar.size }
: null,
resume: resume
? { name: resume.originalname, size: resume.size }
: null,
});
}
);