How TREK Manages File Uploads: NestJS and Multer Architecture Explained
TREK handles file uploads through a centralized NestJS pipeline using Multer middleware, with dedicated storage directories, strict size limits, and multi-layered security checks including demo-mode safeguards and permission validation.
The open-source TREK travel management application processes all user-generated content—trip covers, avatars, generic attachments, and backup archives—through a consistent upload architecture. This implementation leverages standard NestJS patterns while enforcing security controls through environment variables and service-level permission checks. Understanding how TREK manages file uploads reveals enterprise-grade patterns for handling multipart form data in Node.js applications.
NestJS and Multer Upload Pipeline
TREK implements file handling through the standard NestJS FileInterceptor pattern combined with Multer disk storage. Every upload endpoint decorates controller methods with @UseInterceptors(FileInterceptor(...)), which configures Multer to parse multipart/form-data requests before they reach the route handler.
In server/src/nest/files/files.controller.ts, the generic file upload endpoint uses this configuration:
@Post()
@UseInterceptors(FileInterceptor('file', UPLOAD))
async upload(
@CurrentUser() user: User,
@Param('tripId') tripId: string,
@UploadedFile() file: Express.Multer.File | undefined,
@Body() body: { place_id?: string; description?: string; reservation_id?: string },
) {
// Business logic and persistence
const created = this.files.createFile(tripId, file!, user.id, {
place_id: body.place_id,
description: body.description,
reservation_id: body.reservation_id,
});
this.files.broadcast(tripId, 'file:created', { file: created });
return { file: created };
}
The Multer configuration object (UPLOAD, COVER_UPLOAD, or AVATAR_UPLOAD) defines the storage engine, file filters, and size limits. According to the source code in server/src/nest/trips/trips.controller.ts, the cover upload uses diskStorage with a custom destination callback that ensures directories exist before writing.
Storage Configuration and Directory Structure
TREK organizes uploaded files into purpose-specific directories under the /uploads root. The storage locations are:
- Covers:
uploads/covers(trip cover images) - Avatars:
uploads/avatars(user profile pictures) - Files:
uploads/files(generic trip attachments) - Temporary: System temp directory (backup uploads only)
The destination logic in server/src/nest/files/files.controller.ts creates directories recursively if they do not exist:
destination: (req, file, cb) => {
const dir = fileService.filesDir;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
cb(null, dir);
}
Backup uploads follow a different pattern. In server/src/nest/backup/backup.controller.ts, files are written to a temporary directory returned by getUploadTmpDir() and deleted immediately after processing using a finally block with fs.unlinkSync.
File Size Limits and Validation
TREK enforces strict file size limits through Multer configuration objects, with different thresholds for each upload type:
- Cover images: 20 MiB (
MAX_COVER_SIZE) - Avatars: 5 MiB (inline
limits.fileSize) - Generic files: 50 MiB (
MAX_FILE_SIZEfromfileService) - Backup archives: ~500 MiB (
MAX_BACKUP_UPLOAD_SIZE, configurable viaBACKUP_UPLOAD_LIMIT_MB)
The size validation occurs at the middleware level before the controller method executes. In server/src/nest/trips/trips.controller.ts, the cover upload configuration defines:
export const COVER_UPLOAD = {
storage: diskStorage({ /* ... */ }),
limits: { fileSize: MAX_COVER_SIZE }, // 20 * 1024 * 1024
fileFilter: (req, file, cb) => { /* MIME type check */ }
};
When a file exceeds these limits, Multer throws an error that server/src/nest/common/trek-exception.filter.ts transforms into HTTP 413 Payload Too Large or generic 400 Bad Request responses.
Security Safeguards and Permission Controls
TREK implements three layers of security validation before accepting any file: demo-mode blocks, extension filtering, and permission checks.
Demo Mode Protection
When DEMO_MODE is set to true in the environment and the authenticated user matches a demo email address, all upload endpoints immediately return 403 Forbidden with the message "Uploads are disabled in demo mode." This check appears in server/src/nest/files/files.controller.ts and server/src/nest/auth/auth.controller.ts:
if (process.env.DEMO_MODE?.toLowerCase() === 'true' && isDemoEmail(user.email)) {
throw new HttpException({ error: 'Uploads are disabled in demo mode.' }, 403);
}
File Type Validation
Each upload configuration includes a fileFilter that validates extensions and MIME types. The system rejects files matching BLOCKED_EXTENSIONS and specifically blocks SVG uploads for images to prevent XSS attacks. In server/src/nest/auth/auth.controller.ts, the avatar filter restricts uploads to images while excluding SVGs.
Permission Verification
After passing Multer's validation, controllers verify user permissions through service-layer checks. The files controller calls this.files.can() to validate file_upload, file_edit, or file_delete permissions, while the trips controller checks trip_cover_upload permissions. Failure results in an immediate 403 response before any file persistence occurs.
Error Handling and Cleanup
TREK centralizes upload error handling through server/src/nest/common/trek-exception.filter.ts, which normalizes Multer errors into appropriate HTTP status codes. Custom validation errors (such as blocked extensions) use the i18n key files.uploadErrorType for localization.
Cleanup operations vary by upload type:
- Backup uploads: Automatically deleted from the temporary directory after processing via
fs.unlinkSyncin afinallyblock - Generic files: Soft-delete and permanent deletion logic delegated to
FilesService - Cover images: Replaced in storage when new covers are uploaded, with old file cleanup handled by the service layer
Summary
- TREK uses NestJS FileInterceptor with Multer diskStorage for all multipart uploads, writing files to segregated directories under
/uploads. - File size limits are enforced at the middleware level: 20 MiB for covers, 5 MiB for avatars, 50 MiB for generic files, and ~500 MiB for backup archives.
- Security layers include demo-mode blocking (via
DEMO_MODEenvironment variable), extension/MIME filtering (blockingBLOCKED_EXTENSIONSand SVGs), and service-layer permission checks. - Error handling is centralized in
trek-exception.filter.ts, converting Multer errors to HTTP 400/413 responses. - Cleanup is automatic for temporary backup files, while production files use service-layer trash and deletion workflows.
Frequently Asked Questions
What is the maximum file size allowed for uploads in TREK?
TREK applies different size limits depending on the upload type. Cover images are limited to 20 MiB (MAX_COVER_SIZE), avatars to 5 MiB, generic trip files to 50 MiB (MAX_FILE_SIZE), and backup archives to approximately 500 MiB (configurable via BACKUP_UPLOAD_LIMIT_MB). These limits are enforced by Multer's limits.fileSize configuration before the file reaches the controller.
How does TREK prevent malicious file uploads?
TREK implements multiple safeguards: a fileFilter function rejects blocked extensions and dangerous MIME types (such as SVGs for images), the DEMO_MODE environment variable disables uploads entirely for demo instances, and service-layer permission checks verify the user holds file_upload or trip_cover_upload capabilities before processing. Additionally, the trek-exception.filter.ts normalizes errors to prevent information leakage.
Where does TREK store uploaded files?
Uploaded files are stored in dedicated subdirectories under /uploads: uploads/covers for trip cover images, uploads/avatars for user profile pictures, and uploads/files for generic attachments. Backup uploads are temporarily stored in a system temp directory returned by getUploadTmpDir() and deleted immediately after processing. The destination callbacks ensure directories exist by calling fs.mkdirSync with recursive: true.
Can I upload files in TREK's demo mode?
No. When DEMO_MODE is set to true and the authenticated user matches a demo email pattern, all upload endpoints in FilesController, AuthController, and TripsController immediately return HTTP 403 Forbidden with the message "Uploads are disabled in demo mode." This prevents content proliferation in public demonstration instances.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →