
Serie Vue.js + Go con Echo v5: Parte 1: entorno y primera API · Parte 2: frontend Vue con Vite · Parte 3: API REST de tareas · Parte 4: inicio de sesión con sesión y cookie · Parte 5: embed, binario único y Docker
Código completo: todos los archivos de la serie están en el gist vue-go-echo-v5, con un README que muestra en qué carpeta se encuentra cada uno.
En la parte 1 subimos la primera ruta de la API con Echo v5. Ahora es el turno del frontend: vamos a crear un proyecto Vue 3 con Vite dentro del repositorio Go, instalar Vue Router y configurar el proxy de desarrollo. Con el proxy, el navegador ve el frontend y la API en el mismo origen, sin CORS, que es exactamente lo que ocurre en producción cuando Go sirva el Vue incrustado.
Crea el proyecto Vue con Vite
Vite es la herramienta oficial de build del ecosistema Vue: servidor de desarrollo con recarga instantánea y build de producción optimizado. En la raíz del proyecto (vueapp/), crea el frontend en la carpeta web con la plantilla vue:
cd vueapp
npm create vite@latest web -- --template vue --no-interactive --no-immediate
cd web
npm install
npm install vue-router@4
Las opciones evitan las preguntas interactivas: --template vue elige Vue con JavaScript, y --no-immediate no levanta el servidor automáticamente. Si prefieres TypeScript, usa --template vue-ts; el resto de la serie funciona igual, solo con extensiones .ts.
En la prueba de esta serie, el package.json quedó con:
{
"name": "web",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.5.42",
"vue-router": "^4.6.4"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.8",
"vite": "^8.3.0"
}
}
Borra el componente de ejemplo, que no vamos a usar:
rm -rf src/components src/assets/*.svg
mkdir -p src/views
Proxy de Vite para la API Go
En desarrollo corren dos servidores: el Vite en localhost:5173 y el Go en 127.0.0.1:8080. Si el navegador llamara a Go directamente, serían orígenes diferentes y tendrías que configurar CORS, y la cookie de sesión de la parte 4 lo complicaría aún más. La salida más simple es que Vite reenvíe /api a Go. Edita web/vite.config.js:
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [vue()],
server: {
// Em desenvolvimento, o Vite (5173) repassa /api para o Go (8080)
proxy: {
'/api': 'http://127.0.0.1:8080',
},
},
})
Así el navegador solo habla con localhost:5173. En producción, Go sirve el frontend y la API en el mismo puerto, y el código Vue no cambia nada: siempre llama /api/... con ruta relativa.
Un cliente de API pequeño
No necesitamos una biblioteca HTTP. Crea web/src/api.js con una función que envía y recibe JSON y transforma respuestas de error en excepción con el mensaje de la API:
// Pequeno cliente da API: JSON na ida e na volta, cookie de sessão automático.
export async function api(method, path, body) {
const res = await fetch(`/api${path}`, {
method,
headers: body ? { 'Content-Type': 'application/json' } : {},
body: body ? JSON.stringify(body) : undefined,
})
if (res.status === 204) return null
const data = await res.json().catch(() => ({}))
if (!res.ok) {
const err = new Error(data.message || `HTTP ${res.status}`)
err.status = res.status
throw err
}
return data
}
Como la API está en el mismo origen, el fetch envía la cookie de sesión automáticamente; el estándar credentials: 'same-origin' ya basta. Echo devuelve los errores en el formato {"message": "..."}, entonces data.message vira el texto mostrado en la pantalla.
Rutas del Vue Router
Tendremos dos páginas: /login e /tarefas. Crea web/src/router.js. La protección de ruta (meta.auth) ya queda declarada aquí y pasa a funcionar cuando exista la API de inicio de sesión, en la parte 4:
import { createRouter, createWebHistory } from 'vue-router'
import { api } from './api'
import LoginView from './views/LoginView.vue'
import TasksView from './views/TasksView.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', redirect: '/tarefas' },
{ path: '/login', component: LoginView },
{ path: '/tarefas', component: TasksView, meta: { auth: true } },
],
})
// Antes de abrir uma página protegida, pergunta ao Go se a sessão vale
router.beforeEach(async (to) => {
if (!to.meta.auth) return true
try {
await api('GET', '/me')
return true
} catch {
return { path: '/login', query: { next: to.fullPath } }
}
})
export default router
O createWebHistory() usa URL limpias, como /tarefas, sin el #. El precio es que el servidor tiene que devolver el index.html cuando alguien abre /tarefas directamente en el navegador. Vite lo hace solo en desarrollo; en la parte 5 configuramos Echo para hacer lo mismo.
Conecta el router en web/src/main.js y reduce el App.vue a un contenedor con el <RouterView />:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
<template>
<main class="container">
<RouterView />
</main>
</template>
Páginas provisionales
Las pantallas completas vienen en las partes 3 y 4. Por ahora, crea las dos vistas con una llamada a la ruta /api/health de la parte 1, para demostrar que el proxy funciona. web/src/views/TasksView.vue:
<script setup>
import { onMounted, ref } from 'vue'
import { api } from '../api'
const status = ref('...')
onMounted(async () => {
status.value = (await api('GET', '/health')).status
})
</script>
<template>
<section class="card">
<h1>Tarefas</h1>
<p>API: {{ status }}</p>
</section>
</template>
Y un web/src/views/LoginView.vue mínimo, para que la ruta exista:
<template>
<section class="card"><h1>Entrar</h1></section>
</template>
Mientras la ruta /api/me no existe, la guarda del router envía /tarefas al login. Para probar el proxy en esta etapa, comenta temporalmente el meta: { auth: true }.
Ejecuta los dos servidores
Terminal 1, en la raíz del proyecto:
go run .
Terminal 2, en web/:
npm run dev
VITE v8.3.0 ready in 122 ms
➜ Local: http://localhost:5173/
Abre http://localhost:5173/tarefas: la página muestra API: ok, procedente de Go a través del proxy. También se puede comprobar desde la terminal:
curl http://localhost:5173/api/health
{"status":"ok"}
Fíjate en la dirección: Vite escucha en localhost, que en muchos sistemas se resuelve a ::1 (IPv6). Si curl http://127.0.0.1:5173 rechaza la conexión, usa localhost, o levanta Vite con npm run dev -- --host 127.0.0.1.
Build de producción
Para ver lo que Go va a incrustar al final, ejecuta el build:
npm run build
dist/index.html 0.45 kB │ gzip: 0.29 kB
dist/assets/index-o26dGMpn.css 0.92 kB │ gzip: 0.49 kB
dist/assets/index-U0LYTB29.js 92.56 kB │ gzip: 35.69 kB
✓ built in 59ms
La carpeta web/dist tiene un index.html y los archivos con hash en el nombre, listos para caché de larga duración. El .gitignore generado por Vite ya ignora dist; en la parte 5 eso va a importar para el go:embed.
Archivos de esta parte en el gist
Cada archivo abre directamente en el gist de la serie. El gist trae la versión final del proyecto: main.go e routes.go también obtienen login en la parte 4 y el frontend integrado en la parte 5.
vite.config.js(enweb/vite.config.js)api.js(enweb/src/api.js)router.js(enweb/src/router.js)main.js(enweb/src/main.js)App.vue(enweb/src/App.vue)
Próximo paso
El frontend y el backend ya se comunican. En la parte 3 escribimos la API REST de tareas en Echo v5, con grupo de rutas, Bind de JSON, validación, códigos HTTP correctos y la pantalla de tareas completa en Vue.
Serie Vue.js + Go con Echo v5: Parte 1: entorno y primera API · Parte 2: frontend Vue con Vite · Parte 3: API REST de tareas · Parte 4: inicio de sesión con sesión y cookie · Parte 5: embed, binario único y Docker