URL Shortener

短链接生成

Powered by Codong · Open Source · MIT License

Long URL
Short Link
Copied!
§ 01 Source Code

The complete service — compare across languages

main.py Flask + SQLAlchemy ~90 lines
from flask import Flask, request, redirect, jsonify
from flask_sqlalchemy import SQLAlchemy
import random, string, datetime

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///shorturl.db'
db = SQLAlchemy(app)

class URL(db.Model):
    __tablename__ = 'urls'
    code       = db.Column(db.String(10), primary_key=True)
    long_url   = db.Column(db.String(2048), nullable=False)
    hits       = db.Column(db.Integer, default=0)
    created_at = db.Column(db.DateTime,
                   default=datetime.datetime.utcnow)

with app.app_context():
    db.create_all()

def generate_id(n=6):
    chars = string.ascii_lowercase + string.digits
    return ''.join(random.choice(chars) for _ in range(n))

def is_valid_url(s):
    return s and (s.startswith('http://') or
                   s.startswith('https://'))

@app.route('/api/shorten', methods=['POST'])
def shorten():
    data = request.get_json()
    if not data or not is_valid_url(data.get('url')):
        return jsonify({'error': 'invalid url'}), 400
    code = generate_id()
    entry = URL(code=code, long_url=data['url'])
    db.session.add(entry)
    db.session.commit()
    return jsonify({
        'code': code,
        'short_url': f'https://codong.org/s/{code}'
    })

@app.route('/s/<code>')
def resolve(code):
    entry = URL.query.get(code)
    if not entry:
        return jsonify({'error': 'not found'}), 404
    entry.hits += 1
    db.session.commit()
    return redirect(entry.long_url, code=301)

@app.route('/api/stats/<code>')
def stats(code):
    entry = URL.query.get(code)
    if not entry:
        return jsonify({'error': 'not found'}), 404
    return jsonify({
        'code': entry.code,
        'long_url': entry.long_url,
        'hits': entry.hits,
        'created_at': entry.created_at.isoformat()
    })

if __name__ == '__main__':
    app.run(port=8082)
main.go stdlib only ~110 lines
package main

import (
  "database/sql"
  "encoding/json"
  "log"; "math/rand"
  "net/http"; "strings"
  _ "modernc.org/sqlite"
)

const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
var db *sql.DB

type ShortenReq struct {
  URL string `json:"url"`
}
type URLRow struct {
  Code      string `json:"code"`
  LongURL   string `json:"long_url"`
  Hits      int    `json:"hits"`
  CreatedAt string `json:"created_at"`
}

func generateID(n int) string {
  b := make([]byte, n)
  for i := range b {
    b[i] = chars[rand.Intn(len(chars))]
  }
  return string(b)
}

func main() {
  db, _ = sql.Open("sqlite", "file:shorturl.db")
  db.Exec(`CREATE TABLE IF NOT EXISTS urls (
    code TEXT PRIMARY KEY,
    long_url TEXT NOT NULL,
    hits INTEGER DEFAULT 0,
    created_at TEXT DEFAULT (datetime('now')))`)

  mux := http.NewServeMux()

  mux.HandleFunc("POST /api/shorten",
    func(w http.ResponseWriter, r *http.Request) {
      var body ShortenReq
      json.NewDecoder(r.Body).Decode(&body)
      if !strings.HasPrefix(body.URL, "http") {
        w.WriteHeader(400)
        json.NewEncoder(w).Encode(
          map[string]string{"error": "invalid url"})
        return
      }
      code := generateID(6)
      db.Exec("INSERT INTO urls (code, long_url) VALUES (?, ?)",
        code, body.URL)
      json.NewEncoder(w).Encode(map[string]string{
        "code": code,
        "short_url": "https://codong.org/s/" + code,
      })
    })

  mux.HandleFunc("GET /s/{code}",
    func(w http.ResponseWriter, r *http.Request) {
      code := r.PathValue("code")
      var longURL string
      if err := db.QueryRow(
        "SELECT long_url FROM urls WHERE code = ?", code,
      ).Scan(&longURL); err != nil {
        w.WriteHeader(404); return
      }
      db.Exec("UPDATE urls SET hits=hits+1 WHERE code=?", code)
      http.Redirect(w, r, longURL, 301)
    })

  mux.HandleFunc("GET /api/stats/{code}",
    func(w http.ResponseWriter, r *http.Request) {
      var row URLRow
      db.QueryRow(
        "SELECT code,long_url,hits,created_at FROM urls WHERE code=?",
        r.PathValue("code"),
      ).Scan(&row.Code, &row.LongURL, &row.Hits, &row.CreatedAt)
      json.NewEncoder(w).Encode(row)
    })

  log.Fatal(http.ListenAndServe(":8082", mux))
}
main.js Express + better-sqlite3 ~95 lines
const express = require('express')
const Database = require('better-sqlite3')

const app = express()
const db = new Database('shorturl.db')
app.use(express.json())

db.exec(`CREATE TABLE IF NOT EXISTS urls (
  code TEXT PRIMARY KEY,
  long_url TEXT NOT NULL,
  hits INTEGER DEFAULT 0,
  created_at TEXT DEFAULT (datetime('now'))
)`)

function generateId(n = 6) {
  const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
  return Array.from({length: n},
    () => chars[Math.floor(Math.random() * chars.length)]
  ).join('')
}

function isValidUrl(s) {
  try {
    const u = new URL(s)
    return u.protocol === 'http:' ||
           u.protocol === 'https:'
  } catch { return false }
}

app.post('/api/shorten', (req, res) => {
  const { url } = req.body
  if (!isValidUrl(url))
    return res.status(400).json({ error: 'invalid url' })
  const code = generateId()
  db.prepare(
    'INSERT INTO urls (code, long_url) VALUES (?, ?)'
  ).run(code, url)
  res.json({
    code,
    short_url: `https://codong.org/s/${code}`
  })
})

app.get('/s/:code', (req, res) => {
  const row = db.prepare(
    'SELECT long_url FROM urls WHERE code = ?'
  ).get(req.params.code)
  if (!row)
    return res.status(404).json({ error: 'not found' })
  db.prepare(
    'UPDATE urls SET hits = hits + 1 WHERE code = ?'
  ).run(req.params.code)
  res.redirect(301, row.long_url)
})

app.get('/api/stats/:code', (req, res) => {
  const row = db.prepare(
    'SELECT * FROM urls WHERE code = ?'
  ).get(req.params.code)
  if (!row)
    return res.status(404).json({ error: 'not found' })
  res.json(row)
})

app.listen(8082,
  () => console.log('listening'))
Language Lines of Code Dependencies DB Built-in HTTP Built-in
Python
~90
Flask, SQLAlchemy
Go
~110
modernc/sqlite
JavaScript
~95
Express, better-sqlite3

Open Source

Full source on GitHub. Use it, fork it, learn from it. MIT License.

View on GitHub →