URL shorteners like Bitly or TinyURL are incredibly popular tools, but have you ever wondered what goes into building one? In this blog, we’ll dive into how I built a custom URL shortener in Go with rate limiting and a Redis database for data storage and IP tracking. We’ll cover the core features, the tech stack, and the challenges I encountered along the way.
This URL shortener application accepts long URLs from users, generates shorter, unique links, and allows users to customize the shortened alias if desired. The server redirects any visitor who uses the shortened URL to the original long URL.
Here's a quick overview of the primary components:
With these features in mind, let’s break down the implementation.
The core files and folders are organized as follows:
. ├── main.go # Entry point for the application ├── routes/ │ ├── shorten.go # Handles URL shortening and redirects ├── database/ │ ├── redis.go # Database connection logic ├── helpers/ │ ├── helper.go # Utility functions for URL validation ├── .env # Environment variables ├── docker-compose.yml # Docker setup for Redis
Our main application file sets up the routes for shortening and resolving URLs. Here’s the code snippet:
package main import ( "log" "os" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/logger" "github.com/joho/godotenv" "github.com/ravikisha/url-shortener/routes" ) func setupRoutes(app *fiber.App) { app.Get("/:url", routes.ResolveURL) app.Post("/api/v1", routes.ShortenURL) } func main() { err := godotenv.Load() if err != nil { log.Fatal("Error loading .env file") } app := fiber.New() app.Use(logger.New()) setupRoutes(app) log.Fatal(app.Listen(os.Getenv("APP_PORT"))) }
To prevent abuse, we use Redis to keep track of each IP address and limit the number of allowed requests. Here’s the flow:
. ├── main.go # Entry point for the application ├── routes/ │ ├── shorten.go # Handles URL shortening and redirects ├── database/ │ ├── redis.go # Database connection logic ├── helpers/ │ ├── helper.go # Utility functions for URL validation ├── .env # Environment variables ├── docker-compose.yml # Docker setup for Redis
In redis.go, we define a helper function to connect to Redis. This connection is used across different components to store short URLs and enforce rate limits. Here's a simple example of how Redis is configured:
package main import ( "log" "os" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/logger" "github.com/joho/godotenv" "github.com/ravikisha/url-shortener/routes" ) func setupRoutes(app *fiber.App) { app.Get("/:url", routes.ResolveURL) app.Post("/api/v1", routes.ShortenURL) } func main() { err := godotenv.Load() if err != nil { log.Fatal("Error loading .env file") } app := fiber.New() app.Use(logger.New()) setupRoutes(app) log.Fatal(app.Listen(os.Getenv("APP_PORT"))) }
To simplify setting up Redis, I used Docker. This makes the application portable and easy to deploy.
package routes import ( "os" "strconv" "time" "github.com/asaskevich/govalidator" "github.com/go-redis/redis/v8" "github.com/gofiber/fiber/v2" "github.com/ravikisha/url-shortener/database" "github.com/ravikisha/url-shortener/helpers" ) // Define structs for the request and response data type request struct { URL string `json:"url"` CustomShort string `json:"short"` Expiry time.Duration `json:"expiry"` } type response struct { URL string `json:"url"` CustomShort string `json:"short"` Expiry time.Duration `json:"expiry"` XRateRemaining int `json:"x-rate-remaining"` XRateLimitReset time.Duration `json:"x-rate-limit-reset"` }
Run the Docker container for Redis:
package database import ( "context" "github.com/go-redis/redis/v8" ) var Ctx = context.Background() func NewClient(dbNum int) *redis.Client { rdb := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", DB: dbNum, }) return rdb }
Set environment variables in .env:
version: '3' services: redis: image: "redis:alpine" ports: - "6379:6379"
Run the Go application:
docker-compose up -d
Now, the application is live, and you can start shortening URLs!
Send a POST request to /api/v1 with the following JSON payload:
DB_ADDR="localhost:6379" DB_PASSWORD="" APP_PORT=":6767" DOMAIN="localhost:6767" APP_QUOTA=10
Use the generated short URL, like http://localhost:6767/exmpl, to be redirected to https://example.com.
There are a few features and optimizations I’d like to add in the future:
Building this URL shortener was a rewarding experience and a great way to explore Go, Fiber, and Redis. This project provides a solid foundation, whether you're learning about backend development or exploring Go’s potential in web services.
If you'd like to see the code in action, check out the GitHub repository here. Let me know your thoughts or if you have suggestions for improving the project!
The above is the detailed content of Building a URL Shortener with Rate Limiting and Redis in Go. For more information, please follow other related articles on the PHP Chinese website!