

Also from Kynth Studios


Also from Kynth Studios


Also from Kynth Studios
1# Go API Development — Cursor Rules2# Comprehensive rules for building APIs in Go34## Project Context5You are working on a Go API application. The codebase follows Go idioms and conventions,6uses the standard library where practical, and emphasizes simplicity, explicit error7handling, and strong typing. The project uses Go modules for dependency management.89## Tech Stack10- Go 1.22+ (latest stable)11- Standard library `net/http` with `http.ServeMux` (Go 1.22+) or chi/gorilla for routing12- `database/sql` with pgx driver for PostgreSQL13- `encoding/json` for JSON marshaling14- `slog` for structured logging (Go 1.21+)15- `testing` package with table-driven tests16- Docker for containerization1718## Coding Style1920### Naming Conventions21- Packages: short, lowercase, single word (e.g., `user`, `order`, `auth`)22- Exported types: PascalCase (e.g., `UserService`, `OrderHandler`)23- Unexported: camelCase (e.g., `validateEmail`, `parseToken`)24- Interfaces: describe behavior, often `-er` suffix (e.g., `Reader`, `UserStore`, `Authenticator`)25- Errors: `Err` prefix for sentinel errors (e.g., `ErrNotFound`, `ErrUnauthorized`)26- Constructors: `New` prefix (e.g., `NewUserService`, `NewRouter`)27- HTTP handlers: verb-noun (e.g., `HandleCreateUser`, `HandleListOrders`)28- Files: snake_case (e.g., `user_handler.go`, `order_service.go`)29- Test files: `*_test.go` adjacent to the code being tested30- Acronyms: all caps (e.g., `userID`, `httpClient`, `parseURL`)3132### Project Structure33```34cmd/35 api/36 main.go # Entry point37internal/38 config/ # Configuration loading39 config.go40 handler/ # HTTP handlers (transport layer)41 user.go42 order.go43 middleware.go44 service/ # Business logic45 user.go46 order.go47 store/ # Data access (repository pattern)48 user.go49 order.go50 postgres.go # Database connection setup51 model/ # Domain types52 user.go53 order.go54 middleware/ # HTTP middleware55 auth.go56 logging.go57 recovery.go58pkg/ # Public, reusable packages (if any)59migrations/ # SQL migration files60```6162## Go Idioms6364### Error Handling65```go66// Always handle errors explicitly. Never use _ for errors.67user, err := s.store.GetUser(ctx, id)68if err != nil {69 if errors.Is(err, store.ErrNotFound) {70 return nil, ErrUserNotFound71 }72 return nil, fmt.Errorf("get user %d: %w", id, err)73}7475// Define sentinel errors for expected conditions76var (77 ErrNotFound = errors.New("not found")78 ErrUnauthorized = errors.New("unauthorized")79 ErrConflict = errors.New("conflict")80)8182// Wrap errors with context using %w for unwrapping83func (s *UserService) Delete(ctx context.Context, id int64) error {84 if err := s.store.DeleteUser(ctx, id); err != nil {85 return fmt.Errorf("delete user %d: %w", id, err)86 }87 return nil88}89```9091### Interface Design92```go93// Define interfaces where they are USED, not where they are implemented.94// Keep interfaces small — one or two methods.9596// In handler package:97type UserService interface {98 GetUser(ctx context.Context, id int64) (*model.User, error)99 CreateUser(ctx context.Context, input model.CreateUserInput) (*model.User, error)100 ListUsers(ctx context.Context, opts model.ListOptions) ([]model.User, error)101}102103// In service package:104type UserStore interface {105 GetUser(ctx context.Context, id int64) (*model.User, error)106 InsertUser(ctx context.Context, u *model.User) error107 ListUsers(ctx context.Context, opts model.ListOptions) ([]model.User, int, error)108}109```110111### HTTP Handler Pattern112```go113type UserHandler struct {114 svc UserService115 logger *slog.Logger116}117118func NewUserHandler(svc UserService, logger *slog.Logger) *UserHandler {119 return &UserHandler{svc: svc, logger: logger}120}121122func (h *UserHandler) HandleGetUser(w http.ResponseWriter, r *http.Request) {123 id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)124 if err != nil {125 writeError(w, http.StatusBadRequest, "invalid user ID")126 return127 }128129 user, err := h.svc.GetUser(r.Context(), id)130 if err != nil {131 switch {132 case errors.Is(err, service.ErrUserNotFound):133 writeError(w, http.StatusNotFound, "user not found")134 default:135 h.logger.Error("get user", "error", err, "user_id", id)136 writeError(w, http.StatusInternalServerError, "internal error")137 }138 return139 }140141 writeJSON(w, http.StatusOK, user)142}143144func writeJSON(w http.ResponseWriter, status int, v any) {145 w.Header().Set("Content-Type", "application/json")146 w.WriteHeader(status)147 json.NewEncoder(w).Encode(v)148}149150func writeError(w http.ResponseWriter, status int, message string) {151 writeJSON(w, status, map[string]string{"error": message})152}153```154155### Middleware Pattern156```go157func LoggingMiddleware(logger *slog.Logger) func(http.Handler) http.Handler {158 return func(next http.Handler) http.Handler {159 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {160 start := time.Now()161 wrapped := &statusRecorder{ResponseWriter: w, status: http.StatusOK}162 next.ServeHTTP(wrapped, r)163 logger.Info("request",164 "method", r.Method,165 "path", r.URL.Path,166 "status", wrapped.status,167 "duration", time.Since(start),168 )169 })170 }171}172```173174## Concurrency175- Use `context.Context` for cancellation and timeouts on all I/O operations176- Prefer `sync.WaitGroup` and channels over raw goroutines177- Use `errgroup.Group` for concurrent operations that can fail178- Never start goroutines without a plan for shutdown179- Use `sync.Once` for one-time initialization180- Avoid goroutine leaks — always have a cancellation path181182## Testing183```go184func TestUserService_GetUser(t *testing.T) {185 tests := []struct {186 name string187 id int64188 mock func(*MockUserStore)189 want *model.User190 wantErr error191 }{192 {193 name: "existing user",194 id: 1,195 mock: func(s *MockUserStore) {196 s.On("GetUser", mock.Anything, int64(1)).Return(&model.User{ID: 1, Name: "Alice"}, nil)197 },198 want: &model.User{ID: 1, Name: "Alice"},199 },200 {201 name: "user not found",202 id: 999,203 mock: func(s *MockUserStore) { s.On("GetUser", mock.Anything, int64(999)).Return(nil, store.ErrNotFound) },204 wantErr: service.ErrUserNotFound,205 },206 }207208 for _, tt := range tests {209 t.Run(tt.name, func(t *testing.T) {210 store := new(MockUserStore)211 tt.mock(store)212 svc := service.NewUserService(store)213 got, err := svc.GetUser(context.Background(), tt.id)214 if tt.wantErr != nil {215 assert.ErrorIs(t, err, tt.wantErr)216 return217 }218 assert.NoError(t, err)219 assert.Equal(t, tt.want, got)220 })221 }222}223```224225## Performance Guidelines226- Use `sync.Pool` for frequently allocated objects227- Buffer channels when the producer/consumer rate is known228- Use `strings.Builder` for string concatenation in loops229- Profile with `pprof` before optimizing230- Use connection pooling for database (pgx pool)231- Set appropriate timeouts on HTTP server and clients232- Use `json.Decoder` for streaming large JSON payloads233234## Security235- Validate all input at the handler layer236- Use parameterized SQL queries (never concatenate user input)237- Set timeouts on the HTTP server: `ReadTimeout`, `WriteTimeout`, `IdleTimeout`238- Use TLS in production239- Hash passwords with `golang.org/x/crypto/bcrypt`240- Rate limit endpoints with middleware241- Sanitize log output — never log passwords or tokens242243## Common Pitfalls244- Forgetting to check `err` — this is Go's most important convention245- Not closing `resp.Body` after `http.Get` (use `defer resp.Body.Close()`)246- Data races from sharing state between goroutines without synchronization247- Using `json.Marshal` with unexported fields (they are silently omitted)248- Nil pointer dereference — always check interface values and pointers249- Forgetting to call `rows.Close()` in database queries (deferred or explicit)250- Shadowing variables with `:=` in inner scopes251- Using `time.Sleep` instead of proper synchronization252
One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| survivorforge/cursor-rulesrules/ai-ml-python/.cursorrules · 17 | .cursorrules | teststylearchdeployment+2 | 81/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/api-microservices/.cursorrules · 17 | .cursorrules | buildteststylearch+5 | 92/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/database-sql/.cursorrules · 17 | .cursorrules | styletypessecuritydatabase+3 | 65/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/devops-docker/.cursorrules · 17 | .cursorrules | setupbuildteststyle+4 | 93/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/devops-infrastructure/.cursorrules · 17 | .cursorrules | buildteststylesecurity+3 | 93/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/django-rest/.cursorrules · 17 | .cursorrules | buildteststylearch+5 | 84/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/docker-devops/.cursorrules · 17 | .cursorrules | setupteststylearch+6 | 85/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/flutter-dart/.cursorrules · 17 | .cursorrules | teststylearchtypes+5 | 89/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/go-gin/.cursorrules · 17 | .cursorrules | testlint-formatstylearch+5 | 84/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/langchain-ai/.cursorrules · 17 | .cursorrules | testlint-formatstylearch+4 | 84/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/mobile-react-native/.cursorrules · 17 | .cursorrules | teststylearchtypes+7 | 89/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nextjs-14-app-router/.cursorrules · 17 | .cursorrules | teststyletypestesting-strategy+3 | 71/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nextjs-app-router/.cursorrules · 17 | .cursorrules | teststylearchtypes+5 | 81/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nextjs-typescript/.cursorrules · 17 | .cursorrules | teststylearchtypes+5 | 81/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nextjs/.cursorrules · 17 | .cursorrules | teststylearchtypes+5 | 81/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nodejs-express-typescript/.cursorrules · 17 | .cursorrules | setupteststylearch+7 | 81/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nodejs-express/.cursorrules · 17 | .cursorrules | testlint-formatstylearch+7 | 92/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/performance-optimization/.cursorrules · 17 | .cursorrules | styledatabaseapiperformance+2 | 65/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/python-django/.cursorrules · 17 | .cursorrules | testlint-formatstylearch+6 | 99/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/python-fastapi/.cursorrules · 17 | .cursorrules | testlint-formatstylearch+6 | 99/100 | 13 days ago |
A badge carrying the measured quality of the strongest agent config file in this repository, out of 100. It reads from this index every time somebody loads your page, so it changes when the measurement changes and there is nothing to keep up to date. Free, no account, and the value is not something you or we can set by hand.
[](https://rulestack.kynth.studio/configs/survivorforge-cursor-rules-rules-golang-api-cursorrules)Would rather not hotlink us? Every badge is also served in shields.io’s endpoint schema, so shields renders the image and your readers never talk to our domain:
Published by Toolproof, the masthead over this index and eight others. The method behind the number is at toolproof.kynth.studio/methodology, and the whole thing is readable as JSON with no key at /api.