Eloquent

Documentation

Backend Services

This guide covers the workflow for modifying Go backend services in the Eloquent platform. Any functionality added to a service requires updates across multiple layers.

Required Changes Checklist

When adding or modifying backend functionality, you MUST update all of these:

LayerWhat to UpdateLocation
1. ServiceHTTP handler + routeservices/<service-name>/
2. API GatewayRoute configurationservices/api-gateway/config/routes.yaml
3. Types PackageTypeScript types (if applicable)eloquent-packages/packages/<pkg>/
4. FrontendAPI client functions (if applicable)@elqnt/api-client or app-specific

Service Layer Composition

Backend services follow a layered composition pattern:

main.go
   │
   ├── Create Database/Redis connections
   │
   ├── Create Repositories (depend on DB)
   │      entityRepo := repositories.NewEntityRepository(db)
   │
   ├── Create Services (depend on Repos)
   │      entityService := services.NewEntityService(entityRepo)
   │
   ├── Create Handlers (depend on Services)
   │      entityHandler := handlers.NewEntityHandler(entityService)
   │
   └── Wire Routes (depend on Handlers)
          routes.Setup(mux, entityHandler)

Adding a New Endpoint

Step 1: Define Models

// models/models.go
package models

type CreateWidgetRequest struct {
    Name        string `json:"name" validate:"required"`
    Description string `json:"description"`
    Config      any    `json:"config"`
}

type Widget struct {
    ID          string `json:"id"`
    Name        string `json:"name"`
    Description string `json:"description"`
    Config      any    `json:"config"`
    CreatedAt   int64  `json:"createdAt"`
}

Step 2: Create Handler

// handlers/widget.go
package handlers

type WidgetHandler struct {
    widgetService *services.WidgetService
}

func NewWidgetHandler(ws *services.WidgetService) *WidgetHandler {
    return &WidgetHandler{widgetService: ws}
}

func (h *WidgetHandler) CreateWidget(w http.ResponseWriter, r *http.Request) {
    // 1. Extract org/user from headers (set by API Gateway)
    orgID := r.Header.Get("X-Org-ID")
    userID := r.Header.Get("X-User-ID")

    if orgID == "" {
        writeError(w, http.StatusBadRequest, "MISSING_ORG_ID", "X-Org-ID header required")
        return
    }

    // 2. Parse request body
    var req models.CreateWidgetRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        writeError(w, http.StatusBadRequest, "INVALID_JSON", err.Error())
        return
    }

    // 3. Call service layer
    widget, err := h.widgetService.Create(r.Context(), orgID, userID, req)
    if err != nil {
        writeError(w, http.StatusInternalServerError, "CREATE_FAILED", err.Error())
        return
    }

    // 4. Return response
    writeJSON(w, http.StatusCreated, widget)
}

Step 3: Register Routes

// routes/routes.go
package routes

func SetupRoutes(mux *http.ServeMux, widgetHandler *handlers.WidgetHandler) {
    mux.HandleFunc("GET /widgets", widgetHandler.ListWidgets)
    mux.HandleFunc("POST /widgets", widgetHandler.CreateWidget)
    mux.HandleFunc("GET /widgets/{id}", widgetHandler.GetWidget)
    mux.HandleFunc("PUT /widgets/{id}", widgetHandler.UpdateWidget)
    mux.HandleFunc("DELETE /widgets/{id}", widgetHandler.DeleteWidget)
}

Step 4: Add to API Gateway

# services/api-gateway/config/routes.yaml
routes:
  - path: /api/v1/widgets
    method: GET
    proxy_upstream: $WIDGET_SERVICE_URL
    auth_required: true
    cache_ttl: 60s

  - path: /api/v1/widgets
    method: POST
    proxy_upstream: $WIDGET_SERVICE_URL
    auth_required: true
    scopes:
      - write:widgets

  - path: /api/v1/widgets/{id}
    method: PUT
    proxy_upstream: $WIDGET_SERVICE_URL
    auth_required: true
    scopes:
      - write:widgets

Step 5: Generate TypeScript Types

cd /Users/ahmed/workspace/eloquent/eloquent
tygo generate

Step 6: Deploy

make docker-rebuild-{service}
make docker-rebuild-api-gateway  # If routes changed

Common Patterns

Path Parameters

// Handler
id := r.PathValue("id")  // Go 1.22+ pattern matching

// Route registration
mux.HandleFunc("GET /widgets/{id}", handler.GetWidget)

// Gateway config
path: /api/v1/widgets/{id}

Query Parameters

page := r.URL.Query().Get("page")
limit := r.URL.Query().Get("limit")

Request Validation

import "github.com/go-playground/validator/v10"

var validate = validator.New()

func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
    var req CreateRequest
    json.NewDecoder(r.Body).Decode(&req)

    if err := validate.Struct(req); err != nil {
        writeError(w, http.StatusBadRequest, "VALIDATION_ERROR", err.Error())
        return
    }
    // ...
}

Testing

Local Testing with curl

# Get JWT token (copy from browser dev tools or generate)
TOKEN="eyJhbGciOiJIUzI1NiIs..."

# Test endpoint
curl -X POST http://localhost:7800/api/v1/widgets \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Org-ID: your-org-id" \
  -d '{"name": "Test Widget", "description": "A test"}'

Check Gateway Logs

docker logs -f api-gateway

Next Steps