前端转后端?用 Vue 的思维学 NestJS,真香!

0 阅读4分钟

引言

作为一名前端开发者,你是否想过扩展自己的技术栈,尝试后端开发?

其实 NestJS 的学习曲线比你想象的平缓得多——因为它的设计思想和 Vue 有很多相似之处!

本文将用前端熟悉的概念,带你快速上手 NestJS。


一、NestJS 是什么?

NestJS 是一个用于构建高效、可扩展的 Node.js 服务端应用程序框架。它使用 TypeScript 构建,结合了 OOP、FP 和 FRP 的元素。

简单来说:

如果说 Vue 是前端界的"组件化框架",那么 NestJS 就是后端界的"模块化框架"


二、Vue vs NestJS:概念对照表

Vue 概念NestJS 对应概念说明
组件 (Component)控制器 (Controller)处理用户交互/请求
组合式函数 (Composables)服务 (Service)封装可复用的业务逻辑
PropsDTO定义数据结构和类型
Pinia/Vuex StoreModule状态/功能模块化组织
Vue Router路由装饰器 (@Get/@Post)定义访问路径
provide/inject依赖注入 (DI)跨组件/模块共享实例
生命周期钩子生命周期钩子应用启动、请求处理等阶段

三、从 Vue 组件到 NestJS 控制器

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 控制器写法

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.id
  • constructor 注入const store = useStore()

四、DTO:相当于 Vue 的 Props 类型定义

Vue 的 Props 类型定义

interface UserProps {
  name: string
  age: number
}

NestJS 的 DTO

// dto/create-person.dto.ts
export class CreatePersonDto {
  name: string
  age: number
}

实际使用

@Post()
create(@Body() createPersonDto: CreatePersonDto) {
  return this.personService.create(createPersonDto)
}

类比理解

  • DTO ≈ Props 类型定义 + 表单校验规则
  • @Body() ≈ 获取 POST 请求的 body,类似 v-model 绑定的数据

五、Service:相当于 Composables

Vue 的 Composables

// composables/useUser.ts
export function useUser() {
  const fetchUser = async (id: string) => {
    // 请求逻辑
  }
  return { fetchUser }
}

NestJS 的 Service

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

// stores/user.ts
export const useUserStore = defineStore('user', {
  state: () => ({ ... }),
  actions: { ... }
})

NestJS 的 Module

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 {}

根模块

@Module({
  imports: [PersonModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

类比理解

  • Module ≈ Pinia Store 模块,用于组织和隔离功能
  • imports ≈ 引入其他 store 模块
  • providers ≈ 模块内部的状态和方法
  • exports ≈ 暴露给其他模块使用的 API

七、依赖注入:比 provide/inject 更强大

Vue 的依赖注入

<!-- 祖先组件 -->
<script setup>
import { provide } from 'vue'
provide('userService', new UserService())
</script>

<!-- 后代组件 -->
<script setup>
import { inject } from 'vue'
const userService = inject('userService')
</script>

NestJS 的依赖注入

@Controller('api/person')
export class PersonController {
  constructor(private readonly personService: PersonService) {}
}

优势

  1. 自动单例管理:类似 Vue 的 provide 默认只创建一次实例
  2. 自动解析依赖:不需要手动 inject,按类型自动匹配
  3. 便于测试:可以轻松 Mock 服务进行单元测试

八、装饰器语法:类似 Vue 的宏函数

Vue 3 的宏函数

<script setup>
defineProps({ ... })
defineEmits(['click'])
defineExpose({ ... })
</script>

NestJS 的装饰器

@Controller('api/person')
@Get(':id')
@Post()
@Body()
@Param('id')
@Query('name')
@Injectable()
@Module({ ... })

类比理解

装饰器 ≈ Vue 的宏函数 + 指令,提供声明式的元数据标记


九、5 种常见请求方式对照示例

根据实际项目中的接口设计,以下是前端 5 种请求方式与后端处理的完整对照:

1. URL 参数(@Param)

前端:

const res = await axios.get('/api/person/1')

后端:

@Get(':id')
findOne(@Param('id') id: string) {
  return `This is a person with id: ${id}`
}

2. Query 参数(@Query)

前端:

const res = await axios.get('/api/person/find', {
  params: { name: '张三', age: 20 }
})

后端:

@Get('find')
query(@Query('name') name: string, @Query('age') age: number) {
  return `name: ${name}, age: ${age}`
}

3. JSON 请求体(@Body)

前端:

const res = await axios.post('/api/person', {
  name: '张三',
  age: 20
})

后端:

@Post()
body(@Body() createPersonDto: CreatePersonDto) {
  return `name: ${createPersonDto.name}, age: ${createPersonDto.age}`
}

4. Form URL Encoded(@Body)

前端:

const res = await axios.post('/api/person', Qs.stringify({
  name: '张三',
  age: 20
}), {
  headers: { 'content-type': 'application/x-www-form-urlencoded' }
})

后端:

@Post()
body(@Body() createPersonDto: CreatePersonDto) {
  return `name: ${createPersonDto.name}, age: ${createPersonDto.age}`
}

5. FormData 文件上传(@UploadedFiles)

前端:

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' }
})

后端:

@Post('file')
@UseInterceptors(AnyFilesInterceptor({ dest: 'uploads' }))
body2(
  @Body() createPersonDto: CreatePersonDto,
  @UploadedFiles() files: Array<Express.Multer.File>
) {
  return `name: ${createPersonDto.name}, age: ${createPersonDto.age}`
}

完整控制器代码

// 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,成为真正的全栈开发者!


推荐学习路径

  1. 先理解 Controller、Service、Module 三大核心概念
  2. 学习装饰器的使用(@Get、@Post、@Body 等)
  3. 掌握 DTO 和管道验证(类似表单校验)
  4. 了解 Guards、Interceptors、Middleware(类似路由守卫、请求拦截)
  5. 实践一个完整的 CRUD 项目

希望这篇文章能帮助你顺利踏上后端开发之旅!