Framework Integrations

Express

Popular Node.js framework integration with pushduck using adapters for req/res API

Express

Express uses the traditional Node.js req/res API pattern. Pushduck provides a simple adapter that converts Web Standard handlers to Express middleware format.

Custom Request/Response API: Express uses req/res objects instead of Web Standards, so pushduck provides the toExpressHandler adapter for seamless integration.

Quick Setup

Install dependencies

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

Configure upload router

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 Express server with upload routes

server.ts
import express from 'express';
import { uploadRouter } from './lib/upload';
import { toExpressHandler } from 'pushduck/adapters/express';

const app = express();

// Convert pushduck handlers to Express middleware
app.all('/api/upload/*', toExpressHandler(uploadRouter.handlers));

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

Basic Integration

Simple Upload Route

server.ts
import express from 'express';
import cors from 'cors';
import { uploadRouter } from './lib/upload';
import { toExpressHandler } from 'pushduck/adapters/express';

const app = express();

// Middleware
app.use(cors());
app.use(express.json());

// Upload routes using adapter
app.all('/api/upload/*', toExpressHandler(uploadRouter.handlers));

// Health check
app.get('/health', (req, res) => {
  res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`🚀 Server running on http://localhost:${port}`);
});

With Authentication Middleware

server.ts
import express from 'express';
import jwt from 'jsonwebtoken';
import { uploadRouter } from './lib/upload';
import { toExpressHandler } from 'pushduck/adapters/express';

const app = express();

app.use(express.json());

// Authentication middleware
const authenticateToken = (req: express.Request, res: express.Response, next: express.NextFunction) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.sendStatus(401);
  }

  jwt.verify(token, process.env.JWT_SECRET!, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
};

// Public upload route (no auth)
app.all('/api/upload/public/*', toExpressHandler(uploadRouter.handlers));

// Private upload route (with auth)
app.all('/api/upload/private/*', authenticateToken, toExpressHandler(uploadRouter.handlers));

app.listen(3000);

Advanced Configuration

Upload Configuration with Express Context

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({
  // Profile pictures with authentication
  profilePicture: s3
    .image()
    .max("2MB")
    .count(1)
    .formats(["jpeg", "png", "webp"])
    .middleware(async ({ req }) => {
      // Extract user from JWT token in Authorization header
      const authHeader = req.headers.get('authorization');
      if (!authHeader?.startsWith('Bearer ')) {
        throw new Error('Authentication required');
      }

      const token = authHeader.substring(7);
      const user = await verifyJWT(token);
      
      return {
        userId: user.id,
        userRole: user.role,
        category: "profile"
      };
    }),

  // Document uploads for authenticated users
  documents: s3
    .file()
    .max("10MB")
    .count(5)
    .types([
      "application/pdf",
      "application/msword",
      "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
      "text/plain"
    ])
    .middleware(async ({ req }) => {
      const authHeader = req.headers.get('authorization');
      if (!authHeader?.startsWith('Bearer ')) {
        throw new Error('Authentication required');
      }

      const token = authHeader.substring(7);
      const user = await verifyJWT(token);
      
      return {
        userId: user.id,
        category: "documents"
      };
    }),

  // Public uploads (no authentication)
  publicImages: s3
    .image()
    .max("1MB")
    .count(1)
    .formats(["jpeg", "png"])
    // No middleware = public access
});

async function verifyJWT(token: string) {
  // Your JWT verification logic
  const jwt = await import('jsonwebtoken');
  return jwt.verify(token, process.env.JWT_SECRET!) as any;
}

export type AppUploadRouter = typeof uploadRouter;

Complete Express Application

server.ts
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
import { uploadRouter } from './lib/upload';
import { toExpressHandler } from 'pushduck/adapters/express';

const app = express();

// Security middleware
app.use(helmet());
app.use(cors({
  origin: process.env.NODE_ENV === 'production' 
    ? ['https://your-domain.com'] 
    : ['http://localhost:3000'],
  credentials: true
}));

// Rate limiting
const uploadLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
  message: 'Too many upload requests from this IP, please try again later.',
  standardHeaders: true,
  legacyHeaders: false,
});

// Body parsing middleware
app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ extended: true, limit: '50mb' }));

// Logging middleware
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
  next();
});

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({
    status: 'healthy',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    memory: process.memoryUsage(),
    version: process.env.npm_package_version || '1.0.0'
  });
});

// API info endpoint
app.get('/api', (req, res) => {
  res.json({
    name: 'Express Upload API',
    version: '1.0.0',
    endpoints: {
      health: '/health',
      upload: '/api/upload/*'
    },
    uploadTypes: [
      'profilePicture - Single profile picture (2MB max)',
      'documents - PDF, Word, text files (10MB max, 5 files)',
      'publicImages - Public images (1MB max)'
    ]
  });
});

// Upload routes with rate limiting
app.all('/api/upload/*', uploadLimiter, toExpressHandler(uploadRouter.handlers));

// 404 handler
app.use('*', (req, res) => {
  res.status(404).json({
    error: 'Not Found',
    message: `Route ${req.originalUrl} not found`,
    timestamp: new Date().toISOString()
  });
});

// Error handler
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
  console.error('Express error:', err);
  
  res.status(500).json({
    error: 'Internal Server Error',
    message: process.env.NODE_ENV === 'development' ? err.message : 'Something went wrong',
    timestamp: new Date().toISOString()
  });
});

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`🚀 Express server running on http://localhost:${port}`);
  console.log(`📁 Upload endpoint: http://localhost:${port}/api/upload`);
});

Project Structure

server.ts
package.json
.env
Dockerfile

Modular Route Organization

Separate Upload Routes

routes/uploads.ts
import { Router } from 'express';
import { uploadRouter } from '../lib/upload';
import { toExpressHandler } from 'pushduck/adapters/express';
import { authenticateToken } from '../middleware/auth';

const router = Router();

// Public uploads
router.all('/public/*', toExpressHandler(uploadRouter.handlers));

// Private uploads (requires authentication)
router.all('/private/*', authenticateToken, toExpressHandler(uploadRouter.handlers));

export default router;
middleware/auth.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

export const authenticateToken = (req: Request, res: Response, next: NextFunction) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'Access token required' });
  }

  jwt.verify(token, process.env.JWT_SECRET!, (err, user) => {
    if (err) {
      return res.status(403).json({ error: 'Invalid or expired token' });
    }
    req.user = user;
    next();
  });
};