引言
作为一名前端开发者,你是否想过扩展自己的技术栈,尝试后端开发?
其实 NestJS 的学习曲线比你想象的平缓得多------因为它的设计思想和 Vue 有很多相似之处!
本文将用前端熟悉的概念,带你快速上手 NestJS。
一、NestJS 是什么?
NestJS 是一个用于构建高效、可扩展的 Node.js 服务端应用程序框架。它使用 TypeScript 构建,结合了 OOP、FP 和 FRP 的元素。
简单来说:
如果说 Vue 是前端界的"组件化框架" ,那么 NestJS 就是后端界的"模块化框架"。
二、Vue vs NestJS:概念对照表
| Vue 概念 | NestJS 对应概念 | 说明 |
|---|---|---|
| 组件 (Component) | 控制器 (Controller) | 处理用户交互/请求 |
| 组合式函数 (Composables) | 服务 (Service) | 封装可复用的业务逻辑 |
| Props | DTO | 定义数据结构和类型 |
| Pinia/Vuex Store | Module | 状态/功能模块化组织 |
| Vue Router | 路由装饰器 (@Get/@Post) | 定义访问路径 |
| provide/inject | 依赖注入 (DI) | 跨组件/模块共享实例 |
| 生命周期钩子 | 生命周期钩子 | 应用启动、请求处理等阶段 |
三、从 Vue 组件到 NestJS 控制器
Vue 组件写法
vue
<script setup>
import { ref } from 'vue'
import { useUserStore } from '@/stores/user'
const props = defineProps({
userId: String
})
const userStore = useUserStore()
const fetchUser = () => {
return userStore.getUser(props.userId)
}
</script>
NestJS 控制器写法
typescript
import { Controller, Get, Param } from '@nestjs/common'
import { PersonService } from './person.service'
@Controller('api/person')
export class PersonController {
constructor(private readonly personService: PersonService) {}
@Get(':id')
findOne(@Param('id') id: string) {
return this.personService.findOne(+id)
}
}
类比理解
@Controller('api/person')≈ 组件的 name + 基础路由路径@Get(':id')≈ Vue Router 的path: '/person/:id'@Param('id')≈useRoute().params.idconstructor 注入≈const store = useStore()
四、DTO:相当于 Vue 的 Props 类型定义
Vue 的 Props 类型定义
typescript
interface UserProps {
name: string
age: number
}
NestJS 的 DTO
typescript
// dto/create-person.dto.ts
export class CreatePersonDto {
name: string
age: number
}
实际使用
typescript
@Post()
create(@Body() createPersonDto: CreatePersonDto) {
return this.personService.create(createPersonDto)
}
类比理解
- DTO ≈ Props 类型定义 + 表单校验规则
- @Body() ≈ 获取 POST 请求的 body,类似 v-model 绑定的数据
五、Service:相当于 Composables
Vue 的 Composables
typescript
// composables/useUser.ts
export function useUser() {
const fetchUser = async (id: string) => {
// 请求逻辑
}
return { fetchUser }
}
NestJS 的 Service
typescript
import { Injectable } from '@nestjs/common'
@Injectable()
export class PersonService {
create(createPersonDto: CreatePersonDto) {
return 'This action adds a new person'
}
findOne(id: number) {
return `This action returns a #${id} person`
}
update(id: number, updatePersonDto: UpdatePersonDto) {
return `This action updates a #${id} person`
}
remove(id: number) {
return `This action removes a #${id} person`
}
}
类比理解
- @Injectable() ≈
export function useXXX(),表示这是一个可复用的服务 - Service 中封装业务逻辑,Controller 只负责接收请求和返回响应
六、Module:相当于 Pinia 的 Store 模块
Vue 的 Pinia Store
typescript
// stores/user.ts
export const useUserStore = defineStore('user', {
state: () => ({ ... }),
actions: { ... }
})
NestJS 的 Module
typescript
import { Module } from '@nestjs/common'
import { PersonController } from './person.controller'
import { PersonService } from './person.service'
@Module({
imports: [],
controllers: [PersonController],
providers: [PersonService],
exports: [PersonService]
})
export class PersonModule {}
根模块
typescript
@Module({
imports: [PersonModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
类比理解
- Module ≈ Pinia Store 模块,用于组织和隔离功能
- imports ≈ 引入其他 store 模块
- providers ≈ 模块内部的状态和方法
- exports ≈ 暴露给其他模块使用的 API
七、依赖注入:比 provide/inject 更强大
Vue 的依赖注入
vue
<!-- 祖先组件 -->
<script setup>
import { provide } from 'vue'
provide('userService', new UserService())
</script>
<!-- 后代组件 -->
<script setup>
import { inject } from 'vue'
const userService = inject('userService')
</script>
NestJS 的依赖注入
typescript
@Controller('api/person')
export class PersonController {
constructor(private readonly personService: PersonService) {}
}
优势
- 自动单例管理:类似 Vue 的 provide 默认只创建一次实例
- 自动解析依赖:不需要手动 inject,按类型自动匹配
- 便于测试:可以轻松 Mock 服务进行单元测试
八、装饰器语法:类似 Vue 的宏函数
Vue 3 的宏函数
vue
<script setup>
defineProps({ ... })
defineEmits(['click'])
defineExpose({ ... })
</script>
NestJS 的装饰器
typescript
@Controller('api/person')
@Get(':id')
@Post()
@Body()
@Param('id')
@Query('name')
@Injectable()
@Module({ ... })
类比理解
装饰器 ≈ Vue 的宏函数 + 指令,提供声明式的元数据标记
九、5 种常见请求方式对照示例
根据实际项目中的接口设计,以下是前端 5 种请求方式与后端处理的完整对照:
1. URL 参数(@Param)
前端:
javascript
const res = await axios.get('/api/person/1')
后端:
typescript
@Get(':id')
findOne(@Param('id') id: string) {
return `This is a person with id: ${id}`
}
2. Query 参数(@Query)
前端:
javascript
const res = await axios.get('/api/person/find', {
params: { name: '张三', age: 20 }
})
后端:
typescript
@Get('find')
query(@Query('name') name: string, @Query('age') age: number) {
return `name: ${name}, age: ${age}`
}
3. JSON 请求体(@Body)
前端:
javascript
const res = await axios.post('/api/person', {
name: '张三',
age: 20
})
后端:
typescript
@Post()
body(@Body() createPersonDto: CreatePersonDto) {
return `name: ${createPersonDto.name}, age: ${createPersonDto.age}`
}
4. Form URL Encoded(@Body)
前端:
javascript
const res = await axios.post('/api/person', Qs.stringify({
name: '张三',
age: 20
}), {
headers: { 'content-type': 'application/x-www-form-urlencoded' }
})
后端:
typescript
@Post()
body(@Body() createPersonDto: CreatePersonDto) {
return `name: ${createPersonDto.name}, age: ${createPersonDto.age}`
}
5. FormData 文件上传(@UploadedFiles)
前端:
javascript
const data = new FormData()
data.set('name', '张三')
data.set('age', 20)
data.set('file1', fileInput.files[0])
data.set('file2', fileInput.files[1])
const res = await axios.post('/api/person/file', data, {
headers: { 'content-type': 'multipart/form-data' }
})
后端:
typescript
@Post('file')
@UseInterceptors(AnyFilesInterceptor({ dest: 'uploads' }))
body2(
@Body() createPersonDto: CreatePersonDto,
@UploadedFiles() files: Array<Express.Multer.File>
) {
return `name: ${createPersonDto.name}, age: ${createPersonDto.age}`
}
完整控制器代码
typescript
// person.controller.ts
import { Controller, Get, Post, Body, Query, Param, UseInterceptors, UploadedFiles } from '@nestjs/common'
import { AnyFilesInterceptor } from '@nestjs/platform-express'
@Controller('api/person')
export class PersonController {
// 1. URL 参数
@Get(':id')
findOne(@Param('id') id: string) {
return `This is a person with id: ${id}`
}
// 2. Query 参数
@Get('find')
query(@Query('name') name: string, @Query('age') age: number) {
return `name: ${name}, age: ${age}`
}
// 3 & 4. JSON / Form URL Encoded
@Post()
body(@Body() createPersonDto: CreatePersonDto) {
return `name: ${createPersonDto.name}, age: ${createPersonDto.age}`
}
// 5. FormData 文件上传
@Post('file')
@UseInterceptors(AnyFilesInterceptor({ dest: 'uploads' }))
uploadFile(
@Body() createPersonDto: CreatePersonDto,
@UploadedFiles() files: Array<Express.Multer.File>
) {
return { message: '上传成功', name: createPersonDto.name, files }
}
}
十、前端开发者学 NestJS 的优势
| 优势 | 说明 |
|---|---|
| TypeScript 无缝衔接 | 和 Vue 3 + TS 的开发体验一致 |
| 装饰器语法熟悉 | 类似 Vue 的宏函数,降低学习成本 |
| 模块化思维 | Vue 的组件化思维直接迁移到后端模块 |
| 依赖注入理解 | Vue 的 provide/inject 让你更容易理解 DI |
| 响应式编程 | RxJS 和 Vue 的响应式系统有异曲同工之妙 |
总结
NestJS 的设计哲学和 Vue 有很多相通之处:
| 前端思维 | 后端实现 |
|---|---|
| 组件化开发 | 控制器 + 服务分离 |
| Props 类型校验 | DTO 数据验证 |
| Pinia 状态管理 | Module 模块组织 |
| Composables 复用 | Service 服务封装 |
| provide/inject | 依赖注入系统 |
如果你已经熟悉 Vue,那么学习 NestJS 只需要转换视角------从"操作 DOM 和状态"转变为"处理请求和数据"。
相信凭借前端的基础,你能很快上手 NestJS,成为真正的全栈开发者!
推荐学习路径
- 先理解 Controller、Service、Module 三大核心概念
- 学习装饰器的使用(@Get、@Post、@Body 等)
- 掌握 DTO 和管道验证(类似表单校验)
- 了解 Guards、Interceptors、Middleware(类似路由守卫、请求拦截)
- 实践一个完整的 CRUD 项目
希望这篇文章能帮助你顺利踏上后端开发之旅!