← Zurück zur Übersicht Nie wieder Bit.ly: Baue deinen eigenen URL-Shortener mit Node.js & SQLite

Nie wieder Bit.ly: Baue deinen eigenen URL-Shortener mit Node.js & SQLite

[WERBUNG: CONTENT OBEN]

Nie wieder Bit.ly: Baue deinen eigenen URL-Shortener

Jeder kennt Bit.ly, TinyURL & Co. – aber warum seine Daten durch fremde Server jagen, wenn man das Ganze in wenigen Minuten selbst bauen kann? In diesem Tutorial erstellst du einen vollständigen URL-Shortener mit eigener Datenbank, Redirect-Logik und einer minimalen, aber schicken Oberfläche.

Was du lernst:

  • REST-API-Design mit Express
  • Datenbank-Grundlagen mit SQLite (ohne externen Server!)
  • Zufällige Short-IDs generieren
  • Deployment mit Docker Compose

🛠️ Projekt aufsetzen

mkdir my-url-shortener && cd my-url-shortener
npm init -y
npm install express better-sqlite3

1. Die Datenbank (SQLite)

SQLite ist perfekt für diesen Use-Case: Kein separater Datenbankserver nötig, alles lebt in einer einzigen Datei.

my-url-shortener/db.js:

const Database = require('better-sqlite3');
const db = new Database('urls.db');

// Tabelle erstellen (falls nicht vorhanden)
db.exec(`
    CREATE TABLE IF NOT EXISTS urls (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        short TEXT UNIQUE NOT NULL,
        original TEXT NOT NULL,
        clicks INTEGER DEFAULT 0,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )
`);

module.exports = db;

2. Der Bauplan (package.json)

Bevor wir Funktionen programmieren, legen wir fest, welche Werkzeuge wir nutzen. Die package.json ist das Gehirn unserer Node-Infrastruktur – sie definiert nicht nur Abhängigkeiten, sondern auch den entscheidenden Start-Befehl für Docker.

my-url-shortener/package.json:

{
  "name": "my-url-shortener",
  "version": "1.0.0",
  "scripts": { "start": "node server.js" },
  "dependencies": {
    "better-sqlite3": "^9.0.0",
    "express": "^4.18.2"
  }
}

3. Short-ID Generator

Wir generieren zufällige, kurze Codes. Keine externe Library nötig.

my-url-shortener/utils.js:

function generateShortId(length = 6) {
    const chars =
        'abcdefghijklmnopqrstuvwxyz' +
        'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    let result = '';
    for (let i = 0; i < length; i++) {
        result += chars.charAt(
            Math.floor(Math.random() * chars.length)
        );
    }
    return result;
}

module.exports = { generateShortId };

4. Der Server (Express)

Das Herzstück: Eine API zum Erstellen und Auflösen von Short-Links.

my-url-shortener/server.js:

const express = require('express');
const path = require('path');
const db = require('./db');
const { generateShortId } = require('./utils');

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));

// API: Neuen Short-Link erstellen
app.post('/api/shorten', (req, res) => {
    const { url } = req.body;
    if (!url) {
        return res.status(400)
            .json({ error: 'URL fehlt' });
    }

    const short = generateShortId();
    const stmt = db.prepare(
        'INSERT INTO urls (short, original) VALUES (?, ?)'
    );
    stmt.run(short, url);

    res.json({
        short,
        shortUrl: `${req.protocol}://${req.get('host')}/${short}`,
        original: url
    });
});

// API: Alle Links auflisten
app.get('/api/links', (req, res) => {
    const links = db.prepare(
        'SELECT * FROM urls ORDER BY created_at DESC'
    ).all();
    res.json(links);
});

// Redirect: Short-Link auflösen
app.get('/:short', (req, res) => {
    const row = db.prepare(
        'SELECT * FROM urls WHERE short = ?'
    ).get(req.params.short);

    if (!row) return res.status(404).send('Link nicht gefunden');

    // Klick-Zähler erhöhen
    db.prepare(
        'UPDATE urls SET clicks = clicks + 1 WHERE short = ?'
    ).run(req.params.short);

    res.redirect(row.original);
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(
        `URL-Shortener läuft auf http://localhost:${PORT}`
    );
});

5. Das Frontend

Eine minimalistische Oberfläche zum Verkürzen und Anzeigen der Links.

my-url-shortener/public/index.html:

