npm package: @pixengine/middleware-express
Description: Express.js middleware for PixEngine image optimization
Author: PixEngine Team
Latest version: 0.3.0

# @pixengine/middleware-express

**English** | [한국어](README_KO.md)

Express.js middleware for PixEngine image optimization.

## Installation

```bash
npm install @pixengine/middleware-express @pixengine/core
# or
pnpm add @pixengine/middleware-express @pixengine/core
# or
yarn add @pixengine/middleware-express @pixengine/core
```

## Quick Start

```typescript
import express from 'express';
import multer from 'multer';
import { pixEngineMiddleware } from '@pixengine/middleware-express';
import { SharpEngine } from '@pixengine/adapter-engine-sharp';
import { LocalStorage } from '@pixengine/adapter-storage-local';

const app = express();
const upload = multer({ storage: multer.memoryStorage() });

app.post(
  '/upload',
  upload.single('image'),
  pixEngineMiddleware({
    engine: new SharpEngine(),
    storage: new LocalStorage({
      baseDir: './public/uploads',
      baseUrl: 'http://localhost:3000/uploads',
    }),
  })
);

app.use('/uploads', express.static('./public/uploads'));
app.listen(3000);
```

**Response:**
```json
{
  "original": {
    "width": 1920,
    "height": 1080,
    "format": "jpeg",
    "bytes": 245760
  },
  "variants": [
    {
      "key": "variants/photo_400w.webp",
      "url": "http://localhost:3000/uploads/variants/photo_400w.webp",
      "width": 400,
      "height": 225,
      "format": "webp",
      "bytes": 8420
    },
    {
      "key": "variants/photo_800w.webp",
      "url": "http://localhost:3000/uploads/variants/photo_800w.webp",
      "width": 800,
      "height": 450,
      "format": "webp",
      "bytes": 24680
    },
    {
      "key": "variants/photo_1200w.webp",
      "url": "http://localhost:3000/uploads/variants/photo_1200w.webp",
      "width": 1200,
      "height": 675,
      "format": "webp",
      "bytes": 48920
    }
  ]
}
```

## Features

- 🚀 **Drop-in Express Middleware**: Simple factory function pattern
- 📤 **Multer Integration**: Works seamlessly with multer file uploads
- 🎨 **Automatic Optimization**: Generates responsive image variants automatically
- 📦 **Default Policy**: Sensible defaults (400w, 800w, 1200w WebP images)
- ⚙️ **Customizable**: Override policy for custom image variants
- 🔒 **Type-Safe**: Full TypeScript support
- ✅ **Auto JSON Response**: Returns manifest directly to client

## API

### `pixEngineMiddleware(config)`

Factory function that creates an Express middleware for image optimization.

#### Parameters

```typescript
interface PixEngineMiddlewareConfig {
  engine: TransformEngine;    // Required: Image processing engine
  storage: StorageAdapter;     // Required: Storage adapter
  policy?: Policy;             // Optional: Custom variant policy
}
```

**Required:**
- `engine: TransformEngine` - Image processing engine (e.g., `SharpEngine`)
- `storage: StorageAdapter` - Storage adapter (e.g., `LocalStorage`, S3, etc.)

**Optional:**
- `policy?: Policy` - Custom policy function to define image variants

#### Returns

`RequestHandler` - Express middleware function

### Default Policy

The middleware provides a default responsive image policy:

```typescript
export const defaultPolicy: Policy = (ctx) => ({
  variants: [
    { width: 400, format: 'webp', quality: 80 },
    { width: 800, format: 'webp', quality: 85 },
    { width: 1200, format: 'webp', quality: 90 },
  ],
});
```

This generates three WebP variants at different widths, suitable for responsive web images.

## Usage Examples

### Basic Usage with Default Policy

```typescript
import express from 'express';
import multer from 'multer';
import { pixEngineMiddleware } from '@pixengine/middleware-express';
import { SharpEngine } from '@pixengine/adapter-engine-sharp';
import { LocalStorage } from '@pixengine/adapter-storage-local';

const app = express();
const upload = multer({ storage: multer.memoryStorage() });

app.post(
  '/upload',
  upload.single('image'),
  pixEngineMiddleware({
    engine: new SharpEngine(),
    storage: new LocalStorage({
      baseDir: './public/uploads',
      baseUrl: 'http://localhost:3000/uploads',
    }),
  })
);

app.use('/uploads', express.static('./public/uploads'));
app.listen(3000);
```

### Custom Policy

```typescript
import { pixEngineMiddleware } from '@pixengine/middleware-express';
import { SharpEngine } from '@pixengine/adapter-engine-sharp';
import { LocalStorage } from '@pixengine/adapter-storage-local';

app.post(
  '/upload',
  upload.single('image'),
  pixEngineMiddleware({
    engine: new SharpEngine(),
    storage: new LocalStorage({
      baseDir: './public/uploads',
      baseUrl: 'http://localhost:3000/uploads',
    }),
    policy: (ctx) => ({
      variants: [
        { width: 200, format: 'webp', quality: 75 },
        { width: 600, format: 'webp', quality: 80 },
        { width: 1000, format: 'jpeg', quality: 85 },
      ],
    }),
  })
);
```

### Context-Based Policy

