@zachtrice/lambda-image-upload
v0.1.0
Published
Parse a multipart image upload in an AWS Lambda (API Gateway), transform it with sharp, and store it to S3. Small, composable, dependency-light helpers.
Maintainers
Readme
@zachtrice/lambda-image-upload
Small, composable helpers to accept an image upload inside an AWS Lambda (behind API Gateway), transform it with sharp, and store it on S3.
- Parse
multipart/form-datastraight from an API Gateway proxy event (handles the base64 body). - Auto-rotate (EXIF), resize to a bounding box, and re-encode (WebP by default).
- Put to S3 using your SDK client, so this package never bundles
aws-sdk. - Zero framework assumptions. Use the one-shot helper, or the small pieces.
Install
npm install @zachtrice/lambda-image-uploadbusboy and sharp are dependencies. @aws-sdk/client-s3 is an optional peer
(the Node 18+ Lambda runtime already ships the v3 SDK, so you usually get it for free).
Quick start (one-shot)
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3')
const crypto = require('crypto')
const { handleImageUpload } = require('@zachtrice/lambda-image-upload')
const s3 = new S3Client({})
exports.handler = async (event) => {
try {
const { key } = await handleImageUpload(event, {
field: 'image',
bucket: process.env.UPLOAD_BUCKET,
s3Client: s3,
PutObjectCommand,
maxBytes: 12 * 1024 * 1024,
process: { maxWidth: 1400, maxHeight: 1400, format: 'webp', quality: 82 },
keyFor: ({ ext }) => `uploads/${crypto.randomUUID()}.${ext}`
})
return { statusCode: 201, body: JSON.stringify({ key }) }
} catch (err) {
const status = err.code === 'TOO_LARGE' ? 413 : (err.code ? 400 : 500)
return { statusCode: status, body: JSON.stringify({ error: err.message }) }
}
}Use the pieces
const {
parseMultipart, // (event, { limits }) -> { files, fields }
processImage, // (buffer, opts) -> { buffer, format, contentType, width, height }
uploadToS3 // ({ client, PutObjectCommand, bucket, key, body, contentType }) -> ...
} = require('@zachtrice/lambda-image-upload')
const { files } = await parseMultipart(event, { limits: { files: 1, fileSize: 12e6 } })
const out = await processImage(files.image.buffer, { maxWidth: 1600, format: 'webp' })
await uploadToS3({ client: s3, PutObjectCommand, bucket, key, body: out.buffer, contentType: out.contentType })API
handleImageUpload(event, options)
Parse, validate, transform, and store in one call. Returns { key, contentType, bytes }.
| option | type | default | notes |
| --- | --- | --- | --- |
| field | string | 'image' | Multipart field name to read. |
| bucket | string | — | Required. |
| keyFor | ({ filename, mimeType, ext }) => string | — | Required. Builds the S3 object key. |
| s3Client | S3Client | — | Required. |
| PutObjectCommand | class | — | Required. |
| process | ProcessImageOptions | {} | Passed to processImage. |
| cacheControl | string | public, max-age=31536000 | |
| maxBytes | number | — | Reject larger files (TOO_LARGE). |
Throws errors with a stable code: NO_FILE, TOO_LARGE, NOT_IMAGE.
parseMultipart(event, { limits })
Returns { files, fields }. Each file is { filename, mimeType, buffer, truncated }.
processImage(buffer, options)
{ maxWidth, maxHeight, format='webp', quality=82, rotate=true, withoutEnlargement=true, fit='inside' }
→ { buffer, format, contentType, width, height }.
uploadToS3({ client, PutObjectCommand, bucket, key, body, contentType, cacheControl })
Thin PutObject wrapper. You provide the client + command class.
API Gateway note
Add multipart/form-data to your API's binary media types so the body arrives
base64-encoded (event.isBase64Encoded === true). This library decodes it for you.
License
MIT © ZachTRice
