$ npx rulesync-cli pull✓ Wrote CLAUDE.md (2 rulesets)# Coding Standards- Always use async/await- Prefer named exports
Rule Writing

CLAUDE.md for Fiber (Go) Web Framework

Fiber's Express-like API is designed for Go developers coming from Node.js — but AI treats it identically to Express. Rules for Fiber's fasthttp, ctx, validators, and Go-native patterns.

7 min read·January 9, 2026

Fiber looks like Express but runs on fasthttp — AI misses the memory model

Copy before storing, BodyParser validation, error returns, and route groups

Why Fiber Needs Rules Different from Gin and Express

Fiber is built on fasthttp — not net/http. This matters because fasthttp reuses request/response objects across requests for performance. Values extracted from c.Body(), c.Params(), and c.Query() are only valid during the handler's lifetime. AI treats Fiber's c (fiber.Ctx) like Express's req/res or Gin's gin.Context, storing references to request data that become invalid after the handler returns.

Fiber's API is intentionally Express-like (app.Get, app.Post, app.Use, app.Group) to be familiar to Node.js developers transitioning to Go. But the underlying runtime is fundamentally different: fasthttp's zero-allocation design means you can't hold onto request data. This is Fiber's biggest gotcha — and the one AI misses every time.

These rules layer on top of Go language rules and complement the Gin rules. If your team is choosing between Gin and Fiber, these rules highlight the key differences.

Rule 1: fasthttp Memory Model — Copy Before Storing

The rule: 'Fiber uses fasthttp, which reuses request and response objects. Values from c.Body(), c.Params(), c.Query(), and c.Get() are only valid during the current handler execution. If you need to pass request data to a goroutine, background job, or store it: copy it first with utils.CopyString() or string(c.Body()) to create a new allocation. Never pass c itself to a goroutine.'

For common patterns: 'Copy body before passing to a service: body := string(c.Body()). Copy params: id := utils.CopyString(c.Params("id")). Copy headers: token := utils.CopyString(c.Get("Authorization")). This is the most critical Fiber rule — violations cause race conditions and corrupted data that only appear under load.'

AI generates code that stores c.Params("id") in a struct or passes c.Body() to a goroutine — both are use-after-free bugs in fasthttp. The data is valid during the handler but may be overwritten by the next request. Always copy.

  • c.Body(), c.Params(), c.Query() are only valid during the handler
  • Copy before storing: string(c.Body()), utils.CopyString(c.Params('id'))
  • Never pass fiber.Ctx to goroutines — extract and copy data first
  • Race conditions only appear under load — test with concurrent requests
  • This is fasthttp's design, not a bug — zero-allocation requires discipline
⚠️ Copy Before Storing

fasthttp reuses request objects across requests. c.Body(), c.Params(), c.Query() are invalid after the handler returns. Always copy: string(c.Body()), utils.CopyString(c.Params('id')). Violations only appear under load — test concurrently.

Rule 2: BodyParser and Validation

The rule: 'Use c.BodyParser(&struct) for request body parsing: type CreateUserRequest struct { Name string `json:"name" validate:"required,min=1"` Email string `json:"email" validate:"required,email"` }. Combine with go-playground/validator for validation: validate.Struct(req). Create a reusable validate middleware that parses and validates in one step. Never manually parse JSON from c.Body().'

For query and params: 'Use c.QueryParser(&struct) for typed query parameters. Use c.ParamsParser(&struct) for typed path parameters. Both work like BodyParser — define a struct with tags and parse into it. This gives you typed access and validation in one call.'

For file uploads: 'Use c.FormFile("file") for single files, c.MultipartForm() for multiple files. Validate file size and MIME type before processing. Save to a temporary location with c.SaveFile(). Never trust the client's filename — generate a safe filename server-side.'

Rule 3: Route Groups and Middleware

The rule: 'Use route groups for organization and scoped middleware: api := app.Group("/api", authMiddleware). Nest groups: v1 := api.Group("/v1"); users := v1.Group("/users"). Apply middleware to groups: admin.Use(requireAdmin). Organize handlers in separate files: routes/users.go registers user routes on a group.'

