Today, I’m going to build a simple API for a to-do application using the Go programming language. For this, I’ll use Gin, one of Go’s simplest and fastest web frameworks, and Gorm, a powerful and flexible ORM for database operations.

To get started, you’ll need to install these packages. Navigate to your workspace directory ($GOPATH/src) and run the following commands:

$ go get gopkg.in/gin-gonic/gin.v1
$ go get -u github.com/jinzhu/gorm
$ go get github.com/go-sql-driver/mysql

In generic crud application we need the API’s as follows:

  • POST todos/
  • GET todos/
  • GET todos/{id}
  • PUT todos/{id}
  • DELETE todos/{id}

Let’s start coding! First, navigate to your $GOPATH/src directory and create a folder named todo. Inside the todo directory, create a new file called main.go.

Now, import the Gin framework into your project and set up the routes. I prefer to add a version prefix like api/v1/ to the API endpoints, so we’ll use Gin’s Group method to organize the routes. Here’s how your main.go file should look:

package main

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

func main() {
	router := gin.Default()
	v1 := router.Group("/api/v1/todos")
	{
		v1.POST("/", createTodo)
		v1.GET("/", fetchAllTodo)
		v1.GET("/:id", fetchSingleTodo)
		v1.PUT("/:id", updateTodo)
		v1.DELETE("/:id", deleteTodo)
	}
	router.Run()
}

Now that we’ve created five routes to handle functions like createTodo, fetchAllTodo, etc., let’s set up the database connection. We’ll use Gorm as our ORM and the MySQL dialect for database operations.

package main

import (
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

var db *gorm.DB

func init() {
	//open a db connection
	var err error
	db, err = gorm.Open("mysql", "root:12345@/demo?charset=utf8&parseTime=True&loc=Local")
	if err != nil {
		panic("failed to connect database")
	}
	//Migrate the schema
	db.AutoMigrate(&todoModel{})
}

In the code above, the “mysql” is our database driver, “root” is the database username, “12345” is the password, “demo” is the database name. Feel free to replace these values with your specific database configuration.

We’ll use a Database function to manage the database connection, making it reusable across the application. Additionally, let’s create two structs:

  • todoModel: Represents the original Todo data as stored in the database.
  • transformedTodo: Represents the API response format, excluding fields like created_at and updated_at, which should not be exposed to consumers.
type (
	// todoModel describes a todoModel type
	todoModel struct {
		gorm.Model
		Title     string `json:"title"`
		Completed int    `json:"completed"`
	}
	// transformedTodo represents a formatted todo
	transformedTodo struct {
		ID        uint   `json:"id"`
		Title     string `json:"title"`
		Completed bool   `json:"completed"`
	}
)

The Todo struct has an additional field called gorm.Model. This field embeds the Model struct, which contains four fields: ID, CreatedAt, UpdatedAt, and DeletedAt.

Gorm provides migration facilities, which we have already used in the init function. When we run the application, it will first establish the database connection, and then perform the migration. This ensures that the necessary database tables are created or updated based on the struct definitions.

//Migrate the schema
 db.AutoMigrate(&todoModel{})

Do you remember the five routes we defined earlier? Let’s implement the five methods one by one.

When a user sends a POST request to the path api/v1/todos/ with the title and completed fields, it will be handled by the createTodo function in the route v1.POST("/" , createTodo).

Let’s now implement the createTodo function.

// createTodo add a new todo
func createTodo(c *gin.Context) {
	completed, _ := strconv.Atoi(c.PostForm("completed"))
	todo := todoModel{Title: c.PostForm("title"), Completed: completed}
	db.Save(&todo)
	c.JSON(http.StatusCreated, gin.H{"status": http.StatusCreated, "message": "Todo item created successfully!", "resourceId": todo.ID})
}

In the code above, we use the Gin Context to receive the posted data and the Gorm database connection to save the todo. After saving the resource, we send the resource ID back to the user along with a meaningful and informative response.

Now, let’s implement the remaining functions.

// fetchAllTodo fetch all todos
func fetchAllTodo(c *gin.Context) {
	var todos []todoModel
	var _todos []transformedTodo
	db.Find(&todos)
	if len(todos) <= 0 {
		c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No todo found!"})
		return
	}
	//transforms the todos for building a good response
	for _, item := range todos {
		completed := false
		if item.Completed == 1 {
			completed = true
		} else {
			completed = false
		}
		_todos = append(_todos, transformedTodo{ID: item.ID, Title: item.Title, Completed: completed})
	}
	c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "data": _todos})
}

// fetchSingleTodo fetch a single todo
func fetchSingleTodo(c *gin.Context) {
	var todo todoModel
	todoID := c.Param("id")
	db.First(&todo, todoID)
	if todo.ID == 0 {
		c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No todo found!"})
		return
	}
	completed := false
	if todo.Completed == 1 {
		completed = true
	} else {
		completed = false
	}
	_todo := transformedTodo{ID: todo.ID, Title: todo.Title, Completed: completed}
	c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "data": _todo})
}

// updateTodo update a todo
func updateTodo(c *gin.Context) {
	var todo todoModel
	todoID := c.Param("id")
	db.First(&todo, todoID)
	if todo.ID == 0 {
		c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No todo found!"})
		return
	}
	db.Model(&todo).Update("title", c.PostForm("title"))
	completed, _ := strconv.Atoi(c.PostForm("completed"))
	db.Model(&todo).Update("completed", completed)
	c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "Todo updated successfully!"})
}

// deleteTodo remove a todo
func deleteTodo(c *gin.Context) {
	var todo todoModel
	todoID := c.Param("id")
	db.First(&todo, todoID)
	if todo.ID == 0 {
		c.JSON(http.StatusNotFound, gin.H{"status": http.StatusNotFound, "message": "No todo found!"})
		return
	}
	db.Delete(&todo)
	c.JSON(http.StatusOK, gin.H{"status": http.StatusOK, "message": "Todo deleted successfully!"})
}

In the fetchAllTodos function, we fetch all the todos and build a transformed response with only the id, title, and completed fields. We exclude the CreatedAt, UpdatedAt, and DeletedAt fields, and we cast the integer value of Completed to a boolean.

Now that we’ve written enough code, let’s build the app and test it. I will test it using the Postman Chrome extension (you can use any REST client like curl to test).

To build the app, open your terminal and navigate to the project directory.

$ go build main.go

The command will build a binary file main and to run the file us this command $ ./main . Wow, our simple todo app is running on port: 8080. It’ll display the debug log, because by default gin run’s in debug mode and port 8080.

Need full source code?