Home ›
Blog › Go mit Claude Code: Microservices & APIs 2026
Go hat sich 2026 fest als bevorzugte Sprache für hochperformante Microservices etabliert.
Die Kombination aus statischer Typisierung, eingebauter Nebenläufigkeit und einem schlanken
Runtime-Footprint macht Go zur ersten Wahl für Backend-Dienste, die tatsächlich unter Last funktionieren müssen.
Claude Code versteht Go idiomatisch — es schlägt nicht einfach "Java-Muster in Go" vor,
sondern arbeitet mit Interfaces, Composition und dem Go-eigenen Fehlermodell.
Voraussetzungen: Go 1.23+, Docker, grundlegende CLI-Kenntnisse.
Alle Codebeispiele sind produktionsnah und direkt einsetzbar.
⚡
Schneller Einstieg
Go kompiliert in Sekunden. Kein langer Build-Zyklus, keine schwerfällige IDE nötig.
🔄
Echte Nebenläufigkeit
Goroutines und Channels sind in der Sprache verankert — kein Framework-Overhead.
📦
Kleiner Footprint
Statisch gelinkte Binaries, minimale Container-Images mit unter 10 MB.
🔒
Typsicherheit
Compiler fängt Fehler frühzeitig — weniger Runtime-Panics in der Produktion.
1. Go Grundlagen & HTTP-Server
Die Standardbibliothek von Go enthält mit net/http einen vollständigen HTTP-Server,
der für viele Anwendungsfälle ohne externe Abhängigkeiten auskommt. Für produktive APIs empfiehlt
sich jedoch Chi als leichtgewichtiger Router, der perfekt zu Go's Philosophie passt.
net/http
chi
Projekt aufsetzen
Initialisierung eines Go-Moduls und Installation der wichtigsten Abhängigkeiten:
go mod init github.com/company/api-service
go get github.com/go-chi/chi/v5
go get github.com/go-chi/chi/v5/middleware
go get github.com/go-chi/cors
Minimaler HTTP-Server mit net/http
package main
import (
"encoding/json"
"log"
"net/http"
"time"
)
type Response struct {
Status string `json:"status"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
Latency string `json:"latency"`
}
func jsonResponse(w http.ResponseWriter, status int, payload interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(payload)
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
jsonResponse(w, http.StatusOK, Response{
Status: "ok",
Latency: time.Now().Format(time.RFC3339),
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/health", healthHandler)
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
log.Printf("Server startet auf :8080")
if err := server.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
Chi Router mit Middleware
Chi bringt ein elegantes Middleware-System mit, das Go's http.Handler-Interface
vollständig respektiert. Claude Code generiert die gesamte Middleware-Chain auf Anfrage:
package router
import (
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"github.com/company/api-service/handlers"
)
func New(h *handlers.Handler) http.Handler {
r := chi.NewRouter()
// Global Middleware Stack
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(30 * time.Second))
r.Use(middleware.Compress(5))
// CORS — produktionskonfiguriert
r.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"https://agentic-movers.com"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowedHeaders: []string{"Authorization", "Content-Type"},
AllowCredentials: true,
}))
// Routen
r.Get("/health", h.Health)
r.Route("/api/v1", func(r chi.Router) {
r.Use(h.AuthMiddleware)
r.Route("/users", func(r chi.Router) {
r.Get("/", h.ListUsers)
r.Post("/", h.CreateUser)
r.Get("/{id}", h.GetUser)
r.Put("/{id}", h.UpdateUser)
r.Delete("/{id}", h.DeleteUser)
})
})
return r
}
JSON Encode/Decode & Error-Handling
Go's Fehlerbehandlung durch explizite Rückgabewerte ist für API-Entwicklung ideal —
jeder Fehler muss bewusst behandelt werden. Claude Code schreibt dabei konsistente
Fehlertypen, die sich über den gesamten Service ziehen:
package errors
import "net/http"
type APIError struct {
Code int `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
}
func (e *APIError) Error() string { return e.Message }
var (
ErrNotFound = &APIError{Code: http.StatusNotFound, Message: "Ressource nicht gefunden"}
ErrBadRequest = &APIError{Code: http.StatusBadRequest, Message: "Ungültige Anfrage"}
ErrUnauth = &APIError{Code: http.StatusUnauthorized, Message: "Nicht autorisiert"}
ErrInternal = &APIError{Code: http.StatusInternalServerError, Message: "Interner Fehler"}
)
func Wrap(err error, details string) *APIError {
if apiErr, ok := err.(*APIError); ok {
apiErr.Details = details
return apiErr
}
return &APIError{Code: 500, Message: err.Error(), Details: details}
}
Claude Code Tipp: Beschreibe deinen gewünschten Handler mit "Erstelle einen POST /users
Handler der JSON-Body parsed, validiert und in die Datenbank speichert — mit korrektem Error-Wrapping."
Claude Code generiert dabei automatisch typsichere Request/Response-Structs mit JSON-Tags.
2. GORM ORM & Datenbankzugriff
GORM
PostgreSQL
Migrations
GORM ist das meistgenutzte ORM in Go und bietet dabei eine pragmatische Balance zwischen
Abstraktion und SQL-Kontrolle. Für komplexe Queries kann GORM auf rohe SQL-Strings fallen —
Claude Code weiß wann was sinnvoll ist.
package models
import (
"time"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Email string `gorm:"uniqueIndex;not null" json:"email"`
Name string `gorm:"size:255;not null" json:"name"`
Role string `gorm:"default:user" json:"role"`
Active bool `gorm:"default:true" json:"active"`
LastLogin *time.Time `json:"last_login,omitempty"`
Posts []Post `gorm:"foreignKey:UserID" json:"posts,omitempty"`
}
type Post struct {
gorm.Model
Title string `gorm:"not null" json:"title"`
Body string `gorm:"type:text" json:"body"`
Published bool `gorm:"default:false" json:"published"`
UserID uint `json:"user_id"`
}
package db
import (
"fmt"
"os"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"github.com/company/api-service/models"
)
func Connect() (*gorm.DB, error) {
dsn := fmt.Sprintf(
"host=%s user=%s password=%s dbname=%s port=%s sslmode=require",
os.Getenv("DB_HOST"), os.Getenv("DB_USER"),
os.Getenv("DB_PASS"), os.Getenv("DB_NAME"),
os.Getenv("DB_PORT"),
)
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
return nil, fmt.Errorf("db connect: %w", err)
}
sqlDB, _ := db.DB()
sqlDB.SetMaxOpenConns(25)
sqlDB.SetMaxIdleConns(10)
sqlDB.SetConnMaxLifetime(5 * time.Minute)
// AutoMigrate — nur für Entwicklung; in Produktion: goose/atlas
if err := db.AutoMigrate(&models.User{}, &models.Post{}); err != nil {
return nil, fmt.Errorf("automigrate: %w", err)
}
return db, nil
}
CRUD-Operationen, Preload & Transactions
package repository
import (
"context"
"github.com/company/api-service/models"
"gorm.io/gorm"
)
type UserRepo struct { db *gorm.DB }
func NewUserRepo(db *gorm.DB) *UserRepo { return &UserRepo{db: db} }
// FindByID — mit Preload der Posts
func (r *UserRepo) FindByID(ctx context.Context, id uint) (*models.User, error) {
var user models.User
err := r.db.WithContext(ctx).
Preload("Posts", "published = ?", true).
First(&user, id).Error
if err != nil {
return nil, err
}
return &user, nil
}
// CreateWithPost — atomische Transaktion
func (r *UserRepo) CreateWithPost(ctx context.Context, user *models.User, post *models.Post) error {
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Create(user).Error; err != nil {
return fmt.Errorf("user erstellen: %w", err)
}
post.UserID = user.ID
if err := tx.Create(post).Error; err != nil {
return fmt.Errorf("post erstellen: %w", err)
}
return nil
})
}
// SoftDelete — GORM's DeletedAt Feld
func (r *UserRepo) Delete(ctx context.Context, id uint) error {
return r.db.WithContext(ctx).Delete(&models.User{}, id).Error
}
Achtung bei AutoMigrate in Produktion: AutoMigrate fügt Spalten hinzu,
löscht aber keine. Für produktive Migrationen empfiehlt sich goose oder
atlas mit versionierten SQL-Dateien.
3. Goroutines & Channels
Goroutines
Channels
errgroup
Goroutines sind Go's Killer-Feature für Microservices: Millionen gleichzeitiger leichtgewichtiger
Threads mit minimalem Overhead. Channels ermöglichen sichere Kommunikation ohne gemeinsamen
Speicher (CSP-Modell). Claude Code hilft dabei, Deadlocks und Race Conditions von Anfang an zu vermeiden.
Goroutines mit WaitGroup & Mutex
package concurrency
import (
"context"
"log"
"sync"
)
type Job struct {
ID int
Payload string
}
type Result struct {
JobID int
Output string
Err error
}
func WorkerPool(ctx context.Context, jobs []Job, workers int) []Result {
jobCh := make(chan Job, len(jobs))
resCh := make(chan Result, len(jobs))
var wg sync.WaitGroup
// Workers starten
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case job, ok := <-jobCh:
if !ok {
return // Channel geschlossen
}
result, err := processJob(job)
resCh <- Result{JobID: job.ID, Output: result, Err: err}
case <-ctx.Done():
log.Printf("Worker beendet: %v", ctx.Err())
return
}
}
}()
}
// Jobs einspeisen
for _, job := range jobs {
jobCh <- job
}
close(jobCh)
// Auf alle Workers warten, dann Ergebnis-Channel schließen
go func() {
wg.Wait()
close(resCh)
}()
var results []Result
for res := range resCh {
results = append(results, res)
}
return results
}
func processJob(job Job) (string, error) {
// Hier echte Verarbeitungslogik
return "verarbeitet: " + job.Payload, nil
}
errgroup für parallele Fehlerbehandlung
Das errgroup-Paket aus golang.org/x/sync ist der moderne Standard
für parallele Tasks mit gemeinsamer Fehlerbehandlung — Claude Code bevorzugt es gegenüber
manuellen WaitGroup+Channel-Kombinationen:
package concurrency
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
)
type ServiceData struct {
Users []string
Products []string
Orders []string
}
func FetchAllParallel(ctx context.Context) (*ServiceData, error) {
g, ctx := errgroup.WithContext(ctx)
data := &ServiceData{}
g.Go(func() error {
users, err := fetchUsers(ctx)
if err != nil { return fmt.Errorf("users: %w", err) }
data.Users = users
return nil
})
g.Go(func() error {
products, err := fetchProducts(ctx)
if err != nil { return fmt.Errorf("products: %w", err) }
data.Products = products
return nil
})
g.Go(func() error {
orders, err := fetchOrders(ctx)
if err != nil { return fmt.Errorf("orders: %w", err) }
data.Orders = orders
return nil
})
// Wartet auf alle — gibt ersten Fehler zurück und cancelt Context
if err := g.Wait(); err != nil {
return nil, err
}
return data, nil
}
Goroutine Best Practices mit Claude Code
- Immer Context übergeben — für Cancellation und Timeouts
- Channel-Richtung angeben —
chan<- T vs <-chan T
- Mutex für geteilten State —
sync.RWMutex für read-heavy Szenarien
- errgroup statt manuelle WaitGroup+Error-Channel
- Goroutine-Leaks vermeiden — Claude Code prüft auf fehlende defer/close Aufrufe
4. gRPC Services
gRPC
Protobuf
Streaming
gRPC ist der Standard für Service-zu-Service-Kommunikation in Go-Microservice-Architekturen.
Starke Typisierung durch Protobuf, automatisch generierter Client-Code und HTTP/2-Streaming
machen es zu einem echten Upgrade gegenüber REST für interne Dienste.
Proto-Definition
syntax = "proto3";
package user.v1;
option go_package = "github.com/company/api-service/proto/userv1;userv1";
service UserService {
// Unary RPC
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
// Server-Side Streaming — sendet Events an Client
rpc WatchUser(WatchUserRequest) returns (stream UserEvent);
// Client-Side Streaming — Bulk-Upload
rpc ImportUsers(stream CreateUserRequest) returns (ImportUsersResponse);
}
message GetUserRequest { uint64 id = 1; }
message GetUserResponse { User user = 1; }
message CreateUserRequest { string email = 1; string name = 2; }
message CreateUserResponse { User user = 1; }
message WatchUserRequest { uint64 id = 1; }
message ImportUsersResponse { int32 imported = 1; int32 failed = 2; }
message User {
uint64 id = 1;
string email = 2;
string name = 3;
string role = 4;
}
message UserEvent {
string type = 1;
User user = 2;
}
gRPC Server-Implementierung
package grpcserver
import (
"context"
"fmt"
"log"
"net"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
pb "github.com/company/api-service/proto/userv1"
"github.com/company/api-service/repository"
)
type UserServer struct {
pb.UnimplementedUserServiceServer
repo *repository.UserRepo
}
func (s *UserServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
user, err := s.repo.FindByID(ctx, uint(req.Id))
if err != nil {
return nil, status.Errorf(codes.NotFound, "user %d nicht gefunden: %v", req.Id, err)
}
return &pb.GetUserResponse{User: toProto(user)}, nil
}
// Server-Side Streaming — Events in Echtzeit
func (s *UserServer) WatchUser(req *pb.WatchUserRequest, stream pb.UserService_WatchUserServer) error {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-stream.Context().Done():
return nil
case <-ticker.C:
user, err := s.repo.FindByID(stream.Context(), uint(req.Id))
if err != nil {
return status.Error(codes.Internal, err.Error())
}
if err := stream.Send(&pb.UserEvent{Type: "heartbeat", User: toProto(user)}); err != nil {
return err
}
}
}
}
func Start(addr string, repo *repository.UserRepo) error {
lis, err := net.Listen("tcp", addr)
if err != nil { return fmt.Errorf("listen: %w", err) }
srv := grpc.NewServer(
grpc.ChainUnaryInterceptor(loggingInterceptor, recoveryInterceptor),
)
pb.RegisterUserServiceServer(srv, &UserServer{repo: repo})
log.Printf("gRPC Server auf %s", addr)
return srv.Serve(lis)
}
gRPC Wann REST, wann gRPC?
| Kriterium | REST/JSON | gRPC/Protobuf |
| Externe APIs | Bevorzugt | Selten |
| Service-zu-Service intern | Möglich | Bevorzugt |
| Streaming | SSE/WebSocket | Nativ eingebaut |
| Schema-Validierung | JSON Schema | Protobuf (stark typisiert) |
| Performance | Gut | 2-10x schneller |
5. Testing in Go
testing.T
httptest
testify
Go's eingebautes Test-Framework ist bewusst schlank gehalten. Zusammen mit testify/assert
und httptest für HTTP-Tests ergibt sich eine sehr produktive Test-Umgebung.
Claude Code schreibt dabei standardmäßig Table-Driven Tests — der idiomatische Go-Stil.
Table-Driven Tests
package handlers_test
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/company/api-service/handlers"
"github.com/company/api-service/mocks"
)
func TestCreateUser(t *testing.T) {
tests := []struct {
name string
body interface{}
mockReturn error
wantStatus int
wantEmail string
}{
{
name: "valide Eingabe",
body: map[string]string{"email": "max@example.com", "name": "Max"},
mockReturn: nil,
wantStatus: http.StatusCreated,
wantEmail: "max@example.com",
},
{
name: "fehlende Email",
body: map[string]string{"name": "Max"},
mockReturn: nil,
wantStatus: http.StatusBadRequest,
},
{
name: "Datenbank-Fehler",
body: map[string]string{"email": "x@x.com", "name": "X"},
mockReturn: errors.New("db down"),
wantStatus: http.StatusInternalServerError,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
mockRepo := mocks.NewUserRepository(t)
if tc.mockReturn != nil || tc.wantStatus == http.StatusCreated {
mockRepo.On("Create", mock.Anything, mock.Anything).Return(tc.mockReturn)
}
h := handlers.New(mockRepo)
body, _ := json.Marshal(tc.body)
req := httptest.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
h.CreateUser(rec, req)
assert.Equal(t, tc.wantStatus, rec.Code)
if tc.wantEmail != "" {
var resp map[string]interface{}
require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp))
assert.Equal(t, tc.wantEmail, resp["email"])
}
})
}
}
Integration Tests mit echtem HTTP-Server
package integration_test
import (
"net/http/httptest"
"testing"
"github.com/stretchr/testify/suite"
"github.com/company/api-service/router"
"github.com/company/api-service/testutil"
)
type UserSuite struct {
suite.Suite
server *httptest.Server
db *gorm.DB
}
func (s *UserSuite) SetupSuite() {
s.db = testutil.SetupTestDB(s.T())
r := router.New(handlers.New(repository.NewUserRepo(s.db)))
s.server = httptest.NewServer(r)
}
func (s *UserSuite) TearDownSuite() {
s.server.Close()
testutil.CleanupTestDB(s.T(), s.db)
}
func (s *UserSuite) TestCreateAndFetchUser() {
// POST /api/v1/users
resp := s.postJSON("/api/v1/users", map[string]string{
"email": "test@example.com", "name": "Testuser",
})
s.Equal(201, resp.StatusCode)
var created map[string]interface{}
json.NewDecoder(resp.Body).Decode(&created)
id := created["id"].(float64)
// GET /api/v1/users/{id}
resp2 := s.getJSON(fmt.Sprintf("/api/v1/users/%d", int(id)))
s.Equal(200, resp2.StatusCode)
}
func TestUserSuite(t *testing.T) { suite.Run(t, new(UserSuite)) }
Claude Code für Tests: "Schreibe Table-Driven Tests für den CreateUser-Handler,
mock den Repository-Layer mit mockery, teste Happy Path, Validierungsfehler und DB-Fehler."
Claude Code generiert dabei automatisch die passenden Mock-Definitionen.
6. Docker & Deployment
Docker
Multi-Stage
Distroless
Go's statisch gelinkte Binaries sind ideal für Container-Deployments. Mit Multi-Stage Builds
und distroless Base-Images entsteht ein produktionsreifer Container unter 15 MB — ohne Shell,
ohne Package-Manager, ohne unnötige Angriffsfläche.
Multi-Stage Dockerfile
# Stage 1: Build
FROM golang:1.23-alpine AS builder
WORKDIR /app
RUN apk add --no-cache git ca-certificates tzdata
# Abhängigkeiten zuerst cachen (Layer-Caching nutzen)
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-w -s -X main.version=$(git describe --tags --always)" \
-o /bin/api-service ./cmd/api
# Stage 2: Runtime (distroless — kein Shell, minimal attack surface)
FROM gcr.io/distroless/static-debian12
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /bin/api-service /api-service
EXPOSE 8080 9090
USER nonroot:nonroot
ENTRYPOINT ["/api-service"]
Docker Compose mit Health Checks
services:
api:
build: .
ports:
- "8080:8080"
- "9090:9090" # gRPC
environment:
DB_HOST: postgres
DB_USER: appuser
DB_PASS: ${DB_PASS}
DB_NAME: appdb
DB_PORT: "5432"
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "/api-service", "--health-check"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${DB_PASS}
POSTGRES_DB: appdb
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 10s
timeout: 5s
retries: 5
volumes:
pgdata:
Graceful Shutdown
Produktionsreife Services müssen laufende Requests abschließen bevor sie sich beenden.
Go's os/signal macht das einfach umsetzbar:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
db, err := database.Connect()
if err != nil { log.Fatal(err) }
r := router.New(handlers.New(repository.NewUserRepo(db)))
srv := &http.Server{
Addr: ":8080",
Handler: r,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 120 * time.Second,
}
// Server in Goroutine starten
go func() {
log.Printf("API startet auf :8080")
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("Server Fehler: %v", err)
}
}()
// Auf OS-Signal warten
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutdown eingeleitet — warte auf laufende Requests...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Printf("Forcierter Shutdown: %v", err)
}
log.Println("Server sauber beendet")
}
Deployment Checkliste für produktionsreife Go-Services
- Graceful Shutdown mit konfigurierbarem Timeout
- Health Endpoint für Kubernetes Liveness/Readiness Probes
- Structured Logging (slog oder zap) statt log.Printf
- Metrics via Prometheus /metrics Endpoint
- Distroless Container für minimale Angriffsfläche
- Non-root User im Container (USER nonroot:nonroot)
- Connection Pooling korrekt konfiguriert (MaxOpenConns, IdleConns)
- Context-Propagation durch alle Schichten
CI/CD mit GitHub Actions
name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: testpass
POSTGRES_DB: testdb
ports: ["5432:5432"]
options: --health-cmd pg_isready --health-interval 10s
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: '1.23' }
- run: go mod download
- run: go vet ./...
- run: go test -race -coverprofile=coverage.out ./...
- run: go tool cover -func=coverage.out
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker Image
run: docker build -t api-service:${{ github.sha }} .
- name: Push to Registry
run: |
docker tag api-service:${{ github.sha }} registry.example.com/api-service:latest
docker push registry.example.com/api-service:latest
Go-Services mit Claude Code entwickeln
Von der ersten Proto-Datei bis zum laufenden Kubernetes-Deployment —
Claude Code kennt Go's Idiome und schreibt produktionsreifen Code ohne Boilerplate-Stress.
Jetzt kostenlos ausprobieren.
Kostenlos testen →
Fazit: Go + Claude Code = Production-Ready in Rekordzeit
Go war schon vor Claude Code eine ausgezeichnete Wahl für Microservices. Mit Claude Code
als intelligentem Pair-Programmer wird die Produktivität nochmals multipliziert: Statt
Boilerplate-Code zu schreiben konzentriert man sich auf Architektur und Business-Logik.
Die Kombination aus net/http + Chi für HTTP-APIs, GORM für den Datenbankzugriff,
errgroup für parallele Tasks, gRPC für Service-zu-Service-Kommunikation und
distroless Docker-Images für das Deployment ist 2026 der bewährte Stack für Go-Microservices.
Claude Code kennt diesen Stack in- und auswendig.