Sharp Node.js integration for server-side image processing

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Showing 1 of 1 servicesAll 2065 services
Sharp Node.js integration for server-side image processing
Medium
from 1 business day to 3 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Sharp Integration (Node.js) for Server-Side Image Processing

Sharp — Node.js library based on libvips, an order of magnitude faster than Jimp or Canvas API. Processes JPEG, PNG, WebP, AVIF, TIFF, GIF, SVG without quality loss, doesn't require ImageMagick and works 4–5 times faster with half the memory consumption compared to alternatives.

Installation and Basic Setup

npm install sharp
# Sharp ships with precompiled libvips binaries
# for Linux x64, macOS arm64, Windows x64

Sharp uses streaming processing — images aren't loaded fully into memory:

const sharp = require('sharp')

// Basic pipeline: resize + WebP + save
await sharp('./input/photo.jpg')
  .resize(800, 600, {
    fit: 'inside',           // fit within frame without cropping
    withoutEnlargement: true // don't enlarge small images
  })
  .webp({ quality: 82, effort: 4 })
  .toFile('./output/photo.webp')

Output Formats and Quality Parameters

Format Method Recommended Quality
JPEG .jpeg({ quality, mozjpeg }) 80–85, mozjpeg: true
WebP .webp({ quality, effort }) 80–85, effort: 4
AVIF .avif({ quality, effort }) 50–60, effort: 4
PNG .png({ compressionLevel }) compressionLevel: 6–8
// Generate multiple formats from single source
async function convertToModernFormats(inputPath, outputDir, baseName) {
  const image = sharp(inputPath)
  const meta = await image.metadata()

  const resized = image.resize(1200, null, {
    fit: 'inside',
    withoutEnlargement: true
  })

  await Promise.all([
    // WebP for modern browsers
    resized.clone()
      .webp({ quality: 82, effort: 4 })
      .toFile(`${outputDir}/${baseName}.webp`),

    // AVIF for Chrome/Firefox
    resized.clone()
      .avif({ quality: 55, effort: 4 })
      .toFile(`${outputDir}/${baseName}.avif`),

    // JPEG as fallback
    resized.clone()
      .jpeg({ quality: 85, mozjpeg: true })
      .toFile(`${outputDir}/${baseName}.jpg`),
  ])

  return { width: meta.width, height: meta.height }
}

Processing EXIF and Orientation

Sharp automatically reads EXIF orientation but doesn't apply it by default:

const image = sharp(buffer)
  .rotate()           // auto-rotate by EXIF orientation
  .withMetadata({     // preserve metadata (except GPS if needed for privacy)
    exif: {
      IFD0: { Copyright: 'My Company 2024' }
    }
  })

To remove GPS data when publishing publicly:

// Remove all metadata (EXIF, IPTC, XMP)
.withMetadata(false)
// Or keep only ICC profile for correct colors
.withMetadata({ icc: true })

Integration with Multer (Express)

const multer = require('multer')
const { v4: uuidv4 } = require('uuid')

// Store in memory, process with Sharp before saving to disk/S3
const upload = multer({ storage: multer.memoryStorage() })

app.post('/api/upload', upload.single('image'), async (req, res) => {
  if (!req.file) return res.status(400).json({ error: 'No file' })

  // Validate format via metadata (not MIME header — it can be spoofed)
  let meta
  try {
    meta = await sharp(req.file.buffer).metadata()
  } catch {
    return res.status(422).json({ error: 'Invalid image' })
  }

  const allowedFormats = ['jpeg', 'png', 'webp', 'gif', 'avif']
  if (!allowedFormats.includes(meta.format)) {
    return res.status(422).json({ error: `Format ${meta.format} not allowed` })
  }

  const id = uuidv4()
  const variants = await processAndUpload(req.file.buffer, id)

  res.json({ id, variants })
})

async function processAndUpload(buffer, id) {
  const image = sharp(buffer).rotate() // EXIF auto-rotate

  const sizes = {
    thumb:  { width: 150, height: 150, fit: 'cover' },
    medium: { width: 800 },
    large:  { width: 1920 }
  }

  const results = {}

  for (const [name, dims] of Object.entries(sizes)) {
    const processed = await image.clone()
      .resize(dims.width, dims.height || null, {
        fit: dims.fit || 'inside',
        withoutEnlargement: true
      })
      .webp({ quality: 82, effort: 4 })
      .toBuffer()

    const key = `images/${id}/${name}.webp`
    await s3.putObject({
      Bucket: process.env.S3_BUCKET,
      Key: key,
      Body: processed,
      ContentType: 'image/webp',
      CacheControl: 'public, max-age=31536000, immutable'
    }).promise()

    results[name] = key
  }

  return results
}

Watermark and Compositing

async function addWatermark(imageBuffer, watermarkPath) {
  const image = sharp(imageBuffer)
  const { width, height } = await image.metadata()

  // Scale watermark to 20% of image width
  const wmSize = Math.floor(width * 0.2)

  const watermark = await sharp(watermarkPath)
    .resize(wmSize)
    .toBuffer()

  return image
    .composite([{
      input: watermark,
      gravity: 'southeast',
      blend: 'over'
    }])
    .toBuffer()
}

Performance: Concurrency and Threads

Sharp uses all CPUs by default. In production, limit this:

sharp.concurrency(2) // maximum 2 libvips threads

// For high load — queue via p-limit
const pLimit = require('p-limit')
const limit = pLimit(4) // 4 parallel tasks

const tasks = images.map(img =>
  limit(() => processImage(img))
)
const results = await Promise.all(tasks)

Error Handling

async function safeProcess(buffer) {
  try {
    const meta = await sharp(buffer).metadata()

    // Protection from decompression bomb
    if (meta.width * meta.height > 50_000_000) {
      throw new Error('Image too large: exceeds 50MP limit')
    }

    return await sharp(buffer)
      .resize(2000, 2000, { fit: 'inside', withoutEnlargement: true })
      .webp({ quality: 82 })
      .toBuffer()

  } catch (err) {
    if (err.message.includes('Input buffer contains unsupported image format')) {
      throw new TypeError('Unsupported image format')
    }
    throw err
  }
}

Timeline

Sharp integration with Multer, multiple output formats, and S3 upload — 1–2 working days.