Pushduck
Pushduck// S3 uploads for any framework

Framework Integrations

Universal file uploads that work with any web framework - from Web Standards to custom request/response APIs

Supported Frameworks

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().maxFileSize("5MB"),
  documentUpload: s3.file().maxFileSize("10MB"),
  videoUpload: s3.file().maxFileSize("100MB").types(["video/*"])
});

// Universal handlers - work with ANY framework
export const { GET, POST } = uploadRouter.handlers;

Framework Categories

Pushduck supports frameworks in two categories:

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';
export const { GET, POST } = toNextJsHandler(uploadRouter.handlers);
import express from 'express';
import { uploadRouter } from '@/lib/upload';
import { toExpressHandler } from 'pushduck/adapters';

const app = express();
app.all("/api/upload/*", toExpressHandler(uploadRouter.handlers));
import Fastify from 'fastify';
import { uploadRouter } from '@/lib/upload';
import { toFastifyHandler } from 'pushduck/adapters';

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 Web Request
  • Elysia: context.request is a Web Request
  • Bun: Native Web Request support
  • TanStack Start: { request } is a Web Request
  • SolidJS Start: event.request is a Web Request

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:

lib/upload.ts
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()
    .maxFileSize("5MB")
    .formats(["jpeg", "png", "webp"])
    .middleware(async ({ req }) => {
      const userId = await getUserId(req);
      return { userId, category: "images" };
    }),

  // Document uploads
  documentUpload: s3
    .file()
    .maxFileSize("10MB")
    .types(["application/pdf", "text/plain"])
    .middleware(async ({ req }) => {
      const userId = await getUserId(req);
      return { userId, category: "documents" };
    }),

  // Video uploads
  videoUpload: s3
    .file()
    .maxFileSize("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:

lib/upload-client.ts
import { createUploadClient } from 'pushduck/client';
import type { AppUploadRouter } from './upload';

export const upload = createUploadClient<AppUploadRouter>({
  endpoint: '/api/upload'
});
components/upload-form.tsx
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:


Universal by Design: Write once, run anywhere. Pushduck's universal handlers make file uploads work across the entire JavaScript ecosystem.