# Routes (https://s3.dimah.dev/docs/server/routes)



A **route** is a named storage policy on your server. Each route manages its own constraints, object keys, and enabled operations (`upload`, `download`, `delete`, and `multipart`).

The client specifies the route name on every request. The server enforces file constraints, validates permissions, and generates object keys.

```ts title="lib/s3.ts"
import { S3Client } from "@aws-sdk/client-s3";
import { dimahS3, route } from "@dimah-s3/server";

export const awsS3 = new S3Client({/* env */});

export const s3 = dimahS3({
  client: awsS3,
  bucket: process.env.S3_BUCKET!,
  routes: {
    avatar: route({
      upload: {
        fileTypes: ["image/*"],
        maxFileSize: 2 * 1024 * 1024,
      },
      download: true,
      delete: true,
    }),
    document: route({
      upload: {
        fileTypes: ["application/pdf", "image/*"],
        maxFileSize: 10 * 1024 * 1024,
      },
      download: true,
      delete: true,
    }),
    video: route({
      upload: {
        fileTypes: ["video/*"],
        maxFileSize: 500 * 1024 * 1024,
        multipart: true,
      },
    }),
  },
});
```

***

## Combining operations [#combining-operations]

Enable operations on the **same route** when they manage the same objects. For example, if users upload, view, and remove their avatars:

```ts
avatar: route({
  upload: {
    fileTypes: ["image/*"],
    maxFileSize: 2 * 1024 * 1024,
  },
  download: true,
  delete: true,
}),
```

Follow-up operations (confirm, download, delete) must target keys within that route's namespace.

***

## Key generation & object identity [#key-generation--object-identity]

By default, the server generates object keys formatted as:

```
{keyPrefix}/{uuid}/{sanitizedFileName}
```

`keyPrefix` defaults to the route name (e.g. `avatar/…`).

To customize key generation or organize files by user/tenant, return `folder` or `key` from `upload.object`:

```ts
avatar: route({
  upload: {
    fileTypes: ["image/*"],
    maxFileSize: 2 * 1024 * 1024,
    object: async ({ request }) => {
      const session = await getSession(request);
      return {
        folder: `users/${session.userId}`,
        // Results in: avatar/users/{userId}/{uuid}/{fileName}
      };
    },
  },
}),
```

***

## Type reference [#type-reference]

```ts
import type {
  DimahS3RouteConfig,
  UploadConfig,
  DownloadConfig,
  DeleteConfig,
  MultipartConfig,
} from "@dimah-s3/server";
```

### DimahS3RouteConfig [#dimahs3routeconfig]

<AutoTypeTable path="packages/server/src/types/config.ts" name="DimahS3RouteConfig" />

***

### UploadConfig [#uploadconfig]

<AutoTypeTable path="packages/server/src/types/config.ts" name="UploadConfig" />

***

### DownloadConfig [#downloadconfig]

<AutoTypeTable path="packages/server/src/types/config.ts" name="DownloadConfig" />

***

### DeleteConfig [#deleteconfig]

<AutoTypeTable path="packages/server/src/types/config.ts" name="DeleteConfig" />

## Frequently asked questions [#frequently-asked-questions]

<Accordions>
  <Accordion title="How do I overwrite an existing avatar key instead of generating new UUIDs?">
    Set `replace: "overwrite"` and return a deterministic `key` from `object`:

    ```ts
    avatar: route({
      upload: {
        fileTypes: ["image/*"],
        maxFileSize: 2 * 1024 * 1024,
        replace: "overwrite",
        object: async ({ request }) => {
          const session = await getSession(request);
          return {
            key: `avatars/${session.userId}/avatar.png`,
          };
        },
      },
    }),
    ```
  </Accordion>

  <Accordion title="How do I clean up the user's previous avatar after a new upload?">
    Return `previousKey` from `upload.object`. Once the new upload confirms successfully, dimah-s3 will automatically delete the old object from S3:

    ```ts
    avatar: route({
      upload: {
        fileTypes: ["image/*"],
        maxFileSize: 2 * 1024 * 1024,
        object: async ({ request }) => {
          const user = await getCurrentUser(request);
          return {
            folder: `users/${user.id}`,
            previousKey: user.currentAvatarKey,
          };
        },
      },
    }),
    ```
  </Accordion>

  <Accordion title="How do I set storage class, Cache-Control, or object tags?">
    Return them from `upload.object`. They are signed into PUT, POST, and
    multipart init (and applied by `s3.put`) so the browser cannot change
    them. Tag values are visible to the client — do not put secrets there.

    ```ts
    avatar: route({
      upload: {
        fileTypes: ["image/*"],
        object: () => ({
          storageClass: "STANDARD_IA",
          cacheControl: "max-age=31536000, immutable",
          tagging: { purpose: "avatar" },
        }),
      },
    }),
    ```
  </Accordion>

  <Accordion title="How do I enforce SHA-256 integrity checksums?">
    Set `checksum: true` in your upload config. The React client will automatically compute the SHA-256 hash in the browser and attach it to the presigned request:

    ```ts
    avatar: route({
      upload: {
        fileTypes: ["image/*"],
        maxFileSize: 2 * 1024 * 1024,
        checksum: true,
      },
    }),
    ```
  </Accordion>

  <Accordion title="How do I stream downloads through my server without direct bucket URLs?">
    Set `mode: "proxy"` on `download`. The download URL will point to a same-origin `/api/s3/file` endpoint that streams the object through your backend:

    ```ts
    avatar: route({
      download: {
        mode: "proxy",
      },
    }),
    ```
  </Accordion>
</Accordions>
