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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@development-team/bg-remover

v1.2.0

Published

Express middleware for background removal and OCR text extraction with HTTP multipart upload and real-time SSE streaming

Downloads

112

Readme

@development-team/bg-remover

NPM Version License: MIT Node.js

A production-ready Express middleware suite for AI-powered image background removal and real-time OCR text extraction.

v1.2.0 — Image is always sent as normal multipart/form-data (PNG/JPG). No WebSocket binary conversion required. Output available as a complete HTTP response or a real-time SSE stream.


Features

| Feature | Description | |---|---| | Background Removal | Remove image backgrounds via HTTP multipart upload | | OCR — Buffered | Upload image as PNG/JPG, get complete text result in req.ocrResult | | OCR — Live Stream | Upload image as PNG/JPG, receive OCR chunks in real-time via SSE | | Fail-Safe | Errors never crash your middleware chain — always calls next() | | Retry Logic | Configurable retry attempts; smart — skips retry on 4xx errors | | Backward Compat | wsUrl still accepted — auto-converted to https:// internally |


Requirements

  • Node.js >= 16.0.0
  • multer (peer dependency for file uploads)

Installation

npm install @development-team/bg-remover multer

Table of Contents

  1. Background Removal
  2. OCR — Buffered (Complete Result)
  3. OCR — Live SSE Stream
  4. Options Reference
  5. Request Object Properties
  6. Frontend Integration
  7. Error Handling
  8. Environment Variables
  9. Changelog

1. Background Removal

removeBgMiddleware(options)

Uploads the image to a background removal REST API and attaches the result to req.processedImage. Always calls next() — even on failure.

Basic Example

const express = require('express');
const multer  = require('multer');
const { removeBgMiddleware } = require('@development-team/bg-remover');

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

app.post(
    '/remove-background',
    upload.single('image'),
    removeBgMiddleware({ timeout: 15000, retries: 2 }),
    (req, res) => {
        if (req.bgError) {
            return res.status(500).json({ error: req.bgError.message });
        }
        // req.processedImage.buffer  → Buffer (transparent PNG)
        // req.processedImage.mimetype → 'image/png'
        res.set('Content-Type', req.processedImage.mimetype);
        res.send(req.processedImage.buffer);
    }
);

Replace Original File In-Place

app.post(
    '/upload',
    upload.single('image'),
    removeBgMiddleware({ replaceOriginal: true }),
    (req, res) => {
        if (req.bgError) return res.status(500).json({ error: 'Processing failed' });
        // req.file.buffer   → processed image
        // req.file.mimetype → 'image/png'
        res.json({ size: req.file.size });
    }
);

Options

| Option | Type | Default | Description | |---|---|---|---| | apiUrl | string | (built-in) | Full URL of the background removal endpoint | | timeout | number | 10000 | Request timeout in ms | | retries | number | 2 | Retry attempts on failure | | replaceOriginal | boolean | false | Overwrite req.file with the processed result | | fieldName | string | "file" | Form-data field name sent to the API |


2. OCR — Buffered (Complete Result)

ocrMiddleware(options)

Uploads the image as normal multipart/form-data (PNG/JPG) to POST /api/ocr, waits for the complete result, then attaches it to req.ocrResult and calls next().

Use this when you want a standard request/response flow and don't need real-time streaming.

Basic Example

const express = require('express');
const multer  = require('multer');
const { ocrMiddleware } = require('@development-team/bg-remover');

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

app.post(
    '/ocr',
    upload.single('image'),       // accepts PNG, JPG, WEBP, etc.
    ocrMiddleware({
        apiUrl:  process.env.OCR_API_URL,  // e.g. "https://your-server.hf.space"
        timeout: 30000,
        retries: 1
    }),
    (req, res) => {
        if (req.ocrError) {
            return res.status(500).json({ error: req.ocrError.message });
        }
        res.json({
            full_text:   req.ocrResult.full_text,    // complete extracted text
            total_lines: req.ocrResult.total_lines,
            lines:       req.ocrResult.lines          // [{text, confidence}, ...]
        });
    }
);

req.ocrResult Shape

{
  "full_text": "Paracetamol 500mg\nTake 2 times a day.",
  "total_lines": 2,
  "lines": [
    { "text": "Paracetamol 500mg",   "confidence": 0.985 },
    { "text": "Take 2 times a day.", "confidence": 0.912 }
  ]
}

Options

| Option | Type | Default | Description | |---|---|---|---| | apiUrl | string | OCR_API_URL env var | HTTP(S) base URL of the OCR server (e.g. https://your-space.hf.space) | | wsUrl | string | OCR_WS_URL env var | Backward compat — auto-converted to https:// | | timeout | number | 30000 | Request timeout in ms | | retries | number | 1 | Retry attempts on failure (skipped on 4xx errors) | | fieldName | string | "file" | Form-data field name sent to the API |

Note: ocrRestMiddleware is an alias of ocrMiddleware — both are identical.


3. OCR — Live SSE Stream

ocrStreamHandler(options)

Uploads the image as normal multipart/form-data (PNG/JPG) to POST /api/ocr/stream and pipes the OCR results back to the client as Server-Sent Events in real-time — line by line, as they are extracted.

This is a route handler, not a middleware — it sends the full HTTP response itself. Do not add another handler after it.

Basic Example

const express = require('express');
const multer  = require('multer');
const { ocrStreamHandler } = require('@development-team/bg-remover');

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

// This single line is the complete route — no extra handler needed.
app.post(
    '/ocr/stream',
    upload.single('image'),       // accepts PNG, JPG, WEBP, etc.
    ocrStreamHandler({
        apiUrl:  process.env.OCR_API_URL,
        timeout: 30000
    })
);

SSE Event Stream Format

The client receives a text/event-stream response with these events:

data: {"event":"status","data":{"message":"📷 Image received","stage":"received"}}

data: {"event":"status","data":{"message":"🔧 Processing image…","stage":"preprocessing"}}

data: {"event":"status","data":{"message":"📝 Reading text…","stage":"ocr_started"}}

data: {"event":"ocr_chunk","data":"Paracetamol 500mg","index":1,"confidence":0.985,"bbox":[[...]]}

data: {"event":"ocr_chunk","data":"Take 2 times a day.","index":2,"confidence":0.912,"bbox":[[...]]}

data: {"event":"ocr_complete","data":{"message":"✅ Done","total_lines":2}}

| Event | Key Fields | Description | |---|---|---| | status | data.message, data.stage | Processing stage update | | ocr_chunk | data (text), index, confidence, bbox | One detected line, streamed live | | ocr_complete | data.total_lines | All lines sent — connection closes | | error | message | Error from OCR server or timeout |

Options

| Option | Type | Default | Description | |---|---|---|---| | apiUrl | string | OCR_API_URL env var | HTTP(S) base URL of the OCR server | | wsUrl | string | OCR_WS_URL env var | Backward compat — auto-converted to https:// | | timeout | number | 30000 | Request timeout in ms | | fieldName | string | "file" | Form-data field name sent to the API |


4. Options Reference (All Middlewares)

removeBgMiddleware(options)

| Option | Type | Default | Description | |---|---|---|---| | apiUrl | string | (internal) | Full URL of the remove-bg endpoint | | timeout | number | 10000 | HTTP request timeout (ms) | | retries | number | 2 | Retry attempts on failure | | replaceOriginal | boolean | false | Overwrite req.file with processed result | | fieldName | string | "file" | Form-data field name sent to the API |

ocrMiddleware(options) / ocrRestMiddleware(options)

| Option | Type | Default | Description | |---|---|---|---| | apiUrl | string | process.env.OCR_API_URL | HTTP(S) base URL of the OCR server | | wsUrl | string | process.env.OCR_WS_URL | Backward compat — auto-converted to https:// | | timeout | number | 30000 | Request timeout (ms) | | retries | number | 1 | Retry attempts (skipped on 4xx errors) | | fieldName | string | "file" | Form-data field name |

ocrStreamHandler(options)

| Option | Type | Default | Description | |---|---|---|---| | apiUrl | string | process.env.OCR_API_URL | HTTP(S) base URL of the OCR server | | wsUrl | string | process.env.OCR_WS_URL | Backward compat — auto-converted to https:// | | timeout | number | 30000 | Request timeout (ms) | | fieldName | string | "file" | Form-data field name |


5. Request Object Properties

removeBgMiddleware

| Property | Type | Set When | |---|---|---| | req.processedImage.buffer | Buffer | Success | | req.processedImage.mimetype | string | Success | | req.bgError | Error | All retries failed |

ocrMiddleware / ocrRestMiddleware

| Property | Type | Set When | |---|---|---| | req.ocrResult.full_text | string | Success | | req.ocrResult.lines | Array<{text, confidence}> | Success | | req.ocrResult.total_lines | number | Success | | req.ocrChunks | Array<{text, confidence}> | Success (shallow copy of lines) | | req.ocrError | Error | All retries failed |

All middlewares silently skip and call next() if req.file is missing or the file is not an image.


6. Frontend Integration

OCR Buffered — Fetch API

const extractText = async (file) => {
    const formData = new FormData();
    formData.append('image', file);   // PNG, JPG, WEBP — any image format

    const res  = await fetch('/ocr', { method: 'POST', body: formData });
    const data = await res.json();

    console.log('Full text:', data.full_text);
    data.lines.forEach(line => {
        console.log(`"${line.text}" (${(line.confidence * 100).toFixed(1)}%)`);
    });
};

OCR Live Stream — fetch with ReadableStream

const streamOcr = async (file, onChunk, onComplete, onError) => {
    const formData = new FormData();
    formData.append('image', file);   // PNG, JPG, WEBP — any image format

    const res     = await fetch('/ocr/stream', { method: 'POST', body: formData });
    const reader  = res.body.getReader();
    const decoder = new TextDecoder();
    let buffer    = '';

    while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        buffer += decoder.decode(value, { stream: true });
        const frames = buffer.split('\n\n');
        buffer = frames.pop();   // keep incomplete last frame

        for (const frame of frames) {
            if (!frame.startsWith('data: ')) continue;
            const event = JSON.parse(frame.slice(6));

            if (event.event === 'ocr_chunk') {
                onChunk(event.data, event.confidence);   // event.data = extracted text
            } else if (event.event === 'ocr_complete') {
                onComplete(event.data.total_lines);
            } else if (event.event === 'error') {
                onError(event.message);
            }
        }
    }
};

