Let's build a URL shortener in Go - Final Part : Forwarding

Throughout the previous tutorial parts, we have been able to build all the necessary components that would lead to the end goal of generating short URL and forwarding to the original url once a shortened version is provided.

For recap, you can find several previous articles from this series here  :

Now it's  time to put the components we built previously to good usage, so during this part we will be making sure those previously built components work together as expected.

We are going to build 2 main endpoints to our API service :

  • One endpoint that  will be used to generate a short url and return it, when the initial long url is provided. /create-short-url
  • The other one will be used to provide the actual redirection  from the shortened version to the original longer URL. /:short-url

IV. 1.  Handlers & Endpoints

IV. 1. 1. Setup & Definitions


Without wasting more time, let's go ahead and create the handler package and define our handers functions in there.
Create a folder called handler and put in a file called handlers.go.
After that our project directory should look like the tree below :

├── go.mod
├── go.sum
├── handler
│   └── handlers.go
├── main.go
├── shortener
│   ├── shorturl_generator.go
│   └── shorturl_generator_test.go
└── store
    ├── store_service.go
    └── store_service_test.go
Project directory structure

Now let's define our handlers stubs.

package handler

import (
	"github.com/gin-gonic/gin"
)


func CreateShortUrl(c *gin.Context) {
	
    /// Implementation to be added

}

func HandleShortUrlRedirect(c *gin.Context) {

	/// Implementation to be added

}


After defining stubs, we should go straight to the main.go file to add the needed endpoints.

package main

import (
	"fmt"
	"github.com/eddywm/go-shortner/handler"
	"github.com/eddywm/go-shortner/store"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Welcome to the URL Shortener API",
		})
	})

	r.POST("/create-short-url", func(c *gin.Context) {
		handler.CreateShortUrl(c)
	})

	r.GET("/:shortUrl", func(c *gin.Context) {
		handler.HandleShortUrlRedirect(c)
	})

	// Note that store initialization happens here
	store.InitializeStore()

	err := r.Run(":9808")
	if err != nil {
		panic(fmt.Sprintf("Failed to start the web server - Error: %v", err))
	}

}
Adding endpoints inside the main.go file
Note : In more complex apps, endpoints should  live in a separate file, but for the sake of simplicity and since they are just 2 endpoints, we will be having them in the main.go file

IV. 1. 2. Implementations



Now that we have defined stubs for our handlers, it's time to add the actual implementation code.

STEP 1 : We will be starting with implementing `CreateShortUrl()` handler function, this should very straightforward :

  • We will getting the creation request body, parse it and extract the initial long url and userId.
  • Call our shortener.GenerateShortLink() that we implemented in the previous PART III and generate our shortened hash.
  • Finally store the mapping of our output hash/shortUrl with the initial long url, here, we will be using the store.SaveUrlMapping() we implemented back in PART II
import (
	"github.com/eddywm/go-shortner/shortener"
	"github.com/eddywm/go-shortner/store"
	"github.com/gin-gonic/gin"
	"net/http"
)

// Request model definition
type UrlCreationRequest struct {
	LongUrl string `json:"long_url" binding:"required"`
	UserId string `json:"user_id" binding:"required"`
}


func CreateShortUrl(c *gin.Context) {
	var creationRequest UrlCreationRequest
	if err := c.ShouldBindJSON(&creationRequest); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	shortUrl := shortener.GenerateShortLink(creationRequest.LongUrl, creationRequest.UserId)
	store.SaveUrlMapping(shortUrl, creationRequest.LongUrl, creationRequest.UserId)

	host := "http://localhost:9808/"
	c.JSON(200, gin.H{
		"message":   "short url created successfully",
		"short_url": host +shortUrl,
	})

}
CreateShortUrl() implementation inside handlers.go

Steps 2 : The second and last step will be about implementing the redirection handler, HandleShortUrlRedirect(), it will consist of :

  • Getting the short url from the path parameter /:shortUrl
  • Call the store to retrieve the initial url that corresponds to the short one provided in the path.
  • And finally apply the http redirection function


func HandleShortUrlRedirect(c *gin.Context) {
	shortUrl := c.Param("shortUrl")
	initialUrl := store.RetrieveInitialUrl(shortUrl)
	c.Redirect(302, initialUrl)
}
Inside the handlers.go file just below CreateShortUrl implementation

IV. 2.  Testing Time 🧪

After completing the handlers implementation, now it's time to test the all thing.

  • Step 1 : Run/Start the project ( main.go file is the entry point )
    A server should start at localhost:9808
{
    "message": "Welcome to the URL Shortener API"
}
This should be the response at localhost:9808 
  • Step 2 : Request URL shortening action.
    We can post the request body below to the specified endpoint.
    Note that you can use any rest client you have installed locally.
{
    "long_url": "https://www.guru3d.com/news-story/spotted-ryzen-threadripper-pro-3995wx-processor-with-8-channel-ddr4,2.html",
    "user_id" : "e0dba740-fc4b-4977-872c-d360239e6b10"
}
POST http://localhost:9808/create-short-url
curl --request POST \
--data '{
    "long_url": "https://www.guru3d.com/news-story/spotted-ryzen-threadripper-pro-3995wx-processor-with-8-channel-ddr4,2.html",
    "user_id" : "e0dba740-fc4b-4977-872c-d360239e6b10"
}' \
  http://localhost:9808/create-short-url
Same request here, but using curl to post to the endpoint.

The response should look like the json below :

{
    "message": "short url created successfully",
    "short_url": "http://localhost:9808/9Zatkhpi"
}
Response returned after the successful request above, 👏 We have shortened our first url.
  • Step 3 : Testing the redirection.

    Click on the link returned in the previous response http://localhost:9808/9Zatkhpi and check if it redirects back to the original long url.
    If everything was done right, this shortened url should be able to redirect to the old initial url.

    Step 2 & 3 can be repeated n times, for any any number of urls we'd like to apply shortening on.


IV. 3. Conclusion & Next Steps

If you have reached up to this point, it's fair to assume you have invested a good amount  of time following up with this tutorial series, I'm really hoping you enjoyed reading and practicing along the way.
Our service is now up and running and it can shorten whatever url you throw at it.
You can find the complete project at this repository https://github.com/eddywm/go-shortener-wm.

A few  improvements that can be added as voluntary next steps :

  • Dockerize and deploy the service in the cloud.
  • Add a SQL DB (Postgres or Mysql) as backup to the Redis main store, that should be used for store of cold values/least frequently fetched data.
  • Build a minimal front-end that would be consuming this service.

If you have any feedback, questions or request regarding tutorial series you want me to write about, please feel free to contact me via Twitter or Email.




Ciao ! 👋