Overview
Universal file uploads that work with any web framework - from Web Standards to custom request/response APIs
Framework Integrations Overview
Pushduck provides universal file upload handlers that work with any web framework through a single, consistent API. Write your upload logic once and deploy it anywhere!
Universal Design: Pushduck uses Web Standards (Request/Response) at its core, making it compatible with both Web Standards frameworks and those with custom request/response APIs without framework-specific code.
🌟 Universal API
All frameworks use the same core API:
import { createS3Router, s3 } from 'pushduck/server';
const uploadRouter = createS3Router({
imageUpload: s3.image().max("5MB"),
documentUpload: s3.file().max("10MB"),
videoUpload: s3.file().max("100MB").types(["video/*"])
});
// Universal handlers - work with ANY framework
export const { GET, POST } = uploadRouter.handlers;
Framework Categories
Pushduck supports frameworks in two categories:
🚀 Web Standards Native
Modern frameworks using Web Request/Response
No adapter needed! Use uploadRouter.handlers
directly.
- Hono
- Elysia
- Bun Runtime
- TanStack Start
- SolidJS Start
🔧 Custom Request/Response APIs
Frameworks with their own request/response patterns
Simple adapters provided for seamless integration.
- Next.js (App & Pages Router)
- Express
- Fastify
Quick Start by Framework
// Works with: Hono, Elysia, Bun, TanStack Start, SolidJS Start
import { uploadRouter } from '@/lib/upload';
// Direct usage - no adapter needed!
app.all('/api/upload/*', (ctx) => {
return uploadRouter.handlers(ctx.request); // or c.req.raw
});
// app/api/upload/route.ts
import { uploadRouter } from '@/lib/upload';
// Direct usage (recommended)
export const { GET, POST } = uploadRouter.handlers;
// Or with explicit adapter for extra type safety
import { toNextJsHandler } from 'pushduck/adapters/nextjs';
export const { GET, POST } = toNextJsHandler(uploadRouter.handlers);
import express from 'express';
import { uploadRouter } from '@/lib/upload';
import { toExpressHandler } from 'pushduck/adapters/express';
const app = express();
app.all("/api/upload/*", toExpressHandler(uploadRouter.handlers));
import Fastify from 'fastify';
import { uploadRouter } from '@/lib/upload';
import { toFastifyHandler } from 'pushduck/adapters/fastify';
const fastify = Fastify();
fastify.all('/api/upload/*', toFastifyHandler(uploadRouter.handlers));
Why Universal Handlers Work
Web Standards Foundation
Pushduck is built on Web Standards (Request
and Response
objects) that are supported by all modern JavaScript runtimes.
// Core handler signature
type Handler = (request: Request) => Promise<Response>
Framework Compatibility
Modern frameworks expose Web Standard objects directly:
- Hono:
c.req.raw
is a WebRequest
- Elysia:
context.request
is a WebRequest
- Bun: Native Web
Request
support - TanStack Start:
{ request }
is a WebRequest
- SolidJS Start:
event.request
is a WebRequest
Framework Adapters
For frameworks with custom request/response APIs, simple adapters convert between formats:
// Express adapter example
export function toExpressHandler(handlers: UniversalHandlers) {
return async (req: Request, res: Response, next: NextFunction) => {
const webRequest = convertExpressToWebRequest(req);
const webResponse = await handlers[req.method](webRequest);
convertWebResponseToExpress(webResponse, res);
};
}
Configuration (Same for All Frameworks)
Your upload configuration is identical across all frameworks:
import { createUploadConfig } from 'pushduck/server';
const { s3, createS3Router } = createUploadConfig()
.provider("cloudflareR2",{
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
region: 'auto',
endpoint: process.env.AWS_ENDPOINT_URL!,
bucket: process.env.S3_BUCKET_NAME!,
accountId: process.env.R2_ACCOUNT_ID!,
})
.paths({
prefix: 'uploads',
generateKey: (file, metadata) => {
return `${metadata.userId}/${Date.now()}/${file.name}`;
}
})
.build();
export const uploadRouter = createS3Router({
// Image uploads with validation
imageUpload: s3
.image()
.max("5MB")
.formats(["jpeg", "png", "webp"])
.middleware(async ({ req }) => {
const userId = await getUserId(req);
return { userId, category: "images" };
}),
// Document uploads
documentUpload: s3
.file()
.max("10MB")
.types(["application/pdf", "text/plain"])
.middleware(async ({ req }) => {
const userId = await getUserId(req);
return { userId, category: "documents" };
}),
// Video uploads
videoUpload: s3
.file()
.max("100MB")
.types(["video/mp4", "video/quicktime"])
.middleware(async ({ req }) => {
const userId = await getUserId(req);
return { userId, category: "videos" };
})
});
export type AppUploadRouter = typeof uploadRouter;
Client Usage (Framework Independent)
The client-side code is identical regardless of your backend framework:
import { createUploadClient } from 'pushduck/client';
import type { AppUploadRouter } from './upload';
export const upload = createUploadClient<AppUploadRouter>({
endpoint: '/api/upload'
});
import { upload } from '@/lib/upload-client';
export function UploadForm() {
// Property-based access with full type safety
const { uploadFiles, files, isUploading } = upload.imageUpload();
const handleUpload = async (selectedFiles: File[]) => {
await uploadFiles(selectedFiles);
};
return (
<div>
<input
type="file"
multiple
onChange={(e) => handleUpload(Array.from(e.target.files || []))}
/>
{files.map(file => (
<div key={file.id}>
<span>{file.name}</span>
<progress value={file.progress} max={100} />
{file.url && <a href={file.url}>View</a>}
</div>
))}
</div>
);
}
Benefits of Universal Design
🔄 Framework Flexibility
Switch frameworks without rewriting upload logic
Migrate from Express to Hono or Next.js to Bun without changing your upload implementation.
⚡ Performance
Zero overhead for modern frameworks
Web Standards native frameworks get direct handler access with no adapter overhead.
🛠️ Developer Experience
One API to learn, works everywhere
Master pushduck once and use it with any framework in your toolkit.
🚀 Future Proof
Built on Web Standards
As more frameworks adopt Web Standards, they automatically work with pushduck.
Next Steps
Choose your framework integration guide:
Next.js
Complete guide for Next.js App Router and Pages Router
Hono
Fast, lightweight, built on Web Standards
Elysia
TypeScript-first framework with Bun
Express
Classic Node.js framework integration
Universal by Design: Write once, run anywhere. Pushduck's universal handlers make file uploads work seamlessly across the entire JavaScript ecosystem.