// Usage
streamOcr(
    imageFile,
    (text, conf) => console.log(`Line: "${text}" (${(conf * 100).toFixed(1)}%)`),
    (total)      => console.log(`Done — ${total} lines extracted`),
    (err)        => console.error('OCR error:', err)
);

React Hook — Live OCR

import { useState } from 'react';

function useOcrStream(endpoint = '/ocr/stream') {
    const [lines,    setLines]    = useState([]);
    const [done,     setDone]     = useState(false);
    const [loading,  setLoading]  = useState(false);
    const [error,    setError]    = useState(null);

    const runOcr = async (file) => {
        setLines([]);
        setDone(false);
        setError(null);
        setLoading(true);

        const formData = new FormData();
        formData.append('image', file);

        const res     = await fetch(endpoint, { method: 'POST', body: formData });
        const reader  = res.body.getReader();
        const decoder = new TextDecoder();
        let buffer    = '';

        while (true) {
            const { done: streamDone, value } = await reader.read();
            if (streamDone) break;

            buffer += decoder.decode(value, { stream: true });
            const frames = buffer.split('\n\n');
            buffer = frames.pop();

            for (const frame of frames) {
                if (!frame.startsWith('data: ')) continue;
                const event = JSON.parse(frame.slice(6));

                if (event.event === 'ocr_chunk') {
                    setLines((prev) => [...prev, { text: event.data, confidence: event.confidence }]);
                } else if (event.event === 'ocr_complete') {
                    setDone(true);
                    setLoading(false);
                } else if (event.event === 'error') {
                    setError(event.message);
                    setLoading(false);
                }
            }
        }
    };

    return { lines, done, loading, error, runOcr };
}

