从零到一:构建高效 Node.js 后端 API (Koa + Prisma 实战)

在学习 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');
});
相关推荐
白日梦想家6812 小时前
实战避坑+性能对比,for与each循环选型指南
开发语言·前端·javascript
捉鸭子2 小时前
某红书X-s X-s-common VMP逆向(算法还原)
python·web安全·网络安全·node.js·网络爬虫
帅帅哥的兜兜2 小时前
猪齿鱼:实现table分页勾选
前端·javascript·vue.js
wicb91wJ62 小时前
手写一个Promise,彻底掌握异步原理
开发语言·前端·javascript
上海云盾-小余2 小时前
Web 业务常见 SQL 注入攻击原理详解及 WAF 防护部署实战教程
前端·数据库·sql
zs宝来了2 小时前
Next.js SSR/SSG:路由与渲染模式深度解析
前端·javascript·框架
ZC跨境爬虫2 小时前
UI前端美化技能提升日志day5:从布局优化到CSS继承原理深度解析
前端·css·ui·html·状态模式
易生一世2 小时前
Kiro CLI的context详解
前端
huangql5202 小时前
CSS布局(六):Grid —— 像围棋一样布局
前端·css
谢尔登2 小时前
【Next】客户端组件和服务端组件
前端·javascript·react.js·架构