「1」基于Next.js的低代码平台:初始化服务端

413 阅读4分钟

前言

本次项目目前初定使用Next.js做服务端及管理系统,搭建平台使用React,相当于是从 0 打造通用型低代码产品的服务端简化版本吧

技术及平台:

  • 数据库:Supabase提供,postgresql,使用prisma链接
  • 样式:TailWindCSS
  • 路由:App Router
  • 校验工具:zod

新建一个项目:

pnpm dlx create-next-app@latest

一路回车即可

image.png

基本配置

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'

image.png

可参考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;

改变 isValidatetruefalse 查看效果

image.png

image.png

配置数据库

pnpm i prisma -D
pnpm i @prisma/client
npx prisma init

创建数据库

进入supabase通过google账号登录注册账号,最后进入Dashboard创建项目

点击New project创建项目: image.png

自定义项目名和数据库密码: image.png

点击create new project后会跳转到下一页,在这个页面等待一段时间 image.png

变成如下页面后,点击connect image.png

可以在里面找到ORMs,将.env.localschema.prisma的配置复制到前面初始化prisma的时候,生成的文件里面,注意这里是没有密码的需要将[YOUR-PASSWORD]改为你创建项目时自定义的数据库密码 image.png

image.png

定义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;

用接口请求工具验证一下: image.png

可以在命令行执行npx prisma studio,在localhost:5555查看数据库

image.png

很棒 接下来可以愉快得编码了

仓库代码:数据库配置