尝试构建Express后端
个人实践之Express后端构建
环境配置
1. 初始化项目
首先,在一个新目录中初始化一个 Node.js 项目:
1 | mkdir express-backend |
这会创建一个基本的 package.json
文件,后续可以通过这个文件管理项目的依赖。
2. 安装依赖(好多不认识的)
安装 Express 框架以及一些常见的开发工具和中间件:
1 | npm install express |
- express: 核心的 Web 框架。
- dotenv: 用于管理环境变量,存储敏感信息(如数据库连接字符串、API 密钥等)。
- morgan: HTTP 请求日志中间件,可以用来记录请求日志,方便调试和监控。
- cors: 解决跨域问题的中间件。
- body-parser: 用于解析请求体中的 JSON 数据(对于 POST 请求等非常常见)。
- nodemon: 开发环境中用于自动重启服务的工具。
- (后面我确认不需要了会进行修改)
3. 项目结构设计(估计不会使用这个设计)
一般来说,可以按以下结构组织 Express 项目:
1 | express-backend/ |
- src/controllers:处理具体的业务逻辑。
- src/middleware:放置自定义中间件,例如身份验证中间件、日志中间件等。
- src/routes:定义 API 路由,调用控制器。
- src/models:数据库模型(如使用 ORM,Mongoose 或 Sequelize 等)。
- src/config:存放项目的配置文件,如数据库连接、第三方 API 密钥等。
4. 创建基础的 Express 应用
在 src/server.js
中创建一个基本的 Express 服务器:
1 | javascript// src/server.js |
(补充)主要区别:CommonJS 与 ES Modules
**
require
与import
**:require
是 CommonJS 模块系统的导入方式,它是 Node.js 默认的模块系统,直到 Node.js v12 之前。import
是 ES Modules 的导入方式,这种方式更符合现代 JavaScript 标准,并且在前端 JavaScript(例如浏览器中)中也广泛使用。
导出:
- CommonJS使用
module.exports
或exports
来导出模块。例如:javascriptmodule.exports = { myFunction };
- ES Modules使用
export
或export default
来导出模块。例如:javascriptexport default myFunction;
- CommonJS使用
同步与异步:
require
是同步加载模块的,因此在代码执行时会直接读取模块。import
是异步加载的,虽然在大多数情况下,JavaScript 引擎会自动处理它,但与require
的行为稍有不同。
文件扩展名:
使用
require
时,你通常不需要加.js
后缀,但在import
中,通常需要加上.js
后缀,除非它是一个标准模块(例如express
)。例如:
1
import something from './module.js'; // 必须加上 .js 扩展名
为什么选择 ES Modules(import
)?
- 现代化标准:
import/export
是现代 JavaScript 标准,符合 ECMAScript 规范。 - 更好的静态分析:与
require
相比,import
语句更容易进行静态分析,这使得工具(如打包器、代码压缩器等)能够更好地优化代码。 - 跨平台一致性:前端 JavaScript 使用
import
,这样你的 Node.js 后端与前端代码的模块化系统保持一致。
5. 使用环境变量管理敏感信息
在根目录下创建 .env
文件来存储环境变量(如数据库连接字符串、API 密钥等):
1 | bashtouch .env |
在 .env
文件中添加环境变量:
1 | PORT=3000 |
在代码中通过 dotenv
加载环境变量:
1 | require('dotenv').config(); |
6. 设置开发与生产环境
- 使用
nodemon
来自动重启服务器,这对于开发阶段非常有用。可以在package.json
中配置dev
脚本:
1 | json"scripts": { |
- 在生产环境中,通常使用
pm2
来管理进程,它能确保应用在生产中稳定运行,并提供负载均衡、日志管理等功能。安装pm2
:
1 | npm install pm2 --save-dev |
配置生产环境时可以在 .env
文件中加入不同的变量,或者使用外部的环境变量进行配置。
(补充)JavaScript 的 Build 脚本
在 JavaScript 中,build
脚本通常用于将源代码编译、打包或压缩成可用于生产环境的文件。通常使用工具如 webpack
, rollup
, esbuild
或 parcel
来进行打包和构建。
假设你在 JavaScript 项目中,tsc
主要用于 TypeScript 构建。如果是纯 JavaScript 项目,build
脚本通常会使用以下工具之一:
1. **使用 webpack
**:
Webpack 是最常用的 JavaScript 构建工具,通常用于打包和压缩 JavaScript 代码。
安装依赖:
1
npm install --save-dev webpack webpack-cli
配置
webpack.config.js
:1
2
3
4
5
6
7
8
9
10const path = require('path');
module.exports = {
entry: './src/index.js', // 入口文件
output: {
filename: 'bundle.js', // 输出文件
path: path.resolve(__dirname, 'dist'),
},
mode: 'production', // 使用生产模式
};在
package.json
中配置build
脚本:1
2
3"scripts": {
"build": "webpack"
}
7.配置git
运行git init
命令,创建创建.gitignore
文件,配置一些基本信息
1 | node_modules |
代码演示
(因为在另外一个文件《全栈开发之路》里面有简单提到了,所以这里只是列举一些JavaScript和typescript的区别)
- javascript代码中无类型声明
- import文件必须加上 .js 扩展名
(补充:)
已经使用了 body-parser
来处理 JSON 数据 (bodyParser.json()
)。因此,**不需要再额外添加 json()
和 urlencoded()
**,因为 body-parser
库提供了这些功能。
json()
和urlencoded()
是 Express 4.x 版本自带的中间件,主要用于解析请求体中的数据:- **
app.use(json())
**:解析传入请求的 JSON 数据。如果你发送的是 JSON 格式的数据(例如通过fetch
或axios
发送 JSON),这个中间件会自动解析请求体中的 JSON 数据,并把它放到req.body
中。 - **
app.use(urlencoded({ extended: false }))
**:用于解析 URL 编码的表单数据(application/x-www-form-urlencoded
)。如果你从表单提交数据(通过 POST 请求),这个中间件会解析数据并将其放到req.body
中。extended: false
表示你只能通过简单的字符串和数组传递数据,而不能嵌套对象。
- **
- **你使用了
body-parser
**:body-parser
是一个专门处理请求体的中间件,它提供了json()
和urlencoded()
方法来解析不同格式的请求体。你已经在代码中使用了bodyParser.json()
,它就实现了app.use(json())
的功能。 - 是否需要添加
json()
和urlencoded()
?- **如果你已经使用
body-parser
**,那么不需要再使用 Express 自带的json()
和urlencoded()
。 body-parser
在 Express 4.x 版本之后已经被集成为 Express 的一个独立库,你可以继续使用它,或者直接使用 Express 自带的中间件(如果不依赖于body-parser
)进行解析。
- **如果你已经使用
你的代码已经包含了 body-parser
的配置,实际上已经覆盖了 json()
和 urlencoded()
的功能。因此,不需要再添加这两个中间件,你的代码已经处理了 JSON 数据的解析。如果你需要处理 application/x-www-form-urlencoded
的数据,可以使用 bodyParser.urlencoded({ extended: false })
,或者继续使用 app.use(urlencoded({ extended: false }))
(如果没有使用 body-parser
)。
建立数据库以及使用ORM工具
目前应该使用的是prisma(因为之前使用的是drizzle,所以不想照搬)和postgresql(因为Neon提供了Free的数据库,可以很好地拿来测试)
prisma的官方文档写得挺详细的了,这里就不多做解释(不过我怕类型检查没有用Typescript,希望别被什么折磨到了)
如何在 JavaScript 项目中使用 Prisma:
安装 Prisma 和数据库客户端 首先,你需要在项目中安装 Prisma 及其数据库客户端(如 MySQL、PostgreSQL 等):
1
npm install prisma @prisma/client
初始化 Prisma 配置 然后,使用以下命令初始化 Prisma:
1
npx prisma init
这会生成一个
prisma
文件夹,包含schema.prisma
文件,这个文件定义了数据库模型。配置
schema.prisma
在schema.prisma
文件中,你需要配置数据库连接信息,以及定义数据模型。例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14datasource db {
provider = "postgresql" // 或者使用其他数据库
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js" // 选择 JavaScript 客户端
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
}生成 Prisma 客户端 使用以下命令生成 Prisma 客户端代码:
1
npx prisma generate
(补充我的疑惑:)
npx prisma generate
和 npx prisma db push
是两个不同的命令,它们的功能不一样,且在使用 Prisma 时有不同的目的和作用。
npx prisma generate
和 npx prisma db push
的区别:
- **
npx prisma generate
**:- 功能:该命令的主要作用是 生成 Prisma Client。它根据
schema.prisma
文件中定义的数据模型自动生成客户端代码,用于在应用中与数据库进行交互。 - 何时使用:每当你更改了
schema.prisma
文件中的模型(如添加、修改或删除数据模型)时,都需要运行npx prisma generate
来更新 Prisma Client,以使客户端代码与最新的模型同步。 - 注意:它不涉及数据库结构的更改,仅负责生成客户端代码。
- 功能:该命令的主要作用是 生成 Prisma Client。它根据
- **
npx prisma db push
**:- 功能:该命令的作用是将
schema.prisma
文件中的数据模型推送到数据库。它会基于schema.prisma
中的模型定义,自动在数据库中创建或更新表、字段等结构。 - 何时使用:当你对
schema.prisma
文件中的数据模型进行修改时,如果你希望这些修改反映到实际的数据库中,可以运行npx prisma db push
。它会同步数据库结构,无需手动编写迁移脚本。 - 注意:它只会同步数据库结构,不涉及 Prisma Client 的更新。也就是说,
db push
不会生成 Prisma Client,它只更新数据库。
- 功能:该命令的作用是将
需要先运行哪个命令?
- 需要先运行
npx prisma generate
吗?:如果你只是修改了数据库模型(即修改了schema.prisma
),并且希望生成相应的 Prisma Client 代码,**你应该先运行npx prisma generate
**,这样你的应用程序就可以使用最新的 Prisma Client 来与数据库交互。 - 需要先运行
npx prisma db push
吗?:如果你修改了schema.prisma
中的模型,并希望将这些更改同步到数据库中(例如创建或更新数据库表和字段),你可以运行npx prisma db push
。它会确保数据库结构与schema.prisma
一致。
总结
npx prisma generate
用于生成 Prisma Client,使你能够在应用中通过自动生成的查询方法操作数据库。npx prisma db push
用于将schema.prisma
中的模型推送到数据库,更新数据库结构。
一般的使用顺序是:先修改 schema.prisma
,然后运行 npx prisma db push
更新数据库结构,最后运行 npx prisma generate
生成与之匹配的 Prisma Client。如果只是修改代码而不需要同步数据库结构,可以只运行 npx prisma generate
。
(补充:)
console.dir(allUsers, { depth: null })
是 JavaScript 中用于打印对象的一个方法,它与常见的 console.log()
类似,但具有一些不同之处,特别是在打印复杂或嵌套的对象时。
解释:
console.dir()
:用于打印 JavaScript 对象的内容,提供比console.log()
更详细的信息,特别适合查看深度嵌套的对象。它显示的内容通常比console.log()
更易于查看。{ depth: null }
:这是一个选项,指定了打印的对象的嵌套深度。depth: null
表示没有深度限制,即会递归打印对象的所有层级,直到对象的每一层都被完全展开。- 如果你使用其他数字(比如
{ depth: 2 }
),它将限制打印的嵌套深度到指定的层数。超过这个深度的嵌套结构将不会被完全展开。
在你的代码中:
1 | const allUsers = await prisma.user.findMany({ |
prisma.user.findMany()
查询了所有用户,并且通过include
选项包括了posts
和profile
相关的关联数据。console.dir(allUsers, { depth: null })
会打印出allUsers
数组的内容。如果allUsers
是一个包含多个用户对象的数组,而每个用户对象可能还包含嵌套的posts
和profile
数据,这时使用console.dir()
可以完整地查看这些数据,包括所有层级的嵌套内容。
为什么使用 console.dir()
而不是 console.log()
?
console.dir()
在处理复杂的对象时,能更方便地显示嵌套的结构。例如,如果你要查看allUsers
中每个用户的posts
和profile
数据,console.dir()
会展开并显示这些关联数据。