```typescript
app.post(
  '/upload',
  upload.single('image'),
  pixEngineMiddleware({
    engine: new SharpEngine(),
    storage: new LocalStorage({
      baseDir: './public/uploads',
      baseUrl: 'http://localhost:3000/uploads',
    }),
    policy: (ctx) => {
      // Access original image metadata
      const { width, height, format } = ctx.original;

      // Generate variants based on original size
      if (width > 2000) {
        return {
          variants: [
            { width: 800, format: 'webp', quality: 80 },
            { width: 1600, format: 'webp', quality: 85 },
            { width: 2400, format: 'webp', quality: 90 },
          ],
        };
      }

      // Smaller originals get fewer variants
      return {
        variants: [
          { width: 400, format: 'webp', quality: 80 },
          { width: 800, format: 'webp', quality: 85 },
        ],
      };
    },
  })
);
```

### TypeScript Usage

```typescript
import type { Request, Response, NextFunction } from 'express';
import { pixEngineMiddleware } from '@pixengine/middleware-express';
import type { PixEngineMiddlewareConfig } from '@pixengine/middleware-express';

const config: PixEngineMiddlewareConfig = {
  engine: new SharpEngine(),
  storage: new LocalStorage({
    baseDir: './public/uploads',
    baseUrl: 'http://localhost:3000/uploads',
  }),
};

app.post('/upload', upload.single('image'), pixEngineMiddleware(config));
```

## Error Handling

The middleware handles errors automatically:

### 400 Bad Request
Returned when no file is uploaded:

```json
{
  "error": "No file uploaded"
}
```

### 500 Internal Server Error
Returned when optimization fails:

```json
{
  "error": "Image optimization failed",
  "message": "Unsupported image format"
}
```

### Custom Error Handling

You can add your own error handling middleware after the PixEngine middleware:

```typescript
app.post(
  '/upload',
  upload.single('image'),
  pixEngineMiddleware(config),
  (err: Error, req: Request, res: Response, next: NextFunction) => {
    console.error('Upload error:', err);
    res.status(500).json({
      error: 'Upload failed',
      details: err.message,
    });
  }
);
```

## Requirements

- **Node.js**: >= 18.0.0
- **Express**: ^4.18.0 || ^5.0.0
- **Multer**: For file upload handling (user-provided)
- **PixEngine Core**: @pixengine/core
- **Transform Engine**: e.g., @pixengine/adapter-engine-sharp
- **Storage Adapter**: e.g., @pixengine/adapter-storage-local

## How It Works

1. **User uploads file** via multer middleware
2. **PixEngine middleware** receives `req.file`
3. **Extracts image data** (filename, bytes, contentType)
4. **Calls `optimize()`** with configured engine, storage, and policy
5. **Returns manifest** as JSON response automatically

```
Client → Multer → PixEngine Middleware → optimize() → Storage → JSON Response
```

## Integration with Other Storage Adapters

### AWS S3 Storage

```typescript
import { S3Storage } from '@pixengine/adapter-storage-s3';

app.post(
  '/upload',
  upload.single('image'),
  pixEngineMiddleware({
    engine: new SharpEngine(),
    storage: new S3Storage({
      bucket: 'my-images',
      region: 'us-east-1',
      baseUrl: 'https://cdn.example.com',
    }),
  })
);
```

### Cloudflare R2

```typescript
import { R2Storage } from '@pixengine/adapter-storage-r2';

app.post(
  '/upload',
  upload.single('image'),
  pixEngineMiddleware({
    engine: new SharpEngine(),
    storage: new R2Storage({
      accountId: 'your-account-id',
      accessKeyId: process.env.R2_ACCESS_KEY_ID,
      secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
      bucket: 'my-images',
    }),
  })
);
```

## Best Practices

### 1. Use Memory Storage for Multer

```typescript
const upload = multer({ storage: multer.memoryStorage() });
```

This keeps uploaded files in memory, which is ideal since PixEngine processes them immediately.

### 2. Add File Size Limits

```typescript
const upload = multer({
  storage: multer.memoryStorage(),
  limits: {
    fileSize: 10 * 1024 * 1024, // 10MB
  },
});
```

### 3. Validate File Types

```typescript
const upload = multer({
  storage: multer.memoryStorage(),
  fileFilter: (req, file, cb) => {
    if (file.mimetype.startsWith('image/')) {
      cb(null, true);
    } else {
      cb(new Error('Only image files are allowed'));
    }
  },
});
```

### 4. Set Appropriate baseUrl

For production, use your CDN or domain:

```typescript
storage: new LocalStorage({
  baseDir: './public/uploads',
  baseUrl: process.env.CDN_URL || 'https://cdn.example.com/uploads',
})
```

### 5. Use Environment Variables

```typescript
const config: PixEngineMiddlewareConfig = {
  engine: new SharpEngine(),
  storage: new LocalStorage({
    baseDir: process.env.UPLOAD_DIR || './public/uploads',
    baseUrl: process.env.BASE_URL || 'http://localhost:3000/uploads',
  }),
};
```

## License

MIT © PixEngine Team

## Links

- [PixEngine Core](https://www.npmjs.com/package/@pixengine/core)
- [Sharp Engine Adapter](https://www.npmjs.com/package/@pixengine/adapter-engine-sharp)
- [Local Storage Adapter](https://www.npmjs.com/package/@pixengine/adapter-storage-local)
- [GitHub Repository](https://github.com/pixengine/pixengine)
- [Issue Tracker](https://github.com/pixengine/pixengine/issues)