Generate Barcodes in Node.js with an API

Generate Code 128 and Code 39 barcodes in Node.js with one POST request. Working Express code, real API responses, and a GitHub repo you can clone.

  • 8 min read
  • Published March 8, 2026
  • Updated September 13, 2026
Article featured image

You’re going to build a small Node.js app that turns a SKU, tracking number, or asset ID into a scannable Code 128 or Code 39 barcode image, using one POST request to the Barcode Generator API. The full working project is on GitHub: apiverve/barcode-generator-node-tutorial — clone it if you’d rather read code than prose.

Generate a barcode with one API request

Here’s the whole thing in curl. Swap in your key and run it:

curl -X POST https://api.apiverve.com/v1/barcodegenerator \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{"data": "51001544700", "type": "code128", "displayValue": true}'

And what comes back (the signed URL is trimmed — it’s long):

{
  "status": "ok",
  "error": null,
  "data": {
    "imageName": "cc85680f-a1d0-4627-89ff-7d7e53663a2b.png",
    "format": ".png",
    "type": "CODE128",
    "expires": 1766010012598,
    "downloadURL": "https://storage.googleapis.com/apiverve/APIData/barcodegenerator/cc85680f-....png?GoogleAccessId=...&Expires=1766010012&Signature=..."
  }
}

You don’t get image bytes. You get a PNG hosted at downloadURL, plus expires — a millisecond timestamp roughly 24 hours out. Remember that second field. It matters later.

Three request parameters do all the work:

ParameterRequiredWhat it does
dataYesThe string to encode
typeYescode128 or code39
displayValueNoPrint the value under the bars (defaults to true)

There are also lineColor and backgroundColor parameters, but those need a paid plan. Everything in this tutorial works on the free plan, and honestly, black-on-white is what scanners like best anyway.

Step 1: Set up the project

No key yet? Get a free API key first. Then either clone the repo:

git clone https://github.com/apiverve/barcode-generator-node-tutorial.git
cd barcode-generator-node-tutorial
npm install

Or start from scratch. The only dependency is Express — Node 18+ ships fetch, so there’s no HTTP client to install:

mkdir barcode-generator && cd barcode-generator
npm init -y
npm install express

Step 2: Configure the server

Create server.js. Keep the API key on the server and read it from an environment variable — the browser should never see it.

const express = require('express');
const path = require('path');

const API_KEY = process.env.API_KEY || 'your-api-key-here';
const API_URL = 'https://api.apiverve.com/v1/barcodegenerator';
const PORT = process.env.PORT || 3000;

const app = express();
app.use(express.json());
app.use(express.static('public'));

The repo keeps a placeholder default so the server boots without a key, then refuses to call the API until you set one. Nicer than a cryptic 401 on your first click.

Step 3: Call the Barcode Generator API

This route is the core of the app. It takes data, type, and displayValue from the frontend, forwards them to APIVerve, and hands back just the image URL.

app.post('/api/generate', async (req, res) => {
  const { data, type = 'code128', displayValue = true } = req.body;

  if (!data) {
    return res.status(400).json({ error: 'Data is required' });
  }

  if (API_KEY === 'your-api-key-here') {
    return res.status(500).json({
      error: 'API key not configured. Set API_KEY environment variable or edit server.js'
    });
  }

  try {
    const response = await fetch(API_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': API_KEY
      },
      body: JSON.stringify({ data, type, displayValue })
    });

    const result = await response.json();

    if (result.status === 'ok') {
      res.json({
        success: true,
        data,
        type,
        imageUrl: result.data.downloadURL
      });
    } else {
      res.status(400).json({ error: result.error || 'Generation failed' });
    }
  } catch (err) {
    console.error('API Error:', err);
    res.status(500).json({ error: 'Failed to generate barcode' });
  }
});

app.listen(PORT, () => {
  console.log(`Barcode Generator running at http://localhost:${PORT}`);
});

A few things worth pointing out:

  • Check result.status, not just the HTTP code. Every APIVerve response uses the same envelope: status is "ok" or "error", and on failure data is null and error holds the message. Branching on status means you never try to read result.data.downloadURL off a null.
  • Pass result.error through. When a request fails, the API’s own message is far more useful than “something went wrong.” The repo surfaces it straight to the UI.
  • Defaults live in two places. The route defaults type to code128 because the API requires type — skip it and the request fails. displayValue defaults to true on both sides.

