API Endpoint
Build an HTTP server for on-the-fly image processing.
Source Code
typescript
/**
* API Endpoint Example
* HTTP server for image processing
*
* Endpoints:
* - POST /resize?width=800&height=600
* - POST /convert?format=webp&quality=80
* - POST /transform (JSON body)
* - POST /metadata
* - POST /blurhash
*/
import { metadata, resize, toJpeg, toPng, toWebp, transform, blurhash } from 'bun-image-turbo';
const PORT = 3000;
const server = Bun.serve({
port: PORT,
async fetch(req) {
const url = new URL(req.url);
const path = url.pathname;
// CORS headers
const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
};
// Handle CORS preflight
if (req.method === 'OPTIONS') {
return new Response(null, { headers });
}
// Only accept POST
if (req.method !== 'POST') {
return new Response('Method not allowed', { status: 405, headers });
}
try {
// Get image from request body
const body = await req.arrayBuffer();
if (body.byteLength === 0) {
return new Response('No image provided', { status: 400, headers });
}
// Size limit (10MB)
if (body.byteLength > 10 * 1024 * 1024) {
return new Response('Image too large (max 10MB)', { status: 413, headers });
}
const buffer = Buffer.from(body);
// Route handling
switch (path) {
case '/metadata': {
const info = await metadata(buffer);
return Response.json(info, { headers });
}
case '/resize': {
const width = parseInt(url.searchParams.get('width') || '800');
const height = url.searchParams.get('height')
? parseInt(url.searchParams.get('height')!)
: undefined;
const fit = (url.searchParams.get('fit') as any) || 'cover';
const result = await resize(buffer, { width, height, fit });
return new Response(result, {
// Note: resize() always outputs PNG format
headers: { ...headers, 'Content-Type': 'image/png' }
});
}
case '/convert': {
const format = url.searchParams.get('format') || 'webp';
const quality = parseInt(url.searchParams.get('quality') || '80');
let result: Buffer;
let contentType: string;
switch (format) {
case 'jpeg':
case 'jpg':
result = await toJpeg(buffer, { quality });
contentType = 'image/jpeg';
break;
case 'png':
result = await toPng(buffer);
contentType = 'image/png';
break;
case 'webp':
default:
result = await toWebp(buffer, { quality });
contentType = 'image/webp';
break;
}
return new Response(result, {
headers: { ...headers, 'Content-Type': contentType }
});
}
case '/transform': {
// Get transform options from query params or JSON body
const contentType = req.headers.get('content-type');
let options: any;
if (contentType?.includes('application/json')) {
// If JSON body, parse options from there
// Note: This requires sending image via multipart or base64
return new Response('JSON body not supported yet', { status: 400 });
}
// Use query params
const width = url.searchParams.get('width');
const height = url.searchParams.get('height');
const format = url.searchParams.get('format') || 'webp';
const quality = parseInt(url.searchParams.get('quality') || '80');
const grayscale = url.searchParams.get('grayscale') === 'true';
const rotate = url.searchParams.get('rotate');
const blur = url.searchParams.get('blur');
const sharpen = url.searchParams.get('sharpen');
options = {
output: {
format,
[format]: { quality }
}
};
if (width || height) {
options.resize = {
width: width ? parseInt(width) : undefined,
height: height ? parseInt(height) : undefined,
fit: url.searchParams.get('fit') || 'cover'
};
}
if (grayscale) options.grayscale = true;
if (rotate) options.rotate = parseInt(rotate);
if (blur) options.blur = parseInt(blur);
if (sharpen) options.sharpen = parseInt(sharpen);
const result = await transform(buffer, options);
const resultContentType = format === 'png' ? 'image/png' :
format === 'jpeg' ? 'image/jpeg' : 'image/webp';
return new Response(result, {
headers: { ...headers, 'Content-Type': resultContentType }
});
}
case '/blurhash': {
const componentsX = parseInt(url.searchParams.get('x') || '4');
const componentsY = parseInt(url.searchParams.get('y') || '3');
const result = await blurhash(buffer, componentsX, componentsY);
return Response.json(result, { headers });
}
default:
return new Response('Not found', { status: 404, headers });
}
} catch (error) {
console.error('Processing error:', error);
return new Response(
`Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
{ status: 500, headers }
);
}
}
});
console.log(`🖼️ Image processing API running at http://localhost:${PORT}`);
console.log('');
console.log('Endpoints:');
console.log(' POST /metadata - Get image metadata');
console.log(' POST /resize?width=800 - Resize image');
console.log(' POST /convert?format=webp - Convert format');
console.log(' POST /transform?... - Apply transformations');
console.log(' POST /blurhash - Generate blurhash');
console.log('');
console.log('Example:');
console.log(' curl -X POST -F "file=@image.jpg" http://localhost:3000/resize?width=400');Running the Server
bash
cd examples
bun install
bun run apiAPI Usage
Get Metadata
bash
curl -X POST --data-binary @photo.jpg \
http://localhost:3000/metadataResponse:
json
{
"width": 1920,
"height": 1080,
"format": "jpeg",
"channels": 3,
"hasAlpha": false
}Resize Image
bash
# Resize to 800px width (outputs PNG)
curl -X POST --data-binary @photo.jpg \
"http://localhost:3000/resize?width=800" \
--output resized.png
# Resize with specific dimensions (outputs PNG)
curl -X POST --data-binary @photo.jpg \
"http://localhost:3000/resize?width=400&height=300&fit=cover" \
--output thumbnail.png
# For other formats, use /transform instead:
curl -X POST --data-binary @photo.jpg \
"http://localhost:3000/transform?width=800&format=jpeg&quality=85" \
--output resized.jpgConvert Format
bash
# Convert to WebP
curl -X POST --data-binary @photo.jpg \
"http://localhost:3000/convert?format=webp&quality=85" \
--output photo.webp
# Convert to PNG
curl -X POST --data-binary @photo.jpg \
"http://localhost:3000/convert?format=png" \
--output photo.pngTransform Pipeline
bash
# Resize + grayscale + sharpen
curl -X POST --data-binary @photo.jpg \
"http://localhost:3000/transform?width=800&grayscale=true&sharpen=10&format=webp" \
--output transformed.webp
# Rotate + resize
curl -X POST --data-binary @photo.jpg \
"http://localhost:3000/transform?width=600&rotate=90&format=jpeg&quality=90" \
--output rotated.jpgGenerate Blurhash
bash
curl -X POST --data-binary @photo.jpg \
"http://localhost:3000/blurhash?x=4&y=3"Response:
json
{
"hash": "LEHV6nWB2yk8pyo0adR*.7kCMdnj",
"width": 1920,
"height": 1080
}JavaScript Client
typescript
async function processImage(file: File, options: {
width?: number;
format?: 'jpeg' | 'webp' | 'png';
quality?: number;
}) {
const params = new URLSearchParams();
if (options.width) params.set('width', options.width.toString());
if (options.format) params.set('format', options.format);
if (options.quality) params.set('quality', options.quality.toString());
const response = await fetch(
`http://localhost:3000/transform?${params}`,
{
method: 'POST',
body: file,
}
);
if (!response.ok) {
throw new Error(await response.text());
}
return response.blob();
}
// Usage
const file = document.querySelector('input[type="file"]').files[0];
const processed = await processImage(file, {
width: 800,
format: 'webp',
quality: 85
});Production Considerations
- Rate limiting - Add request limits
- Authentication - Protect endpoints
- Caching - Cache processed images
- CDN - Serve from edge
- Monitoring - Track performance
Next Steps
- Batch Processing - Process multiple files