diff --git a/docs/src/content/docs/guides/gin.mdx b/docs/src/content/docs/guides/gin-routing.mdx similarity index 68% rename from docs/src/content/docs/guides/gin.mdx rename to docs/src/content/docs/guides/gin-routing.mdx index 236996674..9d7c0da08 100644 --- a/docs/src/content/docs/guides/gin.mdx +++ b/docs/src/content/docs/guides/gin-routing.mdx @@ -30,19 +30,19 @@ go get -u github.com/gin-gonic/gin Create a middleware function that will handle the integration between Wails and Gin: ```go -// GinMiddleware creates a middleware that passes requests to Gin +// GinMiddleware creates a middleware that passes requests to Gin if they're not handled by Wails func GinMiddleware(ginEngine *gin.Engine) application.Middleware { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Let Wails handle the `/wails` route - if r.URL.Path == "/wails" { - next.ServeHTTP(w, r) - return - } - // Let Gin handle everything - ginEngine.ServeHTTP(w, r) - }) - } + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Let Wails handle the `/wails` route + if strings.HasPrefix(r.URL.Path, "/wails") { + next.ServeHTTP(w, r) + return + } + // Let Gin handle everything else + ginEngine.ServeHTTP(w, r) + }) + } } ``` @@ -207,6 +207,154 @@ ginEngine.GET("/api/search", func(c *gin.Context) { }) ``` +## Event Communication + +One of the powerful features of Wails is its event system, which allows for communication between the frontend and backend. When using Gin as a service, you can still leverage this event system by serving the Wails runtime.js file to your frontend. + +### Serving the Wails Runtime + +To enable event communication, you need to serve the Wails runtime.js file at the `/wails/runtime.js` path. Here's how to implement this in your Gin service: + +```go +import ( + "io" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/runtime" +) + +// GinService implements a Wails service that uses Gin for HTTP handling +type GinService struct { + ginEngine *gin.Engine + app *application.App + // Other fields... +} + +// ServeHTTP implements the http.Handler interface +func (s *GinService) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Special handling for Wails runtime.js + if r.URL.Path == "/wails/runtime.js" { + s.serveWailsRuntime(w, r) + return + } + + // All other requests go to the Gin router + s.ginEngine.ServeHTTP(w, r) +} + +// serveWailsRuntime serves the Wails runtime.js file +func (s *GinService) serveWailsRuntime(w http.ResponseWriter, r *http.Request) { + // Open the runtime.js file from the public runtime package + runtimeFile, err := runtime.Assets.Open(runtime.RuntimeJSPath) + if err != nil { + http.Error(w, "Failed to access runtime assets", http.StatusInternalServerError) + return + } + defer runtimeFile.Close() + + // Set the content type + w.Header().Set("Content-Type", "application/javascript") + + // Copy the file to the response + _, err = io.Copy(w, runtimeFile) + if err != nil { + http.Error(w, "Failed to serve runtime.js", http.StatusInternalServerError) + } +} +``` + +### Handling Events + +You'll also need to add an endpoint to handle events from the frontend. This endpoint will bridge the gap between the HTTP requests and the Wails event system: + +```go +// In your setupRoutes method +func (s *GinService) setupRoutes() { + // Event handling endpoint + s.ginEngine.POST("/events/emit", func(c *gin.Context) { + var eventData struct { + Name string `json:"name" binding:"required"` + Data interface{} `json:"data"` + } + + if err := c.ShouldBindJSON(&eventData); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // Process the event using the Wails event system + s.app.EmitEvent(eventData.Name, eventData.Data) + + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "Event processed successfully", + }) + }) + + // Other routes... +} +``` + +### Using Events in the Frontend + +In your frontend HTML, include the Wails runtime.js script and use the event API: + +```html + + +
+ + + + + + + + + +``` + +This approach allows you to use the Wails event system seamlessly with your Gin service, providing a consistent experience across your application. + ## Interacting with Wails Your Gin-served web content can interact with Wails features like events and bindings. To enable this interaction, you diff --git a/docs/src/content/docs/guides/gin-services.mdx b/docs/src/content/docs/guides/gin-services.mdx new file mode 100644 index 000000000..55d713b45 --- /dev/null +++ b/docs/src/content/docs/guides/gin-services.mdx @@ -0,0 +1,409 @@ +--- +title: Using Gin in Services +description: A guide to integrating the Gin web framework with Wails v3 Services +--- + +Wails v3 Services provide a powerful way to organize your application logic into reusable, modular components. By implementing the `http.Handler` interface in your services, you can mount them at specific routes in your application, allowing for a clean separation of concerns and more maintainable code. + +Integrating Gin with Wails Services enables you to create modular, mountable HTTP APIs using Gin's powerful routing and middleware capabilities. You can organize your application into domain-specific services, mount multiple Gin-based services at different routes, leverage Gin's extensive feature set while maintaining the benefits of Wails Services, and seamlessly integrate with the Wails event system for real-time communication. + +## Creating a Gin-based Service + +To create a Wails Service that uses Gin for HTTP handling, you need to implement both the Wails Service interface and the `http.Handler` interface. This combination allows your service to be managed by the Wails application lifecycle and handle HTTP requests. Let's walk through each step of the process: + +### 1. Define Your Service Structure + +First, create a new service structure that will hold your Gin router and any state your service needs. This structure serves as the foundation of your service, encapsulating both the HTTP handling capabilities and any business logic or data your service requires. The use of a mutex ensures thread-safe access to shared resources, which is essential for concurrent request handling. + +```go +package services + +import ( + "context" + "net/http" + "sync" + "time" + + "github.com/gin-gonic/gin" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// User represents a user in the system +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + CreatedAt time.Time `json:"createdAt"` +} + +// GinService implements a Wails service that uses Gin for HTTP handling +type GinService struct { + ginEngine *gin.Engine + users []User + nextID int + mu sync.RWMutex + app *application.App +} + +// NewGinService creates a new GinService instance +func NewGinService() *GinService { + // Create a new Gin router + ginEngine := gin.New() + + // Add middlewares + ginEngine.Use(gin.Recovery()) + ginEngine.Use(LoggingMiddleware()) + + service := &GinService{ + ginEngine: ginEngine, + users: []User{ + {ID: 1, Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now().Add(-72 * time.Hour)}, + {ID: 2, Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now().Add(-48 * time.Hour)}, + {ID: 3, Name: "Charlie", Email: "charlie@example.com", CreatedAt: time.Now().Add(-24 * time.Hour)}, + }, + nextID: 4, + } + + // Define routes + service.setupRoutes() + + return service +} +``` + +### 2. Implement the Wails Service Interface + +Your service needs to implement the Wails Service interface with the required methods. The `ServiceName` method provides a human-readable identifier for your service. The `ServiceStartup` method is called when the service starts and gives you access to the Wails application instance, which you can use to register event handlers and access other Wails features. The `ServiceShutdown` method allows you to clean up resources when the service is shutting down. + +```go +// ServiceName returns the name of the service +func (s *GinService) ServiceName() string { + return "Gin API Service" +} + +// ServiceStartup is called when the service starts +func (s *GinService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + // Store the application instance for later use + s.app = application.Get() + + // Register an event handler that can be triggered from the frontend + s.app.OnEvent("gin-api-event", func(event *application.CustomEvent) { + // Log the event data + s.app.Logger.Info("Received event from frontend", "data", event.Data) + + // Emit an event back to the frontend + s.app.EmitEvent("gin-api-response", map[string]interface{}{ + "message": "Response from Gin API Service", + "time": time.Now().Format(time.RFC3339), + }) + }) + + return nil +} + +// ServiceShutdown is called when the service shuts down +func (s *GinService) ServiceShutdown(ctx context.Context) error { + // Clean up resources if needed + return nil +} +``` + +### 3. Implement the http.Handler Interface + +To make your service mountable at a specific route, implement the `http.Handler` interface. This single method, `ServeHTTP`, is the gateway for all HTTP requests to your service. It delegates the request handling to the Gin router, allowing you to use all of Gin's powerful features for routing and middleware. + +```go +// ServeHTTP implements the http.Handler interface +func (s *GinService) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // All requests go to the Gin router + s.ginEngine.ServeHTTP(w, r) +} +``` + +### 4. Set Up Your Routes + +Define your API routes in a separate method for better organization. This approach keeps your code clean and makes it easier to understand the structure of your API. The Gin router provides a fluent API for defining routes, including support for route groups, which help organize related endpoints. + +```go +// setupRoutes configures the API routes +func (s *GinService) setupRoutes() { + // Basic info endpoint + s.ginEngine.GET("/info", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "service": "Gin API Service", + "version": "1.0.0", + "time": time.Now().Format(time.RFC3339), + }) + }) + + // Users group + users := s.ginEngine.Group("/users") + { + // Get all users + users.GET("", func(c *gin.Context) { + s.mu.RLock() + defer s.mu.RUnlock() + c.JSON(http.StatusOK, s.users) + }) + + // Get user by ID + users.GET("/:id", func(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + return + } + + s.mu.RLock() + defer s.mu.RUnlock() + + for _, user := range s.users { + if user.ID == id { + c.JSON(http.StatusOK, user) + return + } + } + + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + }) + + // Create a new user + users.POST("", func(c *gin.Context) { + var newUser User + if err := c.ShouldBindJSON(&newUser); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + s.mu.Lock() + defer s.mu.Unlock() + + // Set the ID and creation time + newUser.ID = s.nextID + newUser.CreatedAt = time.Now() + s.nextID++ + + // Add to the users slice + s.users = append(s.users, newUser) + + c.JSON(http.StatusCreated, newUser) + + // Emit an event to notify about the new user + s.app.EmitEvent("user-created", newUser) + }) + } +} +``` + +### 5. Create Custom Middleware + +You can create custom Gin middleware to enhance your service. Middleware functions in Gin are executed in the order they are added to the router and can perform tasks such as logging, authentication, and error handling. This example shows a simple logging middleware that records request details. + +```go +// LoggingMiddleware is a Gin middleware that logs request details +func LoggingMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + start := time.Now() + + // Process request + c.Next() + + // Calculate latency + latency := time.Since(start) + + // Log request details + log.Printf("[GIN] %s %s %d %s", c.Request.Method, c.Request.URL.Path, c.Writer.Status(), latency) + } +} +``` + +## Registering Your Service + +To use your Gin-based service in a Wails application, you need to register it with the application and specify the route where it should be mounted. This is done when creating the Wails application instance. The route you specify becomes the base path for all endpoints defined in your Gin router. + +```go +app := application.New(application.Options{ + Name: "Gin Service Demo", + Description: "A demo of using Gin in Wails services", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + LogLevel: slog.LevelDebug, + Services: []application.Service{ + application.NewServiceWithOptions(services.NewGinService(), application.ServiceOptions{ + Route: "/api", + }), + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, +}) +``` + +In this example, the Gin service is mounted at the `/api` route. This means that if your Gin router has an endpoint defined as `/info`, it will be accessible at `/api/info` in your application. This approach allows you to organize your API endpoints logically and avoid conflicts with other parts of your application. + +## Integrating with the Wails Event System + +One of the powerful features of using Gin with Wails Services is the ability to seamlessly integrate with the Wails event system. This allows for real-time communication between your backend service and the frontend. + +In your service's `ServiceStartup` method, you can register event handlers to process events from the frontend: + +```go +s.app.OnEvent("gin-api-event", func(event *application.CustomEvent) { + // Log the event data + s.app.Logger.Info("Received event from frontend", "data", event.Data) + + // Emit an event back to the frontend + s.app.EmitEvent("gin-api-response", map[string]interface{}{ + "message": "Response from Gin API Service", + "time": time.Now().Format(time.RFC3339), + }) +}) +``` + +You can also emit events to the frontend from your Gin routes or other parts of your service: + +```go +// After creating a new user +s.app.EmitEvent("user-created", newUser) +``` + +## Frontend Integration + +To interact with your Gin service from the frontend, you'll need to import the Wails runtime, make HTTP requests to your API endpoints, and use the Wails event system for real-time communication. + +For production use, it's recommended to use the `@wailsio/runtime` package instead of directly importing `/wails/runtime.js`. This ensures type safety, better IDE support, version management through npm, and compatibility with modern JavaScript tooling. + +Install the package: +```bash +npm install @wailsio/runtime +``` + +Then use it in your code: +```javascript +import * as wails from '@wailsio/runtime'; + +// Event emission +wails.Events.Emit({name: 'gin-api-event', data: eventData}); +``` + +Here's an example of how to set up frontend integration: + +```html + + + + + +Try the Gin API endpoints mounted at /api:
+ + + + + + +Results will appear here...+
Trigger an event to communicate with the Gin service:
+ + + +Event responses will appear here...+
This page is being served by Gin.
+Click the button below to trigger a Wails event:
+ + +Try the Gin API endpoint:
@@ -48,17 +55,42 @@