# Golang : gin tutorial - 5 (Create a global error handler)

Now that we know how to write few basic endpoints such as [GET](https://spdeepak.hashnode.dev/golang-gin-tutorial-2), [POST (JSON consumption)](https://spdeepak.hashnode.dev/golang-gin-tutorial-3), [POST (multipart/form-data)](https://spdeepak.hashnode.dev/golang-gin-tutorial-4). We have seen that there are situations where we return an error and we need to return the error in the API response body so that the API consumer knows what went wrong as their request was not full-filled.

We can write a gin middleware and use it as a global error handler. But before we write a middleware we need to write a custom error implementation.

### Step 1
Create a file `errors.go` in a directory named `error` in the project. 

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1661335471222/WJYmStlVq.png align="left")

This is what a custom error implementation would look like.

```go
package error

import "fmt"

type Http struct {
	Description string `json:"description,omitempty"`
	Metadata    string `json:"metadata,omitempty"`
	StatusCode  int    `json:"statusCode"`
}

func (e Http) Error() string {
	return fmt.Sprintf("description: %s,  metadata: %s", e.Description, e.Metadata)
}

func NewHttpError(description, metadata string, statusCode int) Http {
	return Http{
		Description: description,
		Metadata:    metadata,
		StatusCode:  statusCode,
	}
}
```
Now, that we have a custom error implementation we need to write a gin middleware to use as a global error handler.

### Step 2
Create  a file `error.go` in a directory named `middleware` in the project. 

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1661335524934/BA_N52Amx.png align="left")

This is what a global error handler would look like.

```go
package middleware

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"practice/error"
)

func ErrorHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Next()
		for _, err := range c.Errors {
			switch e := err.Err.(type) {
			case error.Http:
				c.AbortWithStatusJSON(e.StatusCode, e)
			default:
				c.AbortWithStatusJSON(http.StatusInternalServerError, map[string]string{"message": "Service Unavailable"})
			}
		}
	}
}
```

### Step 3
Now that we have a global error handler in place we need to register it so that gin can make use of it. Register the above created `ErrorHandler` in the `main.go` file.

```go
func main(){
	//.......................................
	engine.Use(
		middleware.ErrorHandler(),
	)

	v1 := engine.Group("/api/v1")
	{
		v1.GET("/contents", content.GetContents)
		v1.POST("/contents", content.PostContents)
		v1.POST("/contents/import", content.ConsumeFile)
	}
	//.......................................
```

## Step 4
Just by creating an error handler and registering it would not work. We will have to add the above created `Http` struct object in to the gin `context` instead of the generic errors which we have been adding to the gin `context`.

In `controller.go` file in the `content` directory we currently have something like this when we encounter an `error` in an endpoint.
```go
ctx.Error(fmt.Errorf("name is required"))
ctx.AbortWithStatus(http.StatusBadRequest)
return
```
Now, this has to changed to something like this in the `GET ` endpoint
```go
func GetContents(ctx *gin.Context) {
	name, present := ctx.GetQuery("name")
	if !present {
		err := httperror.NewHttpError("Query parameter not found", "name query parameter is required", http.StatusBadRequest)
		ctx.Error(err)
		return
	}
	response := make(map[string]string, 0)
	response["name"] = name

	ctx.JSON(http.StatusOK, response)
}
```
And the `POST` endpoint would look like this
```go
func PostContents(ctx *gin.Context) {
	var content content
	if err := ctx.ShouldBindJSON(&content); err != nil {
		err := httperror.NewHttpError("Invalid request body", "", http.StatusBadRequest)
		ctx.Error(err)
		return
	}
	ctx.JSON(http.StatusOK, content)
}
```
Something similar can be done in the `multipart/form-data` `POST` endpoint to look like this
```go
func ConsumeFile(ctx *gin.Context) {
	fileHeader, err := ctx.FormFile("file")
	if err != nil {
		err := httperror.NewHttpError("file not found", "", http.StatusBadRequest)
		ctx.Error(err)
		return
	}

	//Open received file
	csvFileToImport, err := fileHeader.Open()
	if err != nil {
		err := httperror.NewHttpError("Invalid file", "", http.StatusBadRequest)
		ctx.Error(err)
		return
	}
	defer csvFileToImport.Close()

	//Create temp file
	tempFile, err := ioutil.TempFile("", fileHeader.Filename)
	if err != nil {
		err := httperror.NewHttpError("Error while creating temp file", "", http.StatusBadRequest)
		ctx.Error(err)
		return
	}
	defer tempFile.Close()

	//Delete temp file after importing
	defer os.Remove(tempFile.Name())

	//Write data from received file to temp file
	fileBytes, err := ioutil.ReadAll(csvFileToImport)
	if err != nil {
		err := httperror.NewHttpError("Error while wrting data to temp file", "", http.StatusBadRequest)
		ctx.Error(err)
		return
	}
	_, err = tempFile.Write(fileBytes)
	if err != nil {
		err := httperror.NewHttpError("Error while writing data to temp file", "", http.StatusBadRequest)
		ctx.Error(err)
		return
	}

	ctx.JSON(http.StatusOK, string(fileBytes))
}
```

### Step 5
Now that we have created the middleware and registered it so that the gin server can use and we have also made changes in the controller to adapt to use the new errors. We can run our unit tests. If we run them we can see that some tests fail are failing. It is because we have not registered the `middleware` to the gin engine in tests. So, after creating the   `TextContext` of gin, we need to register the error handler middleware to it for usage.

```go
ctx, engine := gin.CreateTestContext(responseRecorder)
//Add the below line after we create the text context as shown in the above line
engine.Use(middleware.ErrorHandler())
```
Now, run the unit tests and we can see that the tests seem to work fine.

### Step 6
Now, when we run the application using the `main.go` file and access the GET endpoint without the query parameter we can see the below error with a 400 status.
```json
{
    "description": "Query parameter not found",
    "metadata": "name query parameter is required",
    "statusCode": 400
}
```
A similar error structure would occur when we try to access any endpoint we created without the required query parameters or the request body etc.
