I'll assume you have a comprehensive understanding of JavaScript and frontend. I'll just focus on the Go code and to familiarize ourselves a bit with Go, we'll create a simple desktop app with Wails to track our subscriptions.
Simply put Wails is like the Tauri of the Go world, they both leverage the native web view of your operating system (WebView 2 in my case as I'm developing on windows) to build desktop apps, but while Tauri uses Rust as the backend logic language, Wails uses Go.
Prerequisites
- Make sure you have Go (1.18+) on your system
- Make sure you have Node & NPM on your system
Wails mentions in their docs how you can download, configure and verify these tools on your system.
Install the Wails CLI
Installing the Wails CLI via go install will allow us to call the Wails cli from anywhere in our system
go install github.com/wailsapp/wails/v2/cmd/wails@latest
Generate a Wails app
Generate a wails app with React and TypeScript, via the CLI
(or with your favorite flavor. Svelte, Vue, Lit, Preact or Vanilla JS)
wails init -n myproject -t react-ts
or without TypeScript
if you prefer
wails init -n myproject -t react
You can run your application in development mode by running wails dev
from your project directory.
Typically package main
and usually main.go
are the entry point of any go program and we'll rarely need to touch this file, but this will create and run our app.
main.go
package main
import (
"embed"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
var assets embed.FS
func main() {
// Create an instance of the app structure
app := NewApp()
// Create application with options
err := wails.Run(&options.App{
Title: "Simple Suscription Tracker",
Width: 1080,
Height: 710,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{
app,
},
})
if err != nil {
println("Error:", err.Error())
}
}
OnStartup
calls a method called startup on our app instance, which is found in app.go
We define a method by using parens after the func
keyword and before the function name, passing in a the thing itself with a pointer type, which references the struct in memory.
In this case, the startup method is already defined for us
// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}
//NOTE: CONNECTING TO A DB FROM A DESTOP APP IS NOT BEST PRACTICES AND NOT RECOMMENED, THIS IS JUST FOR LEARNING PURPOSES, TO LEARN GORM.
In this project, I'm using gorm to connect to a railway Postgres Database to persist subscriptions, so I'll create a method to connect and create the database tables if they don't already exist.
func (a *App) connectToDB() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
return
}
dsn := os.Getenv("DATABASE_URL")
conn, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Error connecting to the db")
return
}
db = conn
log.Println("Connected To DB")
}
func (a *App) createTables() {
db.AutoMigrate(&Subscription{})
}
The connectToDB
func needs a string to connect to the DB an we don't want to expose it in our code, so we'll utilize gotdotenv to load the env variable from our .env and then add it to our .gitignore
.
Install godotenv and gorm
go get github.com/joho/godotenv
go get -u gorm.io/gorm
And if like me, you're using Postgres, you'll need to install the driver for gorm
gorm.io/driver/postgres
You'll notice the connection is assigned to a variable db
, that is outside the scope of the function. I defined it globally, so we can use it from other functions and in go, to initialize a variable without a value, we use the var
keyword and give it a type.
var db *gorm.DB
The type here is a pointer to the gorm connection.
Now we can call these two methods on our app on startup by adding them to the startup func
.
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
a.connectToDB()
a.createTables()
}
Go has a concept of a struct
, a struct defines a custom data structure
type Subscription struct {
Id string `gorm:"primaryKey" json:"Id"`
Name string `json:"Name"`
Price string `json:"Price"`
CreatedAt time.Time `json:"CreatedAt"`
// Automatically managed by GORM for creation time
UpdatedAt time.Time `json:"UpdatedAt"`
// Automatically managed by GORM for update time
}
The code inside of the backticks in the struct are known as field tags, they are optional and tell certain libraries how to treat each struct field and gorm has many.
gorm:"primaryKey"
is pretty self explanatory, but it specifies the column as primary key, gorm automatically uses fields named CreatedAt
and UpdatedAt
to automatically track the creation and update times of records.
GORM simplifies database interactions by mapping Go structs to database tables and AutoMigrate
runs auto migration for given models.
Here, we migrate our subscription struct and pass in an empty struct
.
func (a *App) createTables() {
db.AutoMigrate(&Subscription{})
}
Now we have our DB setup, we'll define some functions to be called from our frontend. You'll notice these functions are capitalized here too, that's to ensure they can be exported from that package.
func (a *App) GetAllRecords() (string, error) {
var subscriptions []Subscription
result := db.Find(&subscriptions)
if result.Error != nil {
log.Fatalln("Error executing query:", result.Error)
}
data, err := json.Marshal(subscriptions)
if err != nil {
log.Fatalf("Failed to marshal data: %v", err)
}
return string(data), nil
}
func (a *App) CreateRecord(name string, price string) {
v4, err := uuid.NewRandom()
if err != nil {
log.Println("Error creating uuid", err)
return
}
v4Str := v4.String()
log.Println(name)
log.Println(price)
subscription := Subscription{Id: v4Str, Name: name, Price: price}
result := db.Create(&subscription)
log.Printf(fmt.Sprint(result.RowsAffected))
}
func (a *App) GenUUID() (string, error) {
v4, err := uuid.NewRandom()
if err != nil {
return "", err
}
return v4.String(), nil
}
func (a *App) DeleteById(id string) {
db.Delete(&Subscription{}, "Id = ?", id)
}
Now we can import and call our functions from our frontend.
frontend\src\App.tsx
import { CreateRecord, GetAllRecords } from "../wailsjs/go/main/App";
If you'd like me to cover what I'm doing in the TS and cover more of the go code, let me know.
The github repo can be found here
Top comments (0)