Creating a Service
Services are the backbone of your application. They handle business logic and manage state.
In this guide, we’ll create a new service that generates QR codes from text. This will show you how to organize your code into reusable services and handle external dependencies.
-
Create the QR Service file
Create a new file called
qrservice.go
in your application directory and add the following code:qrservice.go package mainimport ("github.com/skip2/go-qrcode")// QRService handles QR code generationtype QRService struct {// We can add state here if needed}// NewQRService creates a new QR servicefunc NewQRService() *QRService {return &QRService{}}// GenerateQRCode creates a QR code from the given textfunc (s *QRService) GenerateQRCode(text string, size int) ([]byte, error) {// Generate the QR codeqr, err := qrcode.New(text, qrcode.Medium)if err != nil {return nil, err}// Convert to PNGpng, err := qr.PNG(size)if err != nil {return nil, err}return png, nil}
-
Register the Service
Update your
main.go
to use the new QR service:main.go func main() {app := application.New(application.Options{Name: "myproject",Description: "A demo of using raw HTML & CSS",LogLevel: slog.LevelDebug,Services: []application.Service{application.NewService(NewQRService()),},Assets: application.AssetOptions{Handler: application.AssetFileServerFS(assets),},Mac: application.MacOptions{ApplicationShouldTerminateAfterLastWindowClosed: true,},})app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{Title: "myproject",Width: 600,Height: 400,})// Run the application. This blocks until the application has been exited.err := app.Run()// If an error occurred while running the application, log it and exit.if err != nil {log.Fatal(err)}}
-
Update go.mod
Update your
go.mod
dependencies to include thegithub.com/skip2/go-qrcode
package:go mod tidy
-
Generate the Bindings
To call these methods from your frontend, we need to generate bindings. You can do this by running
wails generate bindings
in your project root directory.Once you’ve run this, you should see something similar to the following in your terminal:
Terminal window % wails3 generate bindingsINFO Processed: 337 Packages, 1 Service, 1 Method, 0 Enums, 0 Models in 740.196125ms.INFO Output directory: /Users/leaanthony/myproject/frontend/bindingsYou should notice that in the frontend directory, there is a new directory called
bindings
:Terminal window frontend/└── bindings└── changeme├── index.js└── qrservice.js
-
Understanding the Bindings
Let’s look at the generated bindings in
bindings/changeme/qrservice.js
:bindings/changeme/qrservice.js // @ts-check// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL// This file is automatically generated. DO NOT EDIT/*** QRService handles QR code generation* @module*/// eslint-disable-next-line @typescript-eslint/ban-ts-comment// @ts-ignore: Unused importsimport {Call as $Call, Create as $Create} from "@wailsio/runtime";/*** GenerateQRCode creates a QR code from the given text* @param {string} text* @param {number} size* @returns {Promise<string> & { cancel(): void }}*/export function GenerateQRCode(text, size) {let $resultPromise = /** @type {any} */($Call.ByID(3576998831, text, size));let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {return $Create.ByteSlice($result);}));$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);return $typingPromise;}We can see that the bindings are generated for the
GenerateQRCode
method. The parameter names have been preserved, as well as the comments. JSDoc has also been generated for the method to provide type information to your IDE.The bindings provide:
- Functions that are equivalent to your Go methods
- Automatic conversion between Go and JavaScript types
- Promise-based async operations
- Type information as JSDoc comments
-
Use Bindings in Frontend
Firstly, update
frontend/src/main.js
to use the new bindings:frontend/src/main.js import { GenerateQRCode } from './bindings/changeme/qrservice.js';async function generateQR() {const text = document.getElementById('text').value;if (!text) {alert('Please enter some text');return;}try {// Generate QR code as base64const qrCodeBase64 = await GenerateQRCode(text, 256);// Display the QR codeconst qrDiv = document.getElementById('qrcode');qrDiv.src = `data:image/png;base64,${qrCodeBase64}`;} catch (err) {console.error('Failed to generate QR code:', err);alert('Failed to generate QR code: ' + err);}}export function initializeQRGenerator() {const button = document.getElementById('generateButton');button.addEventListener('click', generateQR);}Now update
index.html
to use the new bindings in theinitializeQRGenerator
function:frontend/src/index.html <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>QR Code Generator</title><style>body {font-family: Arial, sans-serif;display: flex;flex-direction: column;align-items: center;justify-content: center;height: 100vh;margin: 0;}#qrcode {margin-bottom: 20px;width: 256px;height: 256px;display: flex;align-items: center;justify-content: center;}#controls {display: flex;gap: 10px;}#text {padding: 5px;}#generateButton {padding: 5px 10px;cursor: pointer;}</style></head><body><img id="qrcode"/><div id="controls"><input type="text" id="text" placeholder="Enter text"><button id="generateButton">Generate QR Code</button></div><script type="module">import { initializeQRGenerator } from './main.js';document.addEventListener('DOMContentLoaded', initializeQRGenerator);</script></body></html>Run
wails3 dev
to start the dev server. After a few seconds, the application should open.Type in some text and click the “Generate QR Code” button. You should see a QR code in the center of the page:
-
Alternative Approach
So far, we have covered the following areas:
- Creating a new Service
- Generating Bindings
- Using the Bindings in our Frontend code
If the aim of your service is to serve files/assets/media to the frontend, like a traditional web server, then there is an alternative approach to achieve the same result.
If your service defines Go’s standard http handler function
ServeHTTP(w http.ResponseWriter, r *http.Request)
, then it can be made accessible on the frontend. Let’s extend our QR code service to do this:qrservice.go package mainimport ("github.com/skip2/go-qrcode""net/http""strconv")// QRService handles QR code generationtype QRService struct {// We can add state here if needed}// NewQRService creates a new QR servicefunc NewQRService() *QRService {return &QRService{}}// GenerateQRCode creates a QR code from the given textfunc (s *QRService) GenerateQRCode(text string, size int) ([]byte, error) {// Generate the QR codeqr, err := qrcode.New(text, qrcode.Medium)if err != nil {return nil, err}// Convert to PNGpng, err := qr.PNG(size)if err != nil {return nil, err}return png, nil}func (s *QRService) ServeHTTP(w http.ResponseWriter, r *http.Request) {// Extract the text parameter from the requesttext := r.URL.Query().Get("text")if text == "" {http.Error(w, "Missing 'text' parameter", http.StatusBadRequest)return}// Extract Size parameter from the requestsizeText := r.URL.Query().Get("size")if sizeText == "" {sizeText = "256"}size, err := strconv.Atoi(sizeText)if err != nil {http.Error(w, "Invalid 'size' parameter", http.StatusBadRequest)return}// Generate the QR codeqrCodeData, err := s.GenerateQRCode(text, size)if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}// Write the QR code data to the responsew.Header().Set("Content-Type", "image/png")w.Write(qrCodeData)}Now update
main.go
to specify the route that the QR code service should be accessible on:main.go func main() {app := application.New(application.Options{Name: "myproject",Description: "A demo of using raw HTML & CSS",LogLevel: slog.LevelDebug,Services: []application.Service{application.NewService(NewQRService(), application.ServiceOptions{Route: "/qrservice",}),},Assets: application.AssetOptions{Handler: application.AssetFileServerFS(assets),},Mac: application.MacOptions{ApplicationShouldTerminateAfterLastWindowClosed: true,},})app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{Title: "myproject",Width: 600,Height: 400,})// Run the application. This blocks until the application has been exited.err := app.Run()// If an error occurred while running the application, log it and exit.if err != nil {log.Fatal(err)}}Finally, update
main.js
to make the image source the path to the QR code service, passing the text as a query parameter:frontend/src/main.js import { GenerateQRCode } from './bindings/changeme/qrservice.js';async function generateQR() {const text = document.getElementById('text').value;if (!text) {alert('Please enter some text');return;}const img = document.getElementById('qrcode');// Make the image source the path to the QR code service, passing the textimg.src = `/qrservice?text=${text}`}export function initializeQRGenerator() {const button = document.getElementById('generateButton');if (button) {button.addEventListener('click', generateQR);} else {console.error('Generate button not found');}}Running the application again should result in the same QR code: