NodeJS系列(13)- Next.js 框架 (六) | Node.js + Next.js + Prisma/Sequelize (ORM) + MySQL 搭建 JSON API 服务

发布时间 2023-11-04 22:48:10作者: 垄山小站

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.1
    NPM: 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 数据模型

        上文的示例中,我们手动创建了 MySQL 数据库 testdb 和 user 表,这里在 .env 文件里配置了连接到 testdb 数据库,可以反向生成 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 user {
                id         Int       @id @default(autoincrement())
                username   String    @db.VarChar(50)
                password   String?   @db.VarChar(255)
                age        Int?
                createtime DateTime? @db.Timestamp(0)
            }


        可以看到  prisma\schema.prisma 文件里生成了 user 表的数据模型。

    4) 生成数据库基线 (Baseline)

        基线 (Baseline)是指初始化一个数据库的迁移记录。数据库的表结构可能因为业务需要而多次修改,可以使用 Baseline 技术,记录下表结构多次修改之间的差别。

        创建一个迁移目录 prisma/migrations/0_init,我们将使用 0_init 作为首次迁移的名称,命令如下:

            $ mkdir -p prisma/migrations/0_init

                ...

            注:-p 将在自动创建路径中不存在的中间路径。

        使用 prisma migrate diff 命令生成迁移文件,格式如下:
    
            $ npx prisma migrate diff --from-empty --to-schema-datamodel prisma/schema.prisma --script > prisma/migrations/0_init/migration.sql

                ...

            参数说明:

                --from-empty:假设从中迁移的数据模型为空
                --to-schema-datamodel:使用数据源块中 URL 的当前数据库状态
                --script:输出 SQL 脚本


6. 读写数据库

    在读写数据库的一个表之前,需要确保已经创建了这个表的 prisma 数据模型,并且要安装好 Prisma Client,Prisma Client 提供常用的数据库 CRUD 方法: create、update、delete、findUnique、findMany 等。

    安装 Prisma Client,命令如下:

        $ npm install @prisma/client

            ...

    读取 Prisma 架构并生成 Prisma Client 库,命令如下:

        $ npx prisma generate

            Environment variables loaded from .env
            Prisma schema loaded from prisma\schema.prisma

            ✔ Generated Prisma Client (v5.5.2) to .\node_modules\@prisma\client in 55ms

            Start using Prisma Client in Node.js (See: https://pris.ly/d/client)
            ```
            import { PrismaClient } from '@prisma/client'
            const prisma = new PrismaClient()
            ```
            or start using Prisma Client at the edge (See: https://pris.ly/d/accelerate)
            ```
            import { PrismaClient } from '@prisma/client/edge'
            const prisma = new PrismaClient()
            ```

            See other ways of importing Prisma Client: http://pris.ly/d/importing-client


    示例,创建 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,raw 参数为:

        {"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"}