Fullstack web development con Go & ViteJS


Chi sono?
Antonio De Lucreziis, studente di Matematica e macchinista del PHC
Il PHC è un gruppo di studenti di Matematica con interessi per, open source, Linux, self-hosting e soprattutto smanettare sia con hardware e software (veniteci pure a trovare!)

Cosa stiamo per creare?
Applicazione Contatore "Full Stack"
https://example.xyz/
Contatore: 73
Incrementa
Decrementa
Applicazione "Contatore"
- Si va sul sito e poi si può premere uno di due pulsanti
- Un tasto incrementa il valore mentre l'altro lo decrementa
- Vedremo come anche un'applicazione semplice di questo tipo in realtà ci permette di trattare di molti concetti tra cui frontend, backend ed API (e se ce la facciamo anche Database)
Cos'è più precisamente una richiesta?
HTML
CSS
JS
Go
Frontend
Backend
Client
Server
Go e ViteJS
Go
ViteJS
Go, linguaggio di programmazione compilato, statically typed e con una libreria standard con tutte le cose essenziali.
Un tool per NodeJS per creare velocemente web app in HTML, JS, CSS utilizzando tutto l'ecosistema di NodeJS e NPM senza configurare quasi nulla
Perché Go e ViteJS?
Front-End
Back-End
-
Javascript
-
Typescript
- WASM*
- Webpack
- ...
-
Python
-
NodeJS
-
Golang
- Rust
- ...
HTTP 101
Cosa succede quando proviamo ad andare su un sito?
Utente
Server
Connessione Client-Server
Utente
Server
http://example.xyz
Connessione Client-Server
URL?
Utente
Server
http://example.xyz
Connessione Client-Server
Utente
Server
http://192.0.2.42:80
Il DNS si occupa di risolvere il dominio e convertirlo in un IPv4 (o IPv6)
Connessione Client-Server
Ehm, e l'HTTPS?
Client
Server
Connessione TLS
Scambio di chiavi
Scambio di dati in modo sicuro (HTTP)
?
Server (Macchina)
:80
example.xyz (192.0.2.42)
Server (Macchina)
:80
Server (Programma)
example.xyz (192.0.2.42)
Server (Macchina)
:80
Server (Programma)
Altro programma (Client)
?
Server (Macchina)
:80
Server (Programma)
localhost:80
(127.0.0.1:80)
Altro programma (Client)
:80
:80
FIREWALL
Creazione del progetto
$ mkdir gdg-counter-website
$ cd gdg-counter-websiteCreazione progetto
# inizializziamo il file "go.mod"
$ go mod init gdg-counter-website
# o se avessimo un repo git anche così...
$ go mod init github.com/aziis98/gdg-counter-website
Inizializzazione progetto in Go
# Crea il file "package.json"
$ npm init
# Installa ViteJS come dipendenza di development
$ npm install -D vite
Inizializzazione progetto di NodeJS
O anche con un altro package manager alternativo a npm (ad esempio io userò pnpm)
ViteJS (1)
{
"name": "frontend",
"version": "1.0.0",
"scripts": {
// avvia il server di ViteJS in modalità di development
"dev": "vite",
// crea la cartella "dist/" con tutti gli asset e bundle
"build": "vite build"
},
"devDependencies": {
"vite": "^3.2.3"
}
}
Configurazione di base
package.json
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>GDG Talk Counter</title>
</head>
<body>
<h1>GDG Counter Website</h1>
<div class="app">
<div id="counter-value">???</div>
<button id="btn-decrement">Decrementa</button>
<button id="btn-increment">Incrementa</button>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>Pagina HTML
index.html
const counterElement = document.querySelector('#counter-value')
const incrementButton = document.querySelector('#btn-increment')
const decrementButton = document.querySelector('#btn-decrement')
function updateCounter(value) {
counterElement.textContent = `Counter: ${value}`
}
/* ... */Un po' di JS
src/main.js
Client
Server
PATCH example.xyz/a/b/c
Client
Server
POST example.xyz/a/b/c
Client
Server
GET example.xyz/a/b/c
Client
Server
DELETE example.xyz/a/b/c
Client
Server
PUT example.xyz/a/b/c
Client
Server
PATCH example.xyz/a/b/c
Client
Server
POST example.xyz/a/b/c
Client
Server
GET example.xyz/a/b/c
Client
Server
DELETE example.xyz/a/b/c
Client
Server
PUT example.xyz/a/b/c
Client
Server
GET /api/value
valore aggiornato del contatore
Client
Server
POST /api/increment
valore aggiornato del contatore
Client
Server
POST /api/decrement
valore aggiornato del contatore
/* ... */
incrementButton.addEventListener('click', () => {
fetch('/api/increment', { method: 'POST' })
.then(res => res.json())
.then(data => updateCounter(data))
})
decrementButton.addEventListener('click', () => {
fetch('/api/decrement', { method: 'POST' })
.then(res => res.json())
.then(data => updateCounter(data))
})
fetch('/api/value')
.then(res => res.json())
.then(data => updateCounter(data))Colleghiamo i bottoni con il server
src/main.js
/* ... */
incrementButton.addEventListener('click', async () => {
const res = await fetch('/api/increment', { method: 'POST' })
const data = await res.json()
updateCounter(data)
})
decrementButton.addEventListener('click', async () => {
const res = await fetch('/api/decrement', { method: 'POST' })
const data = await res.json()
updateCounter(data)
})
async function main() {
const res = await fetch('/api/value')
const data = await res.json()
updateCounter(data)
}
main()
Stessa cosa ma con async-await
src/main.js
import { defineConfig } from 'vite'
export default defineConfig({
server: {
port: 3000,
},
})Configurazione base per ViteJS
Demo di quanto fatto fino ad ora
Go (1)
package main
import (
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
setupRoutes(mux) // visto meglio in seguito
server := http.Server{
Addr: ":4000",
Handler: mux,
}
log.Printf("Starting server on port 4000...")
log.Fatal(server.ListenAndServe())
}
Iniziamo a scrivere il server in Go
main.go
/* ... */
func setupRoutes(mux *http.ServeMux) {
counter := 0
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("ok")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
mux.Handle("/", http.FileServer((http.Dir("./dist/"))))
}Serviamo gli asset principali
main.go
ViteJS (2)
import { defineConfig } from 'vite'
export default defineConfig({
server: {
port: 3000,
proxy: {
'/api': 'http://localhost:4000/',
},
},
})Configurazione per ViteJS
vite.config.js
Proxy di ViteJS per il server in Go
Development
ViteJS Dev Server
Go Server
:3000 /api/...
:3000 /...
:4000 /api/...
{...}
{...}
:3000
Proxy di ViteJS per il server in Go
Production
Go Server
:4000 /...
:4000 /api/...
dist/...
{...}
:4000
Demo (2)
Go (2)
/* ... */
func setupRoutes(mux *http.ServeMux) {
counter := 0
/* ... */
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
}
})
/* ... */
}Route di API
main.go
counter := 0
/* ... */
mux.HandleFunc("/api/increment", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
counter++
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(counter)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
Route di API (Incrementa)
main.go
counter := 0
/* ... */
mux.HandleFunc("/api/decrement", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
counter--
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(counter)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
Route di API (Decrementa)
main.go
Demo (3)
Conclusione
Come Pubblicarlo?
Ci sono alcuni siti che forniscono "container" con una quota gratuita di utilizzo al mese che sono molto comodi per fare prove all'inizio
Come Pubblicarlo?
- Affittare un server (ai nostri tempi siamo scesi a circa ~5€/mese) dai provider classici come AWS (Amazon), Google Cloud, Azure...
- Tenere un server in casa propria
- Aggiungere un proxy per tenere più di un server:
Risorse
-
https://git.phc.dm.unipi.it/aziis98/gdg-counter-website/
Tutto il codice versionato mostrato in questo talk. -
https://replit.com/@aziis98/go-and-vite-js
Progetto su repl.it con il codice di questo talk e dato che repl è bello ci serve anche il progetto su un sito vero. - https://slides.com/aziis98/go-and-vite-js
Pagina web con queste slide
Possibili Sviluppi
- Aggiungere del CSS per migliorare l'aspetto della pagina
- Database: SQLite (Per non resettare i dati ogni volta che il server viene spento)
- Login e Utenti (Ogni utente ha un proprio counter su
/u/USERNAMEche gli altri possono cambiare) - Framework JS per la Frontend: ad esempio Preact (Per ora abbiamo fatto tutto in js "vanilla")
- Dockerfile (per automatizzare il deployment)
- WebSocket/SSE per vedere il counter aggiornarsi in diretta
- SSR / Template (Inviare al client HTML già con i valori giusti, al momento inizialmente il counter è "???" e senza JS non si può neanche vedere il valore corrente del counter)
live code fino alla fine del tempo*
*in realtà ho già preparato quasi tutte le varianti
Go & ViteJS
By aziis98
Go & ViteJS
- 264