{T}

NestJS前后端接口对接实战

NestJS前后端接口对接实战

学习目标:掌握前后端端口配置、环境变量管理、跨域问题解决、Next.js Server API 使用、前后端打通流程。


一、前后端接口对接概述

1.1 前后端对接核心要点

code
前后端接口对接核心要点:
│
├── 端口配置
│   ├── 前端端口设置
│   ├── 后端端口设置
│   └── 避免端口冲突
│
├── 环境变量管理
│   ├── 开发环境配置
│   ├── 生产环境配置
│   └── 本地测试环境配置
│
├── 跨域问题
│   ├── Next.js Server API 优势
│   ├── NestJS 跨域配置
│   └── 浏览器跨域限制
│
├── 请求方式
│   ├── useFetch 直接请求
│   ├── useAsyncData 请求
│   └── Server API 代理请求(推荐)
│
└── 工作流组织
    ├── Store 管理请求逻辑
    ├── Server API 封装接口
    └── 页面调用 Store 方法

1.2 前后端对接流程图

code
前后端对接完整流程:
│
├── 第一步:配置端口
│   ├── 修改 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

typescript
// next.config.ts
import type { Config } from 'next'

const config: Config = {
  //  设置开发服务器端口
  devServer: {
    port: 3010, // 前端端口设置为 3010
  },
  
  // 其他配置...
}

export default config

重要提示

code
端口设置注意事项:
│
├──  修改端口后,需要同步修改 .env 中的 BASE_URL
│
├──  前端端口:3010
│
├──  后端端口:3000
│
└──  确保两个端口不冲突

2.2 环境变量配置

不同环境的环境变量文件

code
项目根目录环境变量文件:
│
├── .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:3000

package.json 脚本配置

json
{
  "scripts": {
    "dev": "next dev",
    "dev:local": "next dev --dotenv .env.local",
    "build": "next build --dotenv .env.production",
    "start": "next start"
  }
}

环境变量说明

code
环境变量文件说明:
│
├── .env
│   ├── 默认环境变量
│   ├── 所有环境都会加载
│   └── 通常放置基础配置
│
├── .env.development
│   ├── 开发环境配置
│   ├── npm run dev 时加载
│   └── 连接本地后端接口
│
├── .env.production
│   ├── 生产环境配置
│   ├── npm run build 时加载(需配置 --dotenv)
│   └── 连接生产环境接口
│
└── .env.local
    ├── 本地测试环境配置
    ├── npm run dev:local 时加载
    └── 用于本地测试生产接口

验证环境变量是否生效

typescript
// 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 端口配置完整示例

文件结构

code
前端项目/
├── next.config.ts           # 端口配置
├── .env                     # 默认环境变量
├── .env.development         # 开发环境
├── .env.production          # 生产环境
├── .env.local               # 本地测试环境(可选)
└── package.json             # 脚本配置

完整配置示例

typescript
// next.config.ts
import type { Config } from 'next'

const config: Config = {
  devServer: {
    port: 3010, //  前端端口
  },
  
  // 其他配置...
}

export default config
bash
# .env.development
BASE_URL=http://localhost:3000
bash
# .env.production
BASE_URL=https://api.production.com
bash
# .env.local(可选)
BASE_URL=http://localhost:3000
json
// package.json
{
  "scripts": {
    "dev": "next dev",
    "dev:local": "next dev --dotenv .env.local",
    "build": "next build --dotenv .env.production",
    "start": "next start"
  }
}

三、跨域资源请求问题

3.1 跨域问题概述

什么是跨域

code
跨域问题本质:
│
├── 浏览器同源策略
│   ├── 协议(Protocol)
│   ├── 域名(Domain)
│   └── 端口(Port)
│
├── 跨域场景
│   ├── 前端:http://localhost:3010
│   ├── 后端:http://localhost:3000
│   └── 端口不同 → 跨域
│
└── 跨域限制
    ├── XMLHttpRequest
    ├── Fetch API
    └── Axios(浏览器端)

