用 Go + Vue 3 开发网站,真正费时间的通常不是写出第一个接口或页面,而是把数据库迁移、前后端联调、健康检查、容器构建和生产配置连成一套可重复的流程。
这篇教程会完成一条真实的全栈链路:Vue 3 页面通过类型化 API 客户端调用 Go 服务,Go 将数据写入 PostgreSQL,并通过 Docker Compose 同时管理 Web、API、数据库与 Redis。最后,我们会把同一套项目切换到生产形态并部署到服务器。
如果你只想尽快跑起来,可以直接使用我整理的开源模板:glosc-ai/template-go-vue3-docker。它自带任务 CRUD,不是一个只能看到空白首页的“目录模板”。下文所有命令和目录都基于该项目截至 2026 年 8 月 5 日的 main 分支。
最终会得到什么?
运行完成后,整个请求链路如下:
1 | 浏览器 |
项目内置的任务模块支持四个接口:
| 方法 | 路径 | 功能 |
|---|---|---|
GET |
/api/v1/tasks |
查询最近 100 条任务 |
POST |
/api/v1/tasks |
创建任务 |
PATCH |
/api/v1/tasks/{id} |
修改完成状态 |
DELETE |
/api/v1/tasks/{id} |
删除任务 |
这条 CRUD 链路虽然不复杂,却足以验证浏览器、代理、API、参数校验、数据库迁移和持久化是否全部正常。
为什么选择 Go + Vue 3 + Docker?
这套组合适合中小型后台、SaaS 原型、内部工具和 API 服务:
- Go 编译快、单二进制部署简单,标准库
net/http已经能胜任常见 API; - Vue 3 的组合式 API、TypeScript、Pinia 和 Vite 让页面开发保持轻量;
- Docker 把 Go、Node、PostgreSQL、Redis 的版本写进配置,减少“在我电脑上能运行”的问题;
- Docker Compose 很适合一台服务器上的早期生产环境,也方便本地拉起完整依赖。
这里有一个重要取舍:模板后端没有一开始就堆叠 controller/service/repository 三层,而是按业务边界组织代码。tasks 包同时拥有模型、HTTP 行为、SQL 存储和测试。等到业务真的出现复用需求,再抽象公共能力,通常比预先制造很多空接口更容易维护。
一、准备开发环境
本地只需要安装:
- Git;
- Docker 24 或更高版本;
- Docker Compose v2(使用
docker compose命令)。
克隆项目:
1 | git clone https://github.com/glosc-ai/template-go-vue3-docker.git |
项目的核心目录如下:
1 | . |
二、一条命令启动开发环境
执行:
1 | make dev |
Compose 会构建并启动四个服务:
- Vue 3 + Vite:
http://localhost:5173; - Go API:
http://localhost:8080; - PostgreSQL 17;
- Redis 7.4。
第一次构建需要下载镜像和依赖。服务启动后,先检查存活和就绪状态:
1 | curl http://localhost:8080/health/live |
live 只回答“进程是否活着”,ready 还会检查数据库和 Redis 是否可用。容器编排和负载均衡应该根据 ready 判断是否把流量交给 API。
再创建一条任务:
1 | curl -X POST http://localhost:8080/api/v1/tasks \ |
查询任务:
1 | curl http://localhost:8080/api/v1/tasks |
如果本机的 8080 已被占用,可以改映射到宿主机的其他端口:
1 | API_PORT=18080 make dev |
此时浏览器仍然访问 5173。Vue 容器通过 Compose 内部网络访问 api:8080,只有你直接调用 API 时才使用 18080。
三、理解 Go 后端:先做清晰的业务边界
模板使用 Go 1.25 和标准库 net/http。路由注册非常直接:
1 | func (h *Handler) Register(mux *http.ServeMux) { |
Handler 不直接依赖 *sql.DB,而是依赖一个只描述自身需求的 Store 接口:
1 | type Store interface { |
这样做的好处是:HTTP 测试可以传入内存中的假 Store,生产环境再注入 SQL 实现。接口由使用方定义,边界小,也不会为了“架构整齐”抽象出一套什么业务都能做的通用 Repository。
创建任务时,后端还做了三层保护:
- 请求体最大 1 MiB;
- 拒绝 JSON 中的未知字段和多个对象;
- 标题去除首尾空格后,限制在 1~160 个字符。
这比把所有校验留给前端可靠,因为 API 也可能被脚本、移动端或其他服务调用。
新增自己的业务模块
假设要增加用户模块,可以复制 tasks 的组织思路,而不是把用户代码塞进原目录:
1 | server/users/ |
然后在 server.go 中组装依赖并注册路由。数据库结构变化则新增有序迁移文件:
1 | server/database/migrations/postgres/002_create_users.sql |
已经在线上执行过的迁移不要原地修改。新建下一号迁移,才能让不同环境按相同顺序演进。
四、Vue 3 前端:让类型贯穿请求链路
前端使用 Vue 3、TypeScript、Vite、Vue Router、Pinia、Tailwind CSS v4 和 shadcn-vue。web/src/api/tasks.ts 先声明 API 返回的数据结构:
1 | export interface Task { |
api 目录只处理 HTTP:拼接地址、设置请求头、解析响应和统一错误。features/tasks/store.ts 再负责页面状态:
1 | export const useTaskStore = defineStore('tasks', () => { |
推荐保持以下边界:
src/api:传输类型和请求函数;src/features/业务名:Pinia Store 与领域组件;src/views:组合页面,不直接散落fetch;src/components/ui:由 shadcn-vue 管理的通用 UI 源码。
这样,当后端错误格式、API 前缀或鉴权方式改变时,只需要集中修改 API 层。
五、前后端联调为什么不需要写死地址?
浏览器始终请求相对路径 /api/v1。
开发环境中,Vite 把 /api 和 /health 转发给 Go:
1 | server: { |
生产环境中,Nginx 做相同的事:
1 | location /api/ { |
try_files 最后的 /index.html 也很关键:没有它,用户刷新 Vue Router 的前端路由时会得到 Nginx 404。
这套代理策略还有一个实际收益:浏览器看到的 Web 和 API 同源,生产环境一般不需要把内部服务名或 API 主机名写进前端镜像。
六、用 Docker Compose 部署生产环境
下面假设服务器已经安装 Docker 与 Compose,并且代码位于服务器本地。
1. 创建生产配置
1 | cp .env.example .env |
至少修改以下几项:
1 | APP_ENV=production |
不要提交 .env。更不要继续使用示例中的数据库密码和 JWT Secret。若密码包含 @、:、/ 等 URL 特殊字符,需要先进行 URL 编码,或者使用不需要转义的高强度随机字符串。
启动前先让 Compose 展开并检查配置:
1 | docker compose config |
2. 构建并启动
1 | make up |
它等价于:
1 | docker compose up --build -d |
查看容器和日志:
1 | docker compose ps |
验证 Web、Nginx 与后端依赖:
1 | curl http://127.0.0.1:3000/nginx-health |
浏览器只需要访问 http://服务器IP:3000。生产 Web 容器由 Nginx 提供静态文件,并将 /api 与 /health 转发到 Compose 网络中的 API 容器。
3. 为什么生产镜像更小?
Go Dockerfile 使用多阶段构建:
- 在
golang:1.25-alpine中下载依赖并编译; - 用
CGO_ENABLED=0生成静态二进制; - 最终只把二进制复制到非 root 的 Distroless 镜像。
Vue Dockerfile 也分为三步:
npm ci按 lockfile 安装依赖;- 执行类型检查和 Vite 构建;
- 只把
dist复制到 Nginx 镜像。
Node、npm、Go 编译器和源代码都不会进入最终运行镜像,既减少体积,也缩小了攻击面。
4. 配置域名与 HTTPS
模板中的 Nginx 负责站内静态资源和 API 代理,但没有替你申请 TLS 证书。公网部署时,可以在宿主机或同一 Docker 网络前再放一层 Caddy、Traefik、Nginx Proxy Manager,或者使用云厂商负载均衡。
外层反向代理只需要把域名转发到:
1 | http://127.0.0.1:3000 |
确认域名可以访问后,再把 .env 中的 CORS_ORIGINS 改为实际的 HTTPS 域名并重新创建 API 容器:
1 | docker compose up -d --force-recreate api |
模板默认还将 API 的 8080 映射到宿主机,方便排查问题。公网服务器应使用防火墙阻止外部直连该端口,或者按实际需求移除 api.ports,让外部流量统一经过 Web 容器或网关。
七、数据持久化、升级与备份
Compose 使用命名卷保存 PostgreSQL 和 Redis 数据。普通的 docker compose down 不会删除数据,但下面这个命令会删除卷:
1 | docker compose down -v |
生产环境不要随手加 -v。
更新项目前,先备份 PostgreSQL:
1 | docker compose exec -T postgres sh -c \ |
先在 CI 或开发机运行 make test,确认 Go race tests、Vue 类型检查和生产构建全部通过。服务器只负责拉取已经验证的代码并重建容器:
1 | git pull --ff-only |
模板在 AUTO_MIGRATE=true 时会在 API 启动阶段执行尚未应用的 SQL 迁移。正式项目仍应在升级前备份,并为破坏性 schema 变更设计可回滚方案。
八、几个常见问题
Go 在宿主机运行时为什么连不上数据库?
容器内可以用 postgres:5432 和 redis:6379,因为它们是 Compose 服务名;宿主机运行 make api 时应改用 localhost:5432 和 localhost:6379。
另外,Go 程序不会自动读取 .env。用宿主机启动时,需要由 shell、IDE 或进程管理器注入环境变量。
修改了 Vue 环境变量,为什么页面没有变化?
VITE_ 开头的变量会在前端构建时写入静态文件。修改后需要重新构建 Web 镜像:
1 | docker compose up --build -d web |
密码、JWT Secret 等敏感信息绝对不能使用 VITE_ 前缀,因为进入浏览器的变量对用户可见。
可以切换到 MySQL 吗?
可以。后端已经包含 PostgreSQL 与 MySQL 两套驱动和迁移目录:
1 | DB_DRIVER=mysql |
不过默认 Compose 文件启动的是 PostgreSQL。切换 MySQL 时,还需要提供可访问的 MySQL 实例,或自行在 Compose 中增加 MySQL 服务。
Redis 目前做了什么?
模板已经完成 Redis 连接、关闭和就绪检查,为缓存、限流或会话留出了可靠入口;任务 CRUD 本身没有为了“展示技术栈”而强行缓存。真正出现读热点后,再为具体查询设计缓存键、TTL 和失效策略。
九、这个模板适合谁?
template-go-vue3-docker 比较适合这些场景:
- 想快速启动 Go + Vue 3 前后端分离项目;
- 需要 PostgreSQL、Redis、迁移、健康检查和 Docker 的基础设施;
- 希望从一个真实 CRUD 学习完整请求链路;
- 想保留简洁结构,再按业务添加
users、billing、jobs等模块; - 需要通过 GitHub Actions 测试,并在 Release 后发布多架构 GHCR 镜像。
它不是“下载后直接交付”的万能后台:完整注册登录、权限模型、对象存储、监控告警、数据库备份、域名和 TLS 仍然要按项目需求完成。模板的价值,是把大量重复但容易出错的工程起点准备好,让你把时间放在真正的业务上。
开始自己的项目时,建议按以下顺序修改:
- 在 GitHub 使用模板或直接 Fork;
- 全局替换 Go module
github.com/gloscai/template-go-vue3-docker/server; - 修改
web/package.json、页面品牌、镜像名和环境变量; - 参考
tasks创建第一个真实业务模块; - 上线前替换全部密码与 JWT Secret,补齐 TLS、备份和监控。
总结
一个能长期维护的 Go + Vue 3 网站,不只由“Go 写接口、Vue 写页面”组成。更重要的是让配置有统一入口、让前后端类型和错误可追踪、让数据库变更可迁移、让容器有健康检查、让开发与生产使用相同的请求路径。
如果你准备做一个新的全栈项目,可以从 glosc-ai/template-go-vue3-docker 开始:先用 make dev 跑通完整链路,再沿着 tasks 的边界替换成自己的业务。欢迎 Star、Fork,也欢迎提交 Issue 或 Pull Request,一起把这个模板打磨得更实用。