Skip to content

How to Add an API Endpoint

Every new endpoint must support both HTML and JSON responses via the dual-frontend architecture.

Implementation pattern

func YourHandler(c *gin.Context) {
    isJSON := shouldReturnJSON(c)

    // Get authenticated user
    userInterface, ok := c.Get("User")
    if !ok {
        if isJSON {
            c.JSON(http.StatusNotFound, gin.H{
                "success": false,
                "error": gin.H{
                    "code":    "NOT_FOUND",
                    "message": "User not found in context",
                },
            })
        } else {
            c.HTML(http.StatusNotFound, "404.html", gin.H{
                "title": "Wippidu - requested data not found...",
            })
        }
        return
    }

    user := userInterface.(*model.User)

    // ... business logic ...

    // Success response
    if isJSON {
        c.JSON(http.StatusOK, gin.H{
            "success": true,
            "data": gin.H{
                "key": value,
            },
        })
        return
    }

    c.HTML(http.StatusOK, "template.html", gin.H{
        "title": "Page Title",
        "user":  user,
        "data":  data,
    })
}

Content negotiation

The shouldReturnJSON() helper (defined in internal/controller/child.go) determines the response format:

func shouldReturnJSON(c *gin.Context) bool {
    // Check if route starts with /api/
    if strings.HasPrefix(c.Request.URL.Path, "/api/") {
        return true
    }
    // Check Accept header
    if c.GetHeader("Accept") == "application/json" {
        return true
    }
    return false
}

Register routes

HTML routes

In internal/route/auth.go under MainRoutes():

func MainRoutes(r *gin.Engine) {
    // ...
    r.GET("/your-route/:id", controller.YourHandler)
}

JSON API routes

In internal/route/auth.go under APIRoutes():

func APIRoutes(r *gin.Engine) {
    api := r.Group("/api/v1")
    {
        // ...
        api.GET("/your-route/:id", controller.YourHandler)
    }
}

Use the same controller handler for both routes.

Required tests

Every endpoint needs at minimum:

JSON API tests

// Test successful JSON response via /api/v1/ route
func TestYourHandler_JSONAPI_Success_APIRoute(t *testing.T) { ... }

// Test JSON response via Accept header
func TestYourHandler_JSONAPI_Success_AcceptHeader(t *testing.T) { ... }

// Test error responses
func TestYourHandler_JSONAPI_NotFound(t *testing.T) { ... }
func TestYourHandler_JSONAPI_Unauthorized(t *testing.T) { ... }
func TestYourHandler_JSONAPI_InvalidInput(t *testing.T) { ... }

Database logic tests

func TestYourHandler_DatabaseLogic_ValidData(t *testing.T) { ... }

Test organization

Place tests in internal/controller/*_test.go. Group by function:

  • Test{Handler}_DatabaseLogic_* — Database/business logic
  • Test{Handler}_JSONAPI_* — JSON API HTTP-level
  • Test{Handler}_Authorization_* — Authorization checks

Reference implementations

Controller What to study
child.go Simple resource retrieval, authorization checks
auth.go Login with JWT, logout, error handling
home.go Handling unauthenticated users
notify.go Multiple functions, parameter handling

Checklist

  • [ ] Add isJSON := shouldReturnJSON(c) at function start
  • [ ] Implement JSON error responses for all error cases
  • [ ] Implement JSON success response with {"success": true, "data": {...}}
  • [ ] Implement HTML error responses (templates)
  • [ ] Implement HTML success response (templates)
  • [ ] Register route in MainRoutes() for HTML
  • [ ] Register route in APIRoutes() under /api/v1/ for JSON
  • [ ] Write JSON API tests (at least 4-5 tests covering success, errors, auth)
  • [ ] Write database logic tests
  • [ ] Run go test ./... and ensure 100% pass rate
  • [ ] Test manually with both browser and API client (curl/Postman)

Best practices

  1. Check authorization before resource existence — Return 403 even for non-existent resources to prevent information leakage
  2. Use consistent error codes — See API Response Formats
  3. Don't expose sensitive information in error messages — Generic messages like "Invalid email or password"
  4. Set cookies for both HTML and JSON — Enables session continuity
  5. Test both content negotiation methods — Both /api/v1/ routes and Accept header
  6. Maintain backward compatibility — Never break existing HTML routes