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:
| Layer | What to Update | Location |
|---|---|---|
| 1. Service | HTTP handler + route | services/<service-name>/ |
| 2. API Gateway | Route configuration | services/api-gateway/config/routes.yaml |
| 3. Types Package | TypeScript types (if applicable) | eloquent-packages/packages/<pkg>/ |
| 4. Frontend | API 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
- API Gateway - Gateway configuration details
- Dev Environment - Docker development workflow
- Type System - TypeScript generation from Go
- Error Handling - Unified error patterns