Go Development
Project Structure
myproject/ ├── cmd/ │ └── server/ │ └── main.go ├── internal/ │ ├── handler/ │ ├── service/ │ └── repository/ ├── pkg/ │ └── shared/ ├── go.mod └── go.sum
Error Handling
// Custom error types type NotFoundError struct { Resource string ID string }
func (e *NotFoundError) Error() string { return fmt.Sprintf("%s not found: %s", e.Resource, e.ID) }
// Error wrapping func GetUser(id string) (*User, error) { user, err := db.FindUser(id) if err != nil { return nil, fmt.Errorf("GetUser(%s): %w", id, err) } return user, nil }
// Error checking if errors.Is(err, sql.ErrNoRows) { return nil, &NotFoundError{Resource: "user", ID: id} }
Concurrency
// Goroutines with errgroup func fetchAll(ctx context.Context, urls []string) ([]Response, error) { g, ctx := errgroup.WithContext(ctx) results := make([]Response, len(urls))
for i, url := range urls {
i, url := i, url // capture loop variables
g.Go(func() error {
resp, err := fetch(ctx, url)
if err != nil {
return err
}
results[i] = resp
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
// Channels func producer(ch chan<- int) { for i := 0; i < 10; i++ { ch <- i } close(ch) }
func consumer(ch <-chan int) { for v := range ch { fmt.Println(v) } }
HTTP Handler
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) { ctx := r.Context() id := chi.URLParam(r, "id")
user, err := h.service.GetUser(ctx, id)
if err != nil {
var notFound *NotFoundError
if errors.As(err, &notFound) {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
http.Error(w, "Internal error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
Testing
func TestGetUser(t *testing.T) { t.Parallel()
tests := []struct {
name string
id string
want *User
wantErr bool
}{
{
name: "existing user",
id: "123",
want: &User{ID: "123", Email: "test@example.com"},
},
{
name: "non-existent user",
id: "999",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := service.GetUser(context.Background(), tt.id)
if (err != nil) != tt.wantErr {
t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("got = %v, want %v", got, tt.want)
}
})
}
}
Tooling
Format
gofmt -w . goimports -w .
Lint
golangci-lint run
Test
go test -v -race -cover ./...