使用 Cursor 从 0 到 1 开发一个全栈 Chatbox 项目
环境准备
在正式开发前,你的设备需要有如下环境:
- Node >= 18.18
- pnpm 作为依赖管理工具
- postgreSQL
- Curosr
- chrome 游览器
你需要具备的知识:
- 前端基础
- 数据库基础
- 计算机网络基础
- 熟悉 React 开发
当然,你还需要有良好的软件开发素养,否则你会发现写的代码不好维护,或者不易理解
项目初始化
在真正开发项目之前,让我们先进行需求分析和技术选型
需求分析
- 聊天页面开发(基础能力)
- 包含提示的输入框和发送/停止按钮
- 实现一个聊天区域来显示对话记录,一个列表展示会话历史
- 开发/agent API 来处理请求
- 确保每个对话的数据都被持久存储
- 通过流式传输返回所有结果
- 高级能力
- 增强聊天组件,支持 markdown 渲染、自动滚动、图片上传等
- 实现函数调用,例如检索当前时间
技术选型
根据需求,我选择了我喜欢的并且也是主流的技术:
- Next.js 作为全栈开发基础框架
- hono.js 作为后端框架,优化在 Next.js 中后端开发体验
- PostgreSQL 作为数据库存储对话记录
- DrizzleORM 作为 ORM,更加便捷和高效的方式与数据库进行交互
- shadcn/ui 作为 UI 组件库,tailwindcss 作为 css 框架
- Vercel AI SDK 快速开发 AI 相关的服务
- Biome 进行代码格式化和检测
- zod:TypeScript 优先的数据验证库
对了,我们使用 Github 进行版本控制,维护代码。使用 vercel 进行项目部署上线。
初始化
下面进行初始化项目,初始化项目完成后,我们应该就可以进行业务开发
1)根据 Next.js 官方文档,我们创建一个 Next.js 项目:
npx create-next-app@latest
2)下面,根据官方文档集成 shadcn/ui:
pnpm dlx shadcn@latest init
pnpm dlx shadcn@latest add button
3)下面集成 Biome,保证相关代码风格一致:
pnpm i @biomejs/biome -D
4)下面继续集成 hono.js:
pnpm i hono
# 让 hono 接管所有接口服务
mkdir -p "src/app/api/[[...route]]" && touch "src/app/api/[[...route]]/route.ts"
5)下面,开发 route.ts 内容,让 hono 来接管接口服务:
// src/app/api/[[...route]]/route.ts
import api from "@/server/api";
import { handle } from "hono/vercel";
const handler = handle(api);
export handler as GET,
handler as POST,
handler as PUT,
handler as DELETE,
handler as PATCH,
};
6)下面,我们创建自定义校验器,它的作用是进行请求数据验证的工具函数,确保数据符合预期的格式和类型规范,并提供类型安全的验证结果:
// src/server/api/validator.ts
import type {
Context,
MiddlewareHandler,
Env,
ValidationTargets,
TypedResponse,
Input,
} from "hono";
import { validator } from "hono/validator";
import type { z, ZodSchema, ZodError } from "zod";
export type Hook<
T,
E extends Env,
P extends string,
Target extends keyof ValidationTargets = keyof ValidationTargets,
// biome-ignore lint/complexity/noBannedTypes: <explanation>
O = {}
> = (
result: (
| { success: true; data: T }
| { success: false; error: ZodError; data: T }
) & {
target: Target;
},
c: Context<E, P>
) =>
| Response
| void
| TypedResponse<O>
// biome-ignore lint/suspicious/noConfusingVoidType: <explanation>
| Promise<Response | void | TypedResponse<O>>;
type HasUndefined<T> = undefined extends T ? true : false;
export const zValidator = <
T extends ZodSchema,
Target extends keyof ValidationTargets,
E extends Env,
P extends string,
In = z.input<T>,
Out = z.output<T>,
I extends Input = {
in: HasUndefined<In> extends true
? {
[K in Target]?: K extends "json"
? In
: HasUndefined<keyof ValidationTargets[K]> extends true
? { [K2 in keyof In]?: ValidationTargets[K][K2] }
: { [K2 in keyof In]: ValidationTargets[K][K2] };
}
: {
[K in Target]: K extends "json"
? In
: HasUndefined<keyof ValidationTargets[K]> extends true
? { [K2 in keyof In]?: ValidationTargets[K][K2] }
: { [K2 in keyof In]: ValidationTargets[K][K2] };
};
out: { [K in Target]: Out };
},
V extends I = I
>(
target: Target,
schema: T,
hook?: Hook<z.infer<T>, E, P, Target>
): MiddlewareHandler<E, P, V> =>
// @ts-expect-error not typed well
validator(target, async (value, c) => {
const result = await schema.safeParseAsync(value);
if (hook) {
const hookResult = await hook({ data: value, ...result, target }, c);
if (hookResult) {
if (hookResult instanceof Response) {
return hookResult;
}
if ("response" in hookResult) {
return hookResult.response;
}
}
}
if (!result.success) {
throw result.error;
}
return result.data as z.infer<T>;
});
7)下面,我们创建错误处理文件,给到客户端更好的错误:
// src/server/api/error.ts
import { z } from "zod";
import type { Context } from "hono";
import { HTTPException } from "hono/http-exception";
import type { ContentfulStatusCode } from "hono/utils/http-status";
export class ApiError extends HTTPException {
public readonly code?: ContentfulStatusCode;
constructor({
code,
message,
}: {
code?: ContentfulStatusCode;
message: string;
}) {
super(code, { message });
this.code = code;
}
}
export function handleError(err: Error, c: Context): Response {
if (err instanceof z.ZodError) {
const firstError = err.errors[0];
return c.json(
{ code: 422, message: `\`${firstError.path}\`: ${firstError.message}` },
422
);
}
/**
* This is a generic error, we should log it and return a 500
*/
return c.json(
{
code: 500,
message: "服务端错误, 请稍后再试。",
},
{ status: 500 }
);
}
8)下面,我们创建我们的第一个接口,验证 honojs 是否引入成功:
// src/server/api/routes/hello.ts
import { Hono } from "hono";
const app = new Hono().get("/hello", (c) =>
c.json({ message: "Hello, luckyChat" })
);
export default app;
9)下面,我们开发入口文件:
// src/server/api/index.ts
import { handleError } from "./error";
import { Hono } from "hono";
import helloRoute from "./routes/hello";
const app = new Hono().basePath("/api");
app.onError(handleError);
const routes = app.route("/", helloRoute);
export default app;
export type AppType = typeof routes;
现在我们不仅有了接口,还有了服务端接口的类型声明,我们可以非常方便的在客户端进行类型安全的接口请求,我们不需要写路由,也不需要写类型相关的内容,真的是 amazing,我们赶紧在客户端调用第一个接口吧!
10)下面,我们封装一个 fetch 方法:
// src/lib/fetch.ts
import type { AppType } from "@/server/api";
import { hc } from "hono/client";
import ky from "ky";
const baseUrl =
process.env.NODE_ENV === "development"
? "http://localhost:3000"
: process.env.NEXT_PUBLIC_APP_URL;
export const fetch = ky.extend({
hooks: {
afterResponse: [
async (_, __, response: Response) => {
if (response.ok) {
return response;
// biome-ignore lint/style/noUselessElse: <explanation>
<br/>
**Q&A:使用 Cursor 从 0 到 1 开发一个全栈 chatbox 项目**
=====================================================
**Q1:什么是 Cursor?**
-------------------
A1:Cursor 是一个 AI 编程工具,能够帮助开发者快速构建项目,包括前端和后端代码。
**Q2:如何使用 Cursor?**
-------------------
A2:使用 Cursor 需要先安装 Node.js 和 pnpm,接着创建一个 Next.js 项目,最后使用 Cursor 的 API 来生成项目代码。
**Q3:什么是 DrizzleORM?**
-------------------
A3:DrizzleORM 是一个 ORM(Object-Relational Mapping)工具,能够帮助开发者与数据库进行交互。
**Q4:如何使用 DrizzleORM?**
-------------------
A4:使用 DrizzleORM 需要先安装 DrizzleORM,接着创建一个数据库表结构,最后使用 DrizzleORM 的 API 来与数据库进行交互。
**Q5:什么是 Vercel AI SDK?**
-------------------
A5:Vercel AI SDK 是一个 AI SDK,能够帮助开发者快速构建 AI 相关的服务。
**Q6:如何使用 Vercel AI SDK?**
-------------------
A6:使用 Vercel AI SDK 需要先安装 Vercel AI SDK,接着使用 Vercel AI SDK 的 API 来构建 AI 相关的服务。
**Q7:什么是 Biome?**
-------------------
A7:Biome 是一个代码格式化和检测工具,能够帮助开发者保持代码的格式和质量。
**Q8:如何使用 Biome?**
-------------------
A8:使用 Biome 需要先安装 Biome,接着使用 Biome 的 API 来格式化和检测代码。
**Q9:什么是 zod?**
-------------------
A9:zod 是一个 TypeScript 优先的数据验证库,能够帮助开发者验证数据的格式和类型。
**Q10:如何使用 zod?**
-------------------
A10:使用 zod 需要先安装 zod,接着使用 zod 的 API 来验证数据的格式和类型。
**Q11:如何优化项目?**
-------------------
A11:优化项目需要考虑多个方面,包括代码质量、性能、安全性等。
**Q12:如何测试项目?**
-------------------
A12:测试项目需要使用测试框架和工具,例如 Jest 和 Cypress 等。
**Q13:如何部署项目?**
-------------------
A13:部署项目需要使用部署工具和平台,例如 Vercel 和 Netlify 等。
**Q14:如何维护项目?**
-------------------
A14:维护项目需要持续更新和维护代码,包括 bug 修复和新功能添加等。
**Q15:如何使用 AI 在项目开发中?**
-------------------
A15:使用 AI 在项目开发中需要使用 AI SDK 和工具,例如 Vercel AI SDK 和 Biome 等。