Build your own backend adapter
If none of our existing adapters fit your use case, you can easily build your own using our factory functions.
For demonstration purposes, we'll build an adapter for Bun.
While Bun has some extensions to the Fetch API, it is WinterCG compatible, so
you can also use the uploadthing/server
entrypoint as-is and work with the web
standard Request and Response objects.
The Bun request handlers gets a BunRequest
and a Server
object passed to
them, and should return a Response
object. Here's a simple example of a Bun
server that listens on /api/hello
and returns a JSON response.
Bun.serve({
routes: {
"/api/hello": (req, server) => Response.json({ message: "Hello, world!" }),
},
});
Define your adapter arguments
The first thing you need to do is define the arguments that your adapter will
receive. This is usually tied to the arguments that your framework receives. For
our example, we'll receive a BunRequest
and a Server
object. The
AdapterArgs
can be any record shape you want.
type AdapterArgs = {
req: BunRequest;
server: Server;
};
createBuilder
The createBuilder
function is used to create a file route builder that you
will use to define your file routes.
import { createBuilder } from "uploadthing/server";
const f = createBuilder<AdapterArgs>();
makeAdapterHandler
The makeAdapterHandler
function is used to create a request handler for your
adapter.
The first two arguments are functions that will take all the arguments that your framework provides. The first function should return your adapter arguments, and the second should return a web standard Request object.
import { makeAdapterHandler } from "uploadthing/server";
makeAdapterHandler<[BunRequest, Server], AdapterArgs>(
(req, server) => Effect.succeed({ req, server }),
(req) => Effect.succeed(req),
// ...
);
The third argument is an options object with your file router and configuration options. For information on what configuration can be passed, see the server API reference.
makeAdapterHandler<[BunRequest, Server], AdapterArgs>(
(req, server) => Effect.succeed({ req, server }),
(req) => Effect.succeed(req),
{
router,
// config: { ... },
},
);
example
Here's our full example:
import { Effect } from "effect";
import { createBuilder, makeAdapterHandler } from "uploadthing/server";
import type { FileRouter } from "uploadthing/server";
type AdapterArgs = {
req: BunRequest;
server: Server;
};
const f = createBuilder<AdapterArgs>();
const router = {
imageUploader: f({ image: { maxFileSize: "16MB" } })
.middleware((opts) => {
opts.req;
// ^? BunRequest
opts.server;
// ^? Server
return { userId: "user_123" };
})
.onUploadError((opts) => {
opts.req;
// ^? BunRequest
opts.server;
// ^? Server
})
.onUploadComplete((opts) => {
opts.req;
// ^? BunRequest
opts.server;
// ^? Server
}),
} satisfies FileRouter;
const requestHandler = makeAdapterHandler<[BunRequest, Server], AdapterArgs>(
(req, server) => Effect.succeed({ req, server }),
(req) => Effect.succeed(req),
{ router },
);
Bun.serve({
routes: {
"/api/uploadthing": requestHandler,
},
});