// Usage in component:
// const { lines, loading, runOcr } = useOcrStream();
// <input type="file" onChange={e => runOcr(e.target.files[0])} />
// {lines.map((l, i) => <p key={i}>{l.text}</p>)}

7. Error Handling

All middlewares follow a fail-safe design — they never throw or crash your server.

Background Removal

removeBgMiddleware({ retries: 2 }),
(req, res) => {
    if (req.bgError) {
        return res.status(502).json({ error: 'Image processing service unavailable' });
    }
    // proceed normally
}

OCR Buffered

ocrMiddleware({ retries: 1 }),
(req, res) => {
    if (req.ocrError) {
        // Includes server error detail when available, e.g.:
        // "OCR failed after 1 attempt(s). Last error: Request failed with status code 422 (File too large)"
        return res.status(502).json({ error: req.ocrError.message });
    }
    // proceed normally
}

OCR Stream

The stream handler automatically sends an SSE error event and closes the connection. Always listen for it on the client:

if (event.event === 'error') {
    console.error('OCR error:', event.message);
    // show error UI, stop spinner, etc.
}

8. Environment Variables

| Variable | Used By | Description | |---|---|---| | OCR_API_URL | ocrMiddleware, ocrStreamHandler | HTTP(S) base URL of the OCR server. Recommended. | | OCR_WS_URL | ocrMiddleware, ocrStreamHandler | Legacy: WebSocket URL — auto-converted to https:// |

Set in your .env:

# Recommended (v1.2.0+)
OCR_API_URL=https://your-ocr-server.hf.space

# Or use the legacy variable (still works — auto-converted to https://)
OCR_WS_URL=wss://your-ocr-server.hf.space

If both are set, OCR_API_URL takes precedence.


9. Changelog

v1.2.0

  • Breaking (internal): OCR transport changed from WebSocket binary to HTTP multipart/form-data
    • Images are now sent as normal PNG/JPG files — no binary or base64 conversion needed
    • ocrMiddleware → uses POST /api/ocr (complete JSON result)
    • ocrStreamHandler → uses POST /api/ocr/stream (real-time SSE chunks)
  • Added apiUrl option to both OCR functions (direct HTTP URL)
  • Added ocrRestMiddleware export (alias of ocrMiddleware)
  • Added fieldName option to ocrStreamHandler
  • Added res.flush() calls for immediate SSE event delivery
  • Added smart retry: 4xx errors are not retried (only 5xx)
  • Added upstream error detail in req.ocrError.message
  • Changed req.ocrChunks is now a shallow copy (not an alias) of req.ocrResult.lines
  • Removed ws (WebSocket) dependency — no longer needed
  • Updated Node.js requirement: >= 16.0.0 (was >= 14.0.0)
  • Backward compat: wsUrl still accepted — auto-converted to https:///http://

v1.1.0

  • Added ocrMiddleware — buffered OCR over WebSocket
  • Added ocrStreamHandler — live OCR via Server-Sent Events
  • Added ws dependency for WebSocket client
  • Added OCR_WS_URL environment variable support

v1.0.1

  • Made Hugging Face endpoint internal
  • Package renamed to @development-team/bg-remover

v1.0.0

  • Initial release: removeBgMiddleware for background removal

License

MIT © Development Team