# Files and images

A column that holds an uploaded file is declared on its table:

```ts
files: {
  photo: {
    dir: 'uploads/recipes', url: '/uploads/recipes/',
    accept: /^image\/(jpeg|png|webp)$/, maxBytes: 12_000_000, message: 'A JPEG, PNG or WebP of 12 MB at most',
    required: 'A photo is needed',                                    // on creation
    image: { width: 1600, height: 1600, format: 'webp', quality: 82 }, // processed before it is stored
    variants: { thumb: { width: 640, height: 480, fit: 'cover', format: 'webp' } }, // more sizes, each in its column
    record: { width: 'width', height: 'height', size: 'size', type: 'mime', name: 'original_name' },
  },
}
```

A multipart request (`FormData`) to a route writing that table carries the file
under the column's name; its text fields are values like a JSON body. The file
is checked, processed, stored under a random name, and the column set to
`url + name`. Replacing it or deleting the row removes the old file; a request
that fails its rules leaves nothing behind.

Image processing (`sharp`, from your dependencies — `npm install sharp`, or
`useImages(sharp)`): at most `width` × `height` (never enlarged, unless
`enlarge` or `fit: 'cover'`), re-encoded as `format` at `quality`, rotated
upright, and stripped of its metadata — a phone photo carries its GPS position
(`keepMetadata: true` to keep it). `transform(buffer, file)` for anything else.

Serving them back:

```ts
'GET /uploads/recipes/:name': serveFiles('uploads/recipes', { maxAge: 31536000, guard: (name, event) => {} }),
```

The path stays inside the folder, the type follows the extension, and
`nosniff` is set. A route with `download: row => ({ path, name, type })` sends a
row's file instead.
