# Quickstart (https://s3.dimah.dev/docs/quickstart)



## Create a new project [#create-a-new-project]

Scaffold a full-stack project with preconfigured S3 routes and UI:

<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
    npx @dimah-s3/cli@latest create
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm dlx @dimah-s3/cli@latest create
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn dlx @dimah-s3/cli@latest create
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun x @dimah-s3/cli@latest create
    ```
  </CodeBlockTab>
</CodeBlockTabs>

***

## Manual installation [#manual-installation]

<div className="fd-steps">
  <div className="fd-step">
    ### Install dependencies [#install-dependencies-step]

    <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 @dimah-s3/react @aws-sdk/client-s3
        ```
      </CodeBlockTab>

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

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

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

  <div className="fd-step">
    ### Configure S3 and routes [#configure-s3-and-routes-step]

    Define your AWS S3 client and instance. In this example, we create an `avatar` route for user profile photos:

    ```dotenv title=".env"
    S3_ENDPOINT="https://your-endpoint.example.com"
    S3_REGION="auto"
    S3_ACCESS_KEY_ID="your-access-key-id"
    S3_SECRET_ACCESS_KEY="your-secret-access-key"
    S3_BUCKET="your-bucket-name"
    ```

    ```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
          },
        }),
      },
    });
    ```
  </div>

  <div className="fd-step">
    ### Mount the route handler [#mount-the-route-handler-step]

    <Tabs items="[&#x22;Next.js&#x22;, &#x22;Vite&#x22;, &#x22;Express&#x22;, &#x22;Hono&#x22;, &#x22;SvelteKit&#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="Vite">
        ```ts title="server/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="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>
    </Tabs>
  </div>

  <div className="fd-step">
    ### Create the client client & provider [#create-the-client-client--provider-step]

    ```ts title="lib/s3-client.ts"
    "use client";

    import { createS3Client } from "@dimah-s3/react";

    export const s3Client = createS3Client();
    export const S3Provider = s3Client.Provider;
    ```
  </div>

  <div className="fd-step">
    ### Wrap your application [#wrap-your-application-step]

    <Tabs items="[&#x22;npm package&#x22;, &#x22;shadcn registry&#x22;]">
      <Tab value="npm package">
        <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/ui shadcn
            ```
          </CodeBlockTab>

          <CodeBlockTab value="pnpm">
            ```bash
            pnpm add @dimah-s3/ui shadcn
            ```
          </CodeBlockTab>

          <CodeBlockTab value="yarn">
            ```bash
            yarn add @dimah-s3/ui shadcn
            ```
          </CodeBlockTab>

          <CodeBlockTab value="bun">
            ```bash
            bun add @dimah-s3/ui shadcn
            ```
          </CodeBlockTab>
        </CodeBlockTabs>

        ```css title="app/globals.css"
        @import "shadcn/tailwind.css";
        @import "@dimah-s3/ui/styles.css";

        /* + shadcn theme variables (`--primary`, `--muted`, …) */

        /* Optional: theme dimah-s3 independently of the rest of the app
        @theme {
          --color-dimah-s3-primary: oklch(0.65 0.15 150);
        }
        */

        ```

        ```tsx title="app/layout.tsx"
        import { Toaster } from "@dimah-s3/ui";
        import { S3Provider } from "@/lib/s3-client";

        export default function RootLayout({
          children,
        }: {
          children: React.ReactNode;
        }) {
          return (
            <html lang="en">
              <body>
                <S3Provider>{children}</S3Provider>
                <Toaster />
              </body>
            </html>
          );
        }
        ```
      </Tab>

      <Tab value="shadcn registry">
        ```json title="components.json"
        {
          "registries": {
            "@dimah-s3": "https://s3.dimah.dev/r/{name}.json"
          }
        }
        ```

        <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
            npx shadcn@latest add @dimah-s3/upload-button
            ```
          </CodeBlockTab>

          <CodeBlockTab value="pnpm">
            ```bash
            pnpm dlx shadcn@latest add @dimah-s3/upload-button
            ```
          </CodeBlockTab>

          <CodeBlockTab value="yarn">
            ```bash
            yarn dlx shadcn@latest add @dimah-s3/upload-button
            ```
          </CodeBlockTab>

          <CodeBlockTab value="bun">
            ```bash
            bun x shadcn@latest add @dimah-s3/upload-button
            ```
          </CodeBlockTab>
        </CodeBlockTabs>

        ```tsx title="app/layout.tsx"
        import { Toaster } from "@/components/ui/toast";
        import { S3Provider } from "@/lib/s3-client";

        export default function RootLayout({
          children,
        }: {
          children: React.ReactNode;
        }) {
          return (
            <html lang="en">
              <body>
                <S3Provider>{children}</S3Provider>
                <Toaster />
              </body>
            </html>
          );
        }
        ```
      </Tab>
    </Tabs>
  </div>

  <div className="fd-step">
    ### Add an upload button [#add-an-upload-button-step]

    ```tsx title="app/page.tsx"
    "use client";

    import { useUpload } from "@dimah-s3/react";
    import { UploadButton } from "@dimah-s3/ui";

    export default function AvatarUploadPage() {
      const upload = useUpload({ route: "avatar" });

      return <UploadButton upload={upload} />;
    }
    ```

    Selecting an image presigns a secure URL and uploads directly to your bucket under `avatar/{uuid}/{filename}`.
  </div>
</div>

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

<Accordions>
  <Accordion title="How do I restrict uploads to authenticated users?">
    Add a `guard` on the route or instance. Throw `errors.unauthorized()` or `errors.forbidden()` if the session is invalid:

    ```ts title="lib/s3.ts"
    import { errors, route } from "@dimah-s3/server";

    export const s3 = dimahS3({
      // ...
      routes: {
        avatar: route({
          guard: async ({ request }) => {
            const session = await getSession(request);
            if (!session) throw errors.unauthorized();
          },
          upload: {
            fileTypes: ["image/*"],
            maxFileSize: 2 * 1024 * 1024,
          },
        }),
      },
    });
    ```
  </Accordion>

  <Accordion title="How do I save the uploaded avatar key to my database?">
    Handle `onConfirmed` on the server or `onSuccess` on the client:

    ```ts title="Server-side (recommended)"
    avatar: route({
      upload: {
        fileTypes: ["image/*"],
        maxFileSize: 2 * 1024 * 1024,
        onConfirmed: async ({ key, request }) => {
          const session = await getSession(request);
          await db.user.update({
            where: { id: session.userId },
            data: { avatarKey: key },
          });
        },
      },
    });
    ```

    ```tsx title="Client-side"
    const upload = useUpload({
      route: "avatar",
      onSuccess: (results) => {
        console.log("Uploaded avatar key:", results[0]?.key);
      },
    });
    ```
  </Accordion>

  <Accordion title="How do I use Cloudflare R2 instead of AWS S3?">
    Cloudflare R2 requires `upload: { method: "PUT" }` because it does not support presigned POST:

    ```ts title="lib/s3.ts"
    export const s3 = dimahS3({
      client: r2Client,
      bucket: process.env.R2_BUCKET!,
      routes: {
        avatar: route({
          upload: {
            method: "PUT",
            fileTypes: ["image/*"],
            maxFileSize: 2 * 1024 * 1024,
          },
        }),
      },
    });
    ```

    See the [Cloudflare R2 guide](https://s3.dimah.dev/docs/providers/cloudflare-r2) for full details.
  </Accordion>

  <Accordion title="How do I display or preview the uploaded image?">
    Use `useObjectUrl` to generate a temporary display URL, or construct your CDN URL if the bucket is public:

    ```tsx title="components/avatar-preview.tsx"
    "use client";

    import { useObjectUrl } from "@dimah-s3/react";

    export function AvatarPreview({ avatarKey }: { avatarKey: string }) {
      const { url } = useObjectUrl({
        route: "avatar",
        objectKey: avatarKey,
        disposition: "inline",
      });

      if (!url) return null;
      return (
        <img
          src={url}
          alt="User avatar"
          className="size-16 rounded-full object-cover"
        />
      );
    }
    ```
  </Accordion>
</Accordions>
