Framework Integrations

SolidJS Start

Full-stack SolidJS framework with Web Standards support - no adapter needed!

SolidJS Start

SolidJS Start is a full-stack SolidJS framework with built-in Web Standards support. Since SolidJS Start provides event.request as a Web Standard Request object, pushduck handlers work directly without any adapters!

Web Standards Native: SolidJS Start's API handlers receive event.request as a Web Standard Request object, making pushduck integration seamless with zero overhead.

Quick Setup

Install dependencies

npm install pushduck
yarn add pushduck
pnpm add pushduck
bun add pushduck

Configure upload router

src/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!,
  })
  .build();

export const uploadRouter = createS3Router({
  imageUpload: s3.image().max("5MB"),
  documentUpload: s3.file().max("10MB")
});

export type AppUploadRouter = typeof uploadRouter;

Create API route

src/routes/api/upload/[...path].ts
import { APIEvent } from '@solidjs/start/server';
import { uploadRouter } from '~/lib/upload';

export async function GET(event: APIEvent) {
  return uploadRouter.handlers(event.request);
}

export async function POST(event: APIEvent) {
  return uploadRouter.handlers(event.request);
}

Basic Integration

API Route Handler

src/routes/api/upload/[...path].ts
import { APIEvent } from '@solidjs/start/server';
import { uploadRouter } from '~/lib/upload';

// Method 1: Individual handlers
export async function GET(event: APIEvent) {
  return uploadRouter.handlers.GET(event.request);
}

export async function POST(event: APIEvent) {
  return uploadRouter.handlers.POST(event.request);
}

// Method 2: Combined handler (alternative approach)
// export async function handler(event: APIEvent) {
//   return uploadRouter.handlers(event.request);
// }

With CORS Middleware

src/routes/api/upload/[...path].ts
import { APIEvent } from '@solidjs/start/server';
import { uploadRouter } from '~/lib/upload';

function addCORSHeaders(response: Response) {
  response.headers.set('Access-Control-Allow-Origin', '*');
  response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  return response;
}

export async function GET(event: APIEvent) {
  const response = await uploadRouter.handlers.GET(event.request);
  return addCORSHeaders(response);
}

export async function POST(event: APIEvent) {
  const response = await uploadRouter.handlers.POST(event.request);
  return addCORSHeaders(response);
}

export async function OPTIONS() {
  return new Response(null, {
    status: 200,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    }
  });
}

Advanced Configuration

Authentication with SolidJS Start

src/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({
  // Private uploads with authentication
  privateUpload: s3
    .image()
    .max("5MB")
    .middleware(async ({ req }) => {
      const authHeader = req.headers.get('authorization');
      if (!authHeader?.startsWith('Bearer ')) {
        throw new Error('Authorization required');
      }

      const token = authHeader.substring(7);
      
      try {
        const user = await verifyJWT(token);
        return {
          userId: user.id,
          userRole: user.role
        };
      } catch (error) {
        throw new Error('Invalid token');
      }
    }),

  // Public uploads (no auth)
  publicUpload: s3
    .image()
    .max("2MB")
    // No middleware = public access
});

async function verifyJWT(token: string) {
  // Your JWT verification logic here
  return { id: 'user-123', role: 'user' };
}

export type AppUploadRouter = typeof uploadRouter;

Protected API Routes

src/routes/api/upload/[...path].ts
import { APIEvent } from '@solidjs/start/server';
import { uploadRouter } from '~/lib/upload';

async function requireAuth(request: Request) {
  const authHeader = request.headers.get('authorization');
  if (!authHeader?.startsWith('Bearer ')) {
    throw new Response(JSON.stringify({ error: 'Authorization required' }), {
      status: 401,
      headers: { 'Content-Type': 'application/json' }
    });
  }
  
  const token = authHeader.substring(7);
  // Verify token logic here
  return { userId: 'user-123' };
}

export async function GET(event: APIEvent) {
  await requireAuth(event.request);
  return uploadRouter.handlers.GET(event.request);
}

export async function POST(event: APIEvent) {
  await requireAuth(event.request);
  return uploadRouter.handlers.POST(event.request);
}

Client-Side Integration

Upload Component

src/components/UploadDemo.tsx
import { createSignal } from 'solid-js';
import { createUploadClient } from 'pushduck/client';
import type { AppUploadRouter } from '~/lib/upload';

const uploadClient = createUploadClient<AppUploadRouter>({
  baseUrl: import.meta.env.DEV 
    ? 'http://localhost:3000' 
    : 'https://your-domain.com'
});

