NodeJS系列(13)- Next.js 框架 (六) | Node.js + Next.js + Prisma/Sequelize (ORM) + MySQL 搭建 JSON API 服务
Next.js 是一个用于构建 Web 应用程序的框架。Next.js 是一个用于生产环境的 React 框架,是一个 React 服务端渲染应用框架。
NextJS: https://nextjs.org/
Prisma 是一个基于 promise 的 Node.js 和 TypeScript 的 ORM,目前支持
Mysql,MariaDB,SQLite,PostgreSQL,AWS Aurora Serverless 和 Aws Aurora ,暂不支持
Microsft SQL Server 。Prisma 通过提供 类型安全、丰富的自动补全、平滑的 API 等特性。
Prisma: https://www.prisma.io
Prisma CN: https://prisma.yoga/
Prisma NextJS: https://www.prisma.io/nextjs
Sequelize 是一个基于 promise 的 Node.js 的 ORM,目前支持 Mysql,Postgres,MariaDB,SQLite 以及 Microsft SQL Server。它具有强大的事务支持,关联关系,预读和延迟加载,读取复制等功能。
Sequelize: https://sequelize.org/
Sequelize CN: https://www.sequelize.cn/
Prisma 和 Sequelize 各自支持的功能比较表如下:
Prisma | Sequelize | |
原始查询 | yes | yes |
事务 | yes | yes |
自动生成 Schema | yes | yes |
迁移 | yes | yes |
TypeScript | yes | yes |
子查询 | yes | no |
读写分离 | no | yes |
乐观锁 | yes | no |
高级函数 | yes | no |
本文选择使用 Node.js + Next.js + Prisma + MySQL 搭建 JSON API 服务。
1. 系统环境
NodeJS: 16.20.1NPM: 8.19.4
NextJS: 13.4.12
Prisma:5.5.2
2. 创建 NextJS 项目
安装 create-next-app 脚手架,命令如下:
# 使用 -g 参数,表示该命令只需在本机上运行一次
$ npm install -g create-next-app@13.4.12
...
注:或直接使用如下命令创建 next 项目
$ npx create-next-app@13.4.12
使用 create-next-app 命令创建 NextJS 项目,命令如下:
$ create-next-app furniture-service √ What is your project named? ... furniture-service √ Would you like to use TypeScript? ... No / Yes √ Would you like to use ESLint? ... No / Yes √ Would you like to use Tailwind CSS? ... No / Yes √ Would you like to use `src/` directory? ... No / Yes √ Would you like to use App Router? (recommended) ... No / Yes √ Would you like to customize the default import alias? ... No / Yes Creating a new Next.js app in ..\furniture-service.
注:这里选择 App Router
进入 furniture-service 项目目录安装依赖,命令如下:
$ npm install
...
运行 furniture-service 项目,命令如下:
$ npm run start # npm run dev
...
浏览器访问 http://localhost:3000,显示内容如下:
Get started by editing src/app/page.js
3. API 路由
路由处理程序 (Route Handlers) 允许用户使用 Web 请求和响应 API 为给定路由创建自定义请求处理程序。它定义在 app 目录及其子目录下的 route.js 或 route.ts 文件中,比如:
app/api/route.js
路由处理程序类似于 page.js 和 layout.js,但在同一目录下 page.js 和 router.js 不能同时存在。
支持以下 HTTP 方法:GET、POST、PUT、PATCH、DELETE、HEAD 和 OPTIONS。如果调用了不支持的方法,Next.js 将返回一个 405 method Not Allowed 响应。
示例,创建 app/api/route.js 文件,内容如下:
import { NextResponse } from 'next/server'; export async function GET(request) { //console.log(request.nextUrl.searchParams); return NextResponse.json({ ret: 'GET Success' }, { status: 200 }); } export async function POST(request) { //console.log(request); return NextResponse.json({ ret: 'POST Success' }, { status: 200 }); }
运行 furniture-service 项目,浏览器访问 http://localhost:3000/api,显示内容如下:
{"ret":"GET Success"}
Postman 用 POST 方法访问 http://localhost:3000/api,显示内容如下:
{
"ret": "POST Success"
}
4. 安装 MySQL 支持
手动创建 MySQL 数据库 testdb 和 user 表,SQL 脚本如下:
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, `createtime` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; # 创建一条用户记录 INSERT INTO user (username, password, age, createtime) VALUES ('admin', '123456', 18, Now());
进入 furniture-service 项目目录,安装 MySQL 支持,命令如下:
$ npm install mysql mysql2 --save
...
修改 app/api/route.js 文件,内容如下:
import { NextResponse } from 'next/server'; import mysql from 'mysql'; const getData = () => { return new Promise((resolve, reject) => { var connection = mysql.createConnection({ host : 'localhost', user : 'root', password : '123456', port: '3306', database: 'testdb' }); connection.connect(); connection.query('SELECT * FROM user', function (err, result) { if (err) { //console.log("getData() -> reject: " + err.message); reject({ ret: 'error', msg: err.message }) } //console.log("getData() -> resolve: " + result); resolve({ ret: 'success', data: result}); }); connection.end(); }) } export async function GET(request) { let data = await getData(); //console.log(data); return NextResponse.json( data, { status: 200 }); } export async function POST(request) { console.log(request); return NextResponse.json({ ret: 'POST Success' }, { status: 200 }); }
运行 furniture-service 项目,浏览器访问 http://localhost:3000/api,显示内容如下:
{"ret":"success","data":[{"id":1,"username":"admin","password":"123456","age":18,"createtime":"2023-11-04T07:24:59.000Z"}]}
5. 安装 ORM 支持
1) 安装 Prisma
在 furniture-service 项目目录下安装 Prisma,命令如下:
$ npm install prisma --save
...
注:可以运行 npx prisma 来查看 prisma 的命令使用方法。
创建 Prisma 架构文件模板来设置 Prisma 项目,命令如下:
$ npx prisma init
✔ Your Prisma schema was created at prisma/schema.prisma warn You already have a .gitignore file. Don't forget to add `.env` in it to not commit any private information. Next steps: 1. Set the DATABASE_URL in the .env file to point to your existing database. If your database has no tables yet, read https://pris.ly/d/getting-started 2. Set the provider of the datasource block in schema.prisma to match your database: postgresql, mysql, sqlite, sqlserver, mongodb or cockroachdb. 3. Run prisma db pull to turn your database schema into a Prisma schema. 4. Run prisma generate to generate the Prisma Client. You can then start querying your database. More information in our documentation: https://pris.ly/d/getting-started
以上命令会在 furniture-service 项目的根目录下创建一个 .env 文件和一个 prisma 目录,.env 文件用于定义环境变量(例如数据库连接),prisma 目录下生成了一个 schema.prisma 文件,schema.prisma 文件包含带有数据库连接变量和模式模型的 prisma 模式。
2) 配置 Prisma
修改 .env 文件,内容如下:
DATABASE_URL="mysql://root:123456@localhost:3306/testdb?schema=public"
修改 prisma/schema.prisma
// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") }
3) 安装 Prisma Client
Prisma Client 提供常用的数据库 CRUD 方法: create、update、delete、findUnique、findMany 等。
安装 Prisma Client,命令如下:
$ npm install @prisma/client
...
6. 使用 Prisma Migrate
Prisma Migrate 是一个声明性数据库模型迁移工具,使您能够:
(1) 随着 Prisma 架构的发展,保持数据库架构与 Prisma 数据模型同步
(2) 维护数据库中的现有数据
Prisma Migrate 生成 .sql 迁移文件的历史记录,并在开发和部署中发挥作用。
上文我们是用手动方式在 testdb 数据库创建了 user 表,而且 app/api/route.js 文件里的 GET/POST 函数中没有使用 ORM (Prisma/Sequelize) 数据模型来操作数据库的表。
在我们熟悉 Prisma 数据模型的情况下,我们可以直接在 prisma\schema.prisma 文件里添加表的数据模型,再运行 npx prisma migrate dev --name init 命令把表迁移到数据库。
1) 新增 post 表
修改 prisma\schema.prisma 文件,添加 post 表的数据模型,内容如下:
generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } model post { id Int @id @default(autoincrement()) title String @db.VarChar(255) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }
执行 prisma migrate 命令,创建第 1 个数据库迁移,格式如下:
$ npx prisma migrate dev --name init // dev 表示开发环境
... [+] Added tables - user ? We need to reset the MySQL database "testdb" at "localhost:3306" Do you want to continue? All data will be lost. » (y/N) y Applying migration `20231106104925_init` The following migration(s) have been created and applied from new schema changes: migrations/ └─ 20231106104925_init/ └─ migration.sql Your database is now in sync with your schema. ✔ Generated Prisma Client (v5.5.2) to .\node_modules\@prisma\client in 56ms
注:testdb 数据库里的 user 表是手动创建的,该表不在 prisma migrate 的版本管理范围之内,会提示是否重置数据库,这里选择了 y 重置数据库。
prisma migrate 命令会自动执行 prisma generate 命令读取 Prisma 架构并生成 Prisma Client 库。
查看 testdb 数据库,可以看到新增了两个表 post 和 _prisma_migrations(记录 migration 历史)。查看项目目录,新增了一个 prisma/migrations/20231106104925_init/migration.sql 文件。
可以运行 npx prisma migrate status 命令查看 migration 状态。
2) 修改 post 表
修改 prisma\schema.prisma 文件,修改 post 表的数据模型,内容如下:
generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } model post { id Int @id @default(autoincrement()) title String @db.VarChar(255) content String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }
执行 prisma migrate 命令,创建第 2 个数据库迁移,格式如下:
$ npx prisma migrate dev --name post_add_content
...
查看 testdb 数据库,可以看到 post 表结构已更新,_prisma_migrations 里新增了一条记录。查看项目目录,新增了一个 prisma/migrations/20231106130648_post_add_content/migration.sql 文件。
3) 反向生成 Prisma 数据模型
上文运行 npx prisma migrate dev --name init 命令过程中,我们选择了 y 重置了 testdb。
如果我们需要保留 user 表结构,可以在运行 prisma migrate 命令之前,反向生成 prisma 数据模型,把 user 表数据模型保存到 prisma\schema.prisma 文件,命令如下:
$ npx prisma db pull
Prisma schema loaded from prisma\schema.prisma Environment variables loaded from .env Datasource "db": MySQL database "testdb" at "localhost:3306" ✔ Introspected 1 model and wrote it into prisma\schema.prisma in 79ms Run prisma generate to generate Prisma Client.
查看 prisma\schema.prisma 文件,内容如下:
generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } model post { id Int @id @default(autoincrement()) title String @db.VarChar(255) content String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model user { id Int @id @default(autoincrement()) username String @unique(map: "username") @db.VarChar(50) password String? @db.VarChar(255) age Int? createtime DateTime? @db.Timestamp(0) }
注:prisma\schema.prisma 文件里多了一个 user 表的数据模型。
7. 读写数据库
示例,创建 app/api2/route.js 文件,内容如下:
import { NextResponse } from 'next/server'; import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient(); export async function GET(request) { // By unique identifier let user = await prisma.user.findUnique({ where: { id: 2, }, }) //console.log(user); return NextResponse.json(user, { status: 200 }); } export async function POST(request) { let user = await prisma.user.create({ data: { username: request.get('username'), password: request.get('password'), age: request.get('age'), createtime: time() }, }) //console.log(user); return NextResponse.json(user, { status: 200 }); }
运行 furniture-service 项目,在 postman 上用 POST 方法访问 http://localhost:3000/api2,HTTP 请求的 Body 类型为 raw(JSON 格式),内容如下:
{"username":"user","password":"abcdef","age": 99}
返回结果为:
{ "id": 2, "username": "user", "password": "abcdef", "age": 99, "createtime": "2023-11-04T12:27:47.000Z" }
浏览器访问 http://localhost:3000/api2, 显示结果如下:
{"id":2,"username":"user","password":"abcdef","age":99,"createtime":"2023-11-04T12:27:47.000Z"}