3.2 Next.js Server API 解决跨域

Next.js Server API 优势

code
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

typescript
// 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 配置选项

typescript
// 完整 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 配置建议

code
生产环境 CORS 配置建议:
│
├──  必须限制 origin
│   ├── 不要使用 origin: '*'
│   ├── 只允许特定的域名
│   └── 防止恶意网站请求
│
├──  启用 credentials
│   ├── 允许携带 Cookie
│   ├── 允许携带 Authorization
│   └── 支持鉴权功能
│
└──  设置合理的缓存时间
    ├── 减少 OPTIONS 请求次数
    └── 提高接口响应速度

3.4 跨域问题解决方案对比

方案优点缺点推荐场景
NestJS 启用 CORS配置简单暴露真实接口开发环境
Next.js Server API隐藏接口、无跨域需要额外封装生产环境
Nginx 反向代理生产级方案需要运维配置大型项目
JSONP兼容性好只支持 GET已过时

最佳实践

code
跨域问题最佳实践:
│
├── 开发环境
│   ├── 方案一:NestJS 启用 CORS
│   ├── 方案二:Next.js Server API(推荐)
│   └── 快速联调,提高开发效率
│
├── 生产环境
│   ├── 方案一:Next.js Server API(推荐)
│   ├── 方案二:Nginx 反向代理
│   └── 隐藏真实接口,提高安全性
│
└── 注意事项
    ├── 不要在生产环境使用 origin: '*'
    ├── Server API 是最佳实践
    └── 便于统一管理接口

四、Next.js 请求工作流

4.1 useFetch 详解

useFetch 是什么

code
useFetch 本质:
│
├── useFetch = useAsyncData + $fetch
│
├── useAsyncData
│   ├── 管理异步数据
│   ├── 处理加载状态
│   └── 处理错误状态
│
└── $fetch
    ├── 内置 HTTP 请求实例
    ├── 类似 Axios
    └── 支持 GET、POST 等方法

useFetch 特点

code
useFetch 特点:
│
├──  服务端执行
│   ├── 首次渲染在服务端执行
│   ├── 直接从服务端获取数据
│   └── 不需要客户端重新请求
│
├──  SEO 友好
│   ├── 数据在服务端渲染
│   ├── 搜索引擎可以抓取数据
│   └── 提高页面 SEO 排名
│
├──  自动处理状态
│   ├── data:响应数据
│   ├── pending:加载状态
│   └── error:错误状态
│
└──  可在任何地方使用
    ├── 插件(plugins)
    ├── 路由中间件(middleware)
    └── 页面组件(pages)

useFetch 使用示例

Vue SFC
<!-- 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 的关系

typescript
// useFetch 本质上是 useAsyncData + $fetch
const { data } = await useFetch('/api/courses')

// 等价于
const { data } = await useAsyncData('courses', () => $fetch('/api/courses'))

useAsyncData 使用示例

Vue SFC
<!-- pages/index.vue -->
<script setup lang="ts">
//  使用 useAsyncData
const { data, pending, error } = await useAsyncData(
  'courses', // 唯一 key
  () => $fetch('/api/courses') // 数据获取函数
)
</script>

useFetch vs useAsyncData 对比

维度useFetchuseAsyncData
本质useAsyncData + $fetch底层 API
使用场景简单请求复杂请求逻辑
灵活性简单快捷更加灵活
推荐度

useFetch 高级用法

Vue SFC
<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 文件结构

code
前端项目/
└── server/
    └── api/
        ├── courses/
        │   ├── get.ts         # GET 请求
        │   └── post.ts        # POST 请求
        └── users/
            └── get.ts

GET 请求示例

typescript
// 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 请求示例

typescript
// 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 请求

typescript
// 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 示例

typescript
// 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 封装请求的优势

