前言
本次项目目前初定使用Next.js
做服务端及管理系统,搭建平台使用React
,相当于是从 0 打造通用型低代码产品的服务端简化版本吧
技术及平台:
- 数据库:Supabase提供,postgresql,使用prisma链接
- 样式:TailWindCSS
- 路由:App Router
- 校验工具:zod
新建一个项目:
pnpm dlx create-next-app@latest
一路回车即可
基本配置
prettier
pnpm i eslint@8 prettier eslint-plugin-react eslint-config-prettier eslint-plugin-prettier prettier-plugin-tailwindcss @typescript-eslint/eslint-plugin -D
修改或新建.eslintrc.js
module.exports = {
extends: [
'next',
'next/core-web-vitals',
'eslint:recommended',
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
plugins: ['react', '@typescript-eslint'],
parserOptions: {
ecmaVersion: 2021,
sourceType: 'module',
},
rules: {
'react/react-in-jsx-scope': 'off',
'react/jsx-uses-react': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};
新建.prettierrc.js
module.exports = {
semi: true,
trailingComma: "all",
singleQuote: true,
printWidth: 100,
tabWidth: 2,
plugins: ["prettier-plugin-tailwindcss"],
};
在package.json
中添加运行脚本:
"lint": "eslint .",
"format": "prettier --write './**/*.+(js|ts|jsx|tsx)'"
提交规范
pnpm i @commitlint/config-conventional @commitlint/cli husky -D
初始化husky
npx husky init
修改或新建.husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run lint
npm run format
git add .
修改或新建.husky/commit-msg
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no-install commitlint -e
新增./.commitlintrc.cjs
module.exports = {
extends: ['@commitlint/config-conventional']
};
运行以下命令看看是否配置成功:
git add .
git commit -m 'ffff'
可参考commit规范zhuanlan.zhihu.com/p/182553920
仓库代码如下:github.com/liyunfu1998…
接口规范
统一接口返回规范
新建helpers/transformInterceptor.ts
export function transformInterceptor({
code,
message,
data,
}: {
code?: number;
message?: string;
data?: any;
} = {}) {
return {
code: code || 0,
message: message || "success",
data: data || null,
};
}
接口帮助函数
这里我们需要用到zod
进行数据校验:
安装依赖:
pnpm i zod
新建helpers/validate-middleware.ts
import type { NextRequest } from 'next/server';
import type { Schema } from 'zod';
export default async function validateMiddleware(req: NextRequest, schema: Schema) {
if (!schema) return;
const body = await req.json();
const { error, success, data } = await schema.safeParseAsync(body);
if (!success) {
throw `Validation error: ${error?.errors?.map((x) => `${x.path?.[0]}:${x.message}`).join('; ')}`;
}
req.json = () => data;
}
主要就是在传入schema
的情况,先对数据进行校验
新建helpers/apiHandler.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { errorHandler } from './errorHandler';
import validateMiddleware from './validate-middleware';
import jwtMiddleware from './jwt-middleware';
export function apiHandler(
handler: (req: NextRequest, ...args: any[]) => any,
{ isJwt, schema }: { isJwt?: boolean; schema?: any } = {},
) {
return async (req: NextRequest, ...args: any[]) => {
try {
await jwtMiddleware(req, isJwt);
await validateMiddleware(req, schema);
const responseBody = await handler(req, ...args);
return NextResponse.json(responseBody || {});
} catch (err: any) {
return errorHandler(err);
}
};
}
api
帮助函数,包裹最终handler,在前面进行一些中间件校验
新建helpers/errorHandler.ts
import { NextResponse } from 'next/server';
import { transformInterceptor } from './transformInterceptor';
export function errorHandler(err: any) {
if (typeof err === 'string') {
const is404 = err.toLowerCase().endsWith('not found');
const status = is404 ? 404 : 400;
return NextResponse.json(
transformInterceptor({
message: err,
code: status,
}),
{ status },
);
}
if (err.name === 'JsonWebTokenError') {
return NextResponse.json(
transformInterceptor({
message: 'Unauthorized',
code: 401,
}),
{ status: 401 },
);
}
if (err.name === 'UserExistsError') {
return NextResponse.json(
transformInterceptor({
message: err.message,
code: 422,
}),
{ status: 422 },
);
}
return NextResponse.json(
transformInterceptor({
message: err.message,
code: 500,
}),
{ status: 500 },
);
}
可以验证一下:
新建app/api/home/route.ts
import { transformInterceptor, apiHandler } from "@/helpers";
const getList = apiHandler(
async (req: any) => {
return transformInterceptor({
data: [
{
value: 1,
label: "全人群",
},
{
value: 2,
label: "分人群",
},
],
});
},
{ isJwt: false, isValidate: true },
);
export const GET = getList;
改变 isValidate
为true
和false
查看效果
配置数据库
pnpm i prisma -D
pnpm i @prisma/client
npx prisma init
创建数据库
进入supabase通过google账号登录注册账号,最后进入Dashboard创建项目
点击New project
创建项目:
自定义项目名和数据库密码:
点击create new project
后会跳转到下一页,在这个页面等待一段时间
变成如下页面后,点击connect
可以在里面找到ORMs
,将.env.local
和schema.prisma
的配置复制到前面初始化prisma
的时候,生成的文件里面,注意这里是没有密码的需要将[YOUR-PASSWORD]
改为你创建项目时自定义的数据库密码
定义model
在prisma/schema.prisma
中添加一个model
model User {
id String @id @default(cuid())
email String @unique
name String?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
}
记得每次修改了prisma
的时候需要命令行运行npx prisma migrate dev
现在我们需要定义一个prisma
工具函数,以帮助我们更好得在后面方便访问数据库:
新建helpers/prisma.ts
import { PrismaClient } from '@prisma/client';
const prismaClientSingleton = () => {
return new PrismaClient();
};
type prismaClientSingleton = ReturnType<typeof prismaClientSingleton>;
const globalFormPrisma = globalThis as unknown as {
prisma: prismaClientSingleton | undefined;
};
const prisma = globalFormPrisma.prisma ?? prismaClientSingleton();
export default prisma;
if (process.env.NODE_ENV !== 'production') globalFormPrisma.prisma = prisma;
现在去app/api/home/route.ts
中新建一个添加用户的接口:
import { transformInterceptor, apiHandler } from '@/helpers';
import prisma from '@/helpers/prisma';
const addUser = apiHandler(async (req: any) => {
const result = await req.json();
console.log('req', result);
const user = await prisma.user.create({
data: {
...result,
},
});
if (!user) {
throw new Error('添加用户失败');
}
return transformInterceptor({
data: user,
});
});
export const POST = addUser;
用接口请求工具验证一下:
可以在命令行执行npx prisma studio
,在localhost:5555
查看数据库
很棒 接下来可以愉快得编码了
仓库代码:数据库配置