npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@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

  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

📄 License

MIT © Image2Url