今天的学习真是让我大开眼界啊!作为一个前端初学者,我一直觉得后端数据库操作是件很复杂的事情,但通过今天的实践,我发现用Next.js配合Prisma可以如此轻松地完成全栈开发。让我来好好回顾一下今天学到的内容,把这些知识牢牢地记在脑子里。
这是整体效果,我们所添加的数据,都是从数据库中操作的:

prisma是何方神圣
prisma是什么
一开始我接触到了Prisma
这个神奇的工具。Prisma
是什么呢?简单来说,它是一个ORM(对象关系映射)工具,让我们不需要直接写复杂的SQL语句,就能像操作普通JavaScript对象一样来操作数据库。这真是太方便了!想象一下,如果不用Prisma
,我们要操作数据库就得写各种SQL查询语句,像什么SELECT、INSERT、UPDATE之类的,不仅容易出错,而且代码也很难维护。Prisma
的出现让我们摆脱了这些烦恼,可以用更加直观的方式来处理数据。
prisma安装和初始化
安装Prisma很简单,只需要在项目里运行pnpm i prisma @prisma/client
命令就可以了。这里安装了两个包:prisma是核心工具,提供了命令行功能;@prisma/client则是客户端库,让我们能在代码中实际使用Prisma的功能。
初始化Prisma是通过npx prisma init
命令完成的。这个命令会创建一个prisma文件夹,里面包含一个schema.prisma文件,这是Prisma的核心配置文件。同时还会生成一个.env文件,用来存放环境变量,特别是数据库连接字符串。
当然连接数据库得先有数据库,这里我用得是小皮面板
里面集成了数据库等各种环境,非常方便,一键式安装,不需要做任何配置:

说到数据库连接,我在.env文件中做了如下配置:
ini
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
# The following `prisma+postgres` URL is similar to the URL produced by running a local Prisma Postgres
# server with the `prisma dev` CLI command, when not choosing any non-default ports or settings. The API key, unlike the
# one found in a remote Prisma Postgres URL, does not contain any sensitive information.
DATABASE_URL="mysql://用户:密码@localhost:3306/数据库名"
DATABASE_URL="mysql://用户:密码@localhost:3306/数据库名
这个连接字符串告诉Prisma如何连接到我的MySQL数据库。它包含了用户名、密码、数据库服务器地址(localhost:3306)和数据库名。这样配置好后,Prisma就知道该往哪里读写数据了。
接下来是定义数据模型,这是在schema.prisma文件中完成的。我创建了一个Todo模型,用来表示待办事项:
kotlin
model Todo {
id Int @id @default(autoincrement())
title String
completed Boolean @default(false)
createdAt DateTime @default(now())
}
这个模型定义了四个字段:id是主键,会自动递增;title是字符串类型,存储待办事项的内容;completed是布尔类型,表示是否已完成,默认是false;createdAt是日期时间类型,会自动设置为创建时间。这种定义方式非常直观,就像是在定义一个TypeScript接口一样,但实际上它背后会转换成数据库的表结构。
定义好模型后,需要运行npx prisma migrate dev --name init
命令来创建迁移文件并执行迁移。迁移(Migration)是数据库结构变更的记录,比如建表、改字段等操作。Prisma会帮我们生成SQL语句并执行,同时记录下这些变更,这样团队协作时大家都能保持数据库结构的一致性。这一步真的很重要,它让数据库的版本控制变得像代码版本控制一样简单。
可以看到我们确实是成功建立todo这个表,里面的数据是我在后续的操作存入的
后端API开发
数据库层面准备好后,我开始着手API的开发。Next.js提供了App Router功能
,让我们能很容易地创建API端点,不需要再像react-route-dom
一样搭建路由。我在app/api/todos/route.ts
文件中创建了处理待办事项相关请求的API。
获取待办事项
先来看看如何处理获取所有待办事项的GET请求:
javascript
export async function GET() {
const todos = await prisma.todo.findMany({
orderBy: {
createdAt: 'desc'
}
})
return NextResponse.json(todos)
}
这段代码创建了一个Prisma客户端
实例,然后使用findMany
方法获取所有的待办事项。orderBy参数指定了按创建时间降序排列,这样最新的待办事项会显示在最前面。最后将结果以JSON格式返回给前端。
添加新待办事项
添加新待办事项的POST请求处理也很简单:
javascript
export async function POST(req: Request) {
const { title } = await req.json()
const todo = await prisma.todo.create({
data: {
title,
}
})
return NextResponse.json(todo)
}
这里从请求体中获取title字段,然后使用Prisma的create方法
创建新的待办事项。Prisma会自动处理id、completed和createdAt字段的默认值,我们只需要提供title就可以了。创建成功后返回新创建的待办事项对象。
除了基本的获取和添加功能,我还实现了更新和删除待办事项的API。这些API放在app/api/todos/[id]/route.ts
文件中,使用了动态路由来根据id操作特定的待办事项。
更新待办事项
更新待办事项(主要是切换完成状态)的PATCH请求处理:
javascript
export async function PATCH(req: Request,
{ params }: { params: { id: string } }) {
const { completed } = await req.json();
const todo = await prisma.todo.update({
where: { id: Number(params.id) },
data: { completed }
})
return NextResponse.json(todo)
}
这里从路由参数中获取id,从请求体中获取completed状态,然后使用Prisma的update方法更新特定的待办事项。where条件指定了要更新哪条记录,data指定了要更新的字段。
删除待办事项
删除待办事项的DELETE请求处理:
javascript
export async function DELETE(req: Request,
{ params }: { params: { id: string } }) {
await prisma.todo.delete({
where: { id: Number(params.id) }
})
return NextResponse.json({ success: true })
}
同样从路由参数中获取id,然后使用Prisma的delete方法删除对应的记录。删除成功后返回一个简单的成功标志。
前端页面
前端页面部分,我创建了一个简单的待办事项列表页面。这个页面使用了React的useState和useEffect钩子来管理状态和副作用。
首先定义了Todo接口来规范数据类型:
typescript
export interface Todo {
id: number;
title: string;
completed: boolean;
createdAt: string
}
获取数据
然后在组件中定义了状态和相关的处理函数:
scss
export default function Home() {
const [todos, setTodos] = useState<Todo[]>([]);
const [input, setInput] = useState("");
useEffect(() => {
fetchTodos()
}, [])
// 其他函数...
}
todos状态用来存储待办事项列表,input状态用来存储输入框的内容。useEffect在组件挂载时调用fetchTodos函数
获取数据。
fetchTodos函数
通过调用我们之前创建的API来获取数据:
ini
const fetchTodos = async () => {
const res = await fetch("/api/todos");
const data = await res.json();
setTodos(data);
}
这里使用了原生的fetch API来发送请求,然后解析返回的JSON数据并更新状态。
添加数据
添加待办事项的函数稍微复杂一些:
javascript
const addTodo = async () => {
if (!input.trim()) return
const res = await fetch('api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: input })
})
const newTodo = await res.json()
setTodos([newTodo, ...todos])
setInput('')
}
首先检查输入是否为空,然后发送POST请求到API,将新待办事项添加到数据库。成功后,将返回的新待办事项添加到列表的最前面,并清空输入框。
可以看到我们的数据是加入到了数据库的:

