NestJS前后端接口对接实战
NestJS前后端接口对接实战
学习目标:掌握前后端端口配置、环境变量管理、跨域问题解决、Next.js Server API 使用、前后端打通流程。
一、前后端接口对接概述
1.1 前后端对接核心要点
前后端接口对接核心要点:
│
├── 端口配置
│ ├── 前端端口设置
│ ├── 后端端口设置
│ └── 避免端口冲突
│
├── 环境变量管理
│ ├── 开发环境配置
│ ├── 生产环境配置
│ └── 本地测试环境配置
│
├── 跨域问题
│ ├── Next.js Server API 优势
│ ├── NestJS 跨域配置
│ └── 浏览器跨域限制
│
├── 请求方式
│ ├── useFetch 直接请求
│ ├── useAsyncData 请求
│ └── Server API 代理请求(推荐)
│
└── 工作流组织
├── Store 管理请求逻辑
├── Server API 封装接口
└── 页面调用 Store 方法1.2 前后端对接流程图
前后端对接完整流程:
│
├── 第一步:配置端口
│ ├── 修改 next.config.ts 中的端口
│ ├── 修改 .env 中的 BASE_URL
│ └── 启动前后端项目
│
├── 第二步:环境变量管理
│ ├── 创建 .env.development
│ ├── 创建 .env.production
│ └── 创建 .env.local(可选)
│
├── 第三步:创建 Server API
│ ├── server/api/[resource]/get.ts
│ ├── 使用 $fetch 发起请求
│ └── 返回后端数据
│
├── 第四步:Store 封装请求
│ ├── 在 Store actions 中调用 Server API
│ ├── 使用 useFetch 或 useAsyncData
│ └── 管理响应数据
│
└── 第五步:页面调用
├── 页面调用 Store 方法
├── 渲染数据到页面
└── 处理加载状态二、前后端端口配置
2.1 Next.js 端口设置
问题描述:前后端项目默认端口都是 3000,启动时会冲突。
解决方案:修改 Next.js 项目端口。
修改 next.config.ts:
// next.config.ts
import type { Config } from 'next'
const config: Config = {
// 设置开发服务器端口
devServer: {
port: 3010, // 前端端口设置为 3010
},
// 其他配置...
}
export default config重要提示:
端口设置注意事项:
│
├── 修改端口后,需要同步修改 .env 中的 BASE_URL
│
├── 前端端口:3010
│
├── 后端端口:3000
│
└── 确保两个端口不冲突2.2 环境变量配置
不同环境的环境变量文件:
项目根目录环境变量文件:
│
├── .env # 默认环境变量(所有环境共享)
│ └── BASE_URL=http://localhost:3000
│
├── .env.development # 开发环境
│ └── BASE_URL=http://localhost:3000
│
├── .env.production # 生产环境
│ └── BASE_URL=https://api.production.com
│
└── .env.local # 本地测试环境(可选)
└── BASE_URL=http://localhost:3000package.json 脚本配置:
{
"scripts": {
"dev": "next dev",
"dev:local": "next dev --dotenv .env.local",
"build": "next build --dotenv .env.production",
"start": "next start"
}
}环境变量说明:
环境变量文件说明:
│
├── .env
│ ├── 默认环境变量
│ ├── 所有环境都会加载
│ └── 通常放置基础配置
│
├── .env.development
│ ├── 开发环境配置
│ ├── npm run dev 时加载
│ └── 连接本地后端接口
│
├── .env.production
│ ├── 生产环境配置
│ ├── npm run build 时加载(需配置 --dotenv)
│ └── 连接生产环境接口
│
└── .env.local
├── 本地测试环境配置
├── npm run dev:local 时加载
└── 用于本地测试生产接口验证环境变量是否生效:
// next.config.ts
console.log('BASE_URL:', process.env.BASE_URL)
// 启动项目后查看控制台输出
// npm run dev → BASE_URL: http://localhost:3000
// npm run dev:local → BASE_URL: http://localhost:3000(.env.local)2.3 端口配置完整示例
文件结构:
前端项目/
├── next.config.ts # 端口配置
├── .env # 默认环境变量
├── .env.development # 开发环境
├── .env.production # 生产环境
├── .env.local # 本地测试环境(可选)
└── package.json # 脚本配置完整配置示例:
// next.config.ts
import type { Config } from 'next'
const config: Config = {
devServer: {
port: 3010, // 前端端口
},
// 其他配置...
}
export default config# .env.development
BASE_URL=http://localhost:3000# .env.production
BASE_URL=https://api.production.com# .env.local(可选)
BASE_URL=http://localhost:3000// package.json
{
"scripts": {
"dev": "next dev",
"dev:local": "next dev --dotenv .env.local",
"build": "next build --dotenv .env.production",
"start": "next start"
}
}三、跨域资源请求问题
3.1 跨域问题概述
什么是跨域:
跨域问题本质:
│
├── 浏览器同源策略
│ ├── 协议(Protocol)
│ ├── 域名(Domain)
│ └── 端口(Port)
│
├── 跨域场景
│ ├── 前端:http://localhost:3010
│ ├── 后端:http://localhost:3000
│ └── 端口不同 → 跨域
│
└── 跨域限制
├── XMLHttpRequest
├── Fetch API
└── Axios(浏览器端)3.2 Next.js Server API 解决跨域
Next.js Server API 优势:
Next.js Server API 优势:
│
├── 不存在跨域问题
│ ├── Server API 在 Node.js 端执行
│ ├── 没有浏览器同源策略限制
│ └── 可以直接请求任意后端接口
│
├── 隐藏真实请求路径
│ ├── 前端只知道 Server API 路径
│ ├── 后端真实路径被隐藏
│ └── 提高接口安全性
│
├── 统一请求管理
│ ├── 所有请求逻辑集中在 Server API
│ ├── 便于统一添加鉴权、日志等
│ └── 便于后续维护和扩展
│
└── 支持内置 $fetch
├── 提供 HTTP 请求实例
├── 类似 Axios 的请求方式
└── 自动处理响应数据Next.js 请求方式对比:
| 方式 | 执行环境 | 是否跨域 | 推荐度 |
|---|---|---|---|
| 页面直接 fetch | 浏览器 | 会跨域 | |
| Server API 代理 | Node.js | 不跨域 | |
| useFetch | 服务端 + 客户端 | 不跨域 | |
| useAsyncData | 服务端 + 客户端 | 不跨域 |
3.3 NestJS 服务端跨域配置
在 NestJS 中启用 CORS:
// src/main.ts
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
// 启用 CORS(跨域资源共享)
app.enableCors({
origin: ['http://localhost:3010'], // 允许的前端域名
credentials: true, // 允许携带 Cookie
})
await app.listen(3000)
}
bootstrap()CORS 配置选项:
// 完整 CORS 配置示例
app.enableCors({
// 允许的域名(生产环境必须限制)
origin: ['http://localhost:3010', 'https://example.com'],
// 允许的请求方法
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
// 允许的请求头
allowedHeaders: ['Content-Type', 'Authorization'],
// 允许携带 Cookie
credentials: true,
// 预检请求缓存时间(秒)
maxAge: 3600,
})生产环境 CORS 配置建议:
生产环境 CORS 配置建议:
│
├── 必须限制 origin
│ ├── 不要使用 origin: '*'
│ ├── 只允许特定的域名
│ └── 防止恶意网站请求
│
├── 启用 credentials
│ ├── 允许携带 Cookie
│ ├── 允许携带 Authorization
│ └── 支持鉴权功能
│
└── 设置合理的缓存时间
├── 减少 OPTIONS 请求次数
└── 提高接口响应速度3.4 跨域问题解决方案对比
| 方案 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|
| NestJS 启用 CORS | 配置简单 | 暴露真实接口 | 开发环境 |
| Next.js Server API | 隐藏接口、无跨域 | 需要额外封装 | 生产环境 |
| Nginx 反向代理 | 生产级方案 | 需要运维配置 | 大型项目 |
| JSONP | 兼容性好 | 只支持 GET | 已过时 |
最佳实践:
跨域问题最佳实践:
│
├── 开发环境
│ ├── 方案一:NestJS 启用 CORS
│ ├── 方案二:Next.js Server API(推荐)
│ └── 快速联调,提高开发效率
│
├── 生产环境
│ ├── 方案一:Next.js Server API(推荐)
│ ├── 方案二:Nginx 反向代理
│ └── 隐藏真实接口,提高安全性
│
└── 注意事项
├── 不要在生产环境使用 origin: '*'
├── Server API 是最佳实践
└── 便于统一管理接口四、Next.js 请求工作流
4.1 useFetch 详解
useFetch 是什么:
useFetch 本质:
│
├── useFetch = useAsyncData + $fetch
│
├── useAsyncData
│ ├── 管理异步数据
│ ├── 处理加载状态
│ └── 处理错误状态
│
└── $fetch
├── 内置 HTTP 请求实例
├── 类似 Axios
└── 支持 GET、POST 等方法useFetch 特点:
useFetch 特点:
│
├── 服务端执行
│ ├── 首次渲染在服务端执行
│ ├── 直接从服务端获取数据
│ └── 不需要客户端重新请求
│
├── SEO 友好
│ ├── 数据在服务端渲染
│ ├── 搜索引擎可以抓取数据
│ └── 提高页面 SEO 排名
│
├── 自动处理状态
│ ├── data:响应数据
│ ├── pending:加载状态
│ └── error:错误状态
│
└── 可在任何地方使用
├── 插件(plugins)
├── 路由中间件(middleware)
└── 页面组件(pages)useFetch 使用示例:
<!-- pages/index.vue -->
<script setup lang="ts">
// 直接使用 useFetch
const { data, pending, error } = await useFetch('/api/courses')
// data.value 为响应数据
console.log('课程数据:', data.value)
</script>
<template>
<div v-if="pending">加载中...</div>
<div v-else-if="error">加载失败: {{ error.message }}</div>
<div v-else>
<div v-for="course in data" :key="course.id">
{{ course.name }}
</div>
</div>
</template>4.2 useAsyncData 详解
useAsyncData 与 useFetch 的关系:
// useFetch 本质上是 useAsyncData + $fetch
const { data } = await useFetch('/api/courses')
// 等价于
const { data } = await useAsyncData('courses', () => $fetch('/api/courses'))useAsyncData 使用示例:
<!-- pages/index.vue -->
<script setup lang="ts">
// 使用 useAsyncData
const { data, pending, error } = await useAsyncData(
'courses', // 唯一 key
() => $fetch('/api/courses') // 数据获取函数
)
</script>useFetch vs useAsyncData 对比:
| 维度 | useFetch | useAsyncData |
|---|---|---|
| 本质 | useAsyncData + $fetch | 底层 API |
| 使用场景 | 简单请求 | 复杂请求逻辑 |
| 灵活性 | 简单快捷 | 更加灵活 |
| 推荐度 |
useFetch 高级用法:
<script setup lang="ts">
// 带参数的请求
const { data } = await useFetch('/api/courses', {
query: { type: 1 }, // Query 参数
method: 'GET', // 请求方法
headers: { // 请求头
'Authorization': 'Bearer token',
},
})
// POST 请求
const { data } = await useFetch('/api/courses', {
method: 'POST',
body: { name: '新课程' }, // 请求体
})
// 动态 URL
const courseId = ref(1)
const { data } = await useFetch(() => `/api/courses/${courseId.value}`)
// 条件请求
const enabled = ref(false)
const { data } = await useFetch('/api/courses', {
immediate: enabled.value, // 是否立即执行
})
// 监听参数变化自动重新请求
const page = ref(1)
const { data } = await useFetch('/api/courses', {
query: { page }, // page 变化时自动重新请求
watch: [page], // 监听 page 变化
})
</script>4.3 Server API 实现
Server API 文件结构:
前端项目/
└── server/
└── api/
├── courses/
│ ├── get.ts # GET 请求
│ └── post.ts # POST 请求
└── users/
└── get.tsGET 请求示例:
// server/api/courses/get.ts
export default defineEventHandler(async (event) => {
// 使用 $fetch 发起请求
const data = await $fetch(`${process.env.BASE_URL}/api/v1/courses`, {
method: 'GET',
})
// 返回数据给前端
return data
})POST 请求示例:
// server/api/courses/post.ts
export default defineEventHandler(async (event) => {
// 读取请求体
const body = await readBody(event)
// 发起 POST 请求
const data = await $fetch(`${process.env.BASE_URL}/api/v1/courses`, {
method: 'POST',
body, // 请求体
})
return data
})带 Query 参数的 GET 请求:
// server/api/courses/get.ts
export default defineEventHandler(async (event) => {
// 获取 Query 参数
const query = getQuery(event)
// 发起带参数的请求
const data = await $fetch(`${process.env.BASE_URL}/api/v1/courses`, {
method: 'GET',
query, // Query 参数
})
return data
})完整 Server API 示例:
// server/api/courses/[id].ts(动态路由)
export default defineEventHandler(async (event) => {
// 获取路由参数
const id = getRouterParam(event, 'id')
// 获取 Query 参数
const query = getQuery(event)
// 发起请求
const data = await $fetch(`${process.env.BASE_URL}/api/v1/courses/${id}`, {
method: 'GET',
query,
})
return data
})Server API 常用方法速查:
| 方法 | 用途 | 示例 |
|---|---|---|
| getQuery(event) | 获取 Query 参数 | const query = getQuery(event) |
| readBody(event) | 读取请求体 | const body = await readBody(event) |
| getRouterParam(event, key) | 获取路由参数 | const id = getRouterParam(event, 'id') |
| getHeader(event, key) | 获取请求头 | const auth = getHeader(event, 'authorization') |
| setHeader(event, key, value) | 设置响应头 | setHeader(event, 'Content-Type', 'application/json') |
4.4 Store 封装请求逻辑
Store 封装请求的优势:
Store 封装请求的优势:
│
├── 集中管理请求逻辑
│ ├── 所有请求逻辑在 Store 中
│ ├── 便于统一管理
│ └── 便于后续维护
│
├── 复用性强
│ ├── 多个页面可复用同一请求
│ ├── 避免重复代码
│ └── 提高开发效率
│
├── 统一状态管理
│ ├── 响应数据统一存储
│ ├── 加载状态统一管理
│ └── 错误状态统一处理
│
└── 便于测试
├── 请求逻辑独立
├── 便于单元测试
└── 提高代码质量Store 封装请求示例:
// stores/home.ts
import { defineStore } from 'pinia'
export const useHomeStore = defineStore('home', () => {
// 状态
const courses = ref([])
const loading = ref(false)
const error = ref(null)
// Action:获取课程列表
const fetchCourses = async () => {
loading.value = true
error.value = null
try {
// 调用 Server API
const data = await $fetch('/api/courses')
courses.value = data
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
// Action:获取课程详情
const fetchCourseDetail = async (id: number) => {
const data = await $fetch(`/api/courses/${id}`)
return data
}
return {
courses,
loading,
error,
fetchCourses,
fetchCourseDetail,
}
})页面使用 Store:
<!-- pages/index.vue -->
<script setup lang="ts">
const homeStore = useHomeStore()
// 调用 Store 方法获取数据
await homeStore.fetchCourses()
// 使用 Store 中的数据
const { courses, loading, error } = storeToRefs(homeStore)
</script>
<template>
<div v-if="loading">加载中...</div>
<div v-else-if="error">加载失败</div>
<div v-else>
<div v-for="course in courses" :key="course.id">
{{ course.name }}
</div>
</div>
</template>在 Store 中使用 useFetch:
// stores/course.ts
import { defineStore } from 'pinia'
export const useCourseStore = defineStore('course', () => {
// 封装 useFetch
const useFetchCourses = async () => {
const { data, pending, error } = await useFetch('/api/courses')
return { data, pending, error }
}
// 其他 Store 可以调用
const fetchCourseWithDetail = async () => {
const { data } = await useFetchCourses()
// 处理数据...
return data
}
return {
useFetchCourses,
fetchCourseWithDetail,
}
})五、前后端对接完整实战
5.1 项目结构
前后端项目结构:
│
├── 后端项目(NestJS)
│ ├── src/
│ │ ├── modules/
│ │ │ └── course/
│ │ │ ├── course.controller.ts
│ │ │ ├── course.service.ts
│ │ │ └── dto/
│ │ └── main.ts
│ ├── package.json
│ └── .env
│
└── 前端项目(Next.js)
├── server/
│ └── api/
│ └── courses/
│ └── get.ts # Server API
├── stores/
│ └── home.ts # Store 封装请求
├── pages/
│ └── index.vue # 页面组件
├── next.config.ts # 端口配置
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
└── package.json # 脚本配置5.2 完整配置清单
1. 后端配置(NestJS)
// backend/src/main.ts
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
// 启用 CORS(开发环境)
app.enableCors({
origin: ['http://localhost:3010'],
credentials: true,
})
await app.listen(3000)
console.log('Backend running on: http://localhost:3000')
}
bootstrap()// backend/src/modules/course/course.controller.ts
import { Controller, Get, Query } from '@nestjs/common'
import { CourseService } from './course.service'
@Controller('api/v1/courses')
export class CourseController {
constructor(private readonly courseService: CourseService) {}
@Get()
async getCourses(@Query() query: any) {
return this.courseService.getCourses(query)
}
}2. 前端配置(Next.js)
// frontend/next.config.ts
import type { Config } from 'next'
const config: Config = {
devServer: {
port: 3010, // 前端端口
},
}
export default config# frontend/.env.development
BASE_URL=http://localhost:3000# frontend/.env.production
BASE_URL=https://api.production.com// frontend/package.json
{
"scripts": {
"dev": "next dev",
"dev:local": "next dev --dotenv .env.local",
"build": "next build --dotenv .env.production",
"start": "next start"
}
}3. Server API 实现
// frontend/server/api/courses/get.ts
export default defineEventHandler(async (event) => {
// 获取 Query 参数
const query = getQuery(event)
// 发起请求到后端
const data = await $fetch(`${process.env.BASE_URL}/api/v1/courses`, {
method: 'GET',
query,
})
return data
})4. Store 封装
// frontend/stores/home.ts
import { defineStore } from 'pinia'
export const useHomeStore = defineStore('home', () => {
const courses = ref([])
const loading = ref(false)
const error = ref(null)
const fetchCourses = async (params = {}) => {
loading.value = true
error.value = null
try {
const data = await $fetch('/api/courses', {
query: params,
})
courses.value = data
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
return {
courses,
loading,
error,
fetchCourses,
}
})5. 页面使用
<!-- frontend/pages/index.vue -->
<script setup lang="ts">
const homeStore = useHomeStore()
// 页面加载时获取数据
await homeStore.fetchCourses()
const { courses, loading, error } = storeToRefs(homeStore)
</script>
<template>
<div class="container">
<!-- 加载状态 -->
<div v-if="loading" class="loading">加载中...</div>
<!-- 错误状态 -->
<div v-else-if="error" class="error">
加载失败: {{ error.message }}
</div>
<!-- 课程列表 -->
<div v-else class="courses">
<div v-for="course in courses" :key="course.id" class="course-item">
<h3>{{ course.name }}</h3>
<p>{{ course.description }}</p>
</div>
</div>
</div>
</template>5.3 启动流程
前后端项目启动流程:
│
├── 第一步:启动后端(NestJS)
│ ├── cd backend
│ ├── npm run start:dev
│ └── 访问:http://localhost:3000
│
├── 第二步:启动前端(Next.js)
│ ├── cd frontend
│ ├── npm run dev
│ └── 访问:http://localhost:3010
│
└── 第三步:验证前后端是否打通
├── 打开浏览器访问 http://localhost:3010
├── 查看控制台是否有错误
└── 查看 Network 面板请求是否成功启动命令对比:
| 环境 | 命令 | BASE_URL |
|---|---|---|
| 开发环境 | npm run dev | http://localhost:3000 |
| 本地测试生产 | npm run dev:local | .env.local 中的配置 |
| 生产构建 | npm run build | .env.production 中的配置 |
| 生产运行 | npm run start | .env.production 中的配置 |
六、常见问题与解决方案
6.1 端口冲突问题
问题描述:前后端端口都是 3000,启动时冲突。
解决方案:
// 修改前端端口
// next.config.ts
const config: Config = {
devServer: {
port: 3010,
},
}
// 修改后端端口
// backend/src/main.ts
await app.listen(3000)6.2 环境变量不生效问题
问题描述:修改了 .env 文件,但环境变量没有生效。
解决方案:
# 方案一:重启项目
# 修改 .env 文件后,重启项目才能生效
# 方案二:指定环境变量文件
# package.json
{
"scripts": {
"dev": "next dev --dotenv .env.development",
"build": "next build --dotenv .env.production"
}
}
# 方案三:使用 .env.local
# .env.local 优先级最高环境变量优先级:
环境变量优先级(从高到低):
│
├── .env.local # 最高优先级
├── .env.[environment] # .env.development / .env.production
├── .env # 最低优先级
└── 系统环境变量 # 优先级最高6.3 跨域请求失败问题
问题描述:前端直接请求后端接口,提示跨域错误。
解决方案:
跨域问题解决方案:
│
├── 方案一:使用 Server API(推荐)
│ ├── 前端请求 Server API
│ ├── Server API 请求后端
│ └── 不存在跨域问题
│
├── 方案二:后端启用 CORS
│ ├── backend/src/main.ts
│ ├── app.enableCors()
│ └── 适合开发环境
│
└── 方案三:前端直接请求(不推荐)
├── 浏览器会拦截
└── 存在跨域问题6.4 Mock 数据迁移问题
问题描述:前端使用了 Mock 数据,需要迁移到真实接口。
解决方案:
Mock 数据迁移步骤:
│
├── 第一步:删除 Mock 相关代码
│ ├── 删除 mock 目录
│ ├── 删除 mock 中间件配置
│ └── 删除 Mock 相关依赖
│
├── 第二步:创建 Server API
│ ├── server/api/courses/get.ts
│ ├── 使用 $fetch 请求后端
│ └── 返回真实数据
│
├── 第三步:修改 Store
│ ├── 修改 Store 中的请求路径
│ ├── 指向 Server API
│ └── 处理响应数据结构
│
└── 第四步:测试验证
├── 启动前后端项目
├── 浏览器访问前端
└── 查看数据是否正常七、最佳实践总结
7.1 前后端对接最佳实践
前后端对接最佳实践:
│
├── 1. 端口配置
│ ├── 前端端口:3010
│ ├── 后端端口:3000
│ └── 避免端口冲突
│
├── 2. 环境变量管理
│ ├── 使用 .env.[environment] 文件
│ ├── 区分开发、生产环境
│ └── 使用 --dotenv 指定环境变量文件
│
├── 3. 跨域问题
│ ├── 使用 Server API 代理请求(推荐)
│ ├── 隐藏真实接口路径
│ └── 便于统一管理
│
├── 4. 请求方式
│ ├── Store 封装请求逻辑
│ ├── Server API 封装接口
│ └── 页面调用 Store 方法
│
└── 5. 代码组织
├── Store:管理请求逻辑和状态
├── Server API:封装后端接口
└── 页面:渲染数据和交互7.2 Next.js 请求最佳实践
Next.js 请求最佳实践:
│
├── 1. 使用 useFetch
│ ├── 简单快捷
│ ├── 自动处理状态
│ └── SEO 友好
│
├── 2. 使用 Server API
│ ├── 隐藏真实接口
│ ├── 不存在跨域问题
│ └── 便于统一管理
│
├── 3. 使用 Store 封装
│ ├── 集中管理请求
│ ├── 便于复用
│ └── 便于测试
│
└── 4. 状态管理
├── 使用 storeToRefs 解构响应式数据
├── 使用 await 等待请求完成
└── 处理加载和错误状态7.3 安全性最佳实践
安全性最佳实践:
│
├── 1. 环境变量安全
│ ├── 不要将 .env 文件提交到 Git
│ ├── 生产环境变量要保密
│ └── 使用 .env.example 作为模板
│
├── 2. 接口安全
│ ├── 使用 Server API 隐藏真实接口
│ ├── 后端启用鉴权中间件
│ └── 限制 CORS 域名
│
├── 3. 数据安全
│ ├── 敏感数据不要返回给前端
│ ├── 使用 @Exclude() 装饰器
│ └── 数据库密码加密存储
│
└── 4. 生产环境 CORS
├── 不要使用 origin: '*'
├── 只允许特定域名
└── 启用 credentials八、命令速查表
8.1 启动命令速查
| 环境 | 命令 | 说明 |
|---|---|---|
| 后端开发 | npm run start:dev | 启动 NestJS 开发服务器 |
| 前端开发 | npm run dev | 启动 Next.js 开发服务器 |
| 前端本地测试 | npm run dev:local | 使用 .env.local 环境变量 |
| 生产构建 | npm run build | 构建生产版本 |
| 生产运行 | npm run start | 运行生产版本 |
8.2 NestJS 命令速查
| 命令 | 说明 |
|---|---|
nest g module [name] | 创建模块 |
nest g controller [name] | 创建控制器 |
nest g service [name] | 创建服务 |
nest g interceptor [name] | 创建拦截器 |
nest g decorator [name] | 创建装饰器 |
8.3 Next.js API 方法速查
| 方法 | 用途 | 示例 |
|---|---|---|
| getQuery(event) | 获取 Query 参数 | const query = getQuery(event) |
| readBody(event) | 读取请求体 | const body = await readBody(event) |
| getRouterParam(event, key) | 获取路由参数 | const id = getRouterParam(event, 'id') |
| getHeader(event, key) | 获取请求头 | const auth = getHeader(event, 'authorization') |
| setHeader(event, key, value) | 设置响应头 | setHeader(event, 'Content-Type', 'application/json') |
九、学习要点总结
9.1 核心知识点
NestJS 前后端接口对接核心要点:
│
├── 1. 端口配置
│ ├── 修改 next.config.ts 端口
│ ├── 修改 .env BASE_URL
│ └── 避免前后端端口冲突
│
├── 2. 环境变量管理
│ ├── .env.development(开发环境)
│ ├── .env.production(生产环境)
│ └── .env.local(本地测试)
│
├── 3. 跨域问题解决
│ ├── Next.js Server API(推荐)
│ ├── NestJS 启用 CORS
│ └── 理解跨域本质
│
├── 4. Next.js 请求方式
│ ├── useFetch(简单快捷)
│ ├── useAsyncData(底层 API)
│ └── $fetch(HTTP 请求实例)
│
├── 5. Server API 实现
│ ├── 定义 event handler
│ ├── 使用 $fetch 发起请求
│ └── 返回数据给前端
│
├── 6. Store 封装请求
│ ├── 集中管理请求逻辑
│ ├── 统一状态管理
│ └── 提高代码复用性
│
└── 7. 工作流组织
├── Store:请求逻辑
├── Server API:接口封装
└── 页面:数据渲染9.2 重要程度标注
| 知识点 | 重要程度 | 说明 |
|---|---|---|
| 端口配置 | 必须掌握,前后端对接第一步 | |
| 环境变量管理 | 必须掌握,区分开发生产环境 | |
| 跨域问题 | 必须掌握,前后端对接核心问题 | |
| Server API | 必须掌握,Next.js 最佳实践 | |
| useFetch | 必须掌握,Next.js 请求方式 | |
| Store 封装 | 必须掌握,代码组织最佳实践 |
9.3 学习路径规划
学习路径规划:
│
├── 第一阶段:理解概念(1 天)
│ ├── 理解前后端对接流程
│ ├── 理解跨域问题本质
│ └── 理解 Next.js Server API 优势
│
├── 第二阶段:实践操作(2-3 天)
│ ├── 配置前后端端口
│ ├── 创建 Server API
│ ├── 在 Store 中封装请求
│ └── 在页面中调用数据
│
└── 第三阶段:深入应用(持续)
├── 复杂业务场景对接
├── 性能优化
└── 安全性增强十、完整代码清单
10.1 后端配置完整代码
// backend/src/main.ts
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
// 启用 CORS
app.enableCors({
origin: ['http://localhost:3010'],
credentials: true,
})
await app.listen(3000)
console.log('Backend running on: http://localhost:3000')
}
bootstrap()// backend/src/modules/course/course.controller.ts
import { Controller, Get, Query } from '@nestjs/common'
import { CourseService } from './course.service'
@Controller('api/v1/courses')
export class CourseController {
constructor(private readonly courseService: CourseService) {}
@Get()
async getCourses(@Query() query: any) {
return this.courseService.getCourses(query)
}
}10.2 前端配置完整代码
// frontend/next.config.ts
import type { Config } from 'next'
const config: Config = {
devServer: {
port: 3010,
},
}
export default config# frontend/.env.development
BASE_URL=http://localhost:3000# frontend/.env.production
BASE_URL=https://api.production.com// frontend/package.json
{
"scripts": {
"dev": "next dev",
"dev:local": "next dev --dotenv .env.local",
"build": "next build --dotenv .env.production",
"start": "next start"
}
}10.3 Server API 完整代码
// frontend/server/api/courses/get.ts
export default defineEventHandler(async (event) => {
// 获取 Query 参数
const query = getQuery(event)
// 发起请求到后端
const data = await $fetch(`${process.env.BASE_URL}/api/v1/courses`, {
method: 'GET',
query,
})
return data
})// frontend/server/api/courses/[id].ts
export default defineEventHandler(async (event) => {
// 获取路由参数
const id = getRouterParam(event, 'id')
// 发起请求
const data = await $fetch(`${process.env.BASE_URL}/api/v1/courses/${id}`, {
method: 'GET',
})
return data
})10.4 Store 完整代码
// frontend/stores/home.ts
import { defineStore } from 'pinia'
export const useHomeStore = defineStore('home', () => {
// 状态
const courses = ref([])
const loading = ref(false)
const error = ref(null)
// Action:获取课程列表
const fetchCourses = async (params = {}) => {
loading.value = true
error.value = null
try {
const data = await $fetch('/api/courses', {
query: params,
})
courses.value = data
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
// Action:获取课程详情
const fetchCourseDetail = async (id: number) => {
const data = await $fetch(`/api/courses/${id}`)
return data
}
return {
courses,
loading,
error,
fetchCourses,
fetchCourseDetail,
}
})10.5 页面完整代码
<!-- frontend/pages/index.vue -->
<script setup lang="ts">
const homeStore = useHomeStore()
// 页面加载时获取数据
await homeStore.fetchCourses()
const { courses, loading, error } = storeToRefs(homeStore)
</script>
<template>
<div class="container">
<!-- 加载状态 -->
<div v-if="loading" class="loading">加载中...</div>
<!-- 错误状态 -->
<div v-else-if="error" class="error">
加载失败: {{ error.message }}
</div>
<!-- 课程列表 -->
<div v-else class="courses">
<div v-for="course in courses" :key="course.id" class="course-item">
<h3>{{ course.name }}</h3>
<p>{{ course.description }}</p>
</div>
</div>
</div>
</template>
<style scoped>
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.loading,
.error {
text-align: center;
padding: 40px;
font-size: 18px;
}
.error {
color: #f56c6c;
}
.courses {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.course-item {
border: 1px solid #e4e7ed;
border-radius: 8px;
padding: 20px;
transition: box-shadow 0.3s;
}
.course-item:hover {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.course-item h3 {
margin: 0 0 10px;
font-size: 18px;
}
.course-item p {
margin: 0;
color: #606266;
font-size: 14px;
}
</style>十一、扩展阅读
11.1 Next.js Server API 高级用法
请求拦截器:
// server/utils/request.ts
export const createRequest = () => {
return $fetch.create({
baseURL: process.env.BASE_URL,
onRequest({ options }) {
// 添加请求拦截器
options.headers = {
...options.headers,
Authorization: `Bearer ${getToken()}`,
}
},
onResponse({ response }) {
// 添加响应拦截器
console.log('响应数据:', response._data)
},
onResponseError({ response }) {
// 处理响应错误
console.error('请求失败:', response.status)
},
})
}11.2 Store 高级用法
使用 useFetch 在 Store 中:
// stores/course.ts
export const useCourseStore = defineStore('course', () => {
// 封装 useFetch
const useFetchCourses = (params = {}) => {
return useFetch('/api/courses', {
query: params,
// 监听参数变化
watch: [() => params],
})
}
return {
useFetchCourses,
}
})11.3 性能优化建议
前后端对接性能优化:
│
├── 1. 使用缓存
│ ├── useFetch 的 key 缓存
│ ├── Server API 缓存
│ └── Redis 缓存
│
├── 2. 请求优化
│ ├── 并行请求
│ ├── 请求去重
│ └── 数据预加载
│
└── 3. 响应优化
├── 数据分页
├── 字段精简
└── 数据压缩重要提示:前后端接口对接是全栈开发的核心技能,掌握端口配置、环境变量管理、跨域问题解决、Server API 使用、Store 封装,对实际项目开发非常重要!推荐使用 Server API 代理请求 + Store 封装请求逻辑的方式,代码优雅、安全性高、便于维护!
下节预告:Next.js 路由与状态管理进阶,深入学习页面间数据传递、全局状态管理、路由守卫等高级应用!
笔记已按照您的格式规范整理完成,可直接用于学习复习!