NodeJS系列(13)- Next.js 框架 (六) | Node.js + Next.js + Prisma/Sequelize (ORM) + MySQL 搭建 JSON API 服务
<p>Next.js 是一个用于构建 Web 应用程序的框架。Next.js 是一个用于生产环境的 React 框架,是一个 React 服务端渲染应用框架。</p><p>NextJS: https://nextjs.org/</p>
<p><br>Prisma 是一个基于 promise 的 Node.js 和 TypeScript 的 ORM,目前支持
Mysql,MariaDB,SQLite,PostgreSQL,AWS Aurora Serverless 和 Aws Aurora ,暂不支持
Microsft SQL Server 。Prisma 通过提供 类型安全、丰富的自动补全、平滑的 API 等特性。<br><br>Prisma: https://www.prisma.io <br>Prisma CN: https://prisma.yoga/<br>Prisma NextJS: https://www.prisma.io/nextjs</p>
<p> </p>
<p>Sequelize 是一个基于 promise 的 Node.js 的 ORM,目前支持 Mysql,Postgres,MariaDB,SQLite 以及 Microsft SQL Server。它具有强大的事务支持,关联关系,预读和延迟加载,读取复制等功能。<br>Sequelize: https://sequelize.org/</p>
<p>
Sequelize CN: https://www.sequelize.cn/</p>
<p><br>Prisma 和 Sequelize 各自支持的功能比较表如下:</p>
<table border="0">
<tbody>
<tr>
<td style="text-align: center"> </td>
<td style="text-align: center"><strong>Prisma</strong></td>
<td style="text-align: center"><strong>Sequelize</strong></td>
</tr>
<tr>
<td>原始查询</td>
<td>yes</td>
<td>yes</td>
</tr>
<tr>
<td>事务</td>
<td>yes</td>
<td>yes</td>
</tr>
<tr>
<td>自动生成 Schema</td>
<td>yes</td>
<td>yes</td>
</tr>
<tr>
<td>迁移</td>
<td>yes</td>
<td>yes</td>
</tr>
<tr>
<td>TypeScript</td>
<td>yes</td>
<td>yes</td>
</tr>
<tr>
<td>子查询</td>
<td>yes</td>
<td>no</td>
</tr>
<tr>
<td>读写分离</td>
<td>no</td>
<td>yes</td>
</tr>
<tr>
<td>乐观锁</td>
<td>yes</td>
<td>no</td>
</tr>
<tr>
<td>高级函数</td>
<td>yes</td>
<td>no</td>
</tr>
</tbody>
</table>
<p><br>本文选择使用 Node.js + Next.js + Prisma + MySQL 搭建 JSON API 服务。</p>
<p> </p>
<h3>1. 系统环境</h3>
NodeJS: 16.20.1<br> NPM: 8.19.4<br> NextJS: 13.4.12<br> Prisma:5.5.2<br>
<p> </p>
<h3>2. 创建 NextJS 项目</h3>
<p> 安装 create-next-app 脚手架,命令如下:<br><br> # 使用 -g 参数,表示该命令只需在本机上运行一次<br> $ npm install -g create-next-app@13.4.12 <br><br> ...<br><br> 注:或直接使用如下命令创建 next 项目 <br> <br> $ npx create-next-app@13.4.12<br><br> 使用 create-next-app 命令创建 NextJS 项目,命令如下:</p>
<div class="cnblogs_code">
<pre> $ 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.</pre>
</div>
<p> 注:这里选择 App Router<br><br> 进入 furniture-service 项目目录安装依赖,命令如下:<br><br> $ npm install<br><br> ...<br><br> 运行 furniture-service 项目,命令如下:<br><br> $ npm run start # npm run dev <br><br> ...<br><br> 浏览器访问 http://localhost:3000,显示内容如下:<br> <br> Get started by editing src/app/page.js</p>
<h3><br>3. API 路由</h3>
<p> 路由处理程序 (Route Handlers) 允许用户使用 Web 请求和响应 API 为给定路由创建自定义请求处理程序。它定义在 app 目录及其子目录下的 route.js 或 route.ts 文件中,比如:<br><br> app/api/route.js<br><br> 路由处理程序类似于 page.js 和 layout.js,但在同一目录下 page.js 和 router.js 不能同时存在。<br><br> 支持以下 HTTP 方法:GET、POST、PUT、PATCH、DELETE、HEAD 和 OPTIONS。如果调用了不支持的方法,Next.js 将返回一个 405 method Not Allowed 响应。<br><br> 示例,创建 app/api/route.js 文件,内容如下:</p>
<div class="cnblogs_code">
<pre> import { NextResponse } from 'next/server'<span style="color: rgba(0, 0, 0, 1)">;
export async </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> GET(request) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">console.log(request.nextUrl.searchParams);</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> NextResponse.json({ ret: 'GET Success' }, { status: 200<span style="color: rgba(0, 0, 0, 1)"> });
}
export async </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> POST(request) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">console.log(request);</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> NextResponse.json({ ret: 'POST Success' }, { status: 200<span style="color: rgba(0, 0, 0, 1)"> });
}</span></pre>
</div>
<p><br> 运行 furniture-service 项目,浏览器访问 http://localhost:3000/api,显示内容如下:<br><br> {"ret":"GET Success"}<br><br> Postman 用 POST 方法访问 http://localhost:3000/api,显示内容如下: <br><br> {<br> "ret": "POST Success"<br> }<br><br></p>
<h3><br>4. 安装 MySQL 支持</h3>
<p> 手动创建 MySQL 数据库 testdb 和 user 表,SQL 脚本如下:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">CREATE</span> <span style="color: rgba(0, 0, 255, 1)">TABLE</span> `<span style="color: rgba(255, 0, 255, 1)">user</span><span style="color: rgba(0, 0, 0, 1)">` (
`id` </span><span style="color: rgba(0, 0, 255, 1)">int</span>(<span style="color: rgba(128, 0, 0, 1); font-weight: bold">11</span>) <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)"> AUTO_INCREMENT,
`username` </span><span style="color: rgba(0, 0, 255, 1)">varchar</span>(<span style="color: rgba(128, 0, 0, 1); font-weight: bold">50</span>) <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
`password` </span><span style="color: rgba(0, 0, 255, 1)">varchar</span>(<span style="color: rgba(128, 0, 0, 1); font-weight: bold">255</span>) <span style="color: rgba(0, 0, 255, 1)">DEFAULT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
`age` </span><span style="color: rgba(0, 0, 255, 1)">int</span>(<span style="color: rgba(128, 0, 0, 1); font-weight: bold">11</span>) <span style="color: rgba(0, 0, 255, 1)">DEFAULT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
`createtime` </span><span style="color: rgba(0, 0, 255, 1)">timestamp</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span> <span style="color: rgba(0, 0, 255, 1)">DEFAULT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 0, 255, 1)">PRIMARY</span> <span style="color: rgba(0, 0, 255, 1)">KEY</span><span style="color: rgba(0, 0, 0, 1)"> (`id`),
</span><span style="color: rgba(0, 0, 255, 1)">UNIQUE</span> <span style="color: rgba(0, 0, 255, 1)">KEY</span><span style="color: rgba(0, 0, 0, 1)"> (`username`)
) ENGINE</span><span style="color: rgba(128, 128, 128, 1)">=</span>InnoDB <span style="color: rgba(0, 0, 255, 1)">DEFAULT</span> CHARSET<span style="color: rgba(128, 128, 128, 1)">=</span><span style="color: rgba(0, 0, 0, 1)">utf8;
# 创建一条用户记录
</span><span style="color: rgba(0, 0, 255, 1)">INSERT</span> <span style="color: rgba(0, 0, 255, 1)">INTO</span> <span style="color: rgba(255, 0, 255, 1)">user</span> (username, password, age, createtime) <span style="color: rgba(0, 0, 255, 1)">VALUES</span> (<span style="color: rgba(255, 0, 0, 1)">'</span><span style="color: rgba(255, 0, 0, 1)">admin</span><span style="color: rgba(255, 0, 0, 1)">'</span>, <span style="color: rgba(255, 0, 0, 1)">'</span><span style="color: rgba(255, 0, 0, 1)">123456</span><span style="color: rgba(255, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1); font-weight: bold">18</span>, Now());</pre>
</div>
<p><br> 进入 furniture-service 项目目录,安装 MySQL 支持,命令如下:<br><br> $ npm install mysql mysql2 --save<br><br> ...<br><br> 修改 app/api/route.js 文件,内容如下:</p>
<div class="cnblogs_code">
<pre> import { NextResponse } from 'next/server'<span style="color: rgba(0, 0, 0, 1)">;
import mysql from </span>'mysql'<span style="color: rgba(0, 0, 0, 1)">;
const getData </span>= () =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Promise((resolve, reject) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> connection =<span style="color: rgba(0, 0, 0, 1)"> mysql.createConnection({
host : </span>'localhost'<span style="color: rgba(0, 0, 0, 1)">,
user : </span>'root'<span style="color: rgba(0, 0, 0, 1)">,
password : </span>'123456'<span style="color: rgba(0, 0, 0, 1)">,
port: </span>'3306'<span style="color: rgba(0, 0, 0, 1)">,
database: </span>'testdb'<span style="color: rgba(0, 0, 0, 1)">
});
connection.connect();
connection.query(</span>'SELECT * FROM user', <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> (err, result) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (err) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">console.log("getData() -> reject: " + err.message);</span>
reject({ ret: 'error'<span style="color: rgba(0, 0, 0, 1)">, msg: err.message })
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">console.log("getData() -> resolve: " + result);</span>
resolve({ ret: 'success'<span style="color: rgba(0, 0, 0, 1)">, data: result});
});
connection.end();
})
}
export async </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> GET(request) {
let data </span>=<span style="color: rgba(0, 0, 0, 1)"> await getData();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">console.log(data);</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> NextResponse.json( data, { status: 200<span style="color: rgba(0, 0, 0, 1)"> });
}
export async </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> POST(request) {
console.log(request);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> NextResponse.json({ ret: 'POST Success' }, { status: 200<span style="color: rgba(0, 0, 0, 1)"> });
}</span></pre>
</div>
<p><br> 运行 furniture-service 项目,浏览器访问 http://localhost:3000/api,显示内容如下:<br><br> {"ret":"success","data":[{"id":1,"username":"admin","password":"123456","age":18,"createtime":"2023-11-04T07:24:59.000Z"}]}</p>
<h3><br>5. 安装 ORM 支持</h3>
<p> <strong>1) 安装 Prisma</strong></p>
<p> 在 furniture-service 项目目录下安装 Prisma,命令如下:<br><br> $ npm install prisma --save<br><br> ...<br><br> 注:可以运行 npx prisma 来查看 prisma 的命令使用方法。<br><br> 创建 Prisma 架构文件模板来设置 Prisma 项目,命令如下:<br><br> $ npx prisma init</p>
<div class="cnblogs_code">
<pre> ✔ 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</pre>
</div>
<p> 以上命令会在 furniture-service 项目的根目录下创建一个 .env 文件和一个 prisma 目录,.env 文件用于定义环境变量(例如数据库连接),prisma 目录下生成了一个 schema.prisma 文件,schema.prisma 文件包含带有数据库连接变量和模式模型的 prisma 模式。</p>
<p><br> <strong> 2) 配置 Prisma</strong></p>
<p><br> 修改 .env 文件,内容如下:<br><br> DATABASE_URL="mysql://root:123456@localhost:3306/testdb?schema=public"<br><br> 修改 prisma/schema.prisma </p>
<div class="cnblogs_code">
<pre> // 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")
}</pre>
</div>
<p> <br> <strong> 3) 安装 Prisma Client</strong><br><br> Prisma Client 提供常用的数据库 CRUD 方法: create、update、delete、findUnique、findMany 等。<br> <br> 安装 Prisma Client,命令如下:<br><br> $ npm install @prisma/client<br><br> ...</p>
<p> </p>
<h3>6. 使用 Prisma Migrate</h3>
<p> Prisma Migrate 是一个声明性数据库模型迁移工具,使您能够:<br><br> (1) 随着 Prisma 架构的发展,保持数据库架构与 Prisma 数据模型同步<br> (2) 维护数据库中的现有数据<br><br> Prisma Migrate 生成 .sql 迁移文件的历史记录,并在开发和部署中发挥作用。<br><br> 上文我们是用手动方式在 testdb 数据库创建了 user 表,而且 app/api/route.js 文件里的 GET/POST 函数中没有使用 ORM (Prisma/Sequelize) 数据模型来操作数据库的表。 <br><br> 在我们熟悉 Prisma 数据模型的情况下,我们可以直接在 prisma\schema.prisma 文件里添加表的数据模型,再运行 npx prisma migrate dev --name init 命令把表迁移到数据库。<br><br> <strong>1) 新增 post 表</strong><br> <br> 修改 prisma\schema.prisma 文件,添加 post 表的数据模型,内容如下:</p>
<div class="cnblogs_code">
<pre> 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
}</pre>
</div>
<p><br> 执行 prisma migrate 命令,创建第 1 个数据库迁移,格式如下:<br><br> $ npx prisma migrate dev --name init // dev 表示开发环境</p>
<div class="cnblogs_code">
<pre> ...
[+] 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</pre>
</div>
<p> 注:testdb 数据库里的 user 表是手动创建的,该表不在 prisma migrate 的版本管理范围之内,会提示是否重置数据库,这里选择了 y 重置数据库。<br><br> prisma migrate 命令会自动执行 prisma generate 命令读取 Prisma 架构并生成 Prisma Client 库。<br><br> 查看 testdb 数据库,可以看到新增了两个表 post 和 _prisma_migrations(记录 migration 历史)。查看项目目录,新增了一个 prisma/migrations/20231106104925_init/migration.sql 文件。<br><br> 可以运行 npx prisma migrate status 命令查看 migration 状态。<br><br> <strong>2) 修改 post 表</strong><br><br> 修改 prisma\schema.prisma 文件,修改 post 表的数据模型,内容如下:</p>
<div class="cnblogs_code">
<pre> 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
}</pre>
</div>
<p><br> 执行 prisma migrate 命令,创建第 2 个数据库迁移,格式如下:<br><br> $ npx prisma migrate dev --name post_add_content<br><br> ...<br><br> 查看 testdb 数据库,可以看到 post 表结构已更新,_prisma_migrations 里新增了一条记录。查看项目目录,新增了一个 prisma/migrations/20231106130648_post_add_content/migration.sql 文件。<br><br> <strong>3) 反向生成 Prisma 数据模型</strong><br><br> 上文运行 npx prisma migrate dev --name init 命令过程中,我们选择了 y 重置了 testdb。<br><br> 如果我们需要保留 user 表结构,可以在运行 prisma migrate 命令之前,反向生成 prisma 数据模型,把 user 表数据模型保存到 prisma\schema.prisma 文件,命令如下:<br><br> $ npx prisma db pull</p>
<div class="cnblogs_code">
<pre> 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.</pre>
</div>
<p><br> 查看 prisma\schema.prisma 文件,内容如下:</p>
<div class="cnblogs_code">
<pre> 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)
}</pre>
</div>
<p> 注:prisma\schema.prisma 文件里多了一个 user 表的数据模型。</p>
<h3><br>7. 读写数据库</h3>
<p> 示例,创建 app/api2/route.js 文件,内容如下:</p>
<div class="cnblogs_code">
<pre> 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 });
}</pre>
</div>
<p><br> 运行 furniture-service 项目,在 postman 上用 POST 方法访问 http://localhost:3000/api2,HTTP 请求的 Body 类型为 raw(JSON 格式),内容如下:<br><br> {"username":"user","password":"abcdef","age": 99}<br><br> 返回结果为:</p>
<div class="cnblogs_code">
<pre> {
"id": 2,
"username": "user",
"password": "abcdef",
"age": 99,
"createtime": "2023-11-04T12:27:47.000Z"
}</pre>
</div>
<p><br> 浏览器访问 http://localhost:3000/api2, 显示结果如下:<br><br> {"id":2,"username":"user","password":"abcdef","age":99,"createtime":"2023-11-04T12:27:47.000Z"}<br><br><br></p><br><br>
来源:https://www.cnblogs.com/tkuang/p/17809971.html
頁:
[1]