code
Store 封装请求的优势:
│
├──  集中管理请求逻辑
│   ├── 所有请求逻辑在 Store 中
│   ├── 便于统一管理
│   └── 便于后续维护
│
├──  复用性强
│   ├── 多个页面可复用同一请求
│   ├── 避免重复代码
│   └── 提高开发效率
│
├──  统一状态管理
│   ├── 响应数据统一存储
│   ├── 加载状态统一管理
│   └── 错误状态统一处理
│
└──  便于测试
    ├── 请求逻辑独立
    ├── 便于单元测试
    └── 提高代码质量

Store 封装请求示例

typescript
// 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

Vue SFC
<!-- 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

typescript
// 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 项目结构

code
前后端项目结构:
│
├── 后端项目(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)

typescript
// 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()
typescript
// 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)

typescript
// frontend/next.config.ts
import type { Config } from 'next'

const config: Config = {
  devServer: {
    port: 3010, //  前端端口
  },
}

export default config
bash
# frontend/.env.development
BASE_URL=http://localhost:3000
bash
# frontend/.env.production
BASE_URL=https://api.production.com
json
// 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 实现

typescript
// 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 封装

typescript
// 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. 页面使用

Vue SFC
<!-- 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 启动流程

code
前后端项目启动流程:
│
├── 第一步:启动后端(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 devhttp://localhost:3000
本地测试生产npm run dev:local.env.local 中的配置
生产构建npm run build.env.production 中的配置
生产运行npm run start.env.production 中的配置

六、常见问题与解决方案

6.1 端口冲突问题

问题描述:前后端端口都是 3000,启动时冲突。

解决方案

typescript
//  修改前端端口
// next.config.ts
const config: Config = {
  devServer: {
    port: 3010,
  },
}

//  修改后端端口
// backend/src/main.ts
await app.listen(3000)

6.2 环境变量不生效问题

问题描述:修改了 .env 文件,但环境变量没有生效。

解决方案

bash
#  方案一:重启项目
# 修改 .env 文件后,重启项目才能生效

#  方案二:指定环境变量文件
# package.json
{
  "scripts": {
    "dev": "next dev --dotenv .env.development",
    "build": "next build --dotenv .env.production"
  }
}

#  方案三:使用 .env.local
# .env.local 优先级最高

环境变量优先级

code
环境变量优先级(从高到低):
│
├── .env.local                 # 最高优先级
├── .env.[environment]         # .env.development / .env.production
├── .env                       # 最低优先级
└── 系统环境变量               # 优先级最高

6.3 跨域请求失败问题

问题描述:前端直接请求后端接口,提示跨域错误。

解决方案

code
跨域问题解决方案:
│
├──  方案一:使用 Server API(推荐)
│   ├── 前端请求 Server API
│   ├── Server API 请求后端
│   └── 不存在跨域问题
│
├──  方案二:后端启用 CORS
│   ├── backend/src/main.ts
│   ├── app.enableCors()
│   └── 适合开发环境
│
└──  方案三:前端直接请求(不推荐)
    ├── 浏览器会拦截
    └── 存在跨域问题

6.4 Mock 数据迁移问题

问题描述:前端使用了 Mock 数据,需要迁移到真实接口。

解决方案

code
Mock 数据迁移步骤:
│
├── 第一步:删除 Mock 相关代码
│   ├── 删除 mock 目录
│   ├── 删除 mock 中间件配置
│   └── 删除 Mock 相关依赖
│
├── 第二步:创建 Server API
│   ├── server/api/courses/get.ts
│   ├── 使用 $fetch 请求后端
│   └── 返回真实数据
│
├── 第三步:修改 Store
│   ├── 修改 Store 中的请求路径
│   ├── 指向 Server API
│   └── 处理响应数据结构
│
└── 第四步:测试验证
    ├── 启动前后端项目
    ├── 浏览器访问前端
    └── 查看数据是否正常

七、最佳实践总结

7.1 前后端对接最佳实践

code
前后端对接最佳实践:
│
├── 1. 端口配置
│   ├── 前端端口:3010
│   ├── 后端端口:3000
│   └── 避免端口冲突
│
├── 2. 环境变量管理
│   ├── 使用 .env.[environment] 文件
│   ├── 区分开发、生产环境
│   └── 使用 --dotenv 指定环境变量文件
│
├── 3. 跨域问题
│   ├── 使用 Server API 代理请求(推荐)
│   ├── 隐藏真实接口路径
│   └── 便于统一管理
│
├── 4. 请求方式
│   ├── Store 封装请求逻辑
│   ├── Server API 封装接口
│   └── 页面调用 Store 方法
│
└── 5. 代码组织
    ├── Store:管理请求逻辑和状态
    ├── Server API:封装后端接口
    └── 页面:渲染数据和交互

7.2 Next.js 请求最佳实践

code
Next.js 请求最佳实践:
│
├── 1. 使用 useFetch
│   ├── 简单快捷
│   ├── 自动处理状态
│   └── SEO 友好
│
├── 2. 使用 Server API
│   ├── 隐藏真实接口
│   ├── 不存在跨域问题
│   └── 便于统一管理
│
├── 3. 使用 Store 封装
│   ├── 集中管理请求
│   ├── 便于复用
│   └── 便于测试
│
└── 4. 状态管理
    ├── 使用 storeToRefs 解构响应式数据
    ├── 使用 await 等待请求完成
    └── 处理加载和错误状态

7.3 安全性最佳实践

code
安全性最佳实践:
│
├── 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 核心知识点

code
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 学习路径规划

code
学习路径规划:
│
├── 第一阶段:理解概念(1 天)
│   ├── 理解前后端对接流程
│   ├── 理解跨域问题本质
│   └── 理解 Next.js Server API 优势
│
├── 第二阶段:实践操作(2-3 天)
│   ├── 配置前后端端口
│   ├── 创建 Server API
│   ├── 在 Store 中封装请求
│   └── 在页面中调用数据
│
└── 第三阶段:深入应用(持续)
    ├── 复杂业务场景对接
    ├── 性能优化
    └── 安全性增强

十、完整代码清单

10.1 后端配置完整代码

typescript
// 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()
typescript
// 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 前端配置完整代码

typescript
// frontend/next.config.ts
import type { Config } from 'next'

const config: Config = {
  devServer: {
    port: 3010,
  },
}

export default config
bash
# frontend/.env.development
BASE_URL=http://localhost:3000
bash
# frontend/.env.production
BASE_URL=https://api.production.com
json
// 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 完整代码

typescript
// 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
})
typescript
// 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 完整代码

typescript
// 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 页面完整代码

Vue SFC
<!-- 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 高级用法

请求拦截器

typescript
// 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 中

typescript
// stores/course.ts
export const useCourseStore = defineStore('course', () => {
  //  封装 useFetch
  const useFetchCourses = (params = {}) => {
    return useFetch('/api/courses', {
      query: params,
      //  监听参数变化
      watch: [() => params],
    })
  }
  
  return {
    useFetchCourses,
  }
})

11.3 性能优化建议

code
前后端对接性能优化:
│
├── 1. 使用缓存
│   ├── useFetch 的 key 缓存
│   ├── Server API 缓存
│   └── Redis 缓存
│
├── 2. 请求优化
│   ├── 并行请求
│   ├── 请求去重
│   └── 数据预加载
│
└── 3. 响应优化
    ├── 数据分页
    ├── 字段精简
    └── 数据压缩

重要提示:前后端接口对接是全栈开发的核心技能,掌握端口配置、环境变量管理、跨域问题解决、Server API 使用、Store 封装,对实际项目开发非常重要!推荐使用 Server API 代理请求 + Store 封装请求逻辑的方式,代码优雅、安全性高、便于维护!

下节预告:Next.js 路由与状态管理进阶,深入学习页面间数据传递、全局状态管理、路由守卫等高级应用!

笔记已按照您的格式规范整理完成,可直接用于学习复习!