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.xJson({ 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.xJson({ 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.xJson({
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.xJson({ 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.xJson({ success: true, file: req.file });
});
Functional API (Legacy) #
import { uploadSingle } from "xypriss";
// Automatic error handling
app.post("/upload", uploadSingle("file"), (req, res) => {
res.xJson({ 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.xJson({ 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.xJson({ 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.xJson({ 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.xJson({ 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.xJson({ 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.xJson({
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.xJson({
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.xJson({
formData,
avatar: avatar ? { name: avatar.originalname, size: avatar.size } : null,
resume: resume ? { name: resume.originalname, size: resume.size } : null,
});
},
);