mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
- Initialize git repository - Add comprehensive .gitignore for Go projects - Install golangci-lint v2.6.0 (latest v2) globally - Configure .golangci.yml with appropriate linters and formatters - Fix all formatting issues (gofmt) - Fix all errcheck issues (unchecked errors) - Adjust complexity threshold for validation functions - All checks passing: build, test, vet, lint
429 lines
9.5 KiB
Markdown
429 lines
9.5 KiB
Markdown
# Gin Framework Development Guide
|
|
|
|
**Library**: `github.com/gin-gonic/gin`
|
|
**Used In**: MVP - HTTP API server
|
|
**Purpose**: HTTP web framework for building RESTful APIs
|
|
|
|
---
|
|
|
|
## Where It's Used
|
|
|
|
- **Primary HTTP framework** for API endpoints
|
|
- Business registration, resource flow CRUD operations
|
|
- Match retrieval endpoints
|
|
- Authentication middleware
|
|
|
|
---
|
|
|
|
## Official Documentation
|
|
|
|
- **GitHub**: https://github.com/gin-gonic/gin
|
|
- **Official Docs**: https://gin-gonic.com/docs/
|
|
- **GoDoc**: https://pkg.go.dev/github.com/gin-gonic/gin
|
|
- **Examples**: https://github.com/gin-gonic/gin/tree/master/examples
|
|
|
|
---
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
go get github.com/gin-gonic/gin
|
|
```
|
|
|
|
---
|
|
|
|
## Key Concepts
|
|
|
|
### 1. Basic Server Setup
|
|
|
|
```go
|
|
package main
|
|
|
|
import "github.com/gin-gonic/gin"
|
|
|
|
func main() {
|
|
r := gin.Default() // Includes Logger and Recovery middleware
|
|
|
|
// Or minimal setup
|
|
// r := gin.New()
|
|
// r.Use(gin.Logger())
|
|
// r.Use(gin.Recovery())
|
|
|
|
r.GET("/health", func(c *gin.Context) {
|
|
c.JSON(200, gin.H{"status": "ok"})
|
|
})
|
|
|
|
r.Run(":8080")
|
|
}
|
|
```
|
|
|
|
### 2. Routes and Handlers
|
|
|
|
```go
|
|
// GET request
|
|
r.GET("/api/businesses/:id", getBusiness)
|
|
|
|
// POST request
|
|
r.POST("/api/businesses", createBusiness)
|
|
|
|
// PUT request
|
|
r.PUT("/api/businesses/:id", updateBusiness)
|
|
|
|
// DELETE request
|
|
r.DELETE("/api/businesses/:id", deleteBusiness)
|
|
|
|
// Handler function
|
|
func getBusiness(c *gin.Context) {
|
|
id := c.Param("id") // Get path parameter
|
|
// ... business logic
|
|
c.JSON(200, business)
|
|
}
|
|
```
|
|
|
|
### 3. Request Binding
|
|
|
|
```go
|
|
// JSON binding
|
|
type BusinessRequest struct {
|
|
Name string `json:"name" binding:"required"`
|
|
Email string `json:"email" binding:"required,email"`
|
|
}
|
|
|
|
func createBusiness(c *gin.Context) {
|
|
var req BusinessRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(400, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
// ... process request
|
|
c.JSON(201, business)
|
|
}
|
|
|
|
// Query parameters
|
|
func searchBusinesses(c *gin.Context) {
|
|
name := c.Query("name") // ?name=...
|
|
page := c.DefaultQuery("page", "1") // ?page=... (default: "1")
|
|
// ...
|
|
}
|
|
|
|
// Path parameters
|
|
func getBusiness(c *gin.Context) {
|
|
id := c.Param("id") // /businesses/:id
|
|
// ...
|
|
}
|
|
```
|
|
|
|
### 4. Middleware
|
|
|
|
```go
|
|
// Global middleware
|
|
r.Use(corsMiddleware())
|
|
r.Use(authMiddleware())
|
|
|
|
// Route-specific middleware
|
|
r.GET("/protected", authMiddleware(), protectedHandler)
|
|
|
|
// Custom middleware
|
|
func authMiddleware() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
token := c.GetHeader("Authorization")
|
|
if token == "" {
|
|
c.JSON(401, gin.H{"error": "Unauthorized"})
|
|
c.Abort()
|
|
return
|
|
}
|
|
// Validate token
|
|
c.Set("userID", userID) // Store in context
|
|
c.Next() // Continue to next handler
|
|
}
|
|
}
|
|
|
|
// Logging middleware (built-in)
|
|
r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
|
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
|
|
param.ClientIP,
|
|
param.TimeStamp.Format(time.RFC1123),
|
|
param.Method,
|
|
param.Path,
|
|
param.Request.Proto,
|
|
param.StatusCode,
|
|
param.Latency,
|
|
param.Request.UserAgent(),
|
|
param.ErrorMessage,
|
|
)
|
|
}))
|
|
```
|
|
|
|
### 5. Error Handling
|
|
|
|
```go
|
|
func handleBusiness(c *gin.Context) {
|
|
business, err := service.GetBusiness(c.Param("id"))
|
|
if err != nil {
|
|
if err == ErrNotFound {
|
|
c.JSON(404, gin.H{"error": "Business not found"})
|
|
return
|
|
}
|
|
c.JSON(500, gin.H{"error": "Internal server error"})
|
|
return
|
|
}
|
|
c.JSON(200, business)
|
|
}
|
|
|
|
// Custom error handler
|
|
func errorHandler() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
c.Next()
|
|
if len(c.Errors) > 0 {
|
|
c.JSON(500, gin.H{"errors": c.Errors})
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 6. Grouping Routes
|
|
|
|
```go
|
|
api := r.Group("/api")
|
|
{
|
|
// All routes prefixed with /api
|
|
api.GET("/businesses", listBusinesses)
|
|
|
|
// Nested groups
|
|
v1 := api.Group("/v1")
|
|
{
|
|
v1.GET("/businesses", listBusinessesV1)
|
|
v1.POST("/businesses", createBusinessV1)
|
|
}
|
|
|
|
v2 := api.Group("/v2")
|
|
{
|
|
v2.GET("/businesses", listBusinessesV2)
|
|
}
|
|
}
|
|
|
|
// With middleware
|
|
authenticated := r.Group("/api")
|
|
authenticated.Use(authMiddleware())
|
|
{
|
|
authenticated.GET("/profile", getProfile)
|
|
authenticated.POST("/resources", createResource)
|
|
}
|
|
```
|
|
|
|
### 7. Context Usage
|
|
|
|
```go
|
|
// Get values from context (set by middleware)
|
|
userID := c.MustGet("userID").(string)
|
|
|
|
// Set value in context
|
|
c.Set("businessID", businessID)
|
|
|
|
// Get with default
|
|
page, exists := c.Get("page")
|
|
if !exists {
|
|
page = 1
|
|
}
|
|
|
|
// Request context (for cancellation, timeouts)
|
|
ctx := c.Request.Context()
|
|
```
|
|
|
|
### 8. File Upload
|
|
|
|
```go
|
|
func uploadFile(c *gin.Context) {
|
|
file, err := c.FormFile("file")
|
|
if err != nil {
|
|
c.JSON(400, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Save file
|
|
if err := c.SaveUploadedFile(file, "/uploads/"+file.Filename); err != nil {
|
|
c.JSON(500, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(200, gin.H{"message": "File uploaded"})
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## MVP-Specific Patterns
|
|
|
|
### Resource Flow Handler Example
|
|
|
|
```go
|
|
type ResourceFlowHandler struct {
|
|
service *ResourceFlowService
|
|
}
|
|
|
|
func (h *ResourceFlowHandler) Create(c *gin.Context) {
|
|
var req CreateResourceFlowRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(400, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Validate business belongs to user
|
|
userID := c.MustGet("userID").(string)
|
|
if !h.service.ValidateOwnership(req.BusinessID, userID) {
|
|
c.JSON(403, gin.H{"error": "Forbidden"})
|
|
return
|
|
}
|
|
|
|
flow, err := h.service.Create(c.Request.Context(), req)
|
|
if err != nil {
|
|
c.JSON(500, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(201, flow)
|
|
}
|
|
|
|
func (h *ResourceFlowHandler) FindMatches(c *gin.Context) {
|
|
flowID := c.Param("id")
|
|
matches, err := h.service.FindMatches(c.Request.Context(), flowID)
|
|
if err != nil {
|
|
c.JSON(500, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(200, gin.H{"matches": matches})
|
|
}
|
|
|
|
// Register routes
|
|
func setupRoutes(r *gin.Engine, handlers *Handlers) {
|
|
api := r.Group("/api/v1")
|
|
|
|
resources := api.Group("/resource-flows")
|
|
resources.Use(authMiddleware())
|
|
{
|
|
resources.POST("", handlers.ResourceFlow.Create)
|
|
resources.GET("/:id/matches", handlers.ResourceFlow.FindMatches)
|
|
}
|
|
}
|
|
```
|
|
|
|
### Middleware for Our MVP
|
|
|
|
```go
|
|
// CORS middleware
|
|
func corsMiddleware() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
c.Header("Access-Control-Allow-Origin", "*")
|
|
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
|
|
|
if c.Request.Method == "OPTIONS" {
|
|
c.AbortWithStatus(204)
|
|
return
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// Rate limiting (simple in-memory)
|
|
func rateLimitMiddleware() gin.HandlerFunc {
|
|
// Use token bucket or sliding window
|
|
return func(c *gin.Context) {
|
|
// Check rate limit
|
|
c.Next()
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Tips
|
|
|
|
1. **Use `gin.New()` instead of `gin.Default()`** if you don't need logger/recovery
|
|
2. **Bind only what you need** - avoid binding large structs
|
|
3. **Use context for cancellation** - `c.Request.Context()` for timeouts
|
|
4. **Reuse gin.Engine** - don't create new engine per request
|
|
5. **Use `c.Set()` and `c.Get()`** for request-scoped values
|
|
|
|
---
|
|
|
|
## Tutorials & Resources
|
|
|
|
- **Official Examples**: https://github.com/gin-gonic/gin/tree/master/examples
|
|
- **Go by Example - Gin**: https://gobyexample.com/web-servers (Gin section)
|
|
- **Building REST APIs**: https://www.alexedwards.net/blog/go-rest-api-structure
|
|
- **Testing Gin Handlers**: https://gin-gonic.com/docs/testing/
|
|
|
|
---
|
|
|
|
## Common Patterns
|
|
|
|
### Graceful Shutdown
|
|
|
|
```go
|
|
func main() {
|
|
r := gin.Default()
|
|
// ... setup routes ...
|
|
|
|
srv := &http.Server{
|
|
Addr: ":8080",
|
|
Handler: r,
|
|
}
|
|
|
|
go func() {
|
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Fatalf("listen: %s\n", err)
|
|
}
|
|
}()
|
|
|
|
// Wait for interrupt signal
|
|
quit := make(chan os.Signal, 1)
|
|
signal.Notify(quit, os.Interrupt)
|
|
<-quit
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
if err := srv.Shutdown(ctx); err != nil {
|
|
log.Fatal("Server forced to shutdown:", err)
|
|
}
|
|
}
|
|
```
|
|
|
|
### Structured Logging with Gin
|
|
|
|
```go
|
|
import "github.com/rs/zerolog/log"
|
|
|
|
func loggerMiddleware() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
start := time.Now()
|
|
path := c.Request.URL.Path
|
|
|
|
c.Next()
|
|
|
|
log.Info().
|
|
Str("method", c.Request.Method).
|
|
Str("path", path).
|
|
Int("status", c.Writer.Status()).
|
|
Dur("latency", time.Since(start)).
|
|
Msg("HTTP request")
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
1. **Don't use global variables** - pass dependencies via handlers
|
|
2. **Use dependency injection** - inject services into handlers
|
|
3. **Validate early** - use binding validation
|
|
4. **Handle errors consistently** - create error response helpers
|
|
5. **Use middleware for cross-cutting concerns** - auth, logging, CORS
|
|
6. **Version your API** - use route groups (`/api/v1`, `/api/v2`)
|
|
7. **Document your API** - consider OpenAPI/Swagger integration
|
|
|