Examples
Learn by building real microservices with go-micro
Learn by Example
Runnable examples are the fastest way to move from reading the guides to changing
one thing. Start with the path that matches where you are in the services →
agents → workflows lifecycle.
Start here
For the provider-free first-agent route, run examples/first-agent, then follow No-secret First Agent, Your First Agent, Debugging your agent, and the 0→hero Reference.
| Goal | Runnable example | Why it is useful |
|---|
| 0→1 service | examples/hello-world | Smallest RPC service with a client call and health checks. |
| Provider-free first agent | examples/first-agent | Smallest service-backed agent with a deterministic mock model; no provider key required. |
| First service-backed agent | examples/agent-demo | Multi-service project/task/team app with agent playground integration. |
| 0→hero lifecycle | examples/support | No-secret support-desk story: typed services, an agent, an event-driven flow, and a guardrail. |
| Planning and delegation | examples/agent-plan-delegate | Two agents collaborate through plan and delegate over normal Go Micro RPC. |
| Durable agent runs | examples/agent-durable | Checkpoint and resume a model-directed run without replaying completed tool side effects. |
| Durable workflows | examples/flow-durable | Ordered, checkpointed flow steps resume without duplicating completed side effects. |
| AI-callable services | examples/mcp | MCP examples that expose service endpoints as model tools. |
Guide-to-example map
Repository examples
See the repository examples index
for the complete runnable list, including deployment, auth, gRPC interop, MCP,
agent, and flow examples.
More
1 - Learn by Example
A collection of small, focused examples demonstrating common patterns with Go Micro.
1.1 - Hello Service
A minimal HTTP service using Go Micro, with a single endpoint.
Service
package main
import (
"context"
"go-micro.dev/v6"
)
type Request struct { Name string `json:"name"` }
type Response struct { Message string `json:"message"` }
type Say struct{}
func (h *Say) Hello(ctx context.Context, req *Request, rsp *Response) error {
rsp.Message = "Hello " + req.Name
return nil
}
func main() {
svc := micro.NewService("helloworld")
svc.Init()
svc.Handle(new(Say))
svc.Run()
}
Run it:
Call it:
curl -XPOST \
-H 'Content-Type: application/json' \
-H 'Micro-Endpoint: Say.Hello' \
-d '{"name": "Alice"}' \
http://127.0.0.1:8080
Set a fixed address:
svc := micro.NewService("helloworld",
micro.Address(":8080"),
)
1.2 - Rpc Client
Call a running service using the Go Micro client.
package main
import (
"context"
"fmt"
"go-micro.dev/v6"
)
type Request struct { Name string }
type Response struct { Message string }
func main() {
svc := micro.NewService("caller")
svc.Init()
req := svc.Client().NewRequest("helloworld", "Say.Hello", &Request{Name: "John"})
var rsp Response
if err := svc.Client().Call(context.TODO(), req, &rsp); err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(rsp.Message)
}
1.3 - Service Discovery with Consul
Use Consul as the service registry.
In code
package main
import (
"go-micro.dev/v6"
"go-micro.dev/v6/registry/consul"
)
func main() {
reg := consul.NewConsulRegistry()
svc := micro.NewService("consul-registry", micro.Registry(reg))
svc.Init()
svc.Run()
}
Via environment
Run your service with env vars set:
MICRO_REGISTRY=consul MICRO_REGISTRY_ADDRESS=127.0.0.1:8500 go run main.go
1.4 - Store Postgres
Use the Postgres store for persistent key/value state.
In code
package main
import (
"log"
"go-micro.dev/v6"
"go-micro.dev/v6/store"
postgres "go-micro.dev/v6/store/postgres"
)
func main() {
st := postgres.NewStore()
svc := micro.NewService("postgres-store", micro.Store(st))
svc.Init()
_ = store.Write(&store.Record{Key: "foo", Value: []byte("bar")})
recs, _ := store.Read("foo")
log.Println("value:", string(recs[0].Value))
svc.Run()
}
Via environment
Run your service with env vars set:
MICRO_STORE=postgres \
MICRO_STORE_ADDRESS=postgres://user:pass@127.0.0.1:5432/postgres \
MICRO_STORE_DATABASE=micro \
MICRO_STORE_TABLE=micro \
go run main.go
1.5 - Transport Nats
Use NATS as the transport between services.
In code
package main
import (
"go-micro.dev/v6"
tnats "go-micro.dev/v6/transport/nats"
)
func main() {
t := tnats.NewTransport()
svc := micro.NewService("nats-transport", micro.Transport(t))
svc.Init()
svc.Run()
}
Via environment
Run your service with env vars set:
MICRO_TRANSPORT=nats MICRO_TRANSPORT_ADDRESS=nats://127.0.0.1:4222 go run main.go
2 - Real-World Examples
Production-ready patterns and complete application examples.
Real-World Examples
Production-ready patterns and complete application examples.
Available Examples
Coming Soon
We’re actively working on additional real-world examples. Contributions are welcome!
Complete Applications
- Event-Driven Microservices - Pub/sub patterns
- CQRS Pattern - Command Query Responsibility Segregation
- Saga Pattern - Distributed transactions
Production Patterns
- Health Checks and Readiness
- Retry and Circuit Breaking
- Distributed Tracing with OpenTelemetry
- Structured Logging
- Metrics and Monitoring
Testing Strategies
- Unit Testing Services
- Integration Testing
- Contract Testing
- Load Testing
Deployment
- Kubernetes Deployment
- Docker Compose Setup
- CI/CD Pipeline Examples
- Blue-Green Deployment
Integration Examples
- PostgreSQL with Transactions
- Redis Caching Strategies
- Message Queue Integration
- External API Integration
Each example will include:
- Complete, runnable code
- Configuration for development and production
- Testing approach
- Common pitfalls and solutions
Want to contribute? See our Contributing Guide.
2.1 - API Gateway with Backend Services
A complete example showing an API gateway routing to multiple backend microservices.
Architecture
┌─────────────┐
Client ───────>│ API Gateway │
└──────┬──────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────▼────┐ ┌────▼─────┐ ┌────▼─────┐
│ Users │ │ Orders │ │ Products │
│ Service │ │ Service │ │ Service │
└──────────┘ └──────────┘ └──────────┘
│ │ │
└──────────────┼──────────────┘
│
┌──────▼──────┐
│ PostgreSQL │
└─────────────┘
Services
1. Users Service
// services/users/main.go
package main
import (
"context"
"database/sql"
"go-micro.dev/v6"
"go-micro.dev/v6/server"
_ "github.com/lib/pq"
)
type User struct {
ID int64 `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
}
type UsersService struct {
db *sql.DB
}
type GetUserRequest struct {
ID int64 `json:"id"`
}
type GetUserResponse struct {
User *User `json:"user"`
}
func (s *UsersService) Get(ctx context.Context, req *GetUserRequest, rsp *GetUserResponse) error {
var u User
err := s.db.QueryRow("SELECT id, email, name FROM users WHERE id = $1", req.ID).
Scan(&u.ID, &u.Email, &u.Name)
if err != nil {
return err
}
rsp.User = &u
return nil
}
func main() {
db, err := sql.Open("postgres", "postgres://user:pass@localhost/users?sslmode=disable")
if err != nil {
panic(err)
}
defer db.Close()
svc := micro.NewService("users",
micro.Version("1.0.0"),
)
svc.Init()
server.RegisterHandler(svc.Server(), &UsersService{db: db})
if err := svc.Run(); err != nil {
panic(err)
}
}
2. Orders Service
// services/orders/main.go
package main
import (
"context"
"database/sql"
"time"
"go-micro.dev/v6"
"go-micro.dev/v6/client"
"go-micro.dev/v6/server"
)
type Order struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
ProductID int64 `json:"product_id"`
Amount float64 `json:"amount"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
}
type OrdersService struct {
db *sql.DB
client client.Client
}
type CreateOrderRequest struct {
UserID int64 `json:"user_id"`
ProductID int64 `json:"product_id"`
Amount float64 `json:"amount"`
}
type CreateOrderResponse struct {
Order *Order `json:"order"`
}
func (s *OrdersService) Create(ctx context.Context, req *CreateOrderRequest, rsp *CreateOrderResponse) error {
// Verify user exists
userReq := s.client.NewRequest("users", "UsersService.Get", &struct{ ID int64 }{ID: req.UserID})
userRsp := &struct{ User interface{} }{}
if err := s.client.Call(ctx, userReq, userRsp); err != nil {
return err
}
// Verify product exists
prodReq := s.client.NewRequest("products", "ProductsService.Get", &struct{ ID int64 }{ID: req.ProductID})
prodRsp := &struct{ Product interface{} }{}
if err := s.client.Call(ctx, prodReq, prodRsp); err != nil {
return err
}
// Create order
var o Order
err := s.db.QueryRow(`
INSERT INTO orders (user_id, product_id, amount, status, created_at)
VALUES ($1, $2, $3, $4, $5)
RETURNING id, user_id, product_id, amount, status, created_at
`, req.UserID, req.ProductID, req.Amount, "pending", time.Now()).
Scan(&o.ID, &o.UserID, &o.ProductID, &o.Amount, &o.Status, &o.CreatedAt)
if err != nil {
return err
}
rsp.Order = &o
return nil
}
func main() {
db, err := sql.Open("postgres", "postgres://user:pass@localhost/orders?sslmode=disable")
if err != nil {
panic(err)
}
defer db.Close()
svc := micro.NewService("orders",
micro.Version("1.0.0"),
)
svc.Init()
server.RegisterHandler(svc.Server(), &OrdersService{
db: db,
client: svc.Client(),
})
if err := svc.Run(); err != nil {
panic(err)
}
}
3. API Gateway
// gateway/main.go
package main
import (
"encoding/json"
"net/http"
"strconv"
"go-micro.dev/v6"
"go-micro.dev/v6/client"
)
type Gateway struct {
client client.Client
}
func (g *Gateway) GetUser(w http.ResponseWriter, r *http.Request) {
idStr := r.URL.Query().Get("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
http.Error(w, "invalid id", http.StatusBadRequest)
return
}
req := g.client.NewRequest("users", "UsersService.Get", &struct{ ID int64 }{ID: id})
rsp := &struct{ User interface{} }{}
if err := g.client.Call(r.Context(), req, rsp); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(rsp)
}
func (g *Gateway) CreateOrder(w http.ResponseWriter, r *http.Request) {
var body struct {
UserID int64 `json:"user_id"`
ProductID int64 `json:"product_id"`
Amount float64 `json:"amount"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, "invalid request", http.StatusBadRequest)
return
}
req := g.client.NewRequest("orders", "OrdersService.Create", body)
rsp := &struct{ Order interface{} }{}
if err := g.client.Call(r.Context(), req, rsp); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(rsp)
}
func main() {
svc := micro.NewService("api.gateway",
)
svc.Init()
gw := &Gateway{client: svc.Client()}
http.HandleFunc("/users", gw.GetUser)
http.HandleFunc("/orders", gw.CreateOrder)
http.ListenAndServe(":8080", nil)
}
Running the Example
Development (Local)
# Terminal 1: Users service
cd services/users
go run main.go
# Terminal 2: Products service
cd services/products
go run main.go
# Terminal 3: Orders service
cd services/orders
go run main.go
# Terminal 4: API Gateway
cd gateway
go run main.go
Testing
# Get user
curl http://localhost:8080/users?id=1
# Create order
curl -X POST http://localhost:8080/orders \
-H 'Content-Type: application/json' \
-d '{"user_id": 1, "product_id": 100, "amount": 99.99}'
Docker Compose
version: '3.8'
services:
postgres:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
users:
build: ./services/users
environment:
MICRO_REGISTRY: nats
MICRO_REGISTRY_ADDRESS: nats://nats:4222
DATABASE_URL: postgres://postgres:secret@postgres/users
depends_on:
- postgres
- nats
products:
build: ./services/products
environment:
MICRO_REGISTRY: nats
MICRO_REGISTRY_ADDRESS: nats://nats:4222
DATABASE_URL: postgres://postgres:secret@postgres/products
depends_on:
- postgres
- nats
orders:
build: ./services/orders
environment:
MICRO_REGISTRY: nats
MICRO_REGISTRY_ADDRESS: nats://nats:4222
DATABASE_URL: postgres://postgres:secret@postgres/orders
depends_on:
- postgres
- nats
gateway:
build: ./gateway
ports:
- "8080:8080"
environment:
MICRO_REGISTRY: nats
MICRO_REGISTRY_ADDRESS: nats://nats:4222
depends_on:
- users
- products
- orders
nats:
image: nats:latest
ports:
- "4222:4222"
Run with:
Key Patterns
- Service isolation: Each service owns its database
- Service communication: Via Go Micro client
- Gateway pattern: Single entry point for clients
- Error handling: Proper HTTP status codes
- Registry: mDNS for local, NATS for Docker
Production Considerations
- Add authentication/authorization
- Implement request tracing
- Add circuit breakers for service calls
- Use connection pooling
- Add rate limiting
- Implement proper logging
- Use health checks
- Add metrics collection
See Production Patterns for more details.
2.2 - Graceful Shutdown
Properly shutting down services to avoid dropped requests and data loss.
The Problem
Without graceful shutdown:
- In-flight requests are dropped
- Database connections leak
- Resources aren’t cleaned up
- Load balancers don’t know service is down
Solution
Go Micro handles SIGTERM/SIGINT by default, but you need to implement cleanup logic.
Basic Pattern
package main
import (
"context"
"os"
"os/signal"
"syscall"
"time"
"go-micro.dev/v6"
"go-micro.dev/v6/logger"
)
func main() {
svc := micro.NewService("myservice",
micro.BeforeStop(func() error {
logger.Info("Service stopping, running cleanup...")
return cleanup()
}),
)
svc.Init()
// Your service logic
if err := svc.Handle(new(Handler)); err != nil {
logger.Fatal(err)
}
// Run with graceful shutdown
if err := svc.Run(); err != nil {
logger.Fatal(err)
}
logger.Info("Service stopped gracefully")
}
func cleanup() error {
// Close database connections
// Flush logs
// Stop background workers
// etc.
return nil
}
Database Cleanup
type Service struct {
db *sql.DB
}
func (s *Service) Shutdown(ctx context.Context) error {
logger.Info("Closing database connections...")
// Stop accepting new requests
s.db.SetMaxOpenConns(0)
// Wait for existing connections to finish (with timeout)
done := make(chan struct{})
go func() {
s.db.Close()
close(done)
}()
select {
case <-done:
logger.Info("Database closed gracefully")
return nil
case <-ctx.Done():
logger.Warn("Database close timeout, forcing")
return ctx.Err()
}
}
Background Workers
type Worker struct {
quit chan struct{}
done chan struct{}
}
func (w *Worker) Start() {
w.quit = make(chan struct{})
w.done = make(chan struct{})
go func() {
defer close(w.done)
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
w.doWork()
case <-w.quit:
logger.Info("Worker stopping...")
return
}
}
}()
}
func (w *Worker) Stop(timeout time.Duration) error {
close(w.quit)
select {
case <-w.done:
logger.Info("Worker stopped gracefully")
return nil
case <-time.After(timeout):
return fmt.Errorf("worker shutdown timeout")
}
}
Complete Example
package main
import (
"context"
"database/sql"
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"time"
"go-micro.dev/v6"
"go-micro.dev/v6/logger"
)
type Application struct {
db *sql.DB
workers []*Worker
wg sync.WaitGroup
mu sync.RWMutex
closing bool
}
func NewApplication(db *sql.DB) *Application {
return &Application{
db: db,
workers: make([]*Worker, 0),
}
}
func (app *Application) AddWorker(w *Worker) {
app.workers = append(app.workers, w)
w.Start()
}
func (app *Application) Shutdown(ctx context.Context) error {
app.mu.Lock()
if app.closing {
app.mu.Unlock()
return nil
}
app.closing = true
app.mu.Unlock()
logger.Info("Starting graceful shutdown...")
// Stop accepting new work
logger.Info("Stopping workers...")
for _, w := range app.workers {
if err := w.Stop(5 * time.Second); err != nil {
logger.Warnf("Worker failed to stop: %v", err)
}
}
// Wait for in-flight requests (with timeout)
shutdownComplete := make(chan struct{})
go func() {
app.wg.Wait()
close(shutdownComplete)
}()
select {
case <-shutdownComplete:
logger.Info("All requests completed")
case <-ctx.Done():
logger.Warn("Shutdown timeout, forcing...")
}
// Close resources
logger.Info("Closing database...")
if err := app.db.Close(); err != nil {
logger.Errorf("Database close error: %v", err)
}
logger.Info("Shutdown complete")
return nil
}
func main() {
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
logger.Fatal(err)
}
app := NewApplication(db)
// Add background workers
app.AddWorker(&Worker{name: "cleanup"})
app.AddWorker(&Worker{name: "metrics"})
svc := micro.NewService("myservice",
micro.BeforeStop(func() error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
return app.Shutdown(ctx)
}),
)
svc.Init()
handler := &Handler{app: app}
if err := svc.Handle(handler); err != nil {
logger.Fatal(err)
}
// Run service
if err := svc.Run(); err != nil {
logger.Fatal(err)
}
}
Kubernetes Integration
Liveness and Readiness Probes
func (h *Handler) Health(ctx context.Context, req *struct{}, rsp *HealthResponse) error {
// Liveness: is the service alive?
rsp.Status = "ok"
return nil
}
func (h *Handler) Ready(ctx context.Context, req *struct{}, rsp *ReadyResponse) error {
h.app.mu.RLock()
closing := h.app.closing
h.app.mu.RUnlock()
if closing {
// Stop receiving traffic during shutdown
return fmt.Errorf("shutting down")
}
// Check dependencies
if err := h.app.db.Ping(); err != nil {
return fmt.Errorf("database unhealthy: %w", err)
}
rsp.Status = "ready"
return nil
}
Kubernetes Manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: myservice
spec:
replicas: 3
template:
spec:
containers:
- name: myservice
image: myservice:latest
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
lifecycle:
preStop:
exec:
# Give service time to drain before SIGTERM
command: ["/bin/sh", "-c", "sleep 10"]
terminationGracePeriodSeconds: 40
Best Practices
- Set timeouts: Don’t wait forever for shutdown
- Stop accepting work early: Set readiness to false
- Drain in-flight requests: Let current work finish
- Close resources properly: Databases, file handles, etc.
- Log shutdown progress: Help debugging
- Handle SIGTERM and SIGINT: Kubernetes sends SIGTERM
- Coordinate with load balancer: Use readiness probes
- Test shutdown: Regularly test graceful shutdown works
Testing Shutdown
# Start service
go run main.go &
PID=$!
# Send some requests
for i in {1..10}; do
curl http://localhost:8080/endpoint &
done
# Trigger graceful shutdown
kill -TERM $PID
# Verify all requests completed
wait
Common Pitfalls
- No timeout: Service hangs during shutdown
- Not stopping workers: Background jobs continue
- Database leaks: Connections not closed
- Ignored signals: Service killed forcefully
- No readiness probe: Traffic during shutdown
3 - Hello Service
A minimal HTTP service using Go Micro, with a single endpoint.
Service
package main
import (
"context"
"go-micro.dev/v6"
)
type Request struct { Name string `json:"name"` }
type Response struct { Message string `json:"message"` }
type Say struct{}
func (h *Say) Hello(ctx context.Context, req *Request, rsp *Response) error {
rsp.Message = "Hello " + req.Name
return nil
}
func main() {
svc := micro.NewService("helloworld")
svc.Init()
svc.Handle(new(Say))
svc.Run()
}
Run it:
Call it:
curl -XPOST \
-H 'Content-Type: application/json' \
-H 'Micro-Endpoint: Say.Hello' \
-d '{"name": "Alice"}' \
http://127.0.0.1:8080
Set a fixed address:
svc := micro.NewService("helloworld",
micro.Address(":8080"),
)
4 - NATS Transport
Use NATS as the transport between services.
In code
package main
import (
"go-micro.dev/v6"
tnats "go-micro.dev/v6/transport/nats"
)
func main() {
t := tnats.NewTransport()
svc := micro.NewService("nats-transport", micro.Transport(t))
svc.Init()
svc.Run()
}
Via environment
Run your service with env vars set:
MICRO_TRANSPORT=nats MICRO_TRANSPORT_ADDRESS=nats://127.0.0.1:4222 go run main.go
5 - Pub/Sub with NATS Broker
Use the NATS broker for pub/sub.
In code
package main
import (
"log"
"go-micro.dev/v6"
"go-micro.dev/v6/broker"
bnats "go-micro.dev/v6/broker/nats"
)
func main() {
b := bnats.NewNatsBroker()
svc := micro.NewService("nats-pubsub", micro.Broker(b))
svc.Init()
// subscribe
_, _ = broker.Subscribe("events", func(e broker.Event) error {
log.Printf("received: %s", string(e.Message().Body))
return nil
})
// publish
_ = broker.Publish("events", &broker.Message{Body: []byte("hello")})
svc.Run()
}
Via environment
Run your service with env vars set:
MICRO_BROKER=nats MICRO_BROKER_ADDRESS=nats://127.0.0.1:4222 go run main.go
6 - RPC Client
Call a running service using the Go Micro client.
package main
import (
"context"
"fmt"
"go-micro.dev/v6"
)
type Request struct { Name string }
type Response struct { Message string }
func main() {
svc := micro.NewService("caller")
svc.Init()
req := svc.Client().NewRequest("helloworld", "Say.Hello", &Request{Name: "John"})
var rsp Response
if err := svc.Client().Call(context.TODO(), req, &rsp); err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(rsp.Message)
}
7 - Service Discovery with Consul
Use Consul as the service registry.
In code
package main
import (
"go-micro.dev/v6"
"go-micro.dev/v6/registry/consul"
)
func main() {
reg := consul.NewConsulRegistry()
svc := micro.NewService("consul-registry", micro.Registry(reg))
svc.Init()
svc.Run()
}
Via environment
Run your service with env vars set:
MICRO_REGISTRY=consul MICRO_REGISTRY_ADDRESS=127.0.0.1:8500 go run main.go
8 - State with Postgres Store
Use the Postgres store for persistent key/value state.
In code
package main
import (
"log"
"go-micro.dev/v6"
"go-micro.dev/v6/store"
postgres "go-micro.dev/v6/store/postgres"
)
func main() {
st := postgres.NewStore()
svc := micro.NewService("postgres-store", micro.Store(st))
svc.Init()
_ = store.Write(&store.Record{Key: "foo", Value: []byte("bar")})
recs, _ := store.Read("foo")
log.Println("value:", string(recs[0].Value))
svc.Run()
}
Via environment
Run your service with env vars set:
MICRO_STORE=postgres \
MICRO_STORE_ADDRESS=postgres://user:pass@127.0.0.1:5432/postgres \
MICRO_STORE_DATABASE=micro \
MICRO_STORE_TABLE=micro \
go run main.go