为 Nextjs 接上 PostgreSQL 后端

힘센캥거루
2025년 7월 16일(수정됨)
6
70

简单实现了一个后端,把会员注册功能先做了出来,但奇怪的是却一直没再往下手。

大概是虽然用过 Prisma 和 PostgreSQL,但只是读着别人的文章被动地跟着做,结果没真正学会,这似乎就是问题所在。

所以决定这次自己从头实现一遍。

1. 安装 PostgreSQL

首先用 homebrew 安装 postgresql。

如果是 Windows,可以先装 choco,再通过它来安装,会比较简单。

然后通过 services start 设置成自动启动。

# 如果是 MacBook
brew install postgresql
brew services start postgresql

输入下面的命令,如果能正常连接就说明安装完成了。

postgreSQL 是服务器-客户端结构,所以 postgre 服务器必须在运行中,才能连接到数据库。

psql postgres
为 Nextjs 接上 PostgreSQL 后端-1

现在来设置用户和数据库。

我把用户名设成 testuser,密码也设成 testuser。

CREATE USER testuser WITH PASSWORD 'testuser';
\du
为 Nextjs 接上 PostgreSQL 后端-2

用户总是建不出来,检查了一下才发现,命令末尾一定要加分号,命令才算结束。

然后再创建一个 database。

CREATE DATABASE testdb;

再用 \l\list 查看数据库列表。

为 Nextjs 接上 PostgreSQL 后端-3

这里上面那 3 个 db 是默认创建的。

删掉可能会出问题,最好保持不动。

2. 安装 Prisma

现在来安装 Prisma。

我新建了一个名为 prismaTest 的文件夹。

用 npm 也可以,不过我这里是用 yarn 来初始化的。

yarn

然后安装 Prisma。

用 npx 命令执行 Prisma 的初始化设置。

运行 prisma init 后,会在某个文件夹下生成 Prisma 配置文件。

顺便说一下,npx 是一种命令行工具,可以在不全局安装 npm 包的情况下直接运行它们。

yarn add prisma
npx prisma init

3. 设置 PostgreSQL 连接

接下来进入 prisma/schema.prisma.env,修改环境配置。

把文件内部的设置改成如下所示即可。

DATABASE_URL="postgresql://아이디:비밀번호@localhost:5432/데이터베이스이름?schema=public"

4. 设置 schema.prisma

现在来设置数据库的 schema。

默认情况下,schema 内容如下:

generator client {
  provider = "prisma-client-js"
  output   = "../generated/prisma" <-- 이 줄 삭제할 것!
}

datasource db {
  provider = "postgresql"
  url  = env("DATABASE_URL")
}

接下来可以自由修改。

问题在于,需要先稍微学一下 schema 的写法。

比如说,假设有下面这样的 schema:

model User {
  id    Int @id @default(autoincrement())
  email String  @unique
  name  String?
}

➡️ model User { ... } 是数据库的表名。

➡️ id Int @id @default(autoincrement())

  • 这个字段是主键,数字会按 1、2、3… 这样自动递增。

元素

含义

id

字段(列,column)名

Int

数据类型 = 整型

@id

该字段是 Primary Key

@default(autoincrement())

默认值为自增数字

➡️ email String @unique

  • email 的值必须唯一,不能有两个以上用户使用同一个邮箱。

元素

含义

email

字段名

String

字符串类型

@unique

不可重复(Unique 约束)

➡️ name String?

  • name 可以有,也可以是 null(没有)。

    元素

    含义

    name

    字段名

    String?

    字符串类型,其中 ? 表示 nullable(可为 null)

总结一下 Prisma 的装饰器如下:

装饰器

含义

@id

指定主键

@default(...)

设置默认值

@unique

唯一值约束

@relation(...)

关系设置(外键、关联表)

@map("db_column")

指定在实际数据库中的列名

?

该字段 可为 null(值可以不存在)

[]

数组(例如:String[] = 字符串数组)

@필드명

当字段类型为 datetime 时,更新数据时自动刷新为当前日期

5. 执行迁移

