Build a URL Shortener with QR Codes: Step-by-Step Tutorial

C
Christian Mesa
Feb 26, 2026
5 min read

What We're Building

A URL shortener that automatically generates a QR code for every shortened link. Users paste a long URL, get a short link, and can download a QR code that points to it.

We'll use Node.js with Express for the backend and the ToolCenter QR Code API for generating QR codes.

Prerequisites

  • Node.js 18+ installed
  • A ToolCenter key
  • Basic knowledge of Express.js

Project Setup

mkdir url-shortener-qr
cd url-shortener-qr
npm init -y
npm install express nanoid

Project Structure

url-shortener-qr/
├── server.js
├── public/
│   └── index.html
└── package.json

Step 1: Build the Server

Create server.js:

const express = require('express');
const { nanoid } = require('nanoid');
const app = express();

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

// In-memory store (use a database in production)
const urls = new Map();
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
const DEVTOOLBOX_KEY = process.env.DEVTOOLBOX_API_KEY;

// Shorten a URL
app.post('/api/shorten', async (req, res) => {
  const { url } = req.body;

  if (!url || !isValidUrl(url)) {
    return res.status(400).json({ error: 'Invalid URL' });
  }

  const code = nanoid(7);
  const shortUrl = `${BASE_URL}/${code}`;

  urls.set(code, {
    originalUrl: url,
    shortUrl,
    createdAt: new Date().toISOString(),
    clicks: 0,
  });

  // Generate QR code via ToolCenter
  const qrCodeUrl = await generateQRCode(shortUrl);

  res.json({
    shortUrl,
    originalUrl: url,
    qrCode: qrCodeUrl,
    code,
  });
});

// Redirect short URLs
app.get('/:code', (req, res) => {
  const entry = urls.get(req.params.code);
  if (!entry) {
    return res.status(404).send('URL not found');
  }
  entry.clicks++;
  res.redirect(301, entry.originalUrl);
});

// Get URL stats
app.get('/api/stats/:code', (req, res) => {
  const entry = urls.get(req.params.code);
  if (!entry) {
    return res.status(404).json({ error: 'Not found' });
  }
  res.json(entry);
});

function isValidUrl(string) {
  try {
    new URL(string);
    return true;
  } catch {
    return false;
  }
}

app.listen(3000, () => console.log('Server running on port 3000'));

Step 2: Integrate the QR Code API

Add the QR code generation function to server.js:

const https = require('https');

async function generateQRCode(url) {
  const response = await fetch('https://api.toolcenter.dev/v1/qrcode', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${DEVTOOLBOX_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      data: url,
      size: 300,
      format: 'png',
      errorCorrection: 'M',
      foregroundColor: '#000000',
      backgroundColor: '#ffffff',
    }),
  });

  if (!response.ok) {
    throw new Error(`QR code generation failed: ${response.status}`);
  }

  // Convert to base64 data URL for embedding
  const buffer = await response.arrayBuffer();
  const base64 = Buffer.from(buffer).toString('base64');
  return `data:image/png;base64,${base64}`;
}

Step 3: Build the Frontend

Create public/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>URL Shortener + QR Code</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: system-ui; max-width: 600px; margin: 50px auto; padding: 20px; }
    h1 { margin-bottom: 30px; }
    .input-group { display: flex; gap: 10px; margin-bottom: 20px; }
    input { flex: 1; padding: 12px; border: 2px solid #ddd; border-radius: 8px; font-size: 16px; }
    button { padding: 12px 24px; background: #667eea; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; }
    button:hover { background: #5a6fd6; }
    .result { display: none; background: #f8f9fa; padding: 20px; border-radius: 8px; text-align: center; }
    .result.show { display: block; }
    .short-url { font-size: 20px; font-weight: bold; color: #667eea; margin: 15px 0; word-break: break-all; }
    .qr-code img { max-width: 200px; margin: 15px 0; }
    .stats { font-size: 14px; color: #666; }
  </style>
</head>
<body>
  <h1>🔗 URL Shortener + QR</h1>
  <div class="input-group">
    <input type="url" id="urlInput" placeholder="Paste your long URL here..." />
    <button onclick="shortenUrl()">Shorten</button>
  </div>
  <div class="result" id="result">
    <p>Your shortened URL:</p>
    <div class="short-url" id="shortUrl"></div>
    <div class="qr-code"><img id="qrCode" alt="QR Code" /></div>
    <p class="stats">QR code generated via ToolCenter</p>
  </div>

  <script>
    async function shortenUrl() {
      const url = document.getElementById('urlInput').value;
      if (!url) return;

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

      const data = await response.json();

      document.getElementById('shortUrl').textContent = data.shortUrl;
      document.getElementById('qrCode').src = data.qrCode;
      document.getElementById('result').classList.add('show');
    }
  </script>
</body>
</html>

Step 4: Custom QR Code Styling

Make your QR codes branded with custom colors:

async function generateBrandedQR(url, options = {}) {
  const response = await fetch('https://api.toolcenter.dev/v1/qrcode', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${DEVTOOLBOX_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      data: url,
      size: options.size || 400,
      format: 'png',
      errorCorrection: 'H',  // High correction for logo overlay
      foregroundColor: options.color || '#667eea',
      backgroundColor: '#ffffff',
      margin: 2,
    }),
  });

  return response;
}

Step 5: Adding Analytics

Track clicks and display stats:

app.get('/api/analytics', (req, res) => {
  const allUrls = Array.from(urls.entries()).map(([code, data]) => ({
    code,
    ...data,
  }));

  const totalClicks = allUrls.reduce((sum, u) => sum + u.clicks, 0);

  res.json({
    totalUrls: allUrls.length,
    totalClicks,
    urls: allUrls.sort((a, b) => b.clicks - a.clicks),
  });
});

Step 6: Run It

DEVTOOLBOX_API_KEY=your_key_here node server.js

Visit http://localhost:3000, paste a URL, and get a shortened link with a QR code.

Production Considerations

Before deploying to production:

  1. Use a real database — Replace the in-memory Map with PostgreSQL or Redis
  2. Add rate limiting — Prevent abuse with express-rate-limit
  3. Custom domains — Use a short domain like sho.rt
  4. Bulk generation — Add an endpoint that accepts multiple URLs
  5. QR code caching — Cache generated QR codes to avoid redundant API calls
const cache = new Map();

async function getCachedQR(url) {
  if (cache.has(url)) return cache.get(url);
  const qr = await generateQRCode(url);
  cache.set(url, qr);
  return qr;
}

Conclusion

You've built a functional URL shortener with automatic QR code generation. The ToolCenter QR Code API handles the heavy lifting of generating high-quality, customizable QR codes. From here, you can extend the project with user accounts, custom slugs, analytics dashboards, and branded QR code designs.

Share this article

CM

Christian Mesa

Founder & Developer at ToolCenter

Full-stack developer from the Canary Islands, Spain. Building developer tools and APIs that simplify web development. Passionate about clean code, performance, and making complex things simple.

Try ToolCenter APIs Free

100 API calls/month free. No credit card required.

Related Posts