Routes
Arkos automatically exposes dedicated routes for standalone file operations. These work independently of your Prisma models and are available out of the box once file uploads are configured.
| Method | Endpoint | Description | Operation |
|---|---|---|---|
| GET | /api/uploads/:fileType/:fileName | Serve/retrieve a file | findFile |
| POST | /api/uploads/:fileType | Upload files | uploadFile |
| PATCH | /api/uploads/:fileType/:fileName | Replace an existing file | updateFile |
| DELETE | /api/uploads/:fileType/:fileName | Delete a file | deleteFile |
:fileType accepts: images, videos, documents, or files.
/api/uploads is the default base route and can be customized in your config. See File Upload Setup for details.
Two approaches to file uploads
These standalone routes are one way to handle file uploads. Arkos also supports declaring uploads directly on your routes via experimental.uploads — a single API call that handles both the file and the record together.
Use standalone routes when:
- You need to upload files independently of any model operation
- You're building a file management system
- You need to upload files before deciding where to use them
Use router uploads when:
- Files are tied to a model (user avatars, post images, product galleries)
- You want a single API call that handles data and files together
- You want automatic cleanup on error via
deleteOnError
See Router Upload for the full router upload guide.
Configuring file upload routes
Use the hook named export in src/modules/file-upload/file-upload.router.ts to configure any file upload endpoint. Each key maps to the operation name from the table above.
RouteHook is the new name for export const config: RouterConfig. Existing code using the old name still works but will log a deprecation warning. See the Route Hook guide for details.
import { ArkosRouter, RouteHook } from "arkos";
import uploadPolicy from "@/src/modules/file-upload/file-upload.policy";
export const hook: RouteHook = {
findFile: { authentication: false },
uploadFile: { authentication: uploadPolicy.Upload },
updateFile: { authentication: true },
deleteFile: { disabled: true },
};
const router = ArkosRouter();
export default router;See the full configuration object reference at ArkosRouter.
Intercepting file upload requests
Every file upload endpoint can be intercepted — run logic before or after any operation. For example, to check if a file is still referenced before deleting it:
import { ArkosRequest, ArkosResponse, ArkosNextFunction } from "arkos";
export const beforeDeleteFile = [
async (req: ArkosRequest, res: ArkosResponse, next: ArkosNextFunction) => {
const { fileName } = req.params;
const isReferenced = await checkFileReferences(fileName);
if (isReferenced) {
return res.status(400).json({
code: "FileStillReferenced",
message: "Cannot delete file: still referenced in database",
});
}
next();
},
];Available hooks: beforeUploadFile, afterUploadFile, beforeUpdateFile, afterUpdateFile, beforeDeleteFile, afterDeleteFile, beforeFindFile. See Interceptors for full details.
Sending requests
Retrieve a file
GET /api/uploads/images/1234567890-photo.jpgResponse: The file is served directly as a static asset.
Upload a file
When uploading, the FormData field name must match the :fileType in the URL.
POST /api/uploads/images
Authorization: Bearer YOUR_API_TOKEN
Content-Type: multipart/form-dataconst formData = new FormData();
formData.append("images", imageFile); // field name matches fileType
const response = await fetch("http://localhost:8000/api/uploads/images", {
method: "POST",
body: formData,
});Response:
{
"success": true,
"message": "File uploaded successfully",
"urls": ["http://localhost:8000/uploads/images/1234567890-photo.jpg"]
}Image processing via query parameters:
POST /api/uploads/images?resizeTo=800&format=webp| Parameter | Description |
|---|---|
?width=500 | Resize to width (may distort ratio) |
?height=300 | Resize to height (may distort ratio) |
?resizeTo=800 | Resize to fit within px, keeps ratio |
?format=webp | Convert to format |
Replace a file
Automatically deletes the old file before uploading the new one.
PATCH /api/uploads/images/old-image.jpg
Content-Type: multipart/form-data
Authorization: Bearer YOUR_API_TOKENconst formData = new FormData();
formData.append("images", newImageFile);
const response = await fetch(
"http://localhost:8000/api/uploads/images/old-image.jpg",
{ method: "PATCH", body: formData }
);Response:
{
"success": true,
"message": "File replaced successfully",
"urls": ["http://localhost:8000/uploads/images/new-image.jpg"]
}If the old file doesn't exist, it acts as a regular upload and returns "File uploaded successfully" instead.
Delete a file
DELETE /api/uploads/images/1234567890-photo.jpg
Authorization: Bearer YOUR_API_TOKENResponse: 204 No Content
Common workflow
Upload a file first, then reference it in a subsequent model request:
// 1. Upload the file
const formData = new FormData();
formData.append("images", file);
const uploadResponse = await fetch("http://localhost:8000/api/uploads/images", {
method: "POST",
body: formData,
});
const { urls } = await uploadResponse.json();
const fileUrl = urls[0];
// 2. Use the URL in a model update
await fetch("http://localhost:8000/api/users/123", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ profilePhoto: fileUrl }),
});Default file type support
Images — jpeg, jpg, png, gif, webp, svg, bmp, tiff, heic, avif, psd, and more. Defaults: 30 files max, 15 MB per file.
Videos — mp4, avi, mov, mkv, webm, flv, wmv, and more. Defaults: 10 files max, 5 GB per file.
Documents — pdf, doc, docx, xls, xlsx, ppt, pptx, csv, txt, epub, md, and more. Defaults: 30 files max, 50 MB per file.
Files — any other type. Defaults: 10 files max, 5 GB per file.
Configuration
import { defineConfig } from "arkos/config";
export default defineConfig({
fileUpload: {
baseUploadDir: "/uploads",
baseRoute: "/api/uploads",
expressStatic: {
maxAge: "1d",
},
restrictions: {
images: {
maxCount: 10,
maxSize: 5 * 1024 * 1024,
supportedFilesRegex: /\.(jpg|jpeg|png|webp)$/,
},
},
},
});import { ArkosConfig } from "arkos";
const config: ArkosConfig = {
fileUpload: {
baseUploadDir: "/uploads",
baseRoute: "/api/uploads",
expressStatic: {
maxAge: "1d",
},
restrictions: {
images: {
maxCount: 10,
maxSize: 5 * 1024 * 1024,
supportedFilesRegex: /\.(jpg|jpeg|png|webp)$/,
},
},
},
};
export default config;import arkos from "arkos";
arkos.init({
fileUpload: {
baseUploadDir: "/uploads",
baseRoute: "/api/uploads",
restrictions: {
images: {
maxCount: 10,
maxSize: 5 * 1024 * 1024,
supportedFilesRegex: /\.(jpg|jpeg|png|webp)$/,
},
},
},
});
## Related
- [Router Upload](/docs/guides/file-uploads/router-upload) — attaching uploads to custom and built-in routes
- [Interceptors](/docs/core-concepts/components/interceptors#file-upload-interceptors) — before/after hooks for any file operation
- [File Upload Services](/docs/reference/file-upload-services) — programmatic file operations inside controllers and services
- [Validation with file uploads](/docs/guides/validation/usage#validation-with-file-uploads) — how to combine `validation.body` and `experimental.uploads` in the same request