[[toc]]
Goravel provides several different approaches to validate your application's incoming data. It is most common to use the Validate
method available on all incoming HTTP requests. Goravel includes a wide variety of convenient validation rules.
Let's take a closer look at Goravel's powerful validation features by examining a complete example of how to validate a form and return error messages to the user. This overview will provide you with a general understanding of how to validate incoming request data using Goravel.
First, let's assume we have the following routes defined in our routes/web.go
file:
import "goravel/app/http/controllers"
postController := controllers.NewPostController()
facades.Route().Get("/post/create", postController.Create)
facades.Route().Post("/post", postController.Store)
The GET
route displays a form for creating a new blog post. The POST
route stores the new post in the database.
Next, let's take a look at a simple controller that handles incoming requests to these routes. We'll leave the Store
method empty for now:
package controllers
import (
"github.com/goravel/framework/contracts/http"
)
type PostController struct {
// Dependent services
}
func NewPostController() *PostController {
return &PostController{
// Inject services
}
}
func (r *PostController) Create(ctx http.Context) {
}
func (r *PostController) Store(ctx http.Context) {
}
Now we are ready to fill in our Store
method with the logic to validate the new blog post.
func (r *PostController) Store(ctx http.Context) {
validator, err := ctx.Request().Validate(map[string]string{
"title": "required|max_len:255",
"body": "required",
})
}
If the incoming HTTP request contains "nested" field data, you may specify these fields in your validation rules using the "dot" syntax:
validator, err := ctx.Request().Validate(map[string]string{
"title": "required|max_len:255",
"author.name": "required",
"author.description": "required",
})
If the incoming HTTP request contains "array" field data, you may specify these fields in your validation rules using the *
syntax:
validator, err := ctx.Request().Validate(map[string]string{
"tags.*": "required",
})
For more complex validation scenarios, you may wish to create a "form request". Form requests are custom request classes that encapsulate their own validation and authorization logic. To create a form request class, you may use the make:request
Artisan CLI command:
go run . artisan make:request StorePostRequest
go run . artisan make:request user/StorePostRequest
The generated form request class will be placed in the app/http/requests
directory. If this directory does not exist, it will be created when you run the make:request
command. Each form request generated by Goravel has five methods: Authorize
, Rules
, Messages
, Attributes
and PrepareForValidation
.
As you might have guessed, the Authorize
method is responsible for determining if the currently authenticated user can perform the action represented by the request, while the Rules
method returns the validation rules that should apply to the request's data:
package requests
import (
"github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/contracts/validation"
)
type StorePostRequest struct {
Name string `form:"name" json:"name"`
}
func (r *StorePostRequest) Authorize(ctx http.Context) error {
return nil
}
func (r *StorePostRequest) Rules(ctx http.Context) map[string]string {
return map[string]string{
// The keys are consistent with the incoming keys.
"name": "required|max_len:255",
}
}
func (r *StorePostRequest) Messages(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *StorePostRequest) Attributes(ctx http.Context) map[string]string {
return map[string]string{}
}
func (r *StorePostRequest) PrepareForValidation(ctx http.Context, data validation.Data) error {
return nil
}
So, how are the validation rules evaluated? All you need to do is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic:
func (r *PostController) Store(ctx http.Context) {
var storePost requests.StorePostRequest
errors, err := ctx.Request().ValidateRequest(&storePost)
}
Note that since
form
passed values are ofstring
type by default, all fields in request should also be ofstring
type, otherwise please useJSON
to pass values.
The form request class also contains an Authorize
method. Within this method, you may determine if the authenticated user actually has the authority to update a given resource. For example, you may determine if a user actually owns a blog comment they are attempting to update. Most likely, you will interact with your authorization gates and policies within this method:
func (r *StorePostRequest) Authorize(ctx http.Context) error {
var comment models.Comment
facades.Orm().Query().First(&comment)
if comment.ID == 0 {
return errors.New("no comment is found")
}
if !facades.Gate().Allows("update", map[string]any{
"comment": comment,
}) {
return errors.New("can't update comment")
}
return nil
}
error
will be passed to the return value of ctx.Request().ValidateRequest
.
You may customize the error messages used by the form request by overriding the Messages
method. This method should return an array of attribute / rule pairs and their corresponding error messages:
func (r *StorePostRequest) Messages() map[string]string {
return map[string]string{
"title.required": "A title is required",
"body.required": "A message is required",
}
}
Many of Goravel's built-in validation rule error messages contain an :attribute
placeholder. If you would like the :attribute
placeholder of your validation message to be replaced with a custom attribute name, you may specify the custom names by overriding the Attributes
method. This method should return an array of attribute / name pairs:
func (r *StorePostRequest) Attributes() map[string]string {
return map[string]string{
"email": "email address",
}
}
If you need to prepare or sanitize any data from the request before you apply your validation rules, you may use the PrepareForValidation
method:
func (r *StorePostRequest) PrepareForValidation(data validation.Data) error {
if name, exist := data.Get("name"); exist {
return data.Set("name", name.(string)+"1")
}
return nil
}
If you do not want to use the Validate
method on the request, you may create a validator instance manually using the facades.Validator
. The Make
method of the facade generates a new validator instance:
func (r *PostController) Store(ctx http.Context) http.Response {
validator, _ := facades.Validation().Make(
map[string]any{
"name": "Goravel",
},
map[string]string{
"title": "required|max_len:255",
"body": "required",
})
if validator.Fails() {
// Return fail
}
var user models.User
err := validator.Bind(&user)
...
}
The first argument passed to the Make
method is the data under validation which can be map[string]any
or struct
. The second argument is an array of validation rules to be applied to the data.
If needed, you may provide custom error messages that a validator instance should use instead of the default error messages provided by Goravel. You may pass the custom messages as the third argument to the Make
method (also applicable to ctx.Request().Validate()
):
validator, err := facades.Validation().Make(input, rules, validation.Messages(map[string]string{
"required": "The :attribute field is required.",
}))
Sometimes you may wish to specify a custom error message only for a specific attribute. You may do so using "dot" notation. Specify the attribute's name first, followed by the rule (also applicable to ctx.Request().Validate()
):
validator, err := facades.Validation().Make(input, rules, validation.Messages(map[string]string{
"email.required": "We need to know your email address!",
}))
Many of Goravel's built-in error messages include an :attribute
placeholder that is replaced with the name of the field or attribute under validation. To customize the values used to replace these placeholders for specific fields, you may pass an array of custom attributes as the third argument to the Make
method (also applicable to ctx.Request().Validate()
):
validator, err := facades.Validation().Make(input, rules, validation.Attributes(map[string]string{
"email": "email address",
}))
You can format the data before validating the data for more flexible data validation, and you can pass the method of formatting the data as the third parameter to the Make
method (also applicable to ctx.Request().Validate()
):
import (
validationcontract "github.com/goravel/framework/contracts/validation"
"github.com/goravel/framework/validation"
)
func (r *PostController) Store(ctx http.Context) http.Response {
validator, err := facades.Validation().Make(input, rules,
validation.PrepareForValidation(func(data validationcontract.Data) error {
if name, exist := data.Get("name"); exist {
return data.Set("name", name)
}
return nil
}))
...
}
After validating incoming request data using form requests or manually created validator instances, you still want to bind the request data to a struct
, there are two ways to do this:
- Use the
Bind
method, this will bind all incoming data, including unvalidated data:
validator, err := ctx.Request().Validate(rules)
var user models.User
err := validator.Bind(&user)
validator, err := facades.Validation().Make(input, rules)
var user models.User
err := validator.Bind(&user)
- The incoming data is automatically bound to the form when you use request for validation:
var storePost requests.StorePostRequest
errors, err := ctx.Request().ValidateRequest(&storePost)
fmt.Println(storePost.Name)
validator, err := ctx.Request().Validate(rules)
validator, err := facades.Validation().Make(input, rules)
message := validator.Errors().One("email")
messages := validator.Errors().Get("email")
messages := validator.Errors().All()
if validator.Errors().Has("email") {
//
}
Below is a list of all available validation rules and their function:
Name | Description |
---|---|
required |
Check value is required and cannot be zero value. For example, field type is bool , the passing value is false , it can not pass the validation. |
required_if |
required_if:anotherfield,value,... The field under validation must be present and not empty if the anotherField field is equal to any value. |
required_unless |
required_unless:anotherfield,value,... The field under validation must be present and not empty unless the anotherField field is equal to any value. |
required_with |
required_with:foo,bar,... The field under validation must be present and not empty only if any of the other specified fields are present. |
required_with_all |
required_with_all:foo,bar,... The field under validation must be present and not empty only if all of the other specified fields are present. |
required_without |
required_without:foo,bar,... The field under validation must be present and not empty only when any of the other specified fields are not present. |
required_without_all |
required_without_all:foo,bar,... The field under validation must be present and not empty only when all of the other specified fields are not present. |
int |
Check value is intX uintX type, and support size checking. eg: int int:2 int:2,12 . Notice: Points for using rules |
uint |
Check value is uint(uintX) type, value >= 0 |
bool |
Check value is bool string(true : "1", "on", "yes", "true", false : "0", "off", "no", "false"). |
string |
Check value is string type, and support size checking. eg:string string:2 string:2,12 |
float |
Check value is float(floatX) type |
slice |
Check value is slice type([]intX []uintX []byte []string ) |
in |
in:foo,bar,… Check if the value is in the given enumeration |
not_in |
not_in:foo,bar,… Check if the value is not in the given enumeration |
starts_with |
starts_with:foo Check if the input string value is starts with the given sub-string |
ends_with |
ends_with:foo Check if the input string value is ends with the given sub-string |
between |
between:min,max Check that the value is a number and is within the given range |
max |
max:value Check value is less than or equal to the given value(intX uintX floatX ) |
min |
min:value Check value is greater than or equal to the given value(intX uintX floatX ) |
eq |
eq:value Check that the input value is equal to the given value |
ne |
ne:value Check that the input value is not equal to the given value |
lt |
lt:value Check value is less than the given value(intX uintX floatX ) |
gt |
gt:value Check value is greater than the given value(intX uintX floatX ) |
len |
len:value Check value length is equals to the given size(string array slice map ) |
min_len |
min_len:value Check the minimum length of the value is the given size(string array slice map ) |
max_len |
max_len:value Check the maximum length of the value is the given size(string array slice map ) |
email |
Check value is email address string |
array |
Check value is array, slice type |
map |
Check value is a MAP type |
eq_field |
eq_field:field Check that the field value is equals to the value of another field |
ne_field |
ne_field:field Check that the field value is not equals to the value of another field |
gt_field |
gt_field:field Check that the field value is greater than the value of another field |
gte_field |
gte_field:field Check that the field value is greater than or equal to the value of another field |
lt_field |
lt_field:field Check that the field value is less than the value of another field |
lte_field |
lte_field:field Check if the field value is less than or equal to the value of another field |
file |
Verify if it is an uploaded file |
image |
Check if it is an uploaded image file and support suffix check |
date |
Check the field value is date string |
gt_date |
gt_date:value Check that the input value is greater than the given date string |
lt_date |
lt_date:value Check that the input value is less than the given date string |
gte_date |
gte_date:value Check that the input value is greater than or equal to the given date string |
lte_date |
lte_date:value Check that the input value is less than or equal to the given date string |
alpha |
Verify that the value contains only alphabetic characters |
alpha_num |
Check that only letters, numbers are included |
alpha_dash |
Check to include only letters, numbers, dashes ( - ), and underscores ( _ ) |
json |
Check value is JSON string |
number |
Check value is number string >= 0 |
full_url |
Check value is full URL string(must start with http,https) |
ip |
Check value is IP(v4 or v6) string |
ipv4 |
Check value is IPv4 string |
ipv6 |
Check value is IPv6 string |
Goravel provides a variety of helpful validation rules; however, you may wish to specify some of your own. One method of registering custom validation rules is using rule objects. To generate a new rule object, you can simply use the make:rule
Artisan command.
For instance, if you want to verify that a string is uppercase, you can create a rule with this command. Goravel will then save this new rule in the app/rules
directory. If this directory does not exist, Goravel will create it when you run the Artisan command to create your rule.
go run . artisan make:rule Uppercase
go run . artisan make:rule user/Uppercase
After creating the rule, we need to define its behavior. A rule object has two methods: Passes
and Message
. The Passes method receives all data, including the data to be validated and the validation parameters. It should return true
or false
depending on whether the attribute value is valid. The Message
method should return the error message for validation that should be used when the validation fails.
package rules
import (
"strings"
"github.com/goravel/framework/contracts/validation"
)
type Uppercase struct {
}
// Signature The name of the rule.
func (receiver *Uppercase) Signature() string {
return "uppercase"
}
// Passes Determine if the validation rule passes.
func (receiver *Uppercase) Passes(data validation.Data, val any, options ...any) bool {
return strings.ToUpper(val.(string)) == val.(string)
}
// Message Get the validation error message.
func (receiver *Uppercase) Message() string {
return "The :attribute must be uppercase."
}
Then you need to register the rule to the rules
method in the app/providers/validation_service_provider.go
file, and the rule can be used like other rules:
package providers
import (
"github.com/goravel/framework/contracts/validation"
"github.com/goravel/framework/facades"
"goravel/app/rules"
)
type ValidationServiceProvider struct {
}
func (receiver *ValidationServiceProvider) Register() {
}
func (receiver *ValidationServiceProvider) Boot() {
if err := facades.Validation().AddRules(receiver.rules()); err != nil {
facades.Log().Errorf("add rules error: %+v", err)
}
}
func (receiver *ValidationServiceProvider) rules() []validation.Rule {
return []validation.Rule{
&rules.Uppercase{},
}
}
When using ctx.Request().Validate(rules)
for validation, the incoming int
type data will be parsed by json.Unmarshal
into float64
type, which will cause the int rule validation to fail.
Solutions
Option 1: Add validation.PrepareForValidation
, format the data before validating the data;
Option 2: Use facades.Validation().Make()
for rule validation;