Challenge #4: Build a URL Shortener
Your task this week is a classic interview question :D. To implement a simple URL shortening service similar to Bitly or TinyURL. The service should take a long URL and generate a short, unique URL that can be used to redirect back to the original URL.
Requirements
Shortening URLs:
Create a function
shorten_url(long_url)that takes a long URL as input.Generate a unique, short identifier (e.g., 6 alphanumeric characters) to represent the long URL.
Store the mapping of the short identifier to the original URL in a database.
Expanding URLs:
Create a function
expand_url(short_url)that takes a short URL as input and retrieves the original long URL.If the short URL doesn’t exist, return an error message.
Database Setup:
Use a database (e.g., MySQL, SQLite, or a simple in-memory dictionary if you're prototyping) to store the mapping of short URLs to long URLs.
The table should have at least two fields:
short_id: The unique short identifier.long_url: The original long URL.
Bonus Task (Optional): Implement a basic web interface for testing. Provide input fields for the long URL and a button to generate the short URL.
Additional Tips
Use a hashing function or a base conversion algorithm to generate unique IDs. For simplicity, you could use a random alphanumeric string.
Consider implementing basic error handling for cases like duplicate short URLs, invalid URLs, and attempts to expand non-existent short URLs.


GO CODE
package main
import (
"crypto/rand"
"database/sql"
"encoding/hex"
"fmt"
"html/template"
"log"
"net/http"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
func init() {
var err error
db, err = sql.Open("mysql", "root:password@tcp(localhost:3306)/")
if err != nil {
log.Fatal(err)
}
createDatabaseQuery := `
CREATE DATABASE IF NOT EXISTS url_shortener;
`
_, err = db.Exec(createDatabaseQuery)
if err != nil {
log.Fatal(err)
}
// Now open the connection with the url_shortener database
db, err = sql.Open("mysql", "root:password@tcp(localhost:3306)/url_shortener")
if err != nil {
log.Fatal(err)
}
createTableQuery := `
CREATE TABLE IF NOT EXISTS url_mapping (
short_id VARCHAR(6) PRIMARY KEY,
long_url TEXT NOT NULL
);
`
_, err = db.Exec(createTableQuery)
if err != nil {
log.Fatal(err)
}
}
func shortenURL(longURL string) (string, error) {
// Generate a 6-character random short ID
shortID := generateRandomID(6)
// Check if the short ID already exists in the database
var exists bool
err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM url_mapping WHERE short_id = ?)", shortID).Scan(&exists)
if err != nil {
return "", err
}
if exists {
return shortenURL(longURL)
}
_, err = db.Exec("INSERT INTO url_mapping (short_id, long_url) VALUES (?, ?)", shortID, longURL)
if err != nil {
return "", err
}
return shortID, nil
}
func expandURL(shortID string) (string, error) {
// Retrieve the original long URL using the short ID
var longURL string
err := db.QueryRow("SELECT long_url FROM url_mapping WHERE short_id = ?", shortID).Scan(&longURL)
if err != nil {
return "", fmt.Errorf("short URL not found")
}
return longURL, nil
}
func generateRandomID(length int) string {
bytes := make([]byte, length)
_, err := rand.Read(bytes)
if err != nil {
log.Fatal(err)
}
return hex.EncodeToString(bytes)[:length] // Generate a random string of specified length
}
// Handle shortening URL via web form
func shortenHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
if r.Method == "POST" {
longURL := r.FormValue("long_url")
shortID, err := shortenURL(longURL)
if err != nil {
http.Error(w, "Error shortening URL: "+err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "Shorten URL: http://%s<br><br>", shortID)
fmt.Fprintf(w, `Wants to get to the original page: <a href="http://localhost:8080/expand/%s">Click here</a>`, shortID)
} else {
tmpl := template.Must(template.ParseFiles("index.html"))
tmpl.Execute(w, nil)
}
}
// Handle expanding URL via short URL
func expandHandler(w http.ResponseWriter, r *http.Request) {
shortID := r.URL.Path[len("/expand/"):] // Get the short URL from the path
longURL, err := expandURL(shortID)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
http.Redirect(w, r, longURL, http.StatusFound)
}
func main() {
http.HandleFunc("/", shortenHandler)
http.HandleFunc("/expand/", expandHandler)
log.Println("Server started at http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
HTML CODE
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>URL Shortener</title>
</head>
<body>
<h1>URL Shortener</h1>
<form method="POST">
<label for="long_url">Enter Long URL:</label>
<input type="text" id="long_url" name="long_url" required>
<button type="submit">Shorten</button>
</form>
</body>
</html>