适配人群:有 Express 后端 + Vue3 前端 + mysql2 原生数据库开发基础,转 Nuxt.js 一体化全栈开发
官网:Nuxt.js
前言
适用人群:Vue3 前端开发er,有 Express 后端项目接触经验,想一站式上手 Nuxt.js 一体化全栈;
本文分为两大模块:一、核心概念认知(前端视角理解Nuxt) 、二、小试牛刀:MySQL+Drizzle全栈实操 ;
数据库相关代码全部隐藏敏感密码,可直接替换自身配置使用。
第一部分 核心概念讲解
1. 传统Vue3 + Express 分离架构 VS Nuxt一体化架构
1.1 传统分离模式(你熟悉的开发模式)
- 两个独立项目
- 前端:Vue3 + Vite,仅负责页面渲染、接口请求,无后端能力;
- 后端:Express/Koa,单独启动服务,写接口、操作数据库;
- 启动方式:两个终端分别启动前端、后端;
- 痛点:
- 跨域配置繁琐;
- 前后端类型需要手动同步,容易出现字段不匹配;
- 首页白屏、SEO差(纯客户端渲染);
- 新增接口需要前后端两边改代码。
1.2 Nuxt4 一体化全栈架构
Nuxt = Vue3 + 内置Nitro后端引擎,一个项目同时包含前端页面+后端接口
- 仅需一个项目,单命令
npm run dev同时启动前后端; - 内置服务端渲染SSR/静态渲染SSG,天然解决Vue单页白屏、SEO问题;
- 内置后端能力,无需额外搭建Express,直接在项目内写接口、操作MySQL;
- 全局共享TS类型,前端页面、后端数据库共用一套类型定义;
- 内置路径别名、路由自动生成、请求上下文,省去大量手动配置。
2. 关键名词翻译(Vue前端视角,结合Express经验理解)
2.1 Nitro
Nuxt内置后端服务引擎,替代Express ,负责处理接口请求、中间件、数据库交互;
不需要安装express包,Nuxt底层自带完整后端能力。
2.2 Server Routes(后端接口目录 server/api/)
对标Express的路由文件:
- Express:手动创建路由文件,
app.get('/user', handler)注册接口; - Nuxt:文件即接口,
server/api/v1/user/index.get.ts自动生成接口地址GET /api/v1/user,零手动注册路由;
文件命名规则:xxx.get.ts= GET请求、xxx.post.ts= POST请求。
2.3 Pages 目录(前端页面)
对标Vue Router:
- 传统Vue:手动配置
router/index.js定义页面路由; - Nuxt:
pages/下的Vue文件路径自动映射浏览器路由;
例:pages/index.vue→ 浏览器路由/(首页)pages/about/index.vue→ 浏览器路由/aboutpages/user/[id].vue→ 动态路由/user/:idpages/blog/2025/index.vue→ 嵌套路由/blog/2025
然后记得在app.vue里面引入<NuxtPage />
<NuxtPage />等价原生 Vue Router 的<router-view>,是页面渲染的路由占位出口Nuxt 全局内置组件,不需要手动导入、无需注册,直接在模板使用
作用:根据当前浏览器路由,自动匹配
pages/目录对应的页面组件并渲染到该位置)
同时app.vue 是 Nuxt 项目全局根布局,所有页面都会嵌套在它内部渲染,如果不写 <NuxtPage />,页面无法展示,只会空白。
2.4 ~/ 全局路径别名
Nuxt内置,等价项目根目录,解决Vue多层../../相对路径混乱问题;
后端导入数据库:import { db } from '~/server/db',无需计算层级。
2.5 .nuxt 缓存目录
Nuxt编译、类型生成缓存文件夹;
出现页面白屏、无限编译、TS爆红、接口404时,优先删除该文件夹重启项目。
2.6 Drizzle ORM
轻量TS友好 ORM 工具(ORM 是把数据库表映射成代码里的对象,用面向对象语法替代手写 SQL 操作数据库的工具),底层封装mysql2,替代Express原生手写SQL字符串;
核心优势:表结构定义后自动生成TS类型,前端页面、后端接口共享字段类型,写错字段编辑器直接标红。
3. Nuxt完整项目目录总览(前后端一体)
nuxt-demo/
├── .nuxt/ # 编译缓存(报错直接删除)
├── drizzle.config.ts # Drizzle数据库迁移配置
├── tsconfig.json # TS全局配置,优化编译速度
├── package.json
├── pages/ # 【前端】页面目录,自动生成路由
│ └── index.vue # 首页页面
│ └── user
│ └── index.vue # /user 路由
├── server/ # 【后端】等价Express项目根目录
│ ├── db/ # 数据库核心目录
│ │ ├── index.ts # mysql2连接池 + Drizzle实例
│ │ └── schema.ts # 数据库表结构定义
│ ├── utils/ # 后端通用工具
│ │ └── response.ts # 接口统一返回格式封装
│ └── api/ # 后端接口路由,自动生成API地址
│ └── v1/
│ └── user/
│ └── index.get.ts # GET /api/v1/user 接口
└── node_modules/
4. Vue前端开发者开发习惯转换
- 路由开发:不用写vue-router,新增页面直接丢进pages文件夹;
- 接口开发:不用单独启动Express,在server/api新建文件即接口;
- 类型管理:数据库表定义后全局复用,不用前后端分别写interface;
- 启动项目 :仅一条
npm run dev,同时跑页面和后端接口; - 渲染模式:默认SSR服务端渲染,解决Vue首屏白屏;
- 数据库:同一项目内直接操作MySQL,无需跨域、不用额外后端服务。
第二部分 小试牛刀:Nuxt + MySQL + Drizzle ORM 全栈实操(前端页面 + 后端接口完整流程)
可借助 vibe-coding 简单尝试一下
阶段1:脚手架从零创建纯净最小化Nuxt4项目
1. 执行创建命令
打开终端,进入存放代码的文件夹,执行:
bash
pnpm create nuxt@latest nuxt4-practice-demo
2. 交互式选项(Nuxt4新版真实流程,选minimal最小模板)
bash
1. Select a template(选择模板)
选择:`minimal -- Minimal setup for Nuxt 4 (recommended)`>
> 最小纯净模板,无多余示例页面、冗余插件,适合我们从零写接口、数据库逻辑
2. Which package manager would you like to use?
选择:`pnpm`
3. Initialize git repository?(是否初始化Git)
按需:`Yes` / `No` 都可以
4. Install extra modules?(是否安装官方额外模块)
选择:`No`,全部模块后续手动按需安装,保持项目干净
3. 进入项目目录,启动验证基础环境
bash
# 进入项目
cd nuxt4-practice-demo
# 启动开发服务
pnpm run dev
浏览器打开 http://localhost:3000,出现空白极简页面代表项目创建成功,关闭终端继续数据库开发。
阶段2:前置数据库准备
- 本地MySQL服务启动,DataGrip可视化工具连接测试正常;
- DataGrip新建查询窗口,执行以下SQL创建测试库与表(无敏感信息)
sql
CREATE DATABASE nuxt_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE nuxt_db;
CREATE TABLE `user` (
`id` INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
`name` VARCHAR(50) NOT NULL COMMENT '用户名',
`age` INT NOT NULL COMMENT '年龄'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `user` (name, age) VALUES ('张三',18),('李四',22),('王五',26);
- 安装MySQL与Drizzle相关依赖
bash
# 运行依赖:mysql2连接池、drizzle核心ORM
pnpm add drizzle-orm mysql2
# 开发依赖:drizzle-kit 表结构迁移工具
pnpm add -D drizzle-kit
阶段3:后端数据库 & 接口开发
步骤1:新建数据表结构定义文件 server/db/schema.ts
作用:用代码描述数据库表,自动生成TS类型,前后端页面、接口可以共用类型约束
ts
import { mysqlTable, int, varchar } from 'drizzle-orm/mysql-core';
// 映射数据库内user表
export const userTable = mysqlTable('user', {
id: int('id').primaryKey().autoincrement(),
name: varchar('name', 50).notNull(),
age: int('age').notNull()
});
// 自动推导用户表TS类型,前端页面可导入使用
export type User = typeof userTable.$inferSelect;
步骤2:初始化MySQL连接池与Drizzle实例 server/db/index.ts
底层基于mysql2连接池,配置和Express后端写法完全通用,适配新版Drizzle修复TS重载报错
ts
import { drizzle } from 'drizzle-orm/mysql2';
import mysql from 'mysql2/promise';
import { userTable } from './schema';
// mysql2原生连接池配置
const pool = mysql.createPool({
host: '127.0.0.1',
port: 3306,
user: 'root',
password: '你的本地MySQL密码', // 自行替换为自己数据库密码
database: 'nuxt_db',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
idleTimeout: 60000, // 延长空闲连接保活时间,避免连接意外断开
connectTimeout: 10000
});
// 创建Drizzle操作实例,mode字段是新版本必填参数,解决TS类型匹配报错
export const db = drizzle(pool, {
schema: { userTable },
mode: 'default'
});
步骤3:后端统一返回工具 server/utils/response.ts
封装固定接口返回格式,不用在每个接口重复手写code/data/msg结构,对标Express统一返回中间件
ts
// 成功返回封装
export function success<T>(data: T, msg = '操作成功') {
return {
code: 200,
data,
msg
};
}
// 失败返回封装
export function fail(msg = '操作失败', code = 500) {
return {
code,
data: null,
msg
};
}
步骤4:编写后端查询接口 server/api/v1/user/index.get.ts
Nuxt约定式接口文件,文件名.get.ts代表GET请求,文件路径自动映射/api/v1/user接口地址,替代Express手动注册路由
ts
import { db } from '~/server/db';
import { userTable } from '~/server/db/schema';
import { success, fail } from '~/server/utils/response';
// defineEventHandler:Nuxt内置后端请求处理函数,替代Express的(req,res)回调
export default defineEventHandler(async () => {
try {
// Drizzle ORM链式查询,等价原生SQL:SELECT * FROM user
const userList = await db.select().from(userTable);
return success(userList);
} catch (err)
// 统一捕获数据库异常,不在连接池监听error(存在TS类型冲突)
console.error('数据库查询异常:', err);
return fail('数据库查询失败,请检查MySQL服务');
}
});
步骤5:Drizzle迁移配置(可选,用于同步表结构)drizzle.config.ts
文件作用说明
这是给drizzle-kit迁移工具的配置文件,读取schema.ts内的表结构代码,对比真实MySQL数据库,自动生成/执行表变更SQL,不用手动写CREATE/ALTER TABLE语句。
ts
import type { Config } from 'drizzle-kit';
export default {
schema: './server/db/schema.ts', // 读取表结构定义文件
out: './drizzle', // 自动生成迁移SQL脚本存放目录
driver: 'mysql2', // 指定数据库驱动
dbCredentials: { // 工具连接数据库的账号配置
host: '127.0.0.1',
user: 'root',
password: '你的本地MySQL密码',
database: 'nuxt_db',
port: 3306
}
} satisfies Config;
配套迁移常用命令
# 对比代码表结构与数据库,生成迁移SQL文件
pnpm drizzle-kit generate
# 自动连接MySQL,执行迁移脚本同步表结构
pnpm drizzle-kit migrate
临时接口测试(先验证后端可用)
- 终端启动项目
bash
pnpm run dev
- 浏览器访问接口地址:
http://localhost:3000/api/v1/user - 正常输出用户数组JSON,代表后端数据库接口开发完成。
阶段4:前端页面开发(Nuxt页面路由 + 请求后端接口)
1. 根布局文件 app.vue(全局布局 + 路由出口 <NuxtPage />)
app.vue是Nuxt全局根组件,所有页面都会渲染在<NuxtPage />内部,等价Vue项目根App.vue + <router-view>。
html
<template>
<!-- 全局公共导航栏 -->
<nav style="padding:16px; background:#eee;">
<!-- Nuxt内置跳转组件,替代a标签、vue-router的router-link -->
<NuxtLink to="/" style="margin-right:20px">用户列表首页</NuxtLink>
</nav>
<!-- 路由渲染出口,pages目录页面全部挂载在此,必须存在否则页面空白 -->
<NuxtPage />
<!-- 全局底部 -->
<footer style="padding:16px; margin-top:30px; border-top:1px solid #eee;">
Nuxt4 全栈演示 | Vue3+TS+Drizzle+MySQL
</footer>
</template>
2. 首页页面 pages/index.vue(前端调用后端接口展示数据库数据)
Nuxt约定路由:pages/index.vue 自动映射浏览器路由 /
使用Nuxt内置useFetch请求本地后端接口,自带TS类型推断,无需手动安装axios。
html
<template>
<div style="padding:0 20px;">
<h2>用户列表(从MySQL数据库读取)</h2>
<!-- 加载状态 -->
<div v-if="pending">加载中...</div>
<!-- 错误提示 -->
<div v-else-if="error" style="color:red">
请求失败:{{ error.message }}
</div>
<!-- 数据表格渲染 -->
<table border="1" cellpadding="8" v-else>
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>年龄</th>
</tr>
</thead>
<tbody>
<tr v-for="item in data.data" :key="item.id">
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup lang="ts">
// 导入后端定义好的User类型,前后端类型统一,杜绝字段拼写错误
import type { User } from '~/server/db/schema';
// 定义接口返回完整类型
interface ApiRes {
code: number
data: User[]
msg: string
}
// useFetch Nuxt内置请求函数,直接访问同项目后端/api接口
const { data, pending, error } = useFetch<ApiRes>('/api/v1/user')
</script>
阶段5:完整全栈项目运行测试
- 终端执行启动命令
bash
pnpm run dev
- 浏览器打开首页
http://localhost:3000 - 页面自动请求本地后端
/api/v1/user,读取MySQL数据并渲染表格; - 流程闭环:前端页面 → Nuxt内置Nitro后端 → mysql2连接池 → MySQL数据库。
补充:完成所有文件后的完整目录核对
nuxt4-practice-demo/
├── app.vue # 全局根布局、<NuxtPage>路由出口
├── pages/
│ └── index.vue # 前端首页页面
├── server/
│ ├── db/
│ │ ├── index.ts
│ │ └── schema.ts
│ ├── utils/
│ │ └── response.ts
│ └── api/v1/user/index.get.ts # 后端接口
├── drizzle.config.ts # Drizzle迁移配置
└── package.json
最后小小补充:Nuxt.js 使用 Element-Plus 和 Vue 使用略有不同,详细请参考官网说明@element-plus/nuxt
ok,大致就这样,接下来快去简单试试吧,happy coding~