为了把模型反映到 DB 上,需要进行迁移。

最后面的名称可以随意取。

npx prisma migrate dev --name testMyDB
## 在 --name 后面输入迁移名称

6. Prisma Client 使用示例

现在写一个简单的服务器,在后端测试一下 CRUD。

服务器将用 Express 来写。

yarn add @prisma/client express

先用上面的命令安装依赖。

创建 index.js 文件,并按下面这样输入代码。

然后在终端运行 node index.js,服务器就会启动。

const express = require('express');
const { PrismaClient } = require('@prisma/client');

const app = express();
const prisma = new PrismaClient();

app.use(express.json());
const port = 3001;

app.get("/", (req, res)=> {
   const body = req.params;
   res.send("test");
});

app.get('/users', async (req, res) => {
  const users = await prisma.User.findMany();
  console.log(users);
  res.send(`<div>${users.map((user) => {
    return `<name:$>email : ${user.email} / name:${user.name}</p><br>`
  }).join("")}</div>`);
});

app.get('/input', async (req, res) => {
  const { email, name } = req.query;
  const result = await prisma.User.create({data:{email : email, name: name}})
  console.log(email, name);
  res.send(`<h1>${email
  }</h1>
  <h2>${name}</h2>
  `);
})

app.listen(port, () => {
    console.log("listen http://localhost:3001")
});

Prisma 的用法是 prisma.表名.命令 这样的形式。

服务器已经在运行,现在通过 query string 传入邮箱和姓名试试看。

http://localhost:3001/input?email=testur@email.com&name=힘센캥거루

输入之后,在 /users 里查看,可以看到数据被正确输出。

为 Nextjs 接上 PostgreSQL 后端-4

同样的方法也可以进行修改和删除。

先添加下面这段代码:

...
app.get("/delete", async (req, res) => {
  const { email } = req.query;
  const result = await prisma.User.delete({where : {email: email}});
  console.log(email);
  res.send(`<h1>${email} 삭제 성공</h1>`)
})
...

然后像下面这样发请求,就会删除对应数据。

http://localhost:3001/delete?email=testur@email.com

修改也不难。

在 where 里传入要匹配的数据,在 data 字段里写需要变更的内容即可。

...
app.get("/edit", async (req, res) => {
  const { email, name } = req.query;
  const result = await prisma.User.update({where : {email: email}, 
    data : {name : name}
  });
  console.log(email, name);
  res.send(`<h1>${email} 이름 수정 성공</h1>`)
})
...

然后加上 query string 再去查看,就能看到名字已经按下面这样被修改了。

为 Nextjs 接上 PostgreSQL 后端-5

7. 在 nextjs 中使用

要在 nextjs 中方便地使用 Prisma,需要做一些事前准备。

我在 src/lib 文件夹里新建了 prisma.ts 文件。

写上负责处理所有和 user 相关输入输出的代码。

import { PrismaClient } from "@prisma/client";

const globalWithPrisma = global as typeof globalThis & {
  prisma: PrismaClient;
};

let prisma: PrismaClient;

// 非开发模式时,每次都新建连接
if (process.env.NODE_ENV === "production") {
  prisma = new PrismaClient();
} else {
  if (!globalWithPrisma.prisma) {
    globalWithPrisma.prisma = new PrismaClient();
  }
    // 开发模式时,为防止内存泄漏,复用已有连接
  prisma = globalWithPrisma.prisma;
}

export default prisma;

这样之后,我在 /src/db 目录下定义了用于管理 user 的函数。

import { dbUserObject } from "@/types/allTypes";
import prisma from "@/lib/prisma";

export async function createUser(data: dbUserObject) {
    try {
        const user = await prisma.user.create({
            data: {
                ...
                email : data.user.email as string,
                ...
            }
        });
    
        return user; // 返回创建好的用户
    } catch (error) {
        console.error('Error create user', error);
        throw error;
    }
}

export async function getAllUsers() {
    try {
      const users = await prisma.user.findMany();
      return users;
    } catch (error) {
      console.error('Error fetching users:', error);
      throw error;
    }
  }

  // Read - 查询特定用户
