npm package: @image2url/next
Description: A powerful Next.js API route handler for server-side image upload processing with security, validation, and proxy capabilities.
Latest version: 0.1.0

# @image2url/next

A powerful Next.js API route handler for server-side image upload processing with security, validation, and proxy capabilities.

## 🚀 Features

- 🛡️ **Security** - CORS protection, domain validation, authentication
- 📊 **Validation** - File type, size, and format checking
- 🔄 **Proxy Mode** - Forward uploads to Image2Url service or custom endpoints
- ⏱️ **Timeout Control** - Configurable upload timeouts
- 🔧 **Flexible Auth** - Custom authentication middleware
- 📝 **Logging** - Debug mode for troubleshooting
- 🎯 **TypeScript** - Full type safety
- 🌐 **Environment Based** - Smart defaults with env var support

## 📦 Installation

```bash
npm install @image2url/next
# or
yarn add @image2url/next
# or
pnpm add @image2url/next
```

## 🚀 Quick Start

### Pages Router (`/pages/api/upload.ts`)

```typescript
import { createUploadHandler } from '@image2url/next';

export const config = {
  api: {
    bodyParser: false,
  },
};

const handler = createUploadHandler({
  // Optional: API key for Image2Url service
  apiKey: process.env.IMAGE2URL_API_KEY,
  
  // Enable CORS for browser uploads
  allowCors: true,
  allowedDomains: ['localhost:3000', 'yourdomain.com'],
  
  // File size limit
  maxUploadSize: '5MB',
  
  // Request timeout
  timeoutMs: 30000,
});

export default handler;
```

### App Router (`/app/api/upload/route.ts`)

```typescript
import { createUploadHandler } from '@image2url/next';
import { NextRequest } from 'next/server';

const handler = createUploadHandler({
  allowCors: true,
  maxUploadSize: '5MB',
  apiKey: process.env.IMAGE2URL_API_KEY,
});

export { handler as POST, handler as OPTIONS };
```

### Advanced Configuration

```typescript
import { createUploadHandler, UploadError } from '@image2url/next';
import { verifyAuthToken } from '../lib/auth';

const handler = createUploadHandler({
  // Authentication
  auth: async (request) => {
    const token = request.headers.get('authorization');
    if (!token) {
      throw new UploadError('Missing authorization header', 'FORBIDDEN', 403);
    }
    
    const user = await verifyAuthToken(token);
    if (!user) {
      throw new UploadError('Invalid token', 'FORBIDDEN', 403);
    }
    
    // Attach user info to request for later use
    request.user = user;
  },
  
  // CORS configuration
  allowCors: true,
  allowedDomains: [
    'localhost:3000',
    'yourdomain.com',
    '*.yourdomain.com'
  ],
  
  // Upload configuration
  maxUploadSize: '10MB',
  timeoutMs: 45000,
  
  // Custom upstream service
  uploadUrl: 'https://your-custom-upload-service.com/upload',
  
  // Custom headers
  headers: {
    'User-Agent': 'MyApp/1.0',
    'X-API-Version': 'v1'
  },
  
  // Debug mode (development only)
  debug: process.env.NODE_ENV === 'development',
});

export default handler;
```

## 📖 API Reference

### `createUploadHandler(options)`

Creates a Next.js API route handler for image uploads.

#### Parameters

`options` - Configuration object:

```typescript
interface CreateUploadHandlerOptions {
  // Authentication
  apiKey?: string;                              // API key for Image2Url service
  auth?: (request: Request) => Promise<void> | void;  // Custom auth middleware
  
  // Upload configuration
  uploadUrl?: string;                           // Custom upload endpoint
  maxUploadSize?: string | number;              // Max file size (e.g., '5MB', 10485760)
  timeoutMs?: number;                           // Request timeout in milliseconds
  
  // Security
  allowedDomains?: string[];                    // Allowed CORS origins
  allowCors?: boolean;                          // Enable CORS headers
  
  // Customization
  headers?: Record<string, string>;             // Additional HTTP headers
  debug?: boolean;                              // Enable debug logging
}
```

#### Returns

`NextRequest => NextResponse` - A Next.js API route handler function.

## 🔧 Configuration Options

### Authentication

#### API Key Authentication

```typescript
const handler = createUploadHandler({
  apiKey: process.env.IMAGE2URL_API_KEY,
  // This will automatically add the API key to upstream requests
});
```

#### Custom Authentication

```typescript
import jwt from 'jsonwebtoken';

const handler = createUploadHandler({
  auth: async (request) => {
    const token = request.headers.get('authorization')?.replace('Bearer ', '');
    
    if (!token) {
      throw new UploadError('No token provided', 'UNAUTHORIZED', 401);
    }
    
    try {
      const decoded = jwt.verify(token, process.env.JWT_SECRET!);
      if (!decoded || typeof decoded !== 'object') {
        throw new Error('Invalid token');
      }
      
      // You can attach user info to the request
      (request as any).user = decoded;
      
    } catch (error) {
      throw new UploadError('Invalid authentication token', 'UNAUTHORIZED', 401);
    }
  },
});
```

