| name | Go Expert |
| description | Deep expertise in Go - Gin/Chi patterns, concurrency, error handling, testing |
Go Expert Skill
This skill provides deep Go expertise including Gin/Chi patterns, goroutines, error handling, table-driven tests, and security best practices.
Error Handling
Always Check Errors
user, err := repo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to get user %s: %w", id, err)
}
user, _ := repo.GetByID(ctx, id)
Custom Error Types
type AppError struct {
StatusCode int `json:"-"`
Code string `json:"code"`
Message string `json:"message"`
Err error `json:"-"`
}
func (e *AppError) Error() string {
if e.Err != nil {
return fmt.Sprintf("%s: %v", e.Message, e.Err)
}
return e.Message
}
func (e *AppError) Unwrap() error {
return e.Err
}
func ErrNotFound(resource string) *AppError {
return &AppError{
StatusCode: http.StatusNotFound,
Code: "NOT_FOUND",
Message: fmt.Sprintf("%s not found", resource),
}
}
func ErrBadRequest(message string) *AppError {
return &AppError{
StatusCode: http.StatusBadRequest,
Code: "BAD_REQUEST",
Message: message,
}
}
Error Checking
func handleError(err error) {
var appErr *AppError
if errors.As(err, &appErr) {
log.Printf("App error: %s", appErr.Code)
return
}
if errors.Is(err, context.DeadlineExceeded) {
log.Println("Request timed out")
return
}
if errors.Is(err, sql.ErrNoRows) {
return
}
log.Printf("Unexpected error: %v", err)
}
Concurrency Patterns
Worker Pool
func ProcessItems(ctx context.Context, items []Item, workers int) error {
jobs := make(chan Item, len(items))
results := make(chan error, len(items))
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for item := range jobs {
select {
case <-ctx.Done():
results <- ctx.Err()
return
default:
results <- processItem(ctx, item)
}
}
}()
}
for _, item := range items {
jobs <- item
}
close(jobs)
go func() {
wg.Wait()
close(results)
}()
var errs []error
for err := range results {
if err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}
Context Propagation
func (s *UserService) Create(ctx context.Context, req *CreateUserRequest) (*User, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
user, err := s.repo.Create(ctx, req)
if err != nil {
return nil, err
}
go s.sendWelcomeEmail(context.WithoutCancel(ctx), user.Email)
return user, nil
}
func (h *UserHandler) Create(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
defer cancel()
user, err := h.service.Create(ctx, &req)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
c.JSON(http.StatusRequestTimeout, gin.H{"error": "request timeout"})
return
}
}
}
Fan-out/Fan-in
func FetchAll(ctx context.Context, urls []string) ([]Result, error) {
results := make(chan Result, len(urls))
errs := make(chan error, len(urls))
for _, url := range urls {
go func(url string) {
result, err := fetch(ctx, url)
if err != nil {
errs <- err
return
}
results <- result
}(url)
}
var allResults []Result
var allErrs []error
for i := 0; i < len(urls); i++ {
select {
case result := <-results:
allResults = append(allResults, result)
case err := <-errs:
allErrs = append(allErrs, err)
case <-ctx.Done():
return nil, ctx.Err()
}
}
if len(allErrs) > 0 {
return allResults, errors.Join(allErrs...)
}
return allResults, nil
}
Table-Driven Tests
Basic Pattern
func TestValidateEmail(t *testing.T) {
tests := []struct {
name string
email string
wantErr bool
}{
{"valid email", "test@example.com", false},
{"valid with subdomain", "test@mail.example.com", false},
{"missing @", "testexample.com", true},
{"missing domain", "test@", true},
{"empty string", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateEmail(tt.email)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateEmail(%q) error = %v, wantErr %v",
tt.email, err, tt.wantErr)
}
})
}
}
With Testify
func TestUserService_GetByID(t *testing.T) {
tests := []struct {
name string
userID string
mockSetup func(*MockUserRepository)
want *User
wantErr bool
errContains string
}{
{
name: "returns user when found",
userID: "1",
mockSetup: func(m *MockUserRepository) {
m.On("GetByID", mock.Anything, "1").
Return(&User{ID: "1", Email: "test@example.com"}, nil)
},
want: &User{ID: "1", Email: "test@example.com"},
wantErr: false,
},
{
name: "returns error when not found",
userID: "999",
mockSetup: func(m *MockUserRepository) {
m.On("GetByID", mock.Anything, "999").Return(nil, ErrNotFound)
},
wantErr: true,
errContains: "not found",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockRepo := new(MockUserRepository)
tt.mockSetup(mockRepo)
service := NewUserService(mockRepo)
got, err := service.GetByID(context.Background(), tt.userID)
if tt.wantErr {
require.Error(t, err)
if tt.errContains != "" {
assert.Contains(t, err.Error(), tt.errContains)
}
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
Validation with go-playground/validator
import "github.com/go-playground/validator/v10"
type CreateUserRequest struct {
Email string `json:"email" validate:"required,email"`
Name string `json:"name" validate:"required,min=2,max=100"`
Age *int `json:"age" validate:"omitempty,gte=0,lte=150"`
Password string `json:"password" validate:"required,min=8"`
}
var validate = validator.New()
func (r *CreateUserRequest) Validate() error {
return validate.Struct(r)
}
func (h *UserHandler) Create(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := req.Validate(); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
}
Security Checklist
SQL Injection Prevention
rows, err := db.QueryContext(ctx,
"SELECT * FROM users WHERE email = $1",
userInput,
)
user := User{}
err := db.GetContext(ctx, &user,
"SELECT * FROM users WHERE id = $1", id)
query := fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", userInput)
Input Validation
type Request struct {
Email string `validate:"required,email"`
Age int `validate:"gte=0,lte=150"`
}
func validateEmail(email string) error {
if email == "" {
return errors.New("email required")
}
if !strings.Contains(email, "@") {
return errors.New("invalid email format")
}
return nil
}
Rate Limiting (Gin)
import "github.com/ulule/limiter/v3"
import mgin "github.com/ulule/limiter/v3/drivers/middleware/gin"
import "github.com/ulule/limiter/v3/drivers/store/memory"
func RateLimitMiddleware() gin.HandlerFunc {
rate := limiter.Rate{
Period: 1 * time.Minute,
Limit: 60,
}
store := memory.NewStore()
instance := limiter.New(store, rate)
return mgin.NewMiddleware(instance)
}
Common Commands
go run cmd/api/main.go
air
go test ./...
go test ./... -v
go test -cover ./...
go test -race ./...
golangci-lint run
golangci-lint run --fix
go fmt ./...
go vet ./...
go build -o bin/api cmd/api/main.go
go mod tidy