<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <title>URL Shortener</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }
        body {
            font-family: -apple-system, BlinkMacSystemFont,
                         'Segoe UI', Roboto, sans-serif;
            background: #0f172a;
            color: #e2e8f0;
            min-height: 100vh;
            display: flex;
            justify-content: center;
            padding: 3rem 1rem;
        }
        #app {
            width: 100%;
            max-width: 600px;
        }
        h1 {
            text-align: center;
            margin-bottom: 2rem;
            color: #38bdf8;
        }
        .input-group {
            display: flex;
            gap: 0.5rem;
            margin-bottom: 2rem;
        }
        input {
            flex-grow: 1;
            padding: 0.75rem 1rem;
            border: 1px solid #334155;
            border-radius: 0.5rem;
            background: #1e293b;
            color: #e2e8f0;
            font-size: 1rem;
            outline: none;
        }
        input:focus { border-color: #38bdf8; }
        button {
            padding: 0.75rem 1.5rem;
            background: #38bdf8;
            color: #0f172a;
            border: none;
            border-radius: 0.5rem;
            cursor: pointer;
            font-weight: 600;
            transition: all 0.2s;
        }
        button:hover {
            background: #0ea5e9;
            transform: translateY(-1px);
        }
        .result {
            background: #1e293b;
            border: 1px solid #334155;
            border-radius: 0.5rem;
            padding: 1rem;
            margin-bottom: 2rem;
            display: none;
            text-align: center;
        }
        .result a {
            color: #38bdf8;
            font-size: 1.2rem;
            font-weight: bold;
        }
        table {
            width: 100%;
            border-collapse: collapse;
        }
        th, td {
            text-align: left;
            padding: 0.75rem;
            border-bottom: 1px solid #334155;
            font-size: 0.85rem;
        }
        th { color: #94a3b8; }
        td a {
            color: #38bdf8;
            text-decoration: none;
        }
        .clicks {
            color: #10b981;
            font-weight: bold;
        }
    </style>
</head>
<body>
    <div id="app">
        <h1>🔗 URL Shortener</h1>

        <div class="input-group">
            <input type="url" id="urlInput"
                   placeholder="https://beispiel.de/langer-link">
            <button onclick="shorten()">Kürzen</button>
        </div>

        <div class="result" id="result">
            Dein Kurzlink: <a id="shortLink"
                              href="#"
                              target="_blank"></a>
        </div>

        <table>
            <thead>
                <tr>
                    <th>Kurzlink</th>
                    <th>Original</th>
                    <th>Klicks</th>
                </tr>
            </thead>
            <tbody id="linkList"></tbody>
        </table>
    </div>

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

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

        const result = document.getElementById('result');
        const link = document.getElementById('shortLink');
        link.href = data.shortUrl;
        link.textContent = data.shortUrl;
        result.style.display = 'block';

        document.getElementById('urlInput').value = '';
        loadLinks();
    }

    async function loadLinks() {
        const resp = await fetch('/api/links');
        const links = await resp.json();
        const tbody = document.getElementById('linkList');

        tbody.innerHTML = links.map(l => `
            <tr>
                <td>
                    <a href="/${l.short}"
                       target="_blank">/${l.short}</a>
                </td>
                <td>${l.original.substring(0, 35)}...</td>
                <td class="clicks">${l.clicks}</td>
            </tr>
        `).join('');
    }

    loadLinks();
    </script>
</body>
</html>

5. Docker Deployment

my-url-shortener/docker-compose.yml:

version: '3.8'
services:
  app:
    image: node:18-alpine
    working_dir: /app
    volumes:
      - .:/app
    ports:
      - "3000:3000"
    command: sh -c "npm install && npm start"
docker compose up -d

Öffne http://localhost:3000 – fertig ist dein eigener URL-Shortener!

⚠️ 📸 SCREENSHOT ANFRAGE: Hier einen Screenshot der fertigen Anwendung einfügen, idealerweise mit bereits gekürzten Links und sichtbarem Klick-Zähler.


🧠 Was passiert unter der Haube?

  1. User gibt URL ein → POST an /api/shorten
  2. Server generiert eine 6-stellige Short-ID
  3. SQLite speichert das Mapping short → original
  4. Beim Aufruf von /abc123 wird der Klick gezählt und auf die Original-URL redirected

Das Schöne: Die SQLite-Datei urls.db liegt direkt im Projektordner. Kein externer Datenbankserver, kein Konfigurationsaufwand.


📈 Erweiterungs-Ideen

Feature Schwierigkeit Beschreibung
Custom Slugs Eigene Kurznamen statt zufälliger IDs
QR-Code Generation ⭐⭐ Automatisch einen QR-Code zum Short-Link generieren
Ablaufdatum ⭐⭐ Links nach X Tagen automatisch deaktivieren
Auth & Dashboard ⭐⭐⭐ Login-System mit persönlichem Link-Dashboard

Fazit

In unter 100 Zeilen Backend-Code hast du einen voll funktionsfähigen URL-Shortener gebaut – mit Datenbank, Klick-Tracking und einem sauberen Frontend. Du bist jetzt unabhängig von Bit.ly und hast volle Kontrolle über deine Links.

Viel Spaß beim Kürzen! 🔗

[WERBUNG: CONTENT UNTEN]