#### Database-Based Authentication

```typescript
import { getUserBySessionToken } from '../lib/users';

const handler = createUploadHandler({
  auth: async (request) => {
    const sessionToken = request.headers.get('x-session-token');
    
    if (!sessionToken) {
      throw new UploadError('Session token required', 'UNAUTHORIZED', 401);
    }
    
    const user = await getUserBySessionToken(sessionToken);
    if (!user || !user.isActive) {
      throw new UploadError('Invalid or expired session', 'UNAUTHORIZED', 401);
    }
    
    // Check user permissions
    if (!user.canUpload) {
      throw new UploadError('Upload permission denied', 'FORBIDDEN', 403);
    }
  },
});
```

### CORS Configuration

#### Basic CORS

```typescript
const handler = createUploadHandler({
  allowCors: true,  // Allows all origins
});
```

#### Restricted CORS

```typescript
const handler = createUploadHandler({
  allowCors: true,
  allowedDomains: [
    'localhost:3000',           // Development
    'yourdomain.com',          // Production
    '*.yourdomain.com',        // Subdomains
    'staging.yourdomain.com'   // Staging
  ],
});
```

### Upload Configuration

#### File Size Limits

```typescript
const handler = createUploadHandler({
  maxUploadSize: '5MB',    // String notation
  // or
  maxUploadSize: 5 * 1024 * 1024,  // Bytes
});
```

#### Custom Upload Endpoint

```typescript
const handler = createUploadHandler({
  // Default: Image2Url service
  // uploadUrl: undefined,
  
  // Custom service
  uploadUrl: 'https://your-cdn-provider.com/upload',
  
  // Internal service
  uploadUrl: 'http://localhost:3001/internal-upload',
});
```

#### Custom Headers

```typescript
const handler = createUploadHandler({
  headers: {
    'User-Agent': 'MyApp/1.0.0',
    'X-API-Version': 'v2',
    'X-Request-ID': () => generateRequestId(),  // Dynamic values
    'X-Forwarded-For': process.env.FORWARDED_IP,
  },
});
```

### Timeout Configuration

```typescript
const handler = createUploadHandler({
  timeoutMs: 30000,  // 30 seconds
  
  // Longer timeout for large files
  timeoutMs: 120000,  // 2 minutes
});
```

## 🚨 Error Handling

The handler provides comprehensive error handling with appropriate HTTP status codes:

```typescript
import { UploadError } from '@image2url/next';

// In custom auth middleware
throw new UploadError('File too large', 'SIZE_EXCEEDED', 413);
throw new UploadError('Invalid file type', 'TYPE_NOT_ALLOWED', 415);
throw new UploadError('Rate limit exceeded', 'RATE_LIMITED', 429);
throw new UploadError('Service unavailable', 'SERVICE_UNAVAILABLE', 503);
```

### Error Response Format

```json
{
  "error": "ERROR_CODE",
  "message": "Human-readable error message"
}
```

### Common Error Codes

| Code | Status | Description |
|------|--------|-------------|
| `METHOD_NOT_ALLOWED` | 405 | HTTP method not supported |
| `FORBIDDEN` | 403 | Origin not allowed or auth failed |
| `TYPE_NOT_ALLOWED` | 415 | File type not supported |
| `SIZE_EXCEEDED` | 413 | File too large |
| `VALIDATION_FAILED` | 400 | General validation error |
| `SERVER_ERROR` | 500 | Internal server error |
| `SERVICE_UNAVAILABLE` | 502 | Upstream service unavailable |

## 🎨 Usage Examples

### Simple Upload Handler

```typescript
// pages/api/upload.ts
import { createUploadHandler } from '@image2url/next';

export const config = {
  api: {
    bodyParser: false,
  },
};

export default createUploadHandler({
  allowCors: true,
  maxUploadSize: '5MB',
  apiKey: process.env.IMAGE2URL_API_KEY,
});
```

### Production-Ready Handler

