go-micro.dev/v5@v5.12.0/internal/website/docs/examples/realworld/api-gateway.md (about)

     1  ---
     2  layout: default
     3  ---
     4  
     5  # API Gateway with Backend Services
     6  
     7  A complete example showing an API gateway routing to multiple backend microservices.
     8  
     9  ## Architecture
    10  
    11  ```
    12                    ┌─────────────┐
    13     Client ───────>│ API Gateway │
    14                    └──────┬──────┘
    15                           │
    16            ┌──────────────┼──────────────┐
    17            │              │              │
    18      ┌─────▼────┐   ┌────▼─────┐  ┌────▼─────┐
    19      │  Users   │   │  Orders  │  │ Products │
    20      │ Service  │   │ Service  │  │ Service  │
    21      └──────────┘   └──────────┘  └──────────┘
    22            │              │              │
    23            └──────────────┼──────────────┘
    24                           │
    25                    ┌──────▼──────┐
    26                    │  PostgreSQL │
    27                    └─────────────┘
    28  ```
    29  
    30  ## Services
    31  
    32  ### 1. Users Service
    33  
    34  ```go
    35  // services/users/main.go
    36  package main
    37  
    38  import (
    39      "context"
    40      "database/sql"
    41      "go-micro.dev/v5"
    42      "go-micro.dev/v5/server"
    43      _ "github.com/lib/pq"
    44  )
    45  
    46  type User struct {
    47      ID    int64  `json:"id"`
    48      Email string `json:"email"`
    49      Name  string `json:"name"`
    50  }
    51  
    52  type UsersService struct {
    53      db *sql.DB
    54  }
    55  
    56  type GetUserRequest struct {
    57      ID int64 `json:"id"`
    58  }
    59  
    60  type GetUserResponse struct {
    61      User *User `json:"user"`
    62  }
    63  
    64  func (s *UsersService) Get(ctx context.Context, req *GetUserRequest, rsp *GetUserResponse) error {
    65      var u User
    66      err := s.db.QueryRow("SELECT id, email, name FROM users WHERE id = $1", req.ID).
    67          Scan(&u.ID, &u.Email, &u.Name)
    68      if err != nil {
    69          return err
    70      }
    71      rsp.User = &u
    72      return nil
    73  }
    74  
    75  func main() {
    76      db, err := sql.Open("postgres", "postgres://user:pass@localhost/users?sslmode=disable")
    77      if err != nil {
    78          panic(err)
    79      }
    80      defer db.Close()
    81  
    82      svc := micro.NewService(
    83          micro.Name("users"),
    84          micro.Version("1.0.0"),
    85      )
    86  
    87      svc.Init()
    88  
    89      server.RegisterHandler(svc.Server(), &UsersService{db: db})
    90  
    91      if err := svc.Run(); err != nil {
    92          panic(err)
    93      }
    94  }
    95  ```
    96  
    97  ### 2. Orders Service
    98  
    99  ```go
   100  // services/orders/main.go
   101  package main
   102  
   103  import (
   104      "context"
   105      "database/sql"
   106      "time"
   107      "go-micro.dev/v5"
   108      "go-micro.dev/v5/client"
   109      "go-micro.dev/v5/server"
   110  )
   111  
   112  type Order struct {
   113      ID        int64     `json:"id"`
   114      UserID    int64     `json:"user_id"`
   115      ProductID int64     `json:"product_id"`
   116      Amount    float64   `json:"amount"`
   117      Status    string    `json:"status"`
   118      CreatedAt time.Time `json:"created_at"`
   119  }
   120  
   121  type OrdersService struct {
   122      db     *sql.DB
   123      client client.Client
   124  }
   125  
   126  type CreateOrderRequest struct {
   127      UserID    int64   `json:"user_id"`
   128      ProductID int64   `json:"product_id"`
   129      Amount    float64 `json:"amount"`
   130  }
   131  
   132  type CreateOrderResponse struct {
   133      Order *Order `json:"order"`
   134  }
   135  
   136  func (s *OrdersService) Create(ctx context.Context, req *CreateOrderRequest, rsp *CreateOrderResponse) error {
   137      // Verify user exists
   138      userReq := s.client.NewRequest("users", "UsersService.Get", &struct{ ID int64 }{ID: req.UserID})
   139      userRsp := &struct{ User interface{} }{}
   140      if err := s.client.Call(ctx, userReq, userRsp); err != nil {
   141          return err
   142      }
   143  
   144      // Verify product exists
   145      prodReq := s.client.NewRequest("products", "ProductsService.Get", &struct{ ID int64 }{ID: req.ProductID})
   146      prodRsp := &struct{ Product interface{} }{}
   147      if err := s.client.Call(ctx, prodReq, prodRsp); err != nil {
   148          return err
   149      }
   150  
   151      // Create order
   152      var o Order
   153      err := s.db.QueryRow(`
   154          INSERT INTO orders (user_id, product_id, amount, status, created_at)
   155          VALUES ($1, $2, $3, $4, $5)
   156          RETURNING id, user_id, product_id, amount, status, created_at
   157      `, req.UserID, req.ProductID, req.Amount, "pending", time.Now()).
   158          Scan(&o.ID, &o.UserID, &o.ProductID, &o.Amount, &o.Status, &o.CreatedAt)
   159  
   160      if err != nil {
   161          return err
   162      }
   163  
   164      rsp.Order = &o
   165      return nil
   166  }
   167  
   168  func main() {
   169      db, err := sql.Open("postgres", "postgres://user:pass@localhost/orders?sslmode=disable")
   170      if err != nil {
   171          panic(err)
   172      }
   173      defer db.Close()
   174  
   175      svc := micro.NewService(
   176          micro.Name("orders"),
   177          micro.Version("1.0.0"),
   178      )
   179  
   180      svc.Init()
   181  
   182      server.RegisterHandler(svc.Server(), &OrdersService{
   183          db:     db,
   184          client: svc.Client(),
   185      })
   186  
   187      if err := svc.Run(); err != nil {
   188          panic(err)
   189      }
   190  }
   191  ```
   192  
   193  ### 3. API Gateway
   194  
   195  ```go
   196  // gateway/main.go
   197  package main
   198  
   199  import (
   200      "encoding/json"
   201      "net/http"
   202      "strconv"
   203      "go-micro.dev/v5"
   204      "go-micro.dev/v5/client"
   205  )
   206  
   207  type Gateway struct {
   208      client client.Client
   209  }
   210  
   211  func (g *Gateway) GetUser(w http.ResponseWriter, r *http.Request) {
   212      idStr := r.URL.Query().Get("id")
   213      id, err := strconv.ParseInt(idStr, 10, 64)
   214      if err != nil {
   215          http.Error(w, "invalid id", http.StatusBadRequest)
   216          return
   217      }
   218  
   219      req := g.client.NewRequest("users", "UsersService.Get", &struct{ ID int64 }{ID: id})
   220      rsp := &struct{ User interface{} }{}
   221  
   222      if err := g.client.Call(r.Context(), req, rsp); err != nil {
   223          http.Error(w, err.Error(), http.StatusInternalServerError)
   224          return
   225      }
   226  
   227      w.Header().Set("Content-Type", "application/json")
   228      json.NewEncoder(w).Encode(rsp)
   229  }
   230  
   231  func (g *Gateway) CreateOrder(w http.ResponseWriter, r *http.Request) {
   232      var body struct {
   233          UserID    int64   `json:"user_id"`
   234          ProductID int64   `json:"product_id"`
   235          Amount    float64 `json:"amount"`
   236      }
   237  
   238      if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
   239          http.Error(w, "invalid request", http.StatusBadRequest)
   240          return
   241      }
   242  
   243      req := g.client.NewRequest("orders", "OrdersService.Create", body)
   244      rsp := &struct{ Order interface{} }{}
   245  
   246      if err := g.client.Call(r.Context(), req, rsp); err != nil {
   247          http.Error(w, err.Error(), http.StatusInternalServerError)
   248          return
   249      }
   250  
   251      w.Header().Set("Content-Type", "application/json")
   252      w.WriteHeader(http.StatusCreated)
   253      json.NewEncoder(w).Encode(rsp)
   254  }
   255  
   256  func main() {
   257      svc := micro.NewService(
   258          micro.Name("api.gateway"),
   259      )
   260      svc.Init()
   261  
   262      gw := &Gateway{client: svc.Client()}
   263  
   264      http.HandleFunc("/users", gw.GetUser)
   265      http.HandleFunc("/orders", gw.CreateOrder)
   266  
   267      http.ListenAndServe(":8080", nil)
   268  }
   269  ```
   270  
   271  ## Running the Example
   272  
   273  ### Development (Local)
   274  
   275  ```bash
   276  # Terminal 1: Users service
   277  cd services/users
   278  go run main.go
   279  
   280  # Terminal 2: Products service
   281  cd services/products
   282  go run main.go
   283  
   284  # Terminal 3: Orders service
   285  cd services/orders
   286  go run main.go
   287  
   288  # Terminal 4: API Gateway
   289  cd gateway
   290  go run main.go
   291  ```
   292  
   293  ### Testing
   294  
   295  ```bash
   296  # Get user
   297  curl http://localhost:8080/users?id=1
   298  
   299  # Create order
   300  curl -X POST http://localhost:8080/orders \
   301    -H 'Content-Type: application/json' \
   302    -d '{"user_id": 1, "product_id": 100, "amount": 99.99}'
   303  ```
   304  
   305  ### Docker Compose
   306  
   307  ```yaml
   308  version: '3.8'
   309  
   310  services:
   311    postgres:
   312      image: postgres:15
   313      environment:
   314        POSTGRES_PASSWORD: secret
   315      ports:
   316        - "5432:5432"
   317  
   318    users:
   319      build: ./services/users
   320      environment:
   321        MICRO_REGISTRY: nats
   322        MICRO_REGISTRY_ADDRESS: nats://nats:4222
   323        DATABASE_URL: postgres://postgres:secret@postgres/users
   324      depends_on:
   325        - postgres
   326        - nats
   327  
   328    products:
   329      build: ./services/products
   330      environment:
   331        MICRO_REGISTRY: nats
   332        MICRO_REGISTRY_ADDRESS: nats://nats:4222
   333        DATABASE_URL: postgres://postgres:secret@postgres/products
   334      depends_on:
   335        - postgres
   336        - nats
   337  
   338    orders:
   339      build: ./services/orders
   340      environment:
   341        MICRO_REGISTRY: nats
   342        MICRO_REGISTRY_ADDRESS: nats://nats:4222
   343        DATABASE_URL: postgres://postgres:secret@postgres/orders
   344      depends_on:
   345        - postgres
   346        - nats
   347  
   348    gateway:
   349      build: ./gateway
   350      ports:
   351        - "8080:8080"
   352      environment:
   353        MICRO_REGISTRY: nats
   354        MICRO_REGISTRY_ADDRESS: nats://nats:4222
   355      depends_on:
   356        - users
   357        - products
   358        - orders
   359  
   360    nats:
   361      image: nats:latest
   362      ports:
   363        - "4222:4222"
   364  ```
   365  
   366  Run with:
   367  ```bash
   368  docker-compose up
   369  ```
   370  
   371  ## Key Patterns
   372  
   373  1. **Service isolation**: Each service owns its database
   374  2. **Service communication**: Via Go Micro client
   375  3. **Gateway pattern**: Single entry point for clients
   376  4. **Error handling**: Proper HTTP status codes
   377  5. **Registry**: mDNS for local, NATS for Docker
   378  
   379  ## Production Considerations
   380  
   381  - Add authentication/authorization
   382  - Implement request tracing
   383  - Add circuit breakers for service calls
   384  - Use connection pooling
   385  - Add rate limiting
   386  - Implement proper logging
   387  - Use health checks
   388  - Add metrics collection
   389  
   390  See [Production Patterns](../realworld/) for more details.