You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5.3 KiB

Go & ViteJS

Go

  • Go, linguaggio di programmazione compilato, statically typed e con una libreria standard con tutte le cose essenziali.

  • Ideale per scrivere server HTTP.

ViteJS

  • On demand file serving over native ESM, no bundling required!
  • Hot Module Replacement (HMR) that stays fast regardless of app size.
  • Out-of-the-box support for TypeScript, JSX, CSS and more.
  • Pre-configured Rollup build with multi-page and library mode support.
  • Rollup-superset plugin interface shared between dev and build.
  • Flexible programmatic APIs with full TypeScript typing.

Documentazione di ViteJS


Creazione progetto in Golang

Inizializziamo il progetto in Go per la backend

# inizializziamo il file go.mod
$ go mod init gdg-talk-counter

# o anche così...
$ go mod init github.com/aziis98/gdg-talk-counter

Creazione progetto in ViteJS

Ed il progetto in ViteJS per la frontend

$ npm init              # creates the "package.json" file
$ npm install -D vite

(o anche con un altro package manager alternativo a npm, ad esempio io userò pnpm)


Il file "package.json"

{
    ...
    "scripts": {
        "dev": "vite",              // avvia il server di ViteJS in modalità di development
        "build": "vite build"       // crea la cartella "dist/" con tutti gli asset e bundle
    },
    ...
}
$ npm run dev
$ npm run build

Il file "src/main.go"

Impostiamo un piccolo server sulla porta :4000 per servire gli asset e l'API

package main

...

func main() {
    mux := http.NewServeMux()

    setupRoutes(mux) // vedremo meglio questo in seguito
    
    server := http.Server{
        Addr:    ":4000",
        Handler: mux,
    }

    log.Fatal(server.ListenAndServe())
}

Configurazione di ViteJS

// vite.config.js

import { defineConfig } from 'vite'

export default defineConfig({
    server: {
        port: 3000,
        proxy: {
            '/api': 'http://localhost:4000/',
        },
    },
})

ViteJS come proxy per il server in Go

Development:

~~~graph-easy --as=boxart
[ The developer ] -- :3000 --> [ Dev. Server ]
~~~
~~~graph-easy --as=boxart
[ The developer ] -- :3000/api/... --> [ Dev. Server ] -- :4000/api/... --> [ Backend ]
~~~

Production:

~~~graph-easy --as=boxart
[ The user ] -- :4000 --> [ Backend ]
~~~

Il file "index.html"

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>GDG Talk Counter</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <h1>GDG Talk Counter</h1>

        <div class="counter">Carico...</div>
        <button id="btn-increment">Incrementa</button>

        <script type="module" src="/src/main.js"></script>
    </body>
</html>

Inizializzazione della frontend (VanillaJS)

function mountApp($root) {
    const $counter = $root.querySelector('.counter')
    const $btnIncrement = $root.querySelector('#btn-increment')

    const Counter = mountCounter($counter)

    $btnIncrement.addEventListener('click', async () => {
        Counter.increment()
    })

    setInterval(async () => {
        Counter.refresh()
    }, 1000)
}

mountApp(document.body)

Codice per il contatore

import { fetchJSON } from './utils.js'

function mountCounter($counter) {
    function render(count) {
        $counter.textContent = `Contatore: ${count}`
    }

    async function refresh() {
        const count = await fetchJSON('/api/counter')
        render(count)
    }

    async function increment() {
        const count = await fetchJSON('/api/increment', { method: 'POST' })
        render(count)
    }

    refresh()

    return { refresh, increment }
}

Tornando alla backend

func setupRoutes(mux *http.ServeMux) {
    // The database
    counter := 0

    // GET /api/status
    mux.HandleFunc("/api/status", func(w http.ResponseWriter, r *http.Request) {
        ...
    })
    // GET /api/counter
    mux.HandleFunc("/api/counter", func(w http.ResponseWriter, r *http.Request) {
        ...
    })
    // POST /api/increment
    mux.HandleFunc("/api/increment", func(w http.ResponseWriter, r *http.Request) {
        ...
    })

    // Static Files
    mux.Handle("/", http.FileServer((http.Dir("./dist/"))))
}

La route GET /api/status

type object map[string]any

mux.HandleFunc("/api/status", func(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodGet {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    w.Header().Set("Content-Type", "application/json")

    err := json.NewEncoder(w).Encode(&object{ "status": "ok" })
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
})
mux.HandleFunc("/api/value", func(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodGet {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    err := json.NewEncoder(w).Encode(counter)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
})