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

MethodPathDescription
POST/api/uploadUpload a file (multipart/form-data)
DELETE/api/upload/:idDelete a file and its variants
GET/api/uploadList media records (pagination, filtering, populate)
GET/api/upload/:idGet 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)

ValueDescription
coverCrop to fill the box exactly
containFit within the box, letterbox if needed
fillStretch to fill — ignores aspect ratio
insideResize so both dimensions fit inside the box
outsideResize 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:

FieldTypeDescription
filenamestringGenerated unique filename
originalNamestringOriginal filename from the upload
mimeTypestringMIME type after any conversion
sizenumberFile size in bytes
keystringStorage key — used to build the URL and delete the file
urlstringNot stored. Injected at response time via provider.getUrl(key)
variantsjsonMap of resolution name → MediaVariant (each variant stores key, not url)

Configuration reference

OptionTypeDefaultDescription
providerStorageProviderRequired. Storage backend.
modelNamestring"media"Schema and table name for media records.
format"webp" | "jpeg" | "png" | "avif"Convert all images to this format.
qualitynumber80Compression quality (1–100).
maxSizenumberMaximum file size in bytes.
allowedMimeTypesstring[]Allowed MIME types. Supports wildcards.
resolutionsRecord<string, ResolutionConfig>Named resolution variants to generate.
permissionSchemaPermissionPermission config for the media schema.