Ecosystem
Go’s standard library handles most needs, but these libraries fill the gaps.
Web Frameworks
| Framework | Stars | Market share | Philosophy |
|---|---|---|---|
| Gin | 80k+ | ~48% | Fast, middleware-heavy, Rails-like |
| Echo | 30k+ | ~16% | Similar to Gin, slightly cleaner API |
| Fiber | 34k+ | ~11% | Express.js-inspired, fasthttp-based |
| Chi | 18k+ | ~10% | Composable, stdlib-compatible router |
| stdlib | - | growing | Go 1.22+ ServeMux handles most cases |
When to use what
stdlib net/http: Small-to-medium APIs. Go 1.22+ pattern matching covers most routing needs. No dependency, no lock-in.
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", getUser)
Chi: When you outgrow stdlib but want stdlib compatibility. Chi handlers are http.Handler, so you can mix with any stdlib middleware.
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Get("/users/{id}", getUser) // same http.HandlerFunc signature
Gin: When you want batteries-included (binding, validation, rendering) and don’t mind a custom context type.
r := gin.Default()
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(200, gin.H{"id": id})
})
Tip: Start with stdlib. Move to Chi if you need route groups and middleware chaining. Move to Gin/Echo only if you need their specific features (validation binding, SSE helpers, etc.).
CLI
Cobra is the standard for CLI apps. Used by kubectl, docker, gh, and hugo.
import "github.com/spf13/cobra"
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "Does things",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("hello")
},
}
func main() {
rootCmd.Execute()
}
| Library | Use case |
|---|---|
| Cobra | Full CLI apps with subcommands, flags, completions |
| urfave/cli | Simpler alternative to Cobra |
| flag (stdlib) | Single-command tools with flags |
| Bubble Tea | Terminal UIs (interactive prompts, TUIs) |
Database
| Library | Type | Best for |
|---|---|---|
| database/sql (stdlib) | Raw SQL | Simple queries, full control |
| sqlx | SQL + scanning | Struct scanning, named params |
| pgx | PostgreSQL driver | Best Postgres performance |
| GORM | ORM | Rapid prototyping, ActiveRecord style |
| sqlc | Code gen from SQL | Type-safe queries from SQL files |
| goose | Migrations | Schema versioning |
sqlx is the sweet spot for most projects - raw SQL with convenient struct scanning:
import "github.com/jmoiron/sqlx"
type User struct {
ID int `db:"id"`
Name string `db:"name"`
Email string `db:"email"`
}
var users []User
err := db.SelectContext(ctx, &users,
"SELECT id, name, email FROM users WHERE active = ?", true)
sqlc generates type-safe Go code from SQL queries:
-- query.sql
-- name: GetUser :one
SELECT id, name, email FROM users WHERE id = $1;
sqlc generate
# generates: func (q *Queries) GetUser(ctx context.Context, id int) (User, error)
HTTP Client
stdlib net/http works for most use cases:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.example.com/data", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
var result APIResponse
json.NewDecoder(resp.Body).Decode(&result)
For a fluent API with retries, use resty:
import "github.com/go-resty/resty/v2"
client := resty.New().
SetBaseURL("https://api.example.com").
SetHeader("Authorization", "Bearer "+token).
SetRetryCount(3)
var result APIResponse
resp, err := client.R().
SetResult(&result).
Get("/data")
Other Essential Libraries
| Category | Library | What it does |
|---|---|---|
| Config | viper | Read config from files, env vars, flags |
| Logging | slog (stdlib) | Structured logging (Go 1.21+) |
| Logging | zerolog | Zero-allocation JSON logger |
| Validation | validator | Struct validation with tags |
| UUID | google/uuid | UUID generation |
| Cron | robfig/cron | Scheduled tasks |
| WebSocket | gorilla/websocket | WebSocket connections |
| gRPC | grpc-go | gRPC server and client |
Decision Table
| Need | Recommendation |
|---|---|
| REST API, small | stdlib net/http |
| REST API, medium | Chi or stdlib + middleware |
| REST API, large team | Gin or Echo |
| CLI tool | Cobra |
| PostgreSQL | pgx + sqlc |
| Any SQL database | sqlx |
| Rapid prototype with DB | GORM |
| Config management | viper |
| Structured logging | slog (stdlib) |
Go vs Rust
Both are popular for backend services and CLI tools. Here is how they compare:
| Aspect | Go | Rust |
|---|---|---|
| Learning curve | Days to productive | Weeks to months |
| Compile time | Seconds | Minutes |
| Memory safety | GC (garbage collector) | Ownership system, no GC |
| Concurrency | Goroutines + channels | async/await + tokio |
| Error handling | if err != nil | Result<T, E> + ? |
| Binary size | 5-15 MB typical | 1-5 MB typical |
| Performance | Fast (within 2-3x of C) | Very fast (near C) |
| Ecosystem | Mature, broad | Growing, deep |
| Best for | APIs, DevOps, cloud infra | Systems, WASM, perf-critical |
| Used by | Docker, K8s, Terraform | Ripgrep, fd, Deno, SWC |
Choose Go when: You want fast development, easy deployment, and your team needs to be productive quickly. Great for microservices, APIs, and DevOps tooling.
Choose Rust when: You need maximum performance, minimal memory usage, or are targeting embedded/WASM. Worth the investment for long-lived, performance-critical systems.
Tip: They complement each other. Many projects use Go for services and Rust for performance-critical libraries (called via FFI or as separate binaries).