Choorai
Lv.3 Performance Go Chi Router

Build a Performance-Focused Backend with Go

Go is a compiled language known for fast execution speed and low memory usage. Deploy as a single binary to keep Docker image size under 20MB.

Why Go?

  • Fast execution speed (compiled language)
  • Single binary deployment (no dependencies)
  • Low memory usage (fast Cold Start)
  • Docker image ~15MB
  • Strong concurrency handling (goroutines)

1. Create Project

Terminal
mkdir my-api && cd my-api
go mod init my-api
go get github.com/go-chi/chi/v5
go get github.com/go-chi/cors
go get github.com/google/uuid

Chi is a lightweight and fast Go router. It uses a middleware pattern similar to Express.js.

2. Write Your First API

main.go
package main

import (
    "encoding/json"
    "log"
    "net/http"
    "os"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
    "github.com/go-chi/cors"
)

func main() {
    r := chi.NewRouter()

    // Middleware
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    r.Use(cors.Handler(cors.Options{
        AllowedOrigins:   []string{"http://localhost:*"},
        AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE"},
        AllowedHeaders:   []string{"Content-Type"},
        AllowCredentials: true,
    }))

    // Routes
    r.Get("/health", healthHandler)
    r.Get("/api/hello", helloHandler)

    // Start server
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    log.Printf("Server starting on port %s", port)
    http.ListenAndServe(":"+port, r)
}

func healthHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{
        "status": "healthy",
    })
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
    name := r.URL.Query().Get("name")
    if name == "" {
        name = "World"
    }
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{
        "message": "Hello, " + name + "!",
    })
}

Run

Terminal
go run main.go

# Test
curl http://localhost:8080/health
curl http://localhost:8080/api/hello?name=Choorai

3. Add CRUD API

handlers.go
package main

import (
    "encoding/json"
    "net/http"
    "sync"
    "time"

    "github.com/go-chi/chi/v5"
    "github.com/google/uuid"
)

// Project model
type Project struct {
    ID          string    `json:"id"`
    Name        string    `json:"name"`
    Description string    `json:"description"`
    CreatedAt   time.Time `json:"created_at"`
}

// In-memory storage
var (
    projects = make(map[string]*Project)
    mu       sync.RWMutex
)

// List projects
func listProjects(w http.ResponseWriter, r *http.Request) {
    mu.RLock()
    defer mu.RUnlock()

    items := make([]*Project, 0, len(projects))
    for _, p := range projects {
        items = append(items, p)
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]interface{}{
        "items": items,
        "total": len(items),
    })
}

// Create project
func createProject(w http.ResponseWriter, r *http.Request) {
    var req struct {
        Name        string `json:"name"`
        Description string `json:"description"`
    }
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, `{"error":"invalid body"}`, http.StatusBadRequest)
        return
    }

    project := &Project{
        ID:          uuid.New().String(),
        Name:        req.Name,
        Description: req.Description,
        CreatedAt:   time.Now(),
    }

    mu.Lock()
    projects[project.ID] = project
    mu.Unlock()

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(project)
}

// Get project
func getProject(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id")

    mu.RLock()
    project, exists := projects[id]
    mu.RUnlock()

    if !exists {
        http.Error(w, `{"error":"not found"}`, http.StatusNotFound)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(project)
}

// Delete project
func deleteProject(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id")

    mu.Lock()
    defer mu.Unlock()

    if _, exists := projects[id]; !exists {
        http.Error(w, `{"error":"not found"}`, http.StatusNotFound)
        return
    }

    delete(projects, id)
    w.WriteHeader(http.StatusNoContent)
}

// Router setup
func setupRoutes(r chi.Router) {
    r.Route("/api/v1/projects", func(r chi.Router) {
        r.Get("/", listProjects)
        r.Post("/", createProject)
        r.Get("/{id}", getProject)
        r.Delete("/{id}", deleteProject)
    })
}

sync.RWMutex

Go uses sync.RWMutex for concurrency safety. Multiple goroutines can read simultaneously, but only one can write at a time.

4. Deploy with Docker

Dockerfile
# Build stage
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o server .

# Runtime stage
FROM alpine:3.20
WORKDIR /app
RUN adduser -D -g '' appuser
COPY --from=builder /app/server .
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE 8080
CMD ["./server"]

Multi-stage builds ensure the final image contains only the binary. The resulting image size is approximately 15-20MB.

Terminal
# Local test
docker build -t my-go-api .
docker run -p 8080:8080 my-go-api

# Check image size (< 20MB!)
docker images my-go-api

# Cloud Run deployment
gcloud run deploy my-go-api \
  --source . \
  --region asia-northeast3 \
  --allow-unauthenticated \
  --min-instances 0 \
  --max-instances 1

Framework Comparison

Category Go (Chi) Hono FastAPI
Image Size ~15MB ~150MB ~200MB
Cold Start ~100ms ~300ms ~500ms
Memory Usage ~20MB ~50MB ~80MB
Learning Curve Medium Easy Easy
Type System Static Dynamic (TS) Dynamic (Pydantic)

When Should You Choose Go?

Go Recommended

  • ✅ Serverless where Cold Start matters
  • ✅ Cost optimization is a priority
  • ✅ High concurrent throughput
  • ✅ Microservices architecture

Consider Other Options

  • Quick prototype: Hono
  • Auto documentation: FastAPI
  • Enterprise structure: NestJS

Full Example Code

Check out the Go version of the B2B Admin API: examples/b2b-admin/api-go

Last updated: February 22, 2026 · Version: v0.0.1

Send Feedback

Opens a new issue page with your message.

Open GitHub Issue