# Framework integrations (/docs/frameworks)



[`createRouteHandler`](/docs/route-handler) returns a plain
`(request: Request) => Promise<Response>`. Any framework that gives you a Web
`Request` and lets you return a `Response` can mount it as-is. Below are the common
ones.

In every case the handler holds your `S3Delivery` client (and your secret key),
and the browser uploader points its `endpoint` at the mounted path (default
`/api/s3delivery`).

## Next.js (App Router) [#nextjs-app-router]

Export the handler as the `POST` of a route segment.

```ts
// app/api/s3delivery/route.ts
import { S3Delivery, createRouteHandler } from "s3delivery";

export const POST = createRouteHandler(new S3Delivery(), {
  constraints: { maxFileSize: 10 * 1024 * 1024, allowedFileTypes: ["image/*"] },
});
```

The browser uploads to `/api/s3delivery` by default, so no `endpoint` is needed.
If you mount it elsewhere, set `endpoint` on the uploader:

```tsx
useUploadFiles({ endpoint: "/api/uploads" });
```

## Next.js (Pages Router) [#nextjs-pages-router]

The handler speaks the Web `Request`/`Response` API, but Pages Router API routes
use Node `req`/`res`. Prefer an App Router route segment as above. If you must use
the Pages Router, adapt the body yourself with `createUpload` / `complete` on the
[server client](/docs/server-client) instead of `createRouteHandler`.

## Hono [#hono]

Pass the raw request through and return the response:

```ts
import { Hono } from "hono";
import { S3Delivery, createRouteHandler } from "s3delivery";

const app = new Hono();
const handler = createRouteHandler(new S3Delivery());

app.post("/api/s3delivery", (c) => handler(c.req.raw));
```

## Remix [#remix]

Use the handler in an action.

```ts
// app/routes/api.s3delivery.tsx
import type { ActionFunctionArgs } from "@remix-run/node";
import { S3Delivery, createRouteHandler } from "s3delivery";

const handler = createRouteHandler(new S3Delivery());

export const action = ({ request }: ActionFunctionArgs) => handler(request);
```

## SvelteKit [#sveltekit]

```ts
// src/routes/api/s3delivery/+server.ts
import { S3Delivery, createRouteHandler } from "s3delivery";

const handler = createRouteHandler(new S3Delivery());

export const POST = ({ request }) => handler(request);
```

## Cloudflare Workers / Bun / Deno [#cloudflare-workers--bun--deno]

These runtimes hand you a `Request` and want a `Response`, so the handler is the
whole fetch handler — or one branch of it.

```ts
// Cloudflare Workers
import { S3Delivery, createRouteHandler } from "s3delivery";

const handler = createRouteHandler(new S3Delivery());

export default {
  async fetch(request: Request): Promise<Response> {
    const { pathname } = new URL(request.url);
    if (pathname === "/api/s3delivery") return handler(request);
    return new Response("Not found", { status: 404 });
  },
};
```

```ts
// Bun
import { S3Delivery, createRouteHandler } from "s3delivery";

const handler = createRouteHandler(new S3Delivery());

Bun.serve({
  fetch(request) {
    const { pathname } = new URL(request.url);
    if (pathname === "/api/s3delivery") return handler(request);
    return new Response("Not found", { status: 404 });
  },
});
```

## Express (and other Node frameworks) [#express-and-other-node-frameworks]

Express uses Node's `req`/`res`, not Web `Request`/`Response`. Either upgrade to a
Web-standard adapter, or skip `createRouteHandler` and broker the handshake with
the [server client](/docs/server-client) directly:

```ts
import express from "express";
import { S3Delivery } from "s3delivery";

const app = express();
const s3 = new S3Delivery();

app.use(express.json());

app.post("/api/s3delivery", async (req, res) => {
  try {
    if (req.body.action === "create") {
      res.json(await s3.createUpload(req.body));
    } else if (req.body.action === "complete") {
      res.json(
        await s3.complete(req.body.fileId, {
          uploadId: req.body.uploadId,
          parts: req.body.parts,
        }),
      );
    } else {
      res.status(400).json({ error: { code: "BAD_REQUEST", message: "Unknown action" } });
    }
  } catch (e) {
    res.status(500).json({ error: { code: "INTERNAL", message: "Internal error" } });
  }
});
```

This matches the body shape the browser uploader sends (`{ action, … }`), so the
client SDK works against it unchanged. Add your own auth and constraint checks
before `createUpload`.

## Server-only, no browser [#server-only-no-browser]

If you only ever upload from the server (generated files, agent output, fetched
assets), you do not need a route handler at all — just use
[`s3.upload(...)`](/docs/server-client#upload) directly.
