纯前端转全栈 Day 1:我从第一个 NestJS 接口开始
我是一个前端开发,之前更多关注的是:页面怎么展示、组件怎么拆、状态怎么管理、接口怎么联调。
但最近我越来越明显地感受到,只会写页面已经不太够了。很多业务并不是把 UI 做出来就结束,而是需要理解一个功能从页面、接口、数据、权限到部署的完整链路。
所以我决定开始系统补服务端能力。
我的目标不是立刻成为传统后端,而是先成为一个能独立完成业务闭环的全栈型前端。
为什么从 NestJS 开始?
我选择从 NestJS 开始,是因为它基于 Node.js 和 TypeScript,对前端来说学习成本相对低一些。
而且它的很多概念,比如:
- Controller
- Service
- Module
- 依赖注入
和我之前接触过的 Angular 有一定相似之处。
对于一个前端来说,用 TypeScript 体系切入服务端,是一条成本相对更低的路线。
今天完成了什么?
今天我完成了第一个 NestJS 项目,并写了两个接口。
第一个接口是:
http
GET /
返回:
css
Hello Content Studio API
第二个接口是:
bash
GET /health
返回:
json
{
"status": "ok",
"service": "content-studio-api"
}
虽然这两个接口都很简单,但它让我第一次从"调用接口的人",变成了"写接口的人"。
我理解的 NestJS 请求链路
今天我理解到,NestJS 里最基础的请求链路是:
markdown
浏览器发起请求
↓
Controller 接收请求
↓
Controller 调用 Service
↓
Service 处理逻辑并返回数据
↓
NestJS 把结果响应给浏览器
也就是说:
- Controller 更像是接口入口,负责接住请求
- Service 更像是真正处理业务逻辑的地方
- Module 负责把 Controller 和 Service 组织在一起
今天的接口很简单,所以暂时看不出分层的必要性。
但我能理解,后面如果要做用户注册、登录、内容创建、数据库查询,就不适合把所有逻辑都写在 Controller 里。
今天最困惑的点:constructor 和依赖注入
今天最让我困惑的是 constructor 和依赖注入。
一开始我看到这句代码:
typescript
constructor(private readonly appService: AppService) {}
会有点不理解:
我明明没有
new AppService(),为什么this.appService可以直接使用?
后来我把它理解成:
AppController 需要 AppService,但它不自己创建 AppService,而是在 constructor 里声明:
我需要一个 AppService。
只要 AppService 已经在 Module 的 providers 里注册,NestJS 就会帮我们创建它,并注入到 AppController 里。
所以依赖注入的核心不是某个神秘语法,而是一种分工方式:
类不负责自己创建依赖,而是声明自己需要什么;框架负责创建和管理这些依赖。
换句话说,Controller 不需要关心 Service 是怎么被创建出来的,它只需要声明自己要用这个 Service。
今天遇到的环境问题
今天还遇到了一个环境问题。
我使用 pnpm 安装依赖时,出现了类似这样的提示:
csharp
[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts
后来通过执行:
pnpm approve-builds
允许下面两个包执行构建脚本:
less
@nestjs/core
@swc/core
项目才正常启动。
这个问题也让我意识到,前端或全栈开发里,环境问题本身也是学习的一部分。
有时候并不是代码写错了,而是包管理器、依赖构建、Node 版本、脚本权限这些地方出了问题。
重新理解 localhost
今天我还重新理解了一下 localhost。
以前做前端开发时,我经常会在浏览器里访问 localhost,比如:
arduino
http://localhost:5173
或者:
arduino
http://localhost:3000
以前我只是知道它能访问本地服务,但今天我意识到:
localhost 并不是某个固定的服务器地址,而是"当前运行环境里的自己"。
当我在 Mac 浏览器里访问:
arduino
http://localhost:3000
请求的是我这台 Mac 上 3000 端口的 NestJS 服务。
但如果同样的代码部署到线上,真实用户浏览器里的 localhost 就会变成用户自己的电脑,而不是我的服务器。
所以线上接口地址不能写死成:
arduino
http://localhost:3000
而应该通过环境变量区分不同环境:
开发环境:localhost
测试环境:test-api.xxx.com
生产环境:api.xxx.com
127.0.0.1 和 0.0.0.0
今天也初步理解了 127.0.0.1 和 0.0.0.0 的区别。
127.0.0.1 更像是本机内部访问自己。
而 0.0.0.0 常用于服务端监听所有网络接口,让局域网内其他设备也可能访问到这个服务。
比如:
csharp
await app.listen(3000, '0.0.0.0');
可以先理解成:
这个服务不只监听本机内部访问,也愿意接受来自其他网络入口的请求。
这让我意识到,后端开发里一个很基础但很重要的能力,是理解:
服务运行在哪里?请求从哪里来?最后又会被路由到哪里去?
今天的收获
今天的收获不是写了多少代码,而是理解了一个后端服务最基础的运行链路:
请求从哪里进来
谁接收请求
谁处理逻辑
结果怎么返回
服务运行在哪里
localhost 到底指向谁
以前我更多是"调用接口的人"。
今天开始,我第一次真正从服务端视角去看一个请求。
明天准备做什么?
明天我准备继续做第一个 CRUD。
我会从内容管理业务开始,尝试实现:
- 内容列表
- 内容详情
- 新增内容
- 编辑内容
- 删除内容
也就是从一个真实的小业务开始,继续理解后端接口是怎么设计和实现的。