For middleware: 'Fiber middleware uses func(c *fiber.Ctx) error. Call c.Next() to continue the chain. Return c.Status(401).JSON(...) to short-circuit. Use built-in middleware: logger, recover, cors, limiter, helmet, compress. Custom middleware pattern: check condition → if fail, return error response → if pass, return c.Next().'

For error handling: 'Configure a custom error handler: app := fiber.New(fiber.Config{ ErrorHandler: customErrorHandler }). The error handler receives all errors from handlers and middleware. Map error types to HTTP responses: *fiber.Error → use its status code, AppError → use its status code, other → 500. Log errors with context: request ID, path, method.'

  • app.Group for route organization — nested groups for versioning
  • Group-level middleware: api.Use(auth) — applies to all routes in group
  • Built-in: logger, recover, cors, limiter, helmet, compress
  • Custom: check → fail → return error | pass → return c.Next()
  • ErrorHandler in fiber.Config — centralized, consistent error responses
💡 Return Errors, Not Just Responses

Fiber handlers return error — unlike Gin where handlers return nothing. return fiber.NewError(404, 'not found') propagates to the centralized ErrorHandler. return nil after c.JSON(data) on success. Don't ignore the return.

Rule 4: Fiber-Specific Patterns (vs Gin)

The rule: 'Fiber returns errors from handlers: func(c *fiber.Ctx) error — unlike Gin where handlers return nothing and call c.JSON directly. Return errors to propagate to the error handler: return fiber.NewError(404, "User not found"). Return nil on success after sending a response: return c.JSON(user). Never ignore the error return — it's Fiber's error propagation mechanism.'

For response: 'Use c.JSON(data) for JSON, c.SendString(text) for text, c.Render(template, data) for templates, c.Redirect(path) for redirects. Set status before body: c.Status(201).JSON(user). Use c.SendStatus(204) for no-content responses. Fiber's response methods are chainable: c.Status(201).JSON(created).'

For performance: 'Fiber is built for performance — don't add overhead unnecessarily. Use c.Locals("key", value) for request-scoped data (like Gin's c.Set). Pre-compile routes at startup — Fiber's router is fastest in Go benchmarks. Use Prefork for multi-process scaling on multi-core machines: app := fiber.New(fiber.Config{Prefork: true}).'

Rule 5: Testing Fiber Applications

The rule: 'Use app.Test(req) for handler testing without starting a server: req := httptest.NewRequest("GET", "/users/1", nil); resp, err := app.Test(req). This is Fiber's equivalent of Fastify's inject() — fast, no port binding, works in any test runner. Parse the response body manually: json.NewDecoder(resp.Body).Decode(&result).'

For integration tests: 'Build the full app in test setup, then use app.Test for each request. Override services with test doubles using dependency injection in the route registration. Test middleware by applying it to a test route and verifying behavior.'

For benchmarks: 'Use Go's built-in benchmark framework: func BenchmarkGetUser(b *testing.B). Use app.Test in benchmarks — no network overhead. Compare Fiber handler performance to identify bottlenecks. Profile with pprof for CPU and memory analysis.'

ℹ️ app.Test() = No Server

app.Test(httptest.NewRequest(...)) tests your handler without starting a server or binding a port. Fast, isolated, works in any test runner. Fiber's best testing DX feature.

Complete Fiber Rules Template

Consolidated rules for Fiber Go framework projects (use alongside Go language rules).

  • fasthttp: copy c.Body(), c.Params(), c.Query() before storing — never pass c to goroutines
  • c.BodyParser(&struct) for parsing + go-playground/validator for validation
  • Route groups: app.Group with middleware — handlers in separate files
  • Return errors from handlers: return fiber.NewError(404, 'message') — nil on success
  • ErrorHandler in fiber.Config — centralized, maps error types to HTTP responses
  • c.JSON, c.Status().JSON, c.SendStatus — chainable response methods
  • app.Test(req) for testing — no server needed — fast, isolated
  • Prefork for multi-core — c.Locals for request-scoped data — built-in middleware