Router Upload
Router uploads let you attach file handling directly to a route so that the file path is automatically injected into req.body before your handler runs — no separate upload step required. This works on both custom routes via ArkosRouter and built-in routes via RouteHook.
If you need to upload files independently of any route or model operation, see File Upload Routes for the standalone approach instead.
Upload types
Arkos supports three upload strategies, all configured through the experimental.uploads key.
single
One file per request. The file path is attached to req.body[field] as a string.
import { ArkosRouter } from "arkos";
import reportsController from "../controllers/reports.controller";
const reportsRouter = ArkosRouter();
reportsRouter.post(
{
path: "/api/reports/upload",
authentication: true,
experimental: {
uploads: {
type: "single",
field: "reportFile",
uploadDir: "reports",
maxSize: 50 * 1024 * 1024,
allowedFileTypes: [".xlsx", ".csv", ".pdf"],
deleteOnError: true,
},
},
},
reportsController.processUploadedReport
);
export default reportsRouter;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";
export const hook: RouteHook = {
createOne: {
experimental: {
uploads: {
type: "single",
field: "profilePhoto",
uploadDir: "user-profiles",
maxSize: 5 * 1024 * 1024,
allowedFileTypes: [".jpg", ".jpeg", ".png", ".webp"],
deleteOnError: true,
},
},
},
updateOne: {
experimental: {
uploads: {
type: "single",
field: "profilePhoto",
uploadDir: "user-profiles",
required: false,
},
},
},
};
const userRouter = ArkosRouter();
export default userRouter;array
Multiple files under one field. The paths are attached to req.body[field] as a string[].
import { ArkosRouter } from "arkos";
import galleryController from "../controllers/gallery.controller";
const galleryRouter = ArkosRouter();
galleryRouter.post(
{
path: "/api/gallery",
authentication: true,
experimental: {
uploads: {
type: "array",
field: "photos",
maxCount: 12,
uploadDir: "gallery",
deleteOnError: true,
},
},
},
galleryController.createAlbum
);
export default galleryRouter;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";
export const hook: RouteHook = {
createOne: {
experimental: {
uploads: {
type: "array",
field: "images",
maxCount: 8,
uploadDir: "product-images",
deleteOnError: true,
},
},
},
};
const productRouter = ArkosRouter();
export default productRouter;fields
Multiple named fields, each with its own maxCount. Use this when a single request carries files of fundamentally different kinds.
import { ArkosRouter } from "arkos";
import listingsController from "../controllers/listings.controller";
const listingsRouter = ArkosRouter();
listingsRouter.post(
{
path: "/api/listings",
authentication: true,
experimental: {
uploads: {
type: "fields",
fields: [
{ name: "thumbnail", maxCount: 1 },
{ name: "gallery", maxCount: 6 },
{ name: "floorPlan", maxCount: 1 },
],
uploadDir: "listings",
deleteOnError: true,
},
},
},
listingsController.createListing
);
export default listingsRouter;import { ArkosRouter, RouteHook } from "arkos";
export const hook: RouteHook = {
createOne: {
experimental: {
uploads: {
type: "fields",
fields: [
{ name: "thumbnail", maxCount: 1 },
{ name: "gallery", maxCount: 6 },
{ name: "manual", maxCount: 1 },
],
uploadDir: "products",
deleteOnError: true,
},
},
},
};
const productRouter = ArkosRouter();
export default productRouter;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.
Nested fields
Bracket notation maps uploaded files into nested req.body shapes. This works the same way in both ArkosRouter and RouteHook.
experimental: {
uploads: {
type: "single",
field: "profile[photo]",
uploadDir: "user-profiles",
},
},The file path lands at req.body.profile.photo.
experimental.uploads reference
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
type | "single" | "array" | "fields" | ✓ | — | Upload strategy |
field | string | For single / array | — | FormData field name. Supports bracket notation. |
fields | Array<{ name: string; maxCount: number }> | For fields | — | Per-field config |
required | boolean | — | true | Mark file as optional with false |
uploadDir | string | — | Auto by MIME | Subdirectory inside baseUploadDir |
maxSize | number | — | From global config | Per-file size limit in bytes |
maxCount | number | For array | — | Max number of files |
allowedFileTypes | string[] | RegExp | — | From global config | Allowed extensions or pattern |
attachToBody | "pathname" | "url" | "file" | false | — | "pathname" | How the file reference is attached to req.body |
deleteOnError | boolean | — | true | Delete uploaded files if the request fails |
Related
- File Upload Setup — global configuration,
baseUploadDir,baseRoute, restrictions - File Upload Routes — standalone endpoints and request format
- Interceptors — before/after hooks for file upload operations