More on the response envelope and status codes in the error handling docs.

Step 4: Wire up the frontend

The repo’s public/index.html is a single page with a text input, a type dropdown, and a checkbox. The part that matters is the fetch to your own server:

async function generate() {
  const data = document.getElementById('data').value.trim();
  const type = document.getElementById('type').value;
  const displayValue = document.getElementById('displayValue').checked;

  const response = await fetch('/api/generate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ data, type, displayValue })
  });

  const result = await response.json();

  if (result.error) {
    showError(result.error);
  } else {
    document.getElementById('barcode-image').src = result.imageUrl;
    document.getElementById('download-link').href = result.imageUrl;
  }
}

The image URL goes straight into an <img> tag and a download link. No base64 decoding, no canvas drawing.

Step 5: Run it

API_KEY=your-real-key npm start

(On Windows PowerShell: $env:API_KEY="your-real-key"; npm start.) Open http://localhost:3000, type SKU-12345678, and generate.

Handling Code 39’s character limits

Code 128 takes any ASCII, so it rarely fails on input. Code 39 is pickier: uppercase A-Z, digits, space, and - . $ / + %. Send it a lowercase SKU and generation fails — you’ll get status: "error" back instead of an image.

Don’t spend a request finding that out. Check before you call:

const CODE39_PATTERN = /^[A-Z0-9 \-.$/+%]+$/;

function validateForType(data, type) {
  if (type === 'code39' && !CODE39_PATTERN.test(data)) {
    return 'Code 39 only supports A-Z, 0-9, space, and - . $ / + %';
  }
  return null;
}

Drop that into the route right after the !data check and return a 400 when it gives you a message. You could uppercase the input automatically instead, but then the barcode doesn’t match what’s in your database — and that mismatch is exactly the kind of bug that surfaces in a warehouse, not in testing.

Save the image before the URL expires

This is the gotcha. downloadURL is a signed link that stops working around 24 hours after generation (that’s the expires field). Fine for a preview. Terrible for an inventory record you’ll reprint in six months.

If a barcode ends up on a label, in a PDF, or in your database, download the PNG and store it yourself:

const fs = require('fs/promises');

async function saveBarcode(sku) {
  const response = await fetch('https://api.apiverve.com/v1/barcodegenerator', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.API_KEY },
    body: JSON.stringify({ data: sku, type: 'code128', displayValue: true })
  });

  const result = await response.json();
  if (result.status !== 'ok') throw new Error(result.error);

  const image = await fetch(result.data.downloadURL);
  await fs.writeFile(`${sku}.png`, Buffer.from(await image.arrayBuffer()));
}

async function main() {
  for (const sku of ['SKU-1001', 'SKU-1002', 'SKU-1003']) {
    await saveBarcode(sku);
  }
}

main().catch(console.error);

The loop goes one at a time on purpose — for a few hundred labels that’s plenty fast and keeps you well clear of rate limits. Each call costs credits, so generate once per SKU and reuse the file rather than regenerating every time a label prints.

Code 128 or Code 39, and what breaks on real scanners

Default to Code 128. It encodes full ASCII and packs more data per inch, so the same SKU prints physically shorter than in Code 39. Shorter barcodes fit smaller labels and read more reliably at an angle.

Use Code 39 only when someone makes you. Some government and automotive supply chains still specify it. If there’s no spec forcing it, there’s no reason to pick it.

Keep displayValue on. Scanners fail. Labels get scuffed. When that happens, a human needs to read the number and type it in.

Test on the real printer and the real scanner. A barcode that scans off your monitor can fail on glossy label stock or in a dim warehouse aisle. Leave blank space (a quiet zone) on both sides of the bars — cropping the image tight to the edges is the fastest way to get a label that looks fine and never scans.

Need more than an ID? Linear barcodes are for short identifiers. If you want to encode a URL or structured data, use the QR Code Generator instead — the QR code API guide covers when that’s the better call. For printing full label sheets, pair saved barcode images with HTML to PDF.

Next steps

The same key works for every API on the platform. Get a free API key and generate your first barcode.

Give your software a way to act on the world.

Scaling in production?

The same key runs from your first prototype to millions of calls — on APIVerve's rails, backed by an uptime SLA.

See pricing