--- description: This rule enforces best practices for using the `net/http` ServeMux in Go, promoting clean, maintainable, and efficient code. It covers routing, handler design, and error handling specifics to help developers leverage ServeMux effectively. globs: *.go --- --- # servemux Best Practices and Coding Standards This document provides comprehensive guidelines for using the `net/http` ServeMux in Go, promoting best practices, coding standards, and efficient development. ## 1. Code Organization and Structure ### 1.1 Directory Structure Best Practices - **Modular Design:** Organize your code into logical modules, each responsible for a specific set of functionalities. - **Handler Grouping:** Group related handlers within the same directory or package. For instance, all user-related handlers could reside in a `users` package. - **Middleware Directory:** Create a dedicated `middleware` directory for reusable middleware functions. - **Internal vs. External:** Utilize the `internal` directory to encapsulate code that should not be exposed outside your module. This enhances encapsulation and reduces the API surface. project-root/ ├── cmd/ │ └── my-app/ │ └── main.go ├── internal/ │ ├── handlers/ │ │ ├── users.go │ │ └── products.go │ └── middleware/ │ ├── auth.go │ └── logging.go ├── pkg/ │ └── utils/ │ └── utils.go └── go.mod ### 1.2 File Naming Conventions - **Descriptive Names:** Use clear and descriptive file names that reflect the functionality they contain. For example, `users.go` for user-related handlers, and `auth.go` for authentication middleware. - **Handler Specifics:** If a file contains a specific handler, name it accordingly. For example, `get_user_handler.go` or `create_product_handler.go`. - **Lowercase:** Use lowercase for all file names. ### 1.3 Module Organization - **`go.mod`:** Ensure your project has a `go.mod` file to manage dependencies. Run `go mod init ` to create one. - **Semantic Versioning:** Follow semantic versioning for your modules. Use tags (e.g., `v1.0.0`) to mark releases. - **Vendor Dependencies:** Consider vendoring dependencies using `go mod vendor` to ensure reproducibility. ### 1.4 Component Architecture - **Separation of Concerns:** Design your components to have a single responsibility. Separate handler logic from business logic and data access. - **Interfaces:** Use interfaces to define contracts between components, promoting loose coupling and testability. For example: go type UserStore interface { GetUser(id int) (*User, error) CreateUser(user *User) error } type UserHandler struct { store UserStore } ### 1.5 Code Splitting Strategies - **Package-Based Splitting:** Divide your application into packages based on functionality. Each package should be cohesive and have a clear purpose. - **Feature-Based Splitting:** Organize code based on features. Each feature gets its own directory or package containing all relevant components. - **Layered Architecture:** Implement a layered architecture (e.g., presentation, business logic, data access) and split the code accordingly. ## 2. Common Patterns and Anti-patterns ### 2.1 Design Patterns Specific to servemux - **Middleware Chaining:** Implement middleware as a chain of functions that decorate the handler. This allows for modular and reusable logic. Example: go func LoggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Printf("Request: %s %s", r.Method, r.URL.Path) next.ServeHTTP(w, r) }) } // Usage: mux := http.NewServeMux() mux.Handle("/", LoggingMiddleware(http.HandlerFunc(myHandler))) - **Context-Aware Handlers:** Utilize the `context.Context` to pass request-scoped values to handlers. This facilitates tracing, request cancellation, and data sharing. go func MyHandler(w http.ResponseWriter, r *http.Request) { userID := r.Context().Value("userID").(int) // ... } // Setting context value in middleware: ctx := context.WithValue(r.Context(), "userID", 123) r = r.WithContext(ctx) - **Route Grouping (with custom muxes):** Use separate ServeMux instances for different route groups (e.g., API v1, API v2, admin routes). This improves organization and maintainability. go v1Mux := http.NewServeMux() v1Mux.HandleFunc("/users", v1UsersHandler) v1Mux.HandleFunc("/products", v1ProductsHandler) v2Mux := http.NewServeMux() v2Mux.HandleFunc("/users", v2UsersHandler) v2Mux.HandleFunc("/products", v2ProductsHandler) http.Handle("/api/v1/", v1Mux) http.Handle("/api/v2/", v2Mux) ### 2.2 Recommended Approaches for Common Tasks - **Creating a New ServeMux:** Always use `http.NewServeMux()` to create a new ServeMux instance. Avoid using the default `http.DefaultServeMux` to prevent global state issues. Example: go mux := http.NewServeMux() mux.HandleFunc("/", myHandler) http.ListenAndServe(":8080", mux) - **Registering Handlers:** Use `mux.HandleFunc()` or `mux.Handle()` to register handlers with the ServeMux. go mux.HandleFunc("/users", usersHandler) mux.Handle("/products", productHandler{}) - **Serving Static Files:** Use `http.FileServer()` to serve static files. go fs := http.FileServer(http.Dir("./static")) mux.Handle("/static/", http.StripPrefix("/static/", fs)) - **Handling HTTP Methods:** Use `http.MethodGet`, `http.MethodPost`, etc., constants to check the HTTP method. With Go 1.22+, specify methods in the pattern itself for `HandleFunc`. go mux.HandleFunc("GET /users/{id}", getUserHandler) mux.HandleFunc("POST /users", createUserHandler) ### 2.3 Anti-patterns and Code Smells to Avoid - **Global State:** Avoid using global variables or the default `http.DefaultServeMux` for request handling, as it can lead to race conditions and difficult debugging. - **Overlapping Routes:** Be mindful of overlapping routes. Go 1.22+ will panic if conflicting patterns are registered. Ensure that your routes are distinct and do not cause conflicts. Properly consider precedence rules. - **Ignoring Errors:** Always handle errors returned by functions. Log errors and return appropriate HTTP status codes to the client. - **Long Handler Functions:** Keep handler functions short and focused. Delegate complex logic to other functions or services. - **Hardcoding Values:** Avoid hardcoding configuration values. Use environment variables or configuration files. - **Lack of Input Validation:** Always validate user input to prevent security vulnerabilities. ### 2.4 State Management Best Practices - **Stateless Handlers:** Design handlers to be stateless whenever possible. If state is required, store it in the request context or an external data store. - **Request-Scoped Values:** Use the `context.Context` to store request-scoped values. Avoid using global variables to store state related to a specific request. - **Sessions:** Use secure session management techniques to maintain user sessions. ### 2.5 Error Handling Patterns - **Centralized Error Handling:** Implement a centralized error handling middleware to catch and log errors. Return appropriate HTTP status codes and error messages to the client. go func ErrorHandlingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { log.Printf("Panic: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) } - **Custom Error Types:** Define custom error types to provide more context about the error. go type ValidationError struct { Field string Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("Validation error: %s - %s", e.Field, e.Message) } - **Logging Errors:** Log errors with sufficient detail for debugging. - **Returning Appropriate Status Codes:** Return appropriate HTTP status codes based on the error. Use descriptive error messages in the response body. ## 3. Performance Considerations ### 3.1 Optimization Techniques - **Connection Pooling:** Use connection pooling for database connections to reduce overhead. - **Caching:** Implement caching for frequently accessed data to reduce database load. - **Gzip Compression:** Enable Gzip compression for responses to reduce bandwidth usage. go func GzipMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { gz, err := gzip.NewWriterLevel(w, gzip.BestSpeed) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer gz.Close() w.Header().Set("Content-Encoding", "gzip") next.ServeHTTP(gzipResponseWriter{Writer: gz, ResponseWriter: w}, r) } else { next.ServeHTTP(w, r) } }) } type gzipResponseWriter struct { io.Writer http.ResponseWriter } func (w gzipResponseWriter) Write(b []byte) (int, error) { return w.Writer.Write(b) } - **Keep-Alive Connections:** Enable keep-alive connections to reduce connection establishment overhead. - **Efficient Data Structures:** Use efficient data structures and algorithms for data processing. ### 3.2 Memory Management - **Avoid Memory Leaks:** Be aware of potential memory leaks, especially when using goroutines. Ensure that all goroutines are properly terminated. - **Object Pooling:** Consider using object pooling for frequently allocated objects. - **String Conversions:** Minimize unnecessary string conversions, as they can be expensive. - **Defer Statements:** Use `defer` statements carefully. While convenient, they can add overhead if overused in performance-critical sections. ### 3.3 Rendering Optimization - **Template Caching:** Cache templates to reduce parsing overhead. - **Efficient Template Rendering:** Use efficient template rendering techniques. Consider using pre-compiled templates. - **Minimize DOM Updates:** Minimize DOM updates in the client-side JavaScript code. ### 3.4 Bundle Size Optimization - **Code Minification:** Minify JavaScript and CSS code to reduce bundle size. - **Tree Shaking:** Use tree shaking to remove unused code from the bundle. - **Image Optimization:** Optimize images to reduce file size. ### 3.5 Lazy Loading Strategies - **Lazy Loading Images:** Lazy load images that are not immediately visible. - **Code Splitting:** Split the code into smaller chunks that can be loaded on demand. - **Dynamic Imports:** Use dynamic imports to load modules only when they are needed. ## 4. Security Best Practices ### 4.1 Common Vulnerabilities and How to Prevent Them - **SQL Injection:** Prevent SQL injection by using parameterized queries or ORMs. - **Cross-Site Scripting (XSS):** Prevent XSS by escaping user input before rendering it in HTML. - **Cross-Site Request Forgery (CSRF):** Prevent CSRF by using CSRF tokens. - **Authentication Bypass:** Implement robust authentication and authorization mechanisms to prevent authentication bypass. - **Denial-of-Service (DoS):** Implement rate limiting and request validation to prevent DoS attacks. ### 4.2 Input Validation - **Validate All Inputs:** Validate all user inputs to ensure that they conform to the expected format and values. Use libraries like `ozzo-validation` for complex validation rules. - **Sanitize Inputs:** Sanitize inputs to remove potentially malicious characters. - **Whitelist Inputs:** Use a whitelist approach to only allow specific characters or patterns. ### 4.3 Authentication and Authorization Patterns - **Authentication Middleware:** Implement authentication middleware to verify user credentials. - **Authorization Middleware:** Implement authorization middleware to check user permissions. - **JWT (JSON Web Tokens):** Use JWT for stateless authentication. - **OAuth 2.0:** Use OAuth 2.0 for delegated authorization. - **Role-Based Access Control (RBAC):** Implement RBAC to manage user permissions. ### 4.4 Data Protection Strategies - **Encryption:** Encrypt sensitive data at rest and in transit. - **Hashing:** Hash passwords with a strong hashing algorithm like bcrypt. - **Salting:** Use salts to prevent rainbow table attacks. - **Data Masking:** Mask sensitive data in logs and error messages. ### 4.5 Secure API Communication - **HTTPS:** Use HTTPS to encrypt communication between the client and the server. - **TLS (Transport Layer Security):** Use TLS 1.3 or higher for secure communication. - **Certificate Pinning:** Consider using certificate pinning to prevent man-in-the-middle attacks. - **Rate Limiting:** Implement rate limiting to prevent abuse. ## 5. Testing Approaches ### 5.1 Unit Testing Strategies - **Test Individual Units:** Unit test individual functions and methods in isolation. - **Mock Dependencies:** Use mocks to isolate the unit under test from its dependencies. Use libraries like `gomock` or `testify/mock`. - **Test Boundary Conditions:** Test boundary conditions and edge cases. - **Table-Driven Tests:** Use table-driven tests to test multiple scenarios with different inputs and outputs. ### 5.2 Integration Testing - **Test Interactions Between Components:** Integration test the interactions between different components of the application. - **Test Database Interactions:** Test the interactions with the database. - **Test API Endpoints:** Test the API endpoints. ### 5.3 End-to-End Testing - **Test the Entire Application:** End-to-end test the entire application flow. - **Use Automated Testing Tools:** Use automated testing tools like Selenium or Cypress. ### 5.4 Test Organization - **Keep Tests Close to Code:** Keep test files in the same directory as the code they test. Use the `_test.go` suffix for test files. - **Separate Test Packages:** Consider creating separate test packages for integration and end-to-end tests. - **Descriptive Test Names:** Use descriptive test names that clearly indicate what is being tested. ### 5.5 Mocking and Stubbing - **Use Interfaces:** Define interfaces for dependencies to facilitate mocking. - **Use Mocking Libraries:** Use mocking libraries like `gomock` or `testify/mock` to generate mocks. - **Create Stub Implementations:** Create stub implementations for dependencies that are difficult to mock. ## 6. Common Pitfalls and Gotchas ### 6.1 Frequent Mistakes Developers Make - **Forgetting to Handle Errors:** Always check and handle errors returned by functions. - **Ignoring Context Cancellation:** Respect context cancellation signals and terminate long-running operations. - **Not Validating Inputs:** Always validate user inputs to prevent security vulnerabilities. - **Using Global State:** Avoid using global variables for request handling, as it can lead to race conditions. - **Overlapping Routes:** Failing to understand route precedence, especially in Go 1.22+ can lead to unexpected behavior or panics during server initialization. Thoroughly test route registrations. ### 6.2 Edge Cases to Be Aware Of - **Trailing Slashes:** Be aware of how trailing slashes affect route matching. In Go 1.22+, use `{$}` to match only the path with the trailing slash. - **Wildcard Matching:** Understand how wildcards match different segments of the path. Use `{pathname...}` to match all remaining segments. - **Method Matching:** Ensure that the correct HTTP method is being used for each route. With Go 1.22+, be aware that `GET` also matches `HEAD` requests. ### 6.3 Version-Specific Issues - **Go 1.22 Routing Enhancements:** Be aware of the new routing enhancements in Go 1.22, including method matching and wildcard support. Understand the new precedence rules. - **Compatibility Issues:** Check for compatibility issues when upgrading to new versions of Go or third-party libraries. ### 6.4 Compatibility Concerns - **Backwards Compatibility:** Ensure that changes do not break backwards compatibility. Use feature flags to gradually introduce new features. - **API Versioning:** Use API versioning to manage changes to the API. - **Client Compatibility:** Ensure that client applications are compatible with the API. ### 6.5 Debugging Strategies - **Logging:** Use logging to track the execution flow and identify errors. - **Debugging Tools:** Use debugging tools like `delve` to step through the code and inspect variables. - **Profiling:** Use profiling tools to identify performance bottlenecks. - **Tracing:** Use tracing tools to track requests across different services. ## 7. Tooling and Environment ### 7.1 Recommended Development Tools - **GoLand:** A powerful IDE for Go development. - **Visual Studio Code with Go Extension:** A lightweight and versatile code editor with excellent Go support. - **Delve:** A Go debugger. - **Profiling Tools:** `pprof` is the built-in Go profiler. ### 7.2 Build Configuration - **Makefile:** Use a Makefile to automate common build tasks. - **`go build`:** Use `go build` to compile the application. - **`go test`:** Use `go test` to run tests. ### 7.3 Linting and Formatting - **`go fmt`:** Use `go fmt` to format the code according to the Go style guidelines. - **`go vet`:** Use `go vet` to identify potential errors in the code. - **`golangci-lint`:** Use `golangci-lint` for more advanced linting. ### 7.4 Deployment Best Practices - **Containerization:** Use containers (e.g., Docker) to package the application and its dependencies. - **Orchestration:** Use orchestration tools (e.g., Kubernetes) to manage and scale the application. - **Immutable Infrastructure:** Deploy immutable infrastructure to ensure consistency and reproducibility. ### 7.5 CI/CD Integration - **Continuous Integration:** Integrate with a CI/CD pipeline to automate the build, test, and deployment process. - **Automated Testing:** Run automated tests as part of the CI/CD pipeline. - **Automated Deployment:** Automate the deployment process using tools like Jenkins, CircleCI, or GitHub Actions. ## Additional Best Practices - **Meaningful Handler Names:** Use meaningful, concise names for handlers, avoiding repetition of context in function names (as per the Google Style Guide). - **Document Code:** Write clear and concise documentation for all functions, methods, and types. - **Code Reviews:** Conduct regular code reviews to ensure code quality and identify potential issues. - **Stay Updated:** Stay up-to-date with the latest Go best practices and security vulnerabilities. By following these best practices and coding standards, you can develop robust, maintainable, and efficient applications using the `net/http` ServeMux in Go.