export function UploadDemo() {
  const [uploading, setUploading] = createSignal(false);
  const [results, setResults] = createSignal<any[]>([]);

  const handleUpload = async (files: File[]) => {
    if (!files.length) return;
    
    setUploading(true);
    try {
      const uploadResults = await uploadClient.upload('imageUpload', {
        files,
        metadata: { userId: 'demo-user' }
      });
      
      setResults(uploadResults);
      console.log('Upload successful:', uploadResults);
    } catch (error) {
      console.error('Upload failed:', error);
    } finally {
      setUploading(false);
    }
  };

  return (
    <div class="space-y-4">
      <div>
        <input
          type="file"
          multiple
          accept="image/*"
          onChange={(e) => {
            const files = e.target.files;
            if (files) {
              handleUpload(Array.from(files));
            }
          }}
          disabled={uploading()}
          class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"
        />
      </div>
      
      {uploading() && (
        <div class="text-blue-600">Uploading...</div>
      )}
      
      {results().length > 0 && (
        <div class="space-y-2">
          <h3 class="font-medium">Upload Results:</h3>
          {results().map((result, index) => (
            <div key={index} class="p-2 bg-gray-50 rounded">
              <div class="text-sm">
                <strong>File:</strong> {result.name}
              </div>
              <div class="text-sm">
                <strong>URL:</strong> 
                <a href={result.url} target="_blank" rel="noopener noreferrer" class="text-blue-600 hover:underline ml-1">
                  {result.url}
                </a>
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

Page Integration

src/routes/upload.tsx
import { Title } from '@solidjs/meta';
import { UploadDemo } from '~/components/UploadDemo';

export default function UploadPage() {
  return (
    <>
      <Title>File Upload Demo</Title>
      <div class="container mx-auto px-4 py-8">
        <h1 class="text-3xl font-bold mb-8">File Upload Demo</h1>
        <div class="max-w-md mx-auto">
          <UploadDemo />
        </div>
      </div>
    </>
  );
}

Full Application Example

Project Structure

src/
├── components/
│   └── UploadDemo.tsx
├── lib/
│   └── upload.ts
├── routes/
│   ├── api/
│   │   └── upload/
│   │       └── [...path].ts
│   ├── upload.tsx
│   └── (home).tsx
├── app.tsx
└── entry-server.tsx

Root Layout

src/app.tsx
import { Router } from '@solidjs/router';
import { FileRoutes } from '@solidjs/start/router';
import { Suspense } from 'solid-js';
import './app.css';

export default function App() {
  return (
    <Router
      root={props => (
        <div class="min-h-screen bg-gray-50">
          <nav class="bg-white shadow">
            <div class="container mx-auto px-4 py-4">
              <h1 class="text-xl font-semibold">Upload Demo</h1>
            </div>
          </nav>
          <main>
            <Suspense>{props.children}</Suspense>
          </main>
        </div>
      )}
    >
      <FileRoutes />
    </Router>
  );
}

Home Page

src/routes/(home).tsx
import { A } from '@solidjs/router';
import { Title } from '@solidjs/meta';

export default function Home() {
  return (
    <>
      <Title>SolidJS Start + Pushduck</Title>
      <div class="container mx-auto px-4 py-8">
        <div class="text-center">
          <h1 class="text-4xl font-bold text-gray-900 mb-4">
            SolidJS Start + Pushduck
          </h1>
          <p class="text-xl text-gray-600 mb-8">
            High-performance file uploads with SolidJS
          </p>
          <A 
            href="/upload" 
            class="inline-block bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors"
          >
            Try Upload Demo
          </A>
        </div>
      </div>
    </>
  );
}

Performance Benefits

🚀 Zero Overhead

Direct Web Standards integration

No adapter layer means zero performance overhead - pushduck handlers run directly in SolidJS Start.

⚡ SolidJS Performance

Fastest reactive framework

SolidJS provides exceptional performance with fine-grained reactivity and no virtual DOM.

📦 Full-Stack TypeScript

End-to-end type safety

Complete type safety from server to client with shared types.

🔧 File-Based Routing

Intuitive API structure

Clean, organized API routes with SolidJS Start's file-based routing system.

Deployment

Vercel Deployment

app.config.ts
import { defineConfig } from '@solidjs/start/config';

export default defineConfig({
  server: {
    preset: 'vercel'
  }
});

Netlify Deployment

app.config.ts
import { defineConfig } from '@solidjs/start/config';

export default defineConfig({
  server: {
    preset: 'netlify'
  }
});

Node.js Deployment

app.config.ts
import { defineConfig } from '@solidjs/start/config';

export default defineConfig({
  server: {
    preset: 'node-server'
  }
});

Docker Deployment

Dockerfile
FROM node:18-alpine as base
WORKDIR /app

# Install dependencies
COPY package*.json ./
RUN npm ci --only=production

# Copy source code
COPY . .

# Build the app
RUN npm run build

# Expose port
EXPOSE 3000

# Start the app
CMD ["npm", "start"]

SolidJS + Pushduck: The perfect combination of SolidJS's exceptional performance and pushduck's universal design for lightning-fast file upload experiences.