在学习 Node.js 后端开发时,最令人头疼的往往不是语法,而是:怎么搭建目录?如何连接数据库?为什么要用 ORM?
本篇文章将带你从最原始的 mysql2 连接方式,进化到现代化的 Prisma ORM 架构,手把手教你搭建一个可维护的后端项目。
1. 环境准备:搭建 Koa 骨架
1.1. 初始化项目
首先,我们需要一个 Node.js 环境。
mkdir my-koa-app && cd my-koa-app
npm init -y
npm install koa koa-router koa-bodyparser mysql2
- 为什么用 Koa? Koa 是由 Express 原班人马开发的,它更小巧、更灵活,利用 async/await 处理异步逻辑非常优雅,不再有回调地狱。
- 为什么要安装 koa-bodyparser? 原生的 Node 无法直接解析 POST 请求里的 JSON 数据,这个中间件会帮你把数据解析好放在 ctx.request.body 中。
1.2. 安装 nodemon
1.2.1. 如何安装
在开发过程中,每次修改代码都得手动重启 node app.js。建议安装 nodemon,它会自动检测代码变化并重启:
npm install nodemon --save-dev
1.2.2. 修改 package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon app.js"
},
1.2.3. 启动项目
npm run dev
2. 基础阶段:使用传统方式连接数据库
在没有 ORM(对象关系映射)之前,我们使用 mysql2 驱动直接写 SQL 语句。
2.1. 数据库连接池 (db/index.js)
const mysql = require('mysql2');
const pool = mysql.createPool({
host: 'localhost',
user: 'root', // 你的数据库用户名
password: 'password', // 你的数据库密码
database: 'test_db' // 你的数据库名
});
module.exports = pool.promise();
- 为什么用连接池(Pool)? 频繁地建立和销毁数据库连接非常消耗资源。连接池就像一个"共享单车棚",用的时候取一个,用完放回去,效率极高。
- 为什么用 .promise()? 这样我们可以配合 Koa 的 await 使用,让代码像同步执行一样清晰。
2.2. 路由与控制器:解耦的思想
不要把所有的业务逻辑都塞进 app.js!我们要进行职责分离:
- Routes (路由):负责定义 URL 路径。
- Controllers (控制器):负责具体业务逻辑(增删改查)。
2.2.1. 入口文件(app.js)
javascript
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const userRouter = require('./routes/user');
const app = new Koa();
// 1. 使用 bodyParser 必须在路由之前
app.use(bodyParser());
// 2. 错误处理中间件
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = { code: ctx.status, message: err.message };
}
});
// 3. 注册路由
app.use(userRouter.routes()).use(userRouter.allowedMethods());
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
2.2.2. 控制器(src/controllers/user.js)
javascript
//
const db = require("../db");
class UserController {
// 1. 查询所有用户
async list(ctx) {
const [rows] = await db.query("SELECT * FROM users");
ctx.body = {code: 200, data: rows};
}
// 2. 查询单个用户
async detail(ctx) {
const {id} = ctx.params;
const [rows] = await db.query("SELECT * FROM users WHERE user_id = ?", [id]);
ctx.body = {code: 200, data: rows[0] || {}};
}
// 3. 新增用户
async create(ctx) {
// 从请求体获取 username, age, 以及 password
const {username, age, password} = ctx.request.body;
const [result] = await db.query(
"INSERT INTO users (username, study_point, password) VALUES (?, ?, ?)",
[username, age, password || "123456"], // 如果前端没传密码,给个默认值 '123456'
);
ctx.body = {code: 200, msg: "创建成功", id: result.insertId};
}
// 4. 修改用户
async update(ctx) {
const {id} = ctx.params;
const {username, age} = ctx.request.body;
const [result] = await db.query(
"UPDATE users SET username = ?, study_point = ? WHERE user_id = ?",
[username, age, id],
);
// result.affectedRows 表示受影响的行数
if (result.affectedRows === 0) {
ctx.body = {code: 404, msg: "修改失败,用户不存在"};
} else {
ctx.body = {code: 200, msg: "修改成功"};
}
}
// 5. 删除用户
async delete(ctx) {
const {id} = ctx.params;
await db.query("DELETE FROM users WHERE user_id = ?", [id]);
ctx.body = {code: 200, msg: "删除成功"};
}
}
module.exports = new UserController();
2.2.3. 路由(src/routes/user.js)
javascript
//
const Router = require("koa-router");
const userCtrl = require("../controllers/user");
const router = new Router({prefix: "/users"}); // 统一前缀
router.get("/", userCtrl.list); // GET /users
router.get("/:id", userCtrl.detail); // GET /users/1
router.post("/", userCtrl.create); // POST /users
router.put("/:id", userCtrl.update); // PUT /users/1
router.delete("/:id", userCtrl.delete); // DELETE /users/1
module.exports = router;
3. 进阶阶段:使用 Prisma ORM 彻底解放生产力
写原始 SQL(如 SELECT * FROM users)容易出错,且没有代码提示。Prisma 是目前 Node.js 社区最先进的 ORM,它能让你像操作 JavaScript 对象一样操作数据库。
3.1. 初始化 Prisma
npm install prisma@6 @prisma/client@6
npx prisma init
这会生成一个 .env 文件(存放数据库账号密码)和 prisma/schema.prisma(数据库模型文件)。
3.2. 配置与同步
- 在 .env 中修改连接串。
- 执行 npx prisma db pull:它会自动读取你的数据库结构,并写进 schema.prisma。
- 执行 npx prisma generate:这是最神奇的一步。它会根据数据库结构生成本地类型提示包,从此你在写代码时会有完美的语法补全。
3.3. 代码示列
3.3.1. 封装数据库单例(db/index.js)
javascript
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
module.exports = prisma;
3.3.2. 编写控制器(src/controllers/userController.js)
javascript
const prisma = require('../db');
class UserController {
// 获取所有用户
async getAll(ctx) {
const users = await prisma.users.findMany();
ctx.body = { code: 200, data: users };
}
// 创建用户
async create(ctx) {
const { username, password, role, study_point } = ctx.request.body;
const user = await prisma.users.create({
data: { username, password, role: +role, study_point: +study_point }
});
ctx.body = { code: 200, data: user };
}
// 根据ID获取用户
async getOne(ctx) {
const id = parseInt(ctx.params.id);
const user = await prisma.users.findUnique({ where: { user_id: id } });
ctx.body = { code: 200, data: user };
}
}
module.exports = new UserController();
3.3.3. 定义路由(src/routes/userRoutes.js)
javascript
const Router = require('@koa/router');
const userController = require('../controllers/userController');
const router = new Router({ prefix: '/api' }); // 设置统一前缀
router.get('/users', userController.getAll);
router.get('/users/:id', userController.getOne);
router.post('/users', userController.create);
module.exports = router;
3.3.4. 文件入口(src/app.js)
javascript
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const userRouter = require('./routes/userRoutes');
const app = new Koa();
// 中间件
app.use(bodyParser());
// 注册路由
app.use(userRouter.routes()).use(userRouter.allowedMethods());
// 错误处理 (可选)
app.on('error', (err, ctx) => {
console.error('server error', err);
});
app.listen(3000, () => {
console.log('Server started at http://localhost:3000');
});