Upload
File upload support lives in a separate package so sharp is only loaded when you need it.
$pnpm add @datrix/api-upload
Pass an Upload instance to ApiPlugin via the upload option. The plugin injects a media schema and exposes /upload endpoints automatically.
Setup
import { Upload, LocalStorageProvider } from "@datrix/api-upload"
new ApiPlugin({
upload: new Upload({
provider: new LocalStorageProvider({
basePath: "./uploads",
baseUrl: "https://example.com/uploads",
} satisfies LocalProviderOptions),
} satisfies UploadOptions),
})
Endpoints
| Method | Path | Description |
|---|---|---|
POST | /api/upload | Upload a file (multipart/form-data) |
DELETE | /api/upload/:id | Delete a file and its variants |
GET | /api/upload | List media records (pagination, filtering, populate) |
GET | /api/upload/:id | Get a single media record |
GET requests fall through to normal CRUD — you get pagination, filtering, and populate for free.
Upload a file
POST /api/upload
Content-Type: multipart/form-data
file: <binary>
{
"data": {
"id": 1,
"filename": "1704067200000-abc123.webp",
"originalName": "photo.jpg",
"mimeType": "image/webp",
"size": 48200,
"key": "1704067200000-abc123.webp",
"url": "https://example.com/uploads/1704067200000-abc123.webp",
"variants": {
"thumbnail": {
"key": "1704067200000-abc123-thumbnail.webp",
"url": "...",
"width": 150,
"height": 150,
"size": 4100,
"mimeType": "image/webp"
},
"small": {
"key": "1704067200000-abc123-small.webp",
"url": "...",
"width": 320,
"height": 213,
"size": 12800,
"mimeType": "image/webp"
}
}
}
}
Storage providers
Local
Stores files on the local filesystem.
new LocalStorageProvider({
basePath: "./uploads", // directory to write files into
baseUrl: "https://example.com/uploads", // public URL prefix
ensureDirectory: true, // create basePath if missing — default: true
} satisfies LocalProviderOptions)
S3
Stores files in any S3-compatible object storage (AWS S3, R2, MinIO, etc.).
import { S3StorageProvider } from "@datrix/api-upload"
new S3StorageProvider({
bucket: "my-bucket",
region: "us-east-1",
accessKeyId: "...",
secretAccessKey: "...",
endpoint: "https://...", // custom endpoint for R2 / MinIO
pathPrefix: "uploads/", // optional key prefix
} satisfies S3ProviderOptions)
Format conversion
Convert every uploaded image to a target format before storage. The original file is discarded — only the converted version is stored.
new Upload({
provider,
format: "webp", // "webp" | "jpeg" | "png" | "avif"
quality: 80, // 1–100, applies to jpeg / webp / avif — default: 80
} satisfies UploadOptions)
Resolution variants
Generate resized copies of every uploaded image. Each variant is uploaded via the same provider and stored in the variants JSON field on the media record.
new Upload({
provider,
format: "webp",
resolutions: {
thumbnail: { width: 150, height: 150, fit: "cover" } satisfies ResolutionConfig,
small: { width: 320 } satisfies ResolutionConfig, // height auto — preserves aspect ratio
medium: { width: 640 } satisfies ResolutionConfig,
},
} satisfies UploadOptions)
fit options (when both width and height are set)
| Value | Description |
|---|---|
cover | Crop to fill the box exactly |
contain | Fit within the box, letterbox if needed |
fill | Stretch to fill — ignores aspect ratio |
inside | Resize so both dimensions fit inside the box |
outside | Resize so one dimension fills the box |
Validation
new Upload({
provider,
maxSize: 5 * 1024 * 1024, // 5 MB limit
allowedMimeTypes: ["image/*", "application/pdf"],
} satisfies UploadOptions)
allowedMimeTypes supports wildcards (image/*) and exact types (image/png).
Media schema
When upload is configured, ApiPlugin automatically registers a media schema with these fields:
| Field | Type | Description |
|---|---|---|
filename | string | Generated unique filename |
originalName | string | Original filename from the upload |
mimeType | string | MIME type after any conversion |
size | number | File size in bytes |
key | string | Storage key — used to build the URL and delete the file |
url | string | Not stored. Injected at response time via provider.getUrl(key) |
variants | json | Map of resolution name → MediaVariant (each variant stores key, not url) |
Configuration reference
| Option | Type | Default | Description |
|---|---|---|---|
provider | StorageProvider | — | Required. Storage backend. |
modelName | string | "media" | Schema and table name for media records. |
format | "webp" | "jpeg" | "png" | "avif" | — | Convert all images to this format. |
quality | number | 80 | Compression quality (1–100). |
maxSize | number | — | Maximum file size in bytes. |
allowedMimeTypes | string[] | — | Allowed MIME types. Supports wildcards. |
resolutions | Record<string, ResolutionConfig> | — | Named resolution variants to generate. |
permission | SchemaPermission | — | Permission config for the media schema. |