Simple URL shortener with Go, Gin and MongoDB

Alan Bardales
4 min readOct 26, 2021

This tutorial will show how to make an URL shortener ready to be added to your own project or just give you an idea on how MongoDB and Go can work. We will use Gin Package to make simple API request to create new short url codes and redirect the URLs previously created.

Photo by Joshua Aragon on Unsplash

Prerequisites

  • Go 1.17 or later
  • Code editor of your choice

In our directory of choice we will run the following command to initialize our code and keep track of our dependencies:

go mod init github.com/<Username>/go-shortener-mongo

To start our project we will install our dependencies in our case will be the mongodb go driver, gin package and shortid package:

go get go.mongodb.org/mongo-driver
go get github.com/gin-gonic/gin
go get github.com/teris-io/shortid

Once installed we will create our main.go file and import our packages

First we will create two variables one to store our mongo collection and the other one to store our context, once created in our code we can create our init() function to initialize our mongodb client (in this case I am running my mongo instance locally), you can change the URI string for the one you are using. If the database or collection does not exist once we insert one document to it, mongodb will create both. At the end we will have available our collection variable to use in our code.

Now with the use of Gin package we will create our request endpoints, in this case there are two one POST “/shorten” that will be in charge of create an url code, check if the url code is not in use in our db, and save the new url code in our db. The GET “/:code” will be responsible to redirect to the long url stored in our db.

func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusAccepted, gin.H{"message": "URL Shortener"})
})
r.GET("/:code", redirect)
r.POST("/shorten", shorten)
r.Run(":5000")}

Then we will create our models and a variable that will store the baseUrl of our project as we are running locally its “http://localhost:5000/" . The shortenBody struct will be used to store the post request body and the UrlDoc struct will be used to store our data in our database.

var baseUrl = "http://localhost:5000/"type shortenBody struct {
LongUrl string `json:"longUrl"`
}
type UrlDoc struct {
ID primitive.ObjectID `bson:"_id"`
UrlCode string `bson:"urlCode"`
LongUrl string `bson:"longUrl"`
ShortUrl string `bson:"shortUrl"`
CreatedAt time.Time `bson:"createdAt"`
ExpiresAt time.Time `bson:"expiresAt"`
}

Once our preparations are complete we will start writing our functions that are responsible for make the short URL and to redirect. First we will start with the shorten function.

func shorten(c *gin.Context) {//function code}

We are using the package “net/http” to get all status numbers and.

First we will bind our body to our struct previously created and check if there is any error when getting the body of the request, if there is an error, we will exit the function and return a status 400 and sends the error as a response.

var body shortenBody
if err := c.BindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

The package “net/url” is a helper to check if the longUrl is a valid URL, and can be implemented as follows. If the longUrl is no a valid URL will return a status 400 and the error.

_, urlErr := url.ParseRequestURI(body.LongUrl)
if urlErr != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": urlErr.Error()})
return
}

For the creation of the url code we will use the package shortid that enables the generation of short, fully unique, non-sequential and by default URL friendly Ids. Same as before if there is an error with the creation we will return a status 500 and the error.

urlCode, idErr := shortid.Generate()
if idErr != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error":idErr.Error()})
return
}

In the next code snippet is how we can check if in our db there is url code that is the same as the one we generate with shortid. If there is an error we need to check if is not the ErrNoDocuments, this error means that our query result has no documents (if the query is empty means that we don’t have a duplicate url code), if the error is different from ErrNoDocuments we will return a 500 with the error in the response. Then if the length of the result is greater than 0 means that we have already created that url code, we return a status 400 and send the url code already in use.

Now we can create our new document that will be saved in our db, we create a variable that will store the date, and another that will store the expiration date 5 days later with the AddDate function. We will create the newUrl concatenating the baseUrl and the urlCode. Then we create a new variable with the type as the UrlDoc struct. At last we can insert the variable as follows.

To finish the shorten function we need to return the newUrl data and when it expires.

c.JSON(http.StatusCreated, gin.H{
"newUrl": newUrl,
"expires": expires.Format("2006-01-02 15:04:05"),
"db_id": docId,
})

The shorten function should look like this with all the snippets together:

Once the shorten function is finished the redirect function is more simple, we acquire the code from the param and use it to query the db, if there an error return the error, we can extract the long url and use the gin Redirect function.

With this two function our simple URL shortener is completed, here is the full code:

With all this finish we have our own URL shortener, to handle the expiration directly form MongoDB we can create an Index that will delete our document when the expiresAt time comes. This should code snippet should be executed directly in our MongoDB instance

db.urls.createIndex( { "expiresAt": 1 }, { expireAfterSeconds: 0 } )

If you want to see the whole repository you can go to my github.

Thank you very much for reading…

--

--

Alan Bardales

Mexican FullStack developer, music enthusiast and gamer.