@image2url/next
v0.1.0
Published
A powerful Next.js API route handler for server-side image upload processing with security, validation, and proxy capabilities.
Readme
@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
npm install @image2url/next
# or
yarn add @image2url/next
# or
pnpm add @image2url/next🚀 Quick Start
Pages Router (/pages/api/upload.ts)
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)
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
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:
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
const handler = createUploadHandler({
apiKey: process.env.IMAGE2URL_API_KEY,
// This will automatically add the API key to upstream requests
});Custom Authentication
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
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
const handler = createUploadHandler({
allowCors: true, // Allows all origins
});Restricted CORS
const handler = createUploadHandler({
allowCors: true,
allowedDomains: [
'localhost:3000', // Development
'yourdomain.com', // Production
'*.yourdomain.com', // Subdomains
'staging.yourdomain.com' // Staging
],
});Upload Configuration
File Size Limits
const handler = createUploadHandler({
maxUploadSize: '5MB', // String notation
// or
maxUploadSize: 5 * 1024 * 1024, // Bytes
});Custom Upload Endpoint
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
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
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:
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
{
"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
// 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
// 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
// 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:
# 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:
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)
{
"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)
{
"error": "SIZE_EXCEEDED",
"message": "File is too large"
}🔒 Security Considerations
- Always use HTTPS in production
- Validate file types - Don't rely only on client-side validation
- Set appropriate size limits - Prevent denial of service attacks
- Use authentication - Protect your upload endpoints
- Configure CORS properly - Only allow trusted domains
- Sanitize file names - Prevent path traversal attacks
- Rate limit requests - Prevent abuse
🔗 Related Packages
@image2url/react- React upload hook- Image2Url Service - Backend image processing service
📄 License
MIT © Image2Url
