# Setup (https://s3.dimah.dev/docs/server/setup)



## Install [#install]

<CodeBlockTabs defaultValue="npm">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npm i @dimah-s3/server @aws-sdk/client-s3
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm add @dimah-s3/server @aws-sdk/client-s3
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn add @dimah-s3/server @aws-sdk/client-s3
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun add @dimah-s3/server @aws-sdk/client-s3
    ```
  </CodeBlockTab>
</CodeBlockTabs>

## Configure S3 & routes [#configure-s3--routes]

Initialize `dimahS3` by passing your `@aws-sdk/client-s3` instance and defining named routes.

Operations (`upload`, `download`, `delete`, `multipart`) are disabled by default. Enable only what each route needs.

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

export const awsS3 = new S3Client({
  region: process.env.S3_REGION,
  endpoint: process.env.S3_ENDPOINT,
  credentials: {
    accessKeyId: process.env.S3_ACCESS_KEY_ID!,
    secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
  },
});

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

***

## Mount route handler [#mount-route-handler]

Adapters are available for all major JavaScript runtimes:

<Tabs items="[&#x22;Next.js&#x22;, &#x22;Express&#x22;, &#x22;Hono&#x22;, &#x22;SvelteKit&#x22;, &#x22;Fastify&#x22;, &#x22;Elysia&#x22;, &#x22;Node&#x22;, &#x22;Web&#x22;]">
  <Tab value="Next.js">
    ```ts title="app/api/s3/[...s3]/route.ts"
    import { toNextJsHandler } from "@dimah-s3/server/next";
    import { s3 } from "@/lib/s3";

    export const { GET, POST, PUT, PATCH, DELETE } = toNextJsHandler(s3);
    ```
  </Tab>

  <Tab value="Express">
    ```ts title="server.ts"
    import express from "express";
    import { toExpressHandler } from "@dimah-s3/server/express";
    import { s3 } from "./s3";

    const app = express();
    app.all("/api/s3/*", toExpressHandler(s3));
    app.use(express.json());
    ```
  </Tab>

  <Tab value="Hono">
    ```ts title="src/index.ts"
    import { Hono } from "hono";
    import { toHonoHandler } from "@dimah-s3/server/hono";
    import { s3 } from "./s3";

    const app = new Hono();
    app.on(
      ["GET", "POST", "PUT", "PATCH", "DELETE"],
      "/api/s3/*",
      toHonoHandler(s3),
    );
    ```
  </Tab>

  <Tab value="SvelteKit">
    ```ts title="src/routes/api/s3/[...path]/+server.ts"
    import { toSvelteKitHandler } from "@dimah-s3/server/svelte-kit";
    import { s3 } from "$lib/s3";

    const handler = toSvelteKitHandler(s3);
    export const GET = handler;
    export const POST = handler;
    export const PUT = handler;
    export const PATCH = handler;
    export const DELETE = handler;
    ```
  </Tab>

  <Tab value="Fastify">
    ```ts title="server.ts"
    import Fastify from "fastify";
    import { toFastifyHandler } from "@dimah-s3/server/fastify";
    import { s3 } from "./s3";

    const app = Fastify();
    app.all("/api/s3/*", toFastifyHandler(s3));
    ```
  </Tab>

  <Tab value="Elysia">
    ```ts title="src/index.ts"
    import { Elysia } from "elysia";
    import { toElysiaHandler } from "@dimah-s3/server/elysia";
    import { s3 } from "./s3";

    new Elysia().all("/api/s3/*", toElysiaHandler(s3)).listen(3000);
    ```
  </Tab>

  <Tab value="Node">
    ```ts title="server.ts"
    import { createServer } from "node:http";
    import { toNodeHandler } from "@dimah-s3/server/node";
    import { s3 } from "./s3";

    const handler = toNodeHandler(s3);

    createServer((req, res) => {
      if (req.url?.startsWith("/api/s3")) return handler(req, res);
      res.statusCode = 404;
      res.end("Not found");
    }).listen(3000);
    ```
  </Tab>

  <Tab value="Web">
    ```ts
    export default {
      fetch(request: Request) {
        const url = new URL(request.url);
        if (url.pathname.startsWith("/api/s3")) {
          return s3.handler(request);
        }
        return new Response("Not found", { status: 404 });
      },
    };
    ```
  </Tab>
</Tabs>

***

## Server-side API (`s3.api`) [#server-side-api-s3api]

You can invoke S3 actions directly on the server without going through HTTP endpoints. Pass request `headers` so that route guards and auth hooks execute properly:

```ts title="app/actions.ts"
"use server";

import { headers } from "next/headers";
import { s3 } from "@/lib/s3";

export async function getAvatarDownloadUrl(key: string) {
  return s3.api.download({
    query: { route: "avatar", key },
    headers: await headers(),
  });
}
```

To upload files directly from the server (e.g. background jobs, image processing) while still executing route validation and `onConfirmed` hooks:

```ts
await s3.put({
  route: "avatar",
  fileName: "profile.png",
  contentType: "image/png",
  body: imageBuffer,
  headers: await headers(),
});
```

***

## Type reference [#type-reference]

```ts
import type { DimahS3Config, DimahS3Logger } from "@dimah-s3/server";
```

### DimahS3Config [#dimahs3config]

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

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

<Accordions>
  <Accordion title="How do error handling and status codes work?">
    All handlers throw typed `APIError` objects with stable string codes and numeric HTTP status codes:

    * A plain `Error` thrown in `guard`, `*Guard`, or `object` maps to **403 Forbidden**.
    * A plain `Error` thrown in `onPresigned`, `onConfirmed`, or other `on*` hooks maps to **500 Internal Error**.
    * Use the `errors` helper (e.g. `throw errors.unauthorized()`) to specify exact codes.

    See [Errors](https://s3.dimah.dev/docs/server/errors) for the full list of error codes.
  </Accordion>

  <Accordion title="How does key generation and prefix isolation work?">
    By default, dimah-s3 isolates object keys by route prefix:

    ```
    avatar/{uuid}/{fileName}
    ```

    All follow-up operations (confirm, download, delete) must target keys within the route's `keyPrefix`. Any request targeting a key outside its prefix is rejected with `INVALID_KEY`.

    To customize the folder or key structure, use `upload.object` in your route definition.
  </Accordion>

  <Accordion title="How do I change the base path or support cross-origin APIs?">
    Set `basePath` when initializing `dimahS3`:

    ```ts
    export const s3 = dimahS3({
      basePath: "/api/storage",
      // ...
    });
    ```

    Make sure the client specifies the same `basePath` (or full `baseURL` if hosted on a separate domain):

    ```ts
    export const s3Client = createS3Client({
      baseURL: "https://api.example.com/api/storage",
    });
    ```
  </Accordion>
</Accordions>
