Why Gin as a Junior Engineer learning Golang?

Golang with Gin and some Github Copilot superpowers is fantastic
Marcos
Marcos Fernández
Software Engineer

Gin is one of the most popular web frameworks for Golang because multiple reasons. First, it is super easy to use, and it is very well documented. Second, it is super fast. It is one of the fastest web frameworks for Golang. Third, it is very flexible. For example, it lets you add middleware to the request pipeline; one of the ones I always add is telemetry. Definitely, it is a great framework to use.

About Github copilot, I am not going to lie, I am a bit biased here. I am a fan of GitHub Copilot, I think it is a great tool and it is going to change the way we code. I have been using it for a while now and I have to say that I am impressed by how helpful it is. I think it is also an excellent tool for learning a new language, but it also helps you to write code faster and to see how other people are doing things. Of course, it is not perfect, but it helps a lot.

To be able to develop with this stack, we will need a development environment which in my case is going to be Visual Studio Code, nothing super surprising, to be honest. My plugins of choice here are Go for debugging tools and, of course, GitHub Copilot.

So I created a straightforward Rest API about something random, lamps in this case. The API has the following endpoints:

GET /api/lamps

GET /api/lamps/{id}

POST /api/lamps

PUT /api/lamps/{id}

    
      func main() {
        controller := Controller{}
        controller.initDatabase()

        router := gin.Default()

        // Setup CORS
        config := cors.DefaultConfig()
        config.AllowAllOrigins = true
        router.Use(cors.New(config))

        router.GET("/lamps", controller.getLamps)
        router.GET("/lamps/:id", controller.getLampByID)
        router.POST("/lamps", controller.postLamp)
        router.PUT("/lamps/:id", controller.updateLamp)

        router.Run("0.0.0.0:8080")
      }
    
  

This piece of code does the setup of the server plus the configuration of the endpoints and calls a method to initialise the database. As you can see, it is pretty straightforward. The only thing that I would like to highlight here is the CORS configuration. I am using the default configuration, which allows all origins. This is not a good practice, but it is good enough for this example.

Laptop screen with code editor

For the database initialisation, I am using Gorm, which is an ORM library for Golang. It is super easy to use and very powerful. I am using it to create the database schema and to connect to it.

    
    func (c *Controller) initDatabase() {

	db, err := gorm.Open(postgres.Open(dbConnectionString), &gorm.Config{})
	if err != nil {
		log.Fatalf(“failed to connect database: %v”, err)
	}
	c.Database = db

	c.Database.AutoMigrate(&Lamp{})
    }
    
  

We are using a Postgres database to store the lamps. This database runs in a Docker container initialised by this Docker compose file.

    
    version: "3"

    services:
      db:
        image: postgres
        restart: always
        environment:
          POSTGRES_USER: user
          POSTGRES_PASSWORD: password
          POSTGRES_DB: postgres
        ports:
          - "5432:5432"
        volumes:
          - postgres_data:/var/lib/postgresql/data

    volumes:
      postgres_data:
    
  

Now let’s have a look at the model of the lamp:

    
    type Lamp struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description"`
	Price       int    `json:"price"`
    }
    
  

As you can see, it is a very simple model. It has an ID, a name, a description, and a price. Nothing fancy here.

Now let’s have a look at the endpoints:

    
    func (ctrl *Controller) getLamps(c *gin.Context) {
	var lamps []Lamp
	result := ctrl.Database.Find(&Lamp{})
	result.Scan(&lamps)

	c.IndentedJSON(http.StatusOK, lamps)
    }

    func (ctrl *Controller) getLampByID(c *gin.Context) {
    	id := c.Param("id")
    
    	var lamp Lamp
    	ctrl.Database.Model(&Lamp{}).First(&lamp, id)
    
    	if lamp.ID == 0 {
    		c.IndentedJSON(http.StatusNotFound, gin.H{"message": "lamp not found"})
    		return
    	}
    
    	c.JSON(http.StatusOK, lamp)
    }
    
    func (ctrl *Controller) postLamp(c *gin.Context) {
    	var newLamp Lamp
    	if err := c.ShouldBindJSON(&newLamp); err != nil {
    		c.JSON(http.StatusBadRequest, gin.H{"message": "invalid request"})
    		return
    	}
    
    	ctrl.Database.Create(&newLamp)
    
    	c.JSON(http.StatusCreated, newLamp)
    }
    
    func (ctrl *Controller) updateLamp(c *gin.Context) {
    	id := c.Param("id")
    	var updatedLamp Lamp
    	if err := c.ShouldBindJSON(&updatedLamp); err != nil {
    		c.JSON(http.StatusBadRequest, gin.H{"message": "invalid request"})
    		return
    	}
    
    	var lamp Lamp
    	ctrl.Database.Model(&Lamp{}).First(&lamp, id)
    
    	if lamp.ID != 0 {
    		ctrl.Database.Model(&Lamp{}).Where("id = ?", id).Updates(updatedLamp)
    		c.JSON(http.StatusOK, updatedLamp)
    		return
    	}
    
    	c.JSON(http.StatusNotFound, gin.H{"message": "lamp not found"})
    }
    
  

As you can see, they are pretty simple. The first one returns all the lamps, the second one returns a lamp by ID, the third one creates a new lamp, and the last one updates a lamp by ID.

I would recommend you to test the API with Postman or any other similar tool that you like. I am using Postman for this example.

With this, only being a bit more than 120 LOC, we have a fully functional API with the four endpoints that we have seen before. This can be for sure improved a lot. Still, it is a good example to see a glimpse of how powerful and, most importantly, for a junior engineer trying to learn a new language, how easy it is to set up your own fully functional REST API.

I hope you enjoyed this article and learned something new and valuable.

If you want to have a look at the full code, you can find it here.


Posted

in

,

by

Comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *