Lesson 1
Adding Creation Time with Validators
Introduction to Creation Time and Validation

Welcome back! So far, you've learned how to manipulate new and existing ToDo items with GET, POST, PUT and DELETE requests. Now, let's focus on another useful feature — tracking the creation time of each ToDo item. This helps keep your ToDo list organized and offers valuable insights into task management over time.

In addition to tracking time, ensuring that data being added to your application is valid and accurate is crucial. For instance, it doesn't make sense to add a ToDo item with a creation date in the past. We'll employ validation rules to avoid such scenarios, providing an organized and reliable user experience.

Enhancing the Todo Model

We'll start by introducing a new field, CreationTime, in our Todo struct. This field will automatically capture the current time when a new ToDo item is created. To ensure this field is always captured and valid, we'll use some validation tags.

Here's how the Todo struct looks now:

Go
1package models 2 3import ( 4 "time" 5) 6 7// Todo models a single task item with essential details 8type Todo struct { 9 ID int `json:"id"` 10 Title string `json:"title" binding:"required"` 11 Completed bool `json:"completed"` 12 CreationTime time.Time `json:"creationTime" binding:"notpast"` 13}

In this code snippet we simply added the CreationTime field of type time.Time to record the creation time of each ToDo item. The binding tag on the CreationTime field contains our custom notpast rule, which we will define shortly. The binding tag in Gin is used to apply specific validation rules to struct fields during the binding process, ensuring the data conforms to the specified constraints. Here, it makes the Title field required and it ensures that the CreationTime isn't set to a past date by enforcing this rule when new ToDo items are added.

Implementing Custom Validators in Gin

Validation in web applications helps maintain data integrity. In this lesson, we will implement a custom validator to ensure CreationTime is never set to a past date. This validator will be registered with the Gin framework.

Here's the code snippet implementing the custom validator, which can be placed in a utils package:

Go
1package utils 2 3import ( 4 "time" 5 6 "github.com/go-playground/validator/v10" // Provides validation methods 7) 8 9// NotPastDate is a custom validator that ensures the date is not in the past 10var NotPastDate validator.Func = func(fl validator.FieldLevel) bool { 11 // Attempt to convert the field to a time.Time type 12 date, ok := fl.Field().Interface().(time.Time) 13 if !ok { 14 // If the conversion fails, it's an invalid date, return false 15 return false 16 } 17 // Return true if date is not in the past, false otherwise 18 return !date.Before(time.Now().Add(-1 * time.Second)) 19}

In this code snippet:

  • The notPastDate function is defined with the validator.Func type, ensuring integration with the Gin validator system as a custom function.
  • It takes a parameter of type validator.FieldLevel, providing context about the field being validated, which is essential for checking input data.
  • The line fl.Field().Interface().(time.Time) extracts and asserts the field's value to the time.Time type, allowing for time-specific operations.
  • The validation logic checks !date.Before(time.Now().Add(-1 * time.Second)) to ensure the date is not set in the past, with a one-second buffer. This buffer accounts for minor discrepancies in time calculation or processing delays, enhancing the reliability of the CreationTime field validation.

By following this structure, the function effectively integrates with Gin's validation engine, allowing us to enforce the notpast validation rule on the CreationTime field.

Setting Up Routes with Validation

Now, let's set up the necessary routes to handle the creation and retrieval of ToDo items. We'll include validation to ensure CreationTime is correctly applied during item creation.

Here's how we set up the router definition and handler functions:

/router/router.go:

Go
1package router 2 3import ( 4 "github.com/gin-gonic/gin" 5 "github.com/gin-gonic/gin/binding" 6 "github.com/go-playground/validator/v10" 7 8 "codesignal.com/todoapp/controllers" 9) 10 11func RegisterRoutes(router *gin.Engine) { 12 controllers.RegisterValidators() 13 14 // POST route to add a new todo 15 router.POST("/api/todos", controllers.AddTodo) 16 17 // GET route to retrieve all todos 18 router.GET("/api/todos", controllers.GetTodos) 19}

/controllers/controllers.go:

Go
1package controllers 2 3import ( 4 "net/http" 5 6 "github.com/gin-gonic/gin" 7 8 "codesignal.com/todoapp/models" 9 "codesignal.com/todoapp/services" 10 "codesignal.com/todoapp/utils" 11) 12 13func AddTodo(c *gin.Context) { 14 var newTodo models.Todo 15 if err := c.ShouldBindJSON(&newTodo); err != nil { 16 c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON: " + err.Error()}) 17 return 18 } 19 20 newTodo = services.CreateTodo(newTodo) 21 c.JSON(http.StatusCreated, newTodo) 22} 23 24func GetTodos(c *gin.Context) { 25 todos := services.RetrieveTodos() 26 c.JSON(http.StatusOK, todos) 27} 28 29// RegisterValidators registers custom validators 30func RegisterValidators() { 31 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { 32 v.RegisterValidation("notpast", utils.NotPastDate) 33 } 34}

/services/services.go:

Go
1package services 2 3import ( 4 "time" 5 6 "codesignal.com/todoapp/models" 7) 8 9var todos []models.Todo 10 11func CreateTodo(todo models.Todo) models.Todo { 12 todo.ID = len(todos) + 1 13 todo.CreationTime = time.Now() 14 todos = append(todos, todo) 15 return todo 16} 17 18func RetrieveTodos() []models.Todo { 19 return todos 20}
  • The RegisterRoutes function first adds our custom notPastDate validator.
  • We define a POST route using the AddTodo handler function, which binds JSON data to a new ToDo item, setting the CreationTime to the current time.
  • The CreateTodo function in the services package manages the business logic for adding a new ToDo item.
  • Validation occurs upon binding, ensuring the CreationTime adheres to our rules.
  • The GET route is provided by the GetTodos handler function, which retrieves all ToDo items, displaying their creation times.
Summary and Next Steps

In this lesson, you enhanced your ToDo app by adding a CreationTime field and ensured its validity using custom validators. You've set up routes to add and retrieve these items while maintaining data integrity. Take some time to practice, and explore how these concepts improve the overall functionality and reliability of your application.

As you move forward with your journey, these building blocks will serve as a solid foundation for more complex features. Keep up the great work, and prepare to tackle new challenges!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.