export async function getUserByEmail(email: string) {
    try {
      const user = await prisma.user.findUnique({
        where: { email },
      });
      return user;
    } catch (error) {
      console.error('Error fetching user:', error);
      throw error;
    }
  }

  export async function updateUser(email: string, data: Partial<Omit<dbUserObject, 'email'>>) {
    try {
      const updatedUser = await prisma.user.update({
        where: { email },
        data: {
          nickName : data.user?.nickName,
          ...
        },
      });
      return updatedUser;
    } catch (error) {
      console.error('Error updating user:', error);
      throw error;
    }
  }

  export async function deleteUser(email: string) {
    try {
      const deletedUser = await prisma.user.delete({
        where: { email },
      });
      return deletedUser;
    } catch (error) {
      console.error('Error deleting user:', error);
      throw error;
    }
  }

8. 后记

以前总觉得后端本身很难,但有这些工具帮忙之后就轻松多了。

虽然有很多不同的库,但其中 Prisma 看起来是最直观也最好用的。

之后想用它来给自己的网站,以及项目都试着做一做。

관련 글

Next.js 全栈博客开发记
Next.js 全栈博客开发记
在第一次接触网页开发大约 1 年之后,我开始想着想要拥有一个属于自己的博客。于是我就大概花了 6 个月几乎只埋头做这件事。前端部分的功能,参考金度亨先生的博客就已经足够了。我用 mdx 搭建起一个博客,其实连一周都没花到。事实上,仅靠前端也并不会给博客运营带来什么大问题。有一阵子我也觉得,只要写 m...
使用 Caddy 实现 Next.js 无停机部署(本地服务器)
使用 Caddy 实现 Next.js 无停机部署(本地服务器)
每次一有想在主页上加点什么的念头就去 build,结果中间好像偶尔会有人访问。于是 Search Console 上的分数开始一点点往下掉。觉得这样不行,就开始思考要怎么做无停机部署。1. 两个项目文件夹 + 两个终端答案出乎意料地简单:开两个终端。在一个终端里 build,另一个终端里让服务器跑着...
谷歌搜索索引自动化 - Web Search Indexing API
谷歌搜索索引自动化 - Web Search Indexing API
继上次搞完 IndexNow 之后,也决定在谷歌这边做一下自动化。查了一下,发现谷歌是通过一个叫 Web Search Indexing 的 API 来支持这个功能的。1. 适用范围官方文档中,这个 API 正式支持的范围是招聘公告和流媒体视频服务。说是为了给对实时性要求高的内容创建索引用的,但搜了...
搜索索引生成自动化 - IndexNow
搜索索引生成自动化 - IndexNow
在向 Bing 提交站点收录时才发现,Bing 提供了一个叫做 IndexNow 的功能。核心在于,可以利用 API Key,在写完文章的瞬间就立刻发起索引请求。只要用 fetch 写出类似下面这样的请求,并把它串联到「写文章」流程里,就可以在把文章保存到 DB 的同时发送索引生成请求。POST /...
被入侵的 Nextjs、React 服务器经历
被入侵的 Nextjs、React 服务器经历
我最初接触到这次安全问题是在 12 月 5 日凌晨。据说在 React 中可以在未认证的情况下进行远程代码执行。看到这条新闻后,我虽然告诉了别人,但总觉得自己应该没事,就什么想法也没有地略过了。1. 发现被入侵的痕迹结果当我想登录更新博客代码时,在终端里发现了有命令被执行过的痕迹。/bin/sh:...
在博客中添加多语言功能(NextJS、next-intl、Vercel AI SDK)
在博客中添加多语言功能(NextJS、next-intl、Vercel AI SDK)
最近我觉得博客需要多语言功能。于是决定用 next-intl 来实现多语言服务。1.i18n首先,在做多语言服务时有一些必须遵守的原则。这被称为 internationalization,单词太长,所以把首字母 i 和尾字母 n,以及中间 18 个字母合在一起,写成 i18n。1) i18n 的原则...

댓글을 불러오는 중...