4.1 KiB
title, tags
| title | tags | |||
|---|---|---|---|---|
| File API |
|
File API
Endpoints live under /api/files/*. The router is backend/src/services/file/fileRoutes.ts, delegating to fileController and fileService. Multer is used for multipart parsing and uploaded files are written under uploads/<subfolder>.
All endpoints require Bearer JWT. Static serving is wired in app.ts at /uploads/* with helmet({ crossOriginResourcePolicy: { policy: "cross-origin" } }) so files can be embedded from the frontend domain.
Multer configuration
fileService.getUploadMiddleware(options) produces a Multer instance per route. Options:
subfolder- where the file lands underuploads/fieldName- form field namefileTypes- allowed MIME types (default: any)maxFiles- default 1maxFileSizeMB- default 10 MB
Global body limits in app.ts are 10mb for express.json and express.urlencoded.
Upload
POST /api/files/upload/avatar
Description: Upload an avatar image. Lands in uploads/temp/ and is then moved when the user persists it on their profile.
Auth required: Bearer JWT
Form fields: avatar (image; JPEG / PNG / GIF / WebP)
Response 200:
{
"success": true,
"data": {
"url": "/uploads/temp/avatar-1716459200000.jpg",
"filename": "avatar-1716459200000.jpg",
"mimeType": "image/jpeg",
"size": 51234
}
}
Errors: 400 bad type / too large, 401 not authenticated.
Side effects: None — caller is responsible for PUT /api/user/profile to persist the URL.
POST /api/files/upload/file
Description: Generic single-file upload (any MIME), lands in uploads/temp/.
Auth required: Bearer JWT
Form fields: file
Response 200: { success, data: { url, filename, mimeType, size } }
POST /api/files/upload/files
Description: Multi-file upload (up to 5).
Auth required: Bearer JWT
Form fields: files (repeated)
Response 200: { success, data: { files: [{ url, filename, ... }] } }
POST /api/files/upload/request-template-images
Description: Up to 10 images for a RequestTemplate. Lands in uploads/request-templates/.
Auth required: Bearer JWT
Form fields: images (repeated; JPEG / PNG / GIF / WebP)
Response 200: { success, data: { files: [...] } }
POST /api/files/upload/blog-images
Description: Up to 10 images for a BlogPost. Lands in uploads/blog/.
Auth required: Bearer JWT
Form fields: images (repeated; JPEG / PNG / GIF / WebP)
Response 200: { success, data: { files: [...] } }
Delete
DELETE /api/files/delete
Description: Delete a file by relative path.
Auth required: Bearer JWT
Request body: { filePath: string } (e.g. "temp/avatar-1716459200000.jpg")
Response 200: { success, message: "File deleted" }
Errors: 400 invalid path (must stay inside uploads/), 404 file missing.
Inspect
GET /api/files/info/:filePath
Description: Returns metadata for a file (URL-encoded filePath segment).
Auth required: Bearer JWT
Response 200: { success, data: { url, size, mimeType, createdAt } }
GET /api/files/stats
Description: Aggregate upload statistics (counts and sizes per subfolder).
Auth required: Bearer JWT (admin gating planned per TODO in source)
Response 200: { success, data: { subfolders: [{ name, count, sizeBytes }], total: { count, sizeBytes } } }
Serving
GET /uploads/<path>- static file delivery (no auth, public read). Suitable for embedding avatars and blog/template images.- The server logs
❌ File not found:for missing paths and✅ Serving file:on hits — useful when debugging frontend image refs.
The on-disk root resolves from config.uploadPath. In production this defaults to /app/uploads; in development to <repo>/uploads. Both are normalised to an absolute path before being passed to express.static.
Related
- File Storage Architecture
- User API (avatar consumer)
- Marketplace API (template image consumer)
- Blog API (blog image consumer)
- Chat API (message attachments use a separate multipart route under chat)