💭 How to make clear & pretty error messages from the Go backend to your frontend?
Tags: go, tutorial, beginners, showdev
Published: 2021-09-27 11:19:49

## Introduction

Hey-hey, awesome DEV people! 😉

How about a little time in the company of an article that teaches you how to better communicate with the frontend developers on your team? Intrigued, but don't know what it's about? Don't worry, I'll explain it all now!

👀 I often notice an interesting trend in my work: the backend developer sends error statuses and explanations to the frontend, which are not always clear how to handle and output to the user in the application. As a result, the frontend developer spends precious time understanding what's going on and implementing borderline cases in the code.

💡 But, what if I told you that errors from the backend can be not just readable to the developer, but understandable even to the user? Yes, that's accurately what this article will talk about! 

We will take a look at package [go-playground/validator](https://github.com/go-playground/validator) `v10`, which is almost the number one choice for such problems in Go.

<a name="toc"></a>
### 📝 Table of contents

- [Model to validate](#model-to-validate)
- [Vanilla representation of the error from package](#vanilla-representation-of-the-error-from-package)
- [Recreate validator](#recreate-validator)
  - [Function to check the validation error](#function-to-check-the-validation-error)
  - [Custom validation tag](#custom-validation-tag)
- [Override error message](#override-error-message)

<a name="#model-to-validate"></a>
## Model to validate

Imagine we need to implement backend validation of incoming data from `POST` request to REST API endpoint of new project creation. Can you imagine? Okay, now let's describe this structure in Go code.

Besides the standard attributes `db:"..."` and `json:"..."`, we need to add a new attribute `validate:"..."` with the required validation tag from the `go-playground/validator` package to each structure field that we need to validate.

It only sounds very complicated, in fact, everything is much simpler. Look:

```go
// ./app/models/project_model.go

// Project struct to describe project.
type Project struct {
	Title string `db:"title" json:"title" validate:"required,lte=25"` 
	// --> verify that the field exists and is less than or equal to 25 characters

	Description string `db:"description" json:"description" validate:"required"`
	// --> verify that the field exists

	WebsiteURL string `db:"website_url" json:"website_url" validate:"uri"`
	// --> verify that the field is correct URL string

	Tags []string `db:"tags" json:"tags" validate:"len=3"`
	// --> verify that the field contains exactly three elements
}
```

> ☝️ **Note:** These are not all the parameters by which you can configure field validation for your structures! You can find the full list [here](https://github.com/go-playground/validator#baked-in-validations).

An interesting thing is that if we specify a validator to check, for example, if the URL validated, then we don't need to specify the `required` validation tag anymore. This happens because an empty string is not a valid URL. 

In other words, almost any validation tag will also include a mandatory non-empty value (_empty string_, _zero_, _nil_, …) for the field.

[↑ Table of contents](#toc)

<a name="#vanilla-representation-of-the-error-from-package"></a>
## Vanilla representation of the error from package

Input JSON body (_here and below, we will work with these very input parameters for the JSON request body_):

```json
{
  "title": "",
  "description": "",
  "website_url": "not-valid-uri",
  "tags": [
    "one", "two"
  ]
}
```

I will display the resulting error response as plain text so that you can appreciate why this presentation option for the frontend would _not_ be good:

```
Key: 'Project.Title' Error:Field validation for 'Title' failed on the 'required' tag

Key: 'Project.Description' Error:Field validation for 'Description' failed on the 'required' tag

Key: 'Project.WebsiteURL' Error:Field validation for 'WebsiteURL' failed on the 'uri' tag

Key: 'Project.Tags' Error:Field validation for 'Tags' failed on the 'len' tag
```

And there are several important points that we want to improve right away.

First, the frontend knows nothing about the structures and models in our application. If the backend returns an error in this form (_without specifying at least the field that failed validation_), the frontend will not be able to make a visual output of the error for a particular field.

Second, it's better to specify the exact names of the fields which the frontend works with — not `WebsiteURL` but `website_url`, like in JSON.

Third, the error description itself will not tell the user (or even the frontend developer) anything useful, except that something went wrong somewhere.

Well, let's try to improve it! 

> ☝️ **Note:** I will show you the way I do it on my projects. By the way, I'd be happy to get feedback and examples of how you customize error output for frontend in your projects.

[↑ Table of contents](#toc)

<a name="#recreate-validator"></a>
## Recreate validator

Great, we get rid of the fields with names, like in the structure. We just override their output, so that the validator looks at the `json:"..."` parameter in the structure, not at its actual name.

To complete this, we use the `RegisterTagNameFunc` method built into the package with a little magic. I will put this in a different helper package (`./pkg/utilities/validator.go`) so that there is more readable application code:

```go
// ./pkg/utilities/validator.go

// NewValidator func for create a new validator for struct fields.
func NewValidator() *validator.Validate {
	// Create a new validator.
	validate := validator.New()

	// Rename struct fields to JSON.
	validate.RegisterTagNameFunc(func(fl reflect.StructField) string {
		name := strings.SplitN(fl.Tag.Get("json"), ",", 2)
		if name[1] == "-" {
			return ""
		}
		return name[0]
	})

	return validate
}

// ...
```

If you want to avoid renaming any of the fields, add `,-` (comma + dash) to the end of its JSON name, like this: 

```go
WebsiteURL string `db:"website_url" json:"website_url,-" validate:"uri"`
```

Yes, you got me right, this method opens up great possibilities to customize the error output itself. You can rely not on `json:"..."` attribute in the field, but on your one, for example, `field_name:"..."` or any other one you wish.

> ☝️ **Note:** To understand how it works, please follow [this](https://github.com/go-playground/validator/issues/258) issue.

[↑ Table of contents](#toc)

<a name="#function-to-check-the-validation-error"></a>
### Function to check the validation error

Let's move on. It's time to make a nicer output of validation errors, so that the frontend developer on your team will thank you.

I always use this practice when implementing a REST API in JSON format for internal use (_e.g., for [single-page applications](https://developer.mozilla.org/en-US/docs/Glossary/SPA) aka SPA_):

1. We return JSON in strictly consistent notation with the frontend, but relative to the interaction objects;
2. The status code of the response from the backend is always `HTTP 200 OK`, unless it concerns server errors (status code `5XX`);
3. The server response always contains the `status` field (type `int`) indicating the actual status code;
4. If an error occurred (status code not `2ХХ`), the server response always contains a field `msg` (type `string`) with a short indication of the cause of the error;

Furthermore, in the example below, I took code from my project written using the [Fiber](https://gofiber.io/) web framework. So, some elements from its libraries are present there. Don't be scared, the main thing here is to understand the principle of validation itself.

> ☝️ **Note:** If you want to learn more about Fiber, I have a [series of articles](https://dev.to/koddr/series/14132) to help you do that. I'd be glad if you'd study it later, too.

Okay, my function to check for validation errors would look like this:

```go
// ./pkg/utilities/validator.go

// ...

// CheckForValidationError func for checking validation errors in struct fields.
func CheckForValidationError(ctx *fiber.Ctx, errFunc error, statusCode int, object string) error {
	if errFunc != nil {
		return ctx.JSON(&fiber.Map{
			"status": statusCode,
			"msg":    fmt.Sprintf("validation errors for the %s fields", object),
			"fields": ValidatorErrors(errFunc),
		})
	}
	return nil
}
```

The principle of this function is elementary:

- Accept the Fiber context to have all the possibilities to work with the context that came;
- Accept the object with the validation error defined above;
- Accept the status code, which should return if the error occurs;
- Accept the name of the object (or model) we're currently checking, so we can output a more readable error message;
- Return the generated JSON with all the necessary errors and explanations or `nil`;

I can now easily use the `CheckForValidationError` function in the controller:

```go
// ./app/controllers/project_controller.go

import (
	// ...
	"github.com/my-user/my-repo/pkg/utilities" 
	// --> add local package `utilities`
)

// CreateNewProject func for create a new project.
func CreateNewProject(c *fiber.Ctx) error {

	// ...

	// Create a new validator, using helper function.
	validate := utilities.NewValidator()

	// Validate all incomming fields for rules in Project struct.
	if err := validate.Struct(project); err != nil {
		// Returning error in JSON format with status code 400 (Bad Request).
		return utilities.CheckForValidationError(
			c, err, fiber.StatusBadRequest, "project",
		)
	}

	// ...
}
```

[↑ Table of contents](#toc)

<a name="#custom-validation-tag"></a>
### Custom validation tag

Every so often, it happens that the built-in validation tags (or rather, the rules by which a particular field should be validated) are not always sufficient. To solve this problem, the authors of `go-playground/validator` package provided a special method.

Let's consider its use on a simple example 👇

So, we have a field with type `uuid.UUID` which we create with the package [google/uuid](https://github.com/google/uuid), which we want to check with the built-in validator `uuid.Parse()` of that package. All we need to do is add a new `RegisterValidation` method to the `NewValidator` function (_described above_) with simple logic code:

```go
// ./pkg/utilities/validator.go

// NewValidator func for create a new validator for struct fields.
func NewValidator() *validator.Validate {
	// Create a new validator.
	validate := validator.New()

	// ...

	// Custom validation for uuid.UUID fields.
	_ = validate.RegisterValidation("uuid", func(fl validator.FieldLevel) bool {
		field := fl.Field().String() // convert to string
		if _, err := uuid.Parse(field); err != nil {
			return true // field has error
		}
		return false // field has no error
	})

	// ...

	return validate
}
```

That's it! If the field passed validation, it will return `false` logical value, and if there are any errors it will return `true`.

> ☝️ **Note:** The method `RegisterValidation` should be read and understood like this: “_please check if there is an error in the value of the field with the validation tag `uuid`?_”.

Now we can validate fields of this type like this:

```go
// ./app/models/something_model.go

// MyStruct struct to describe something.
type MyStruct struct {
	ID uuid.UUID `db:"id" json:"id" validate:"uuid"` 
	// --> verify that the field is a valid UUID
}
```

[↑ Table of contents](#toc)

![Override error message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d5trc230d303imerg3k6.jpg)

<a name="#override-error-message"></a>
## Override error message

And now for the best part. Overriding the validation error message itself. 

> ☝️ **Note:** Follow comments in the code to better understand.

This helper function will map all validation errors to each field and then simply pass that map to the `CheckForValidationError` function (_which we described in the previous section_):

```go
// ./pkg/utilities/validator.go

// ...

// ValidatorErrors func for show validation errors for each invalid fields.
func ValidatorErrors(err error) map[string]string {
	// Define variable for error fields.
	errFields := map[string]string{}

	// Make error message for each invalid field.
	for _, err := range err.(validator.ValidationErrors) {
		// Get name of the field's struct.
		structName := strings.Split(err.Namespace(), ".")[0]
		// --> first (0) element is the founded name

		// Append error message to the map, where key is a field name,
		// and value is an error description.
		errFields[err.Field()] = fmt.Sprintf(
			"failed '%s' tag check (value '%s' is not valid for %s struct)",
			err.Tag(), err.Value(), structName,
		)
	}

	return errFields
}
```

As you may have noticed, to override the field error message, we operate on special variables (`err.Namespace()`, `err.Field()`, `err.Tag()` and `err.Value()`) which the authors of the `go-playground/validator` package offer us.

> ☝️ **Note:** You can find a complete list of all available ones [here](https://github.com/go-playground/validator/blob/master/_examples/simple/main.go#L69).

Now, we will get this message when we make an invalid request:

```json
{
  "status": 400,
  "msg": "validation errors for the project fields",
  "fields": {
    "category": "failed 'required' tag check (value '' is not valid for Project struct)",
    "description": "failed 'required' tag check (value '' is not valid for Project struct)",
    "tags": "failed 'len' tag check (value '[one two]' is not valid for Project struct)"
    "title": "failed 'required' tag check (value '' is not valid for Project struct)"
    "website_url": "failed 'uri' tag check (value 'not-valid-uri' is not valid for Project struct)"
  }
}
```

> ☝️ **Note:** After validation, all not valid fields are in alphabetical order, not in the order that was defined by the Go structure.

Hooray! 🎉 We got what we wanted and no one got hurt. On the contrary, everyone is happy, both on the frontend and the backend.

[↑ Table of contents](#toc)

<a name=""></a>
## Photos and videos by

- Authors of the package `go-playground/validator` feat. Vic Shóstak
- Markus Spiske https://unsplash.com/photos/IiEFmIXZWSw

<a name=""></a>
## P.S.

If you want more articles (like this) on this blog, then post a comment below and subscribe to me. Thanks! 😻

And of course, you can help me make developers' lives even better! Just connect to one of my projects as a contributor. It's easy!

My projects that need your help (and stars) 👇

- 🔥 [gowebly][gowebly_url]: A next-generation CLI tool for easily build amazing web applications with Go on the backend, using htmx & hyperscript and the most popular atomic/utility-first CSS frameworks on the frontend.
- ✨ [create-go-app][cgapp_url]: Create a new production-ready project with Go backend, frontend and deploy automation by running one CLI command.
- 🏃 [yatr][yatr_url]: Yet Another Task Runner allows you to organize and automate your routine operations that you normally do in Makefile (or else) for each project.
- 📚 [gosl][gosl_url]: The Go Snippet Library provides snippets collection for working with routine operations in your Go programs with a super user-friendly API and the most efficient performance.
- 🏄‍♂️ [csv2api][csv2api_url]: The parser reads the CSV file with the raw data, filters the records, identifies fields to be changed, and sends a request to update the data to the specified endpoint of your REST API.
- 🚴 [json2csv][json2csv_url]: The parser can read given folder with JSON files, filtering and qualifying input data with intent & stop words dictionaries and save results to CSV files by given chunk size.

<!-- Footer links -->

[gowebly_url]: https://github.com/gowebly
[cgapp_url]: https://github.com/create-go-app
[yatr_url]: https://github.com/koddr/yatr
[gosl_url]: https://github.com/koddr/gosl
[csv2api_url]: https://github.com/koddr/csv2api
[json2csv_url]: https://github.com/koddr/json2csv