```typescript
// pages/api/upload.ts
import { createUploadHandler, UploadError } from '@image2url/next';
import { rateLimit } from '../lib/rate-limit';
import { logUpload } from '../lib/analytics';

export const config = {
  api: {
    bodyParser: false,
  },
};

const handler = createUploadHandler({
  // Authentication
  auth: async (request) => {
    const token = request.headers.get('authorization')?.replace('Bearer ', '');
    if (!token) {
      throw new UploadError('Authentication required', 'UNAUTHORIZED', 401);
    }
    
    // Verify JWT token
    const user = await verifyJWT(token);
    if (!user) {
      throw new UploadError('Invalid token', 'UNAUTHORIZED', 401);
    }
    
    // Rate limiting
    const canUpload = await rateLimit(user.id);
    if (!canUpload) {
      throw new UploadError('Rate limit exceeded', 'RATE_LIMITED', 429);
    }
    
    // Attach user for logging
    (request as any).user = user;
  },
  
  // Security
  allowCors: true,
  allowedDomains: [
    'localhost:3000',
    'app.yourdomain.com',
    'admin.yourdomain.com'
  ],
  
  // Configuration
  maxUploadSize: '10MB',
  timeoutMs: 60000,
  apiKey: process.env.IMAGE2URL_API_KEY,
  
  // Custom headers
  headers: {
    'X-App-Version': process.env.APP_VERSION,
    'X-Request-ID': () => crypto.randomUUID(),
  },
  
  // Debug mode
  debug: process.env.NODE_ENV === 'development',
});

// Add logging middleware
export default async (req: any, res: any) => {
  const startTime = Date.now();
  
  try {
    const result = await handler(req, res);
    
    // Log successful upload
    await logUpload({
      userId: req.user?.id,
      fileSize: req.fileSize,
      duration: Date.now() - startTime,
      status: 'success'
    });
    
    return result;
    
  } catch (error) {
    // Log failed upload
    await logUpload({
      userId: req.user?.id,
      fileSize: req.fileSize,
      duration: Date.now() - startTime,
      status: 'error',
      error: error instanceof Error ? error.message : 'Unknown error'
    });
    
    throw error;
  }
};
```

### Multi-Service Upload Handler

```typescript
// pages/api/upload.ts
import { createUploadHandler, UploadError } from '@image2url/next';

export const config = {
  api: {
    bodyParser: false,
  },
};

export default createUploadHandler({
  // Choose upload service based on file type or user preference
  uploadUrl: async (req) => {
    const fileType = req.headers.get('content-type')?.split(';')[0];
    const userPreference = req.headers.get('x-upload-preference');
    
    if (userPreference === 'cdn') {
      return 'https://my-cdn.com/upload';
    }
    
    if (fileType?.includes('png')) {
      return 'https://png-processor.com/upload';
    }
    
    return process.env.IMAGE2URL_UPLOAD_URL || 'https://www.image2url.com/api/upload';
  },
  
  // Service-specific authentication
  headers: async (req) => {
    const service = await determineService(req);
    
    switch (service) {
      case 'cdn':
        return { 'X-CDN-API-Key': process.env.CDN_API_KEY };
      case 'png-processor':
        return { 'Authorization': `Bearer ${process.env.PNG_API_KEY}` };
      default:
        return { 'x-api-key': process.env.IMAGE2URL_API_KEY };
    }
  },
});
```

## 🌍 Environment Variables

Configure the handler using environment variables:

```bash
# Image2Url service configuration
IMAGE2URL_BASE_URL=https://www.image2url.com
IMAGE2URL_UPLOAD_PATH=/api/upload
IMAGE2URL_UPLOAD_URL=https://custom-upload-service.com/upload
IMAGE2URL_MAX_BYTES=10485760

# Your application configuration
IMAGE2URL_API_KEY=your-api-key
NODE_ENV=production
```

## 🏗️ TypeScript Support

The package is fully typed. Import types for enhanced development experience:

```typescript
import type {
  CreateUploadHandlerOptions,
  UpstreamResponse,
  UploadError
} from '@image2url/next';

// Type your custom handler options
const options: CreateUploadHandlerOptions = {
  maxUploadSize: '5MB',
  allowCors: true,
  allowedDomains: ['localhost:3000'],
};
```

## 🔄 Response Format

### Success Response (200)

```json
{
  "url": "https://cdn.example.com/uploads/image123.jpg",
  "upstream": {
    "id": "upload_123",
    "created_at": "2023-12-01T10:30:00Z",
    "size": 1024000
  }
}
```

### Error Response (4xx/5xx)

```json
{
  "error": "SIZE_EXCEEDED",
  "message": "File is too large"
}
```

## 🔒 Security Considerations

1. **Always use HTTPS** in production
2. **Validate file types** - Don't rely only on client-side validation
3. **Set appropriate size limits** - Prevent denial of service attacks
4. **Use authentication** - Protect your upload endpoints
5. **Configure CORS properly** - Only allow trusted domains
6. **Sanitize file names** - Prevent path traversal attacks
7. **Rate limit requests** - Prevent abuse

## 🔗 Related Packages

- [`@image2url/react`](../react) - React upload hook
- [Image2Url Service](https://www.image2url.com) - Backend image processing service

## 📄 License

MIT © Image2Url