Skip to content
cccskills
FreePublished on GitHub

fiber-rest-api

Use when adding or changing a Go HTTP endpoint — Fiber v2 or v3 (detect which), thin handlers, central error mapping, boundary validation; the same rules apply to net/http, chi, echo or gin

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md5.7 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Go HTTP Endpoint Patterns

Overview

A handler is a translator: HTTP in → service call → HTTP out. Business logic never lives there. The recurring defects are fat handlers, per-handler error mapping that drifts, endpoints that quietly drop the auth middleware their neighbors have, and copying a value Fiber is about to reuse.

Core principle: handlers parse, validate, delegate, and map. Nothing else.

Detect the repo's router and framework version first

grep -E 'gofiber/fiber/v[23]|go-chi/chi|labstack/echo|gin-gonic/gin' go.mod

Never change the router or the Fiber major version in a feature task; a repo on Fiber v2 stays v2 (security-fix branch only). A brand-new repo with no router yet gets Fiber v3 (needs Go ≥1.25) unless the task says otherwise.

Fiber v2 → v3

v2v3
Handler signaturefunc(c *fiber.Ctx) errorfunc(c fiber.Ctx) error
Bodyc.BodyParser(&req)c.Bind().Body(&req)
Query / paramsc.QueryParser / c.ParamsParserc.Bind().Query / c.Bind().URI
Struct tag for path paramsparams:uri:
Typed query helperc.QueryInt("limit", 20)fiber.Query[int](c, "limit", 20)
Go-standard request contextc.UserContext()c.Context()
Raw fasthttp contextc.Context() (returns *fasthttp.RequestCtx)c.RequestCtx()
Request id from contextc.Locals("requestid")requestid.FromContext(c)
Static files / sub-app mountapp.Static(...) / app.Mount(...)static.New(...) / app.Use(...)

fiber migrate --to v3 exists for a dedicated upgrade task; never fold a framework-major bump into a feature task.

The v2 bug this corrects: on Fiber v2, c.Context() returns the pooled *fasthttp.RequestCtx, not the request-scoped context.Context — passing it where a context.Context is expected either fails to compile or silently drops whatever middleware put into the real context (auth claims, request id, trace span). Use c.UserContext() on v2.

Rules

  • Thin handlers: parse/validate input → call the application service → map result to response DTO. No business logic, no SQL, no cross-entity rules in the handler.
  • Explicit DTO structs aligned with domain types — never map[string]any.
  • Validate and bound all input at the boundary: lengths, ranges, enum values, UUID parsing (go-playground/validator v10 if the repo already uses it; v3 has a StructValidator hook). Reject early with 4xx and a field-specific message in the repo's one error shape (see api-design-conventions).
  • Map domain errors to status in ONE place (a shared ErrorHandler / mapper), not per handler, so the mapping can't drift. Show it mapping errors.Is/errors.As against domain sentinel errors to status + the error body.
  • New endpoints get the SAME middleware chain (auth, logging, request-id) as their neighbors — copy the adjacent route's wiring, don't invent one.
  • Zero-allocation rule (Fiber-specific): values Fiber hands you from c.Params/c.Query/c.Body/c.Get are only valid for the lifetime of the handler call — Fiber reuses the underlying buffer across requests. strings.Clone (or otherwise copy) anything you store in a struct that outlives the request, send to a goroutine, or put in a cache, unless the app is configured with Immutable: true.
  • Bound the request body: fiber.Config{BodyLimit: ...} (v2/v3 default 4 MB) or http.MaxBytesReader on net/http — don't rely on the default alone for upload endpoints.

net/http (Go ≥1.22, when the repo has no router)

mux.HandleFunc("POST /tasks/{id}/children", h.CreateChild)
id := r.PathValue("id")
r.Body = http.MaxBytesReader(w, r.Body, maxBody)

Worked Example

func (h *TaskHandler) Create(c fiber.Ctx) error {
    var req createTaskRequest
    if err := c.Bind().Body(&req); err != nil {
        return fiber.NewError(fiber.StatusBadRequest, "invalid body")
    }
    if err := req.validate(); err != nil {
        return fiber.NewError(fiber.StatusBadRequest, err.Error())
    }
    task, err := h.svc.Create(c.Context(), req.Title)
    if err != nil {
        return err
    }
    return c.Status(fiber.StatusCreated).JSON(taskResponseFrom(task))
}

The title-length rule lives in the service/domain, not the handler. The central error middleware maps domain.ErrInvalidTitle → 400, domain.ErrConflict → 409 — every handler benefits.

Handler test in-process (no network)

req := httptest.NewRequest(http.MethodPost, "/tasks", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)       // v2: app.Test(req, -1)
// v3:  app.Test(req, fiber.TestConfig{Timeout: 0})

Assert status code and decoded body — this is still a unit test; it does not replace running the real service once (backend-runtime-check).

Common Mistakes

  • Business logic or SQL in the handler.
  • map[string]any request/response instead of DTOs.
  • Each handler mapping its own errors to codes → drift.
  • A new route missing the auth middleware its neighbors have.
  • 500 for a validation failure.
  • Storing a value read from c.Params/c.Query/c.Body past the handler without copying it first.
  • Passing v2's c.Context() where the request's context.Context is needed.

Red Flags

  • A handler longer than ~30 lines.
  • pgx/repository calls inside a handler.
  • A route registered without the neighbor's middleware chain.
  • A struct field or goroutine capturing a byte slice straight from c.Body()/c.Params().

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

See all skills by makifbaysal

Report a problem with this skill