feat: improve resource routing API and add comprehensive quality standards
BREAKING CHANGES:
- Renamed HandleGET -> MemberGET for member-level routes
- Renamed HandlePOST -> MemberPOST for member-level routes
- Renamed HandlePUT -> MemberPUT for member-level routes
- Renamed HandlePATCH -> MemberPATCH for member-level routes
- Renamed HandleDELETE -> MemberDELETE for member-level routes
New Features:
- Added collection-level route methods: GET, POST, PUT, PATCH, DELETE
- Clear distinction between collection (/pattern/action) and member (/pattern/{id}/action) routes
- Comprehensive documentation (README, CONTRIBUTING, QUICKSTART, DOCS)
- Development tooling (Makefile, check.sh script)
- AI coding assistant guidelines (.cursorrules)
- GitHub Actions CI/CD pipeline
- golangci-lint configuration
Code Quality:
- Optimized struct field alignment for better memory usage
- All code passes go vet, staticcheck, and fieldalignment
- All tests pass with race detector
- Go 1.25+ requirement enforced
Documentation:
- Complete README rewrite with examples
- CONTRIBUTING.md with development guidelines
- QUICKSTART.md for new users
- CHANGELOG.md with version history
- SUMMARY.md documenting all changes
- DOCS.md as documentation index
This commit is contained in:
329
QUICKSTART.md
Normal file
329
QUICKSTART.md
Normal file
@@ -0,0 +1,329 @@
|
||||
# Quick Start Guide
|
||||
|
||||
Get up and running with Mux in under 5 minutes!
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get code.patial.tech/go/mux
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Go 1.25 or higher
|
||||
|
||||
## Your First Route
|
||||
|
||||
Create `main.go`:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"code.patial.tech/go/mux"
|
||||
)
|
||||
|
||||
func main() {
|
||||
m := mux.New()
|
||||
|
||||
m.GET("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, "Hello, Mux!")
|
||||
})
|
||||
|
||||
m.Serve(func(srv *http.Server) error {
|
||||
srv.Addr = ":8080"
|
||||
fmt.Println("Server listening on http://localhost:8080")
|
||||
return srv.ListenAndServe()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Run it:
|
||||
|
||||
```bash
|
||||
go run main.go
|
||||
```
|
||||
|
||||
Visit [http://localhost:8080](http://localhost:8080) 🎉
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### 1. Multiple Routes
|
||||
|
||||
```go
|
||||
m := mux.New()
|
||||
|
||||
m.GET("/", homeHandler)
|
||||
m.GET("/about", aboutHandler)
|
||||
m.GET("/users/{id}", showUser)
|
||||
m.POST("/users", createUser)
|
||||
```
|
||||
|
||||
### 2. Using Middleware
|
||||
|
||||
```go
|
||||
m := mux.New()
|
||||
|
||||
// Add global middleware
|
||||
m.Use(loggingMiddleware)
|
||||
m.Use(authMiddleware)
|
||||
|
||||
// All routes will use both middleware
|
||||
m.GET("/protected", protectedHandler)
|
||||
```
|
||||
|
||||
### 3. Route Groups
|
||||
|
||||
```go
|
||||
m := mux.New()
|
||||
|
||||
// Public routes
|
||||
m.GET("/", homeHandler)
|
||||
|
||||
// API routes with shared middleware
|
||||
m.Group(func(api *mux.Mux) {
|
||||
api.Use(jsonMiddleware)
|
||||
api.Use(apiAuthMiddleware)
|
||||
|
||||
api.GET("/api/users", listUsers)
|
||||
api.POST("/api/users", createUser)
|
||||
})
|
||||
```
|
||||
|
||||
### 4. RESTful Resources
|
||||
|
||||
```go
|
||||
m := mux.New()
|
||||
|
||||
m.Resource("/posts", func(res *mux.Resource) {
|
||||
res.Index(listPosts) // GET /posts
|
||||
res.Create(createPost) // POST /posts
|
||||
res.View(showPost) // GET /posts/{id}
|
||||
res.Update(updatePost) // PUT /posts/{id}
|
||||
res.Delete(deletePost) // DELETE /posts/{id}
|
||||
|
||||
// Custom collection routes
|
||||
res.POST("/search", searchPosts) // POST /posts/search
|
||||
|
||||
// Custom member routes
|
||||
res.MemberPOST("/publish", publishPost) // POST /posts/{id}/publish
|
||||
})
|
||||
```
|
||||
|
||||
### 5. URL Parameters
|
||||
|
||||
```go
|
||||
m.GET("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
userID := r.PathValue("id")
|
||||
fmt.Fprintf(w, "User ID: %s", userID)
|
||||
})
|
||||
|
||||
m.GET("/posts/{year}/{month}/{slug}", func(w http.ResponseWriter, r *http.Request) {
|
||||
year := r.PathValue("year")
|
||||
month := r.PathValue("month")
|
||||
slug := r.PathValue("slug")
|
||||
// Handle request...
|
||||
})
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a more complete example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"code.patial.tech/go/mux"
|
||||
)
|
||||
|
||||
func main() {
|
||||
m := mux.New()
|
||||
|
||||
// Global middleware
|
||||
m.Use(loggingMiddleware)
|
||||
|
||||
// Routes
|
||||
m.GET("/", homeHandler)
|
||||
m.GET("/health", healthHandler)
|
||||
|
||||
// API group
|
||||
m.Group(func(api *mux.Mux) {
|
||||
api.Use(jsonMiddleware)
|
||||
|
||||
// Users resource
|
||||
api.Resource("/users", func(res *mux.Resource) {
|
||||
res.Index(listUsers)
|
||||
res.Create(createUser)
|
||||
res.View(showUser)
|
||||
res.Update(updateUser)
|
||||
res.Delete(deleteUser)
|
||||
|
||||
// Custom routes
|
||||
res.POST("/search", searchUsers)
|
||||
res.MemberGET("/posts", getUserPosts)
|
||||
})
|
||||
})
|
||||
|
||||
// Debug routes
|
||||
m.GET("/debug/routes", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
m.PrintRoutes(w)
|
||||
})
|
||||
|
||||
// Start server
|
||||
m.Serve(func(srv *http.Server) error {
|
||||
srv.Addr = ":8080"
|
||||
srv.ReadTimeout = 30 * time.Second
|
||||
srv.WriteTimeout = 30 * time.Second
|
||||
|
||||
slog.Info("Server starting", "addr", srv.Addr)
|
||||
return srv.ListenAndServe()
|
||||
})
|
||||
}
|
||||
|
||||
// Middleware
|
||||
func loggingMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
next.ServeHTTP(w, r)
|
||||
slog.Info("request",
|
||||
"method", r.Method,
|
||||
"path", r.URL.Path,
|
||||
"duration", time.Since(start))
|
||||
})
|
||||
}
|
||||
|
||||
func jsonMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// Handlers
|
||||
func homeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, "Welcome to Mux!")
|
||||
}
|
||||
|
||||
func healthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func listUsers(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(map[string][]string{"users": {}})
|
||||
}
|
||||
|
||||
func createUser(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse request body and create user
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "User created"})
|
||||
}
|
||||
|
||||
func showUser(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
json.NewEncoder(w).Encode(map[string]string{"id": id, "name": "John Doe"})
|
||||
}
|
||||
|
||||
func updateUser(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": fmt.Sprintf("User %s updated", id)})
|
||||
}
|
||||
|
||||
func deleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": fmt.Sprintf("User %s deleted", id)})
|
||||
}
|
||||
|
||||
func searchUsers(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(map[string][]string{"results": {}})
|
||||
}
|
||||
|
||||
func getUserPosts(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"user_id": id,
|
||||
"posts": []string{},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Read the [full README](README.md) for detailed documentation
|
||||
- Check [CONTRIBUTING.md](CONTRIBUTING.md) for code quality standards
|
||||
- Look at the [example directory](example/) for more examples
|
||||
- Review the [middleware package](middleware/) for built-in middleware
|
||||
|
||||
## Testing Your Routes
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"code.patial.tech/go/mux"
|
||||
)
|
||||
|
||||
func TestHomeHandler(t *testing.T) {
|
||||
m := mux.New()
|
||||
m.GET("/", homeHandler)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
m.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
If you see "address already in use", change the port:
|
||||
|
||||
```go
|
||||
srv.Addr = ":8081" // Use a different port
|
||||
```
|
||||
|
||||
### 404 Not Found
|
||||
|
||||
Make sure your route patterns start with `/`:
|
||||
|
||||
```go
|
||||
m.GET("/users", handler) // ✅ Correct
|
||||
m.GET("users", handler) // ❌ Wrong
|
||||
```
|
||||
|
||||
### Middleware Not Working
|
||||
|
||||
Ensure middleware is registered before routes:
|
||||
|
||||
```go
|
||||
m.Use(middleware1) // ✅ Register first
|
||||
m.GET("/route", handler) // Then add routes
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Check the [README](README.md) for detailed documentation
|
||||
- Look at [examples](example/) for working code
|
||||
- Open an issue on GitHub for bugs or questions
|
||||
|
||||
---
|
||||
|
||||
Happy routing! 🚀
|
||||
Reference in New Issue
Block a user