更新数据
切换待办事项完成状态的函数:
typescript
const toogleTodo = async (id: number, completed: boolean) => {
const res = await fetch(`api/todos/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ completed: !completed })
})
const updated = await res.json()
setTodos(todos.map(todo => todo.id === id ? updated : todo))
}
发送PATCH请求更新特定待办事项的完成状态,然后更新本地状态以反映变化。
删除
删除待办事项的函数:
typescript
const deleteTodo = async (id: number) => {
await fetch(`api/todos/${id}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
setTodos(todos.filter(todo => todo.id !== id))
}
发送DELETE请求删除特定待办事项,然后从本地状态中移除该项。
Tailwind CSS
页面的UI部分使用了简单的HTML和Tailwind CSS类来样式化:
ini
<main className=" max-w-xl max-auto p-6">
<h1 className="text-2xl font-bold mb-4">Todos</h1>
<div className="flex gap-2 mb-4">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add todo..."
className="border p-2 rounded flex-1"
type="text"
/>
<button
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
onClick={addTodo}
>
Add
</button>
</div>
<ul className="space-y-2">
{todos.map(todo => (
<li key={todo.id} className="flex justify-between items-center p-2 border rounded">
<span
onClick={() => toogleTodo(todo.id, todo.completed)}
className={`cursor-pointer select-none ${todo.completed ? 'line-through text-gray-500' : ''}`}
>{todo.title}</span>
<button
onClick={() => deleteTodo(todo.id)}
className="text-red-500 hover:text-red-700"
>
Delete
</button>
</li>
))}
</ul>
</main>
有一个输入框和添加按钮用于添加新待办事项,还有一个列表显示所有待办事项。每个待办事项可以点击文本来切换完成状态(会有删除线效果),也可以点击删除按钮来删除。
总结
回顾整个开发过程,我发现Prisma+Next.js的组合真的很强大。Prisma让我们能够以类型安全的方式操作数据库,减少了出错的可能性,提高了开发效率。Next.js则提供了全栈开发的能力,前后端可以在同一个项目中协同工作,避免了跨项目协作的复杂性。
这种开发模式的好处是显而易见的:首先,数据类型前后一致,因为我们都使用TypeScript
,前后端可以共享类型定义;其次,开发效率高,不需要频繁切换上下文;最后,部署简单,整个应用可以一起部署,减少了配置复杂度。
通过今天的学习,我对全栈开发有了更深入的理解。不再觉得后端开发是前端开发者的禁地,反而发现只要选对工具,后端开发也可以很直观、很高效。Prisma的模型定义、查询语法都非常直观,Next.js的API路由让创建后端接口变得简单易懂。
当然,这只是一个开始,还有很多高级特性等待我去探索,比如Prisma的关系查询、事务处理、聚合查询等,以及Next.js的服务器组件、中间件、缓存策略等。但今天的基础打得很扎实,让我有信心继续深入学习。
如果你也是前端开发者,想尝试全栈开发,我真的强烈推荐从Next.js+Prisma开始。这个组合入门门槛低,但能力强大,能够帮你快速构建完整的Web应用。今天的待办事项应用虽然简单,但涵盖了一个完整应用的核心功能:数据的增删改查。理解了这些基础,后续学习更复杂的功能就会容易得多。
好了,今天的回顾就到这里。希望通过这篇文章,不仅能帮自己巩固知识,也能给其他学习者一些启发。全栈开发并不难,关键在于选对工具和方法。Next.js+Prisma无疑是一个极佳的选择,值得每一个前端开发者学习和掌握。