go
42 lines · 7 steps
Validated signup handler in Gin
A Gin endpoint that binds JSON, returns precise validation errors, and maps store failures to the right HTTP status.
Explained by
highlit
1package handlers
2
3type SignupRequest struct {
4 Email string `json:"email" binding:"required,email"`
5 Password string `json:"password" binding:"required,min=8,max=72"`
6 ConfirmPassword string `json:"confirm_password" binding:"required,eqfield=Password"`
7 DisplayName string `json:"display_name" binding:"required,min=2,max=50"`
8 AcceptTerms bool `json:"accept_terms" binding:"required"`
9}
10
11func (h *AuthHandler) Signup(c *gin.Context) {
12 var req SignupRequest
13 if err := c.ShouldBindJSON(&req); err != nil {
14 var ve validator.ValidationErrors
15 if errors.As(err, &ve) {
16 fields := make(map[string]string, len(ve))
17 for _, fe := range ve {
18 fields[fe.Field()] = fe.Tag()
19 }
20 c.JSON(http.StatusBadRequest, gin.H{"error": "validation_failed", "fields": fields})
21 return
22 }
23 c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
24 return
25 }
26
27 user, err := h.users.Create(c.Request.Context(), req.Email, req.Password, req.DisplayName)
28 if errors.Is(err, store.ErrEmailTaken) {
29 c.JSON(http.StatusConflict, gin.H{"error": "email already registered"})
30 return
31 }
32 if err != nil {
33 c.JSON(http.StatusInternalServerError, gin.H{"error": "could not create account"})
34 return
35 }
36
37 c.JSON(http.StatusCreated, gin.H{
38 "id": user.ID,
39 "email": user.Email,
40 "display_name": user.DisplayName,
41 })
42}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Declarative `binding` tags push input validation out of handler logic and into the struct definition.
- 2Inspecting error types with `errors.As` and `errors.Is` lets one handler return distinct responses for distinct failures.
- 3Returning a curated response struct keeps internal fields like password hashes from leaking to clients.
Related explainers
go
package handlers import ( "net/http"
Serving embedded static meta files in Gin
embedding
static assets
http caching
Intermediate
6 steps
go
package config import ( "fmt"
A thread-safe config singleton in Go
singleton
concurrency
environment-variables
Intermediate
7 steps
go
package scheduler import ( "container/heap"
A priority job queue with Go's container/heap
priority-queue
heap
interfaces
Intermediate
9 steps
go
func UploadChunk(c *gin.Context) { uploadID := c.Param("uploadID") if !validUploadID.MatchString(uploadID) { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid upload id"})
Resumable chunked uploads in Gin
file-upload
content-range
streaming
Advanced
9 steps
go
func (h *ArticleHandler) Create(c *gin.Context) { var req struct { Title string `json:"title" binding:"required"` Body string `json:"body" binding:"required"`
Handling a POST request in Gin
request binding
validation
http status codes
Intermediate
7 steps
go
package cache import ( "sync"
A thread-safe TTL cache in Go
concurrency
mutex
caching
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/validated-signup-handler-in-gin-explained-go-04b8/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.