SolidJS Start
Full-stack SolidJS framework with Web Standards support - no adapter needed!
Using pushduck with 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 pushduckyarn add pushduckpnpm add pushduckbun add pushduckConfigure upload router
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().maxFileSize("5MB"),
documentUpload: s3.file().maxFileSize("10MB")
});
export type AppUploadRouter = typeof uploadRouter;Create API route
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
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
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
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()
.maxFileSize("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()
.maxFileSize("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
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
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>Public URL:</strong>
<a href={result.url} target="_blank" rel="noopener noreferrer" class="text-blue-600 hover:underline ml-1">
{result.url}
</a>
</div>
{result.presignedUrl && (
<div class="text-sm">
<strong>Download URL:</strong>
<a href={result.presignedUrl} target="_blank" rel="noopener noreferrer" class="text-green-600 hover:underline ml-1">
{result.presignedUrl}
</a>
<span class="text-xs text-gray-500 ml-2">(expires in 1 hour)</span>
</div>
)}
</div>
))}
</div>
)}
</div>
);
}Page Integration
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.tsxRoot Layout
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
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
import { defineConfig } from '@solidjs/start/config';
export default defineConfig({
server: {
preset: 'vercel'
}
});Netlify Deployment
import { defineConfig } from '@solidjs/start/config';
export default defineConfig({
server: {
preset: 'netlify'
}
});Node.js Deployment
import { defineConfig } from '@solidjs/start/config';
export default defineConfig({
server: {
preset: 'node-server'
}
});Docker Deployment
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.