Skip to content

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.


  1. Create the QR Service file

    Create a new file called qrservice.go in your application directory and add the following code:

    qrservice.go
    package main
    import (
    "github.com/skip2/go-qrcode"
    )
    // QRService handles QR code generation
    type QRService struct {
    // We can add state here if needed
    }
    // NewQRService creates a new QR service
    func NewQRService() *QRService {
    return &QRService{}
    }
    // GenerateQRCode creates a QR code from the given text
    func (s *QRService) GenerateQRCode(text string, size int) ([]byte, error) {
    // Generate the QR code
    qr, err := qrcode.New(text, qrcode.Medium)
    if err != nil {
    return nil, err
    }
    // Convert to PNG
    png, err := qr.PNG(size)
    if err != nil {
    return nil, err
    }
    return png, nil
    }

  2. 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)
    }
    }

  3. Update go.mod

    Update your go.mod dependencies to include the github.com/skip2/go-qrcode package:

    go mod tidy

  4. 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 bindings
    INFO Processed: 337 Packages, 1 Service, 1 Method, 0 Enums, 0 Models in 740.196125ms.
    INFO Output directory: /Users/leaanthony/myproject/frontend/bindings

    You should notice that in the frontend directory, there is a new directory called bindings:

    Terminal window
    frontend/
    └── bindings
    └── changeme
    ├── index.js
    └── qrservice.js

  5. 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 imports
    import {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

  6. 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 base64
    const qrCodeBase64 = await GenerateQRCode(text, 256);
    // Display the QR code
    const 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 the initializeQRGenerator 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:

    QR Code

  7. 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 main
    import (
    "github.com/skip2/go-qrcode"
    "net/http"
    "strconv"
    )
    // QRService handles QR code generation
    type QRService struct {
    // We can add state here if needed
    }
    // NewQRService creates a new QR service
    func NewQRService() *QRService {
    return &QRService{}
    }
    // GenerateQRCode creates a QR code from the given text
    func (s *QRService) GenerateQRCode(text string, size int) ([]byte, error) {
    // Generate the QR code
    qr, err := qrcode.New(text, qrcode.Medium)
    if err != nil {
    return nil, err
    }
    // Convert to PNG
    png, 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 request
    text := r.URL.Query().Get("text")
    if text == "" {
    http.Error(w, "Missing 'text' parameter", http.StatusBadRequest)
    return
    }
    // Extract Size parameter from the request
    sizeText := 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 code
    qrCodeData, err := s.GenerateQRCode(text, size)
    if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
    }
    // Write the QR code data to the response
    w.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 text
    img.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:

    QR Code