ZenStack全栈开发工具(一)快速使用指南

简介

ZenStack是一个TypeScript工具,通过灵活的授权和自动生成的类型安全的 API/钩子来增强 Prisma ORM,从而简化全栈开发
数据库-》应用接口
数据库-》前端
参考官方网站:https://zenstack.dev/

如果我们想做一个全栈开发的web应用程序,之前有选择的是java的jsp页面,后面流行的使用TypeScript,node.js来实现后端业务逻辑,而node.js最流行的ORM框架就是Prisma。
ZenStack 是一个构建在 Prisma 之上的开源工具包 - 最流行的 Node.js ORM。ZenStack 将 Prisma 的功能提升到一个新的水平,并提高了堆栈每一层的开发效率 - 从访问控制到 API 开发,一直到前端。

ZenStack可以做什么,更方便做什么

ZenStack 的一些最常见用例包括:

  • 多租户 SaaS
  • 具有复杂访问控制要求的应用程序
  • CRUD 密集型 API 或 Web 应用程序

ZenStack 对您选择的框架没有主见。它可以与它们中的任何一个一起使用。

特征

具有内置访问控制、数据验证、多态关系等的 ORM

自动生成的CRUD API - RESTful & tRPC

自动生成的 OpenAPI 文档

自动生成的前端数据查询钩子 - SWR & TanStack查询

与流行的身份验证服务和全栈/后端框架集成

具有出色可扩展性的插件系统

出色的能力

后端能力

带访问控制的 ORM:ZenStack 通过强大的访问控制层扩展了 Prisma ORM。通过在数据模型中定义策略,您的 Schema 成为单一事实来源。通过使用启用策略的数据库客户端,您可以享受您已经喜欢的相同 Prisma API,ZenStack 会自动执行访问控制规则。它的核心与框架无关,可以在 Prisma 运行的任何位置运行。
在这里插入图片描述

应用程序接口能力

自动 CRUD API:将 API 包装到数据库中是乏味且容易出错的。ZenStack 只需几行代码即可内省架构并将 CRUD API 安装到您选择的框架中。由于内置访问控制支持,API 是完全安全的,可以直接向公众公开。文档呢?打开一个插件,几秒钟内就会生成一个 OpenAPI 规范。

在这里插入图片描述

全栈能力

数据查询和变更是前端开发中最难的话题之一。ZenStack 通过生成针对您选择的数据查询库(SWR、TanStack Query 等)的全类型客户端数据访问代码(又名钩子)来简化它。钩子调用自动生成的 API,这些 API 由访问策略保护。
在这里插入图片描述

搭建我们的是全栈应用程序

参考官方文档:https://zenstack.dev/docs/quick-start/nextjs-app-router
我们搭建一个专门做crud的全栈程序

前置准备工作

  1. 确保您已安装 Node.js 18 或更高版本。
  2. 安装 VSCode 扩展以编辑数据模型。

1、构建应用程序

使用样板创建 Next.js 项目的最简单方法是使用 。运行以下命令以使用 Prisma、NextAuth 和 TailwindCSS 创建新项目。create-t3-app

npx create-t3-app@latest --prisma --nextAuth --tailwind --appRouter --CI my-crud-app
cd my-crud-app

从 中删除相关代码,因为我们不打算使用 Discord 进行身份验证。之后,启动 dev 服务器:DISCORD_CLIENT_IDDISCORD_CLIENT_SECRETsrc/env.js

npm run dev

如果一切正常,您应该在 http://localhost:3000 有一个正在运行的 Next.js 应用程序。
在这里插入图片描述

2、初始化 ZenStack 的项目

让我们运行 CLI 来准备您的项目以使用 ZenStack。zenstack

npx zenstack@latest init

3. 准备用于身份验证的 User 模型

首先,在 中,对模型进行一些更改:schema.zmodel User

schema.zmodel

model User {id            String    @id @default(cuid())name          String?email         String?   @uniqueemailVerified DateTime?password      String @password @omitimage         String?accounts      Account[]sessions      Session[]posts         Post[]// everyone can signup, and user profile is also publicly readable@@allow('create,read', true)// only the user can update or delete their own profile@@allow('update,delete', auth() == this)
}

4. 将 NextAuth 配置为使用基于凭证的身份验证

/src/server/auth.ts

这里可以改造成通过外部API验证身份信息
也可以先不配置

import { PrismaAdapter } from "@auth/prisma-adapter";
import type { PrismaClient } from "@prisma/client";
import { compare } from "bcryptjs";
import {getServerSession,type DefaultSession,type NextAuthOptions,
} from "next-auth";
import { type Adapter } from "next-auth/adapters";
import CredentialsProvider from "next-auth/providers/credentials";import { db } from "~/server/db";/*** Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`* object and keep type safety.** @see https://next-auth.js.org/getting-started/typescript#module-augmentation*/
declare module "next-auth" {interface Session extends DefaultSession {user: {id: string;} & DefaultSession["user"];}
}/*** Options for NextAuth.js used to configure adapters, providers, callbacks, etc.** @see https://next-auth.js.org/configuration/options*/
export const authOptions: NextAuthOptions = {session: {strategy: "jwt",},callbacks: {session({ session, token }) {if (session.user) {// eslint-disable-next-line @typescript-eslint/no-non-null-assertionsession.user.id = token.sub!;}return session;},},adapter: PrismaAdapter(db) as Adapter,providers: [CredentialsProvider({credentials: {email: { type: "email" },password: { type: "password" },},authorize: authorize(db),}),/*** ...add more providers here.** Most other providers require a bit more work than the Discord provider. For example, the* GitHub provider requires you to add the `refresh_token_expires_in` field to the Account* model. Refer to the NextAuth.js docs for the provider you want to use. Example:** @see https://next-auth.js.org/providers/github*/],
};function authorize(prisma: PrismaClient) {return async (credentials: Record<"email" | "password", string> | undefined,) => {if (!credentials) throw new Error("Missing credentials");if (!credentials.email)throw new Error('"email" is required in credentials');if (!credentials.password)throw new Error('"password" is required in credentials');const maybeUser = await prisma.user.findFirst({where: { email: credentials.email },select: { id: true, email: true, password: true },});if (!maybeUser?.password) return null;// verify the input password with stored hashconst isValid = await compare(credentials.password, maybeUser.password);if (!isValid) return null;return { id: maybeUser.id, email: maybeUser.email };};
}/*** Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file.** @see https://next-auth.js.org/configuration/nextjs*/
export const getServerAuthSession = () => getServerSession(authOptions);

5. 挂载 CRUD 服务并生成钩子

ZenStack 内置了对 Next.js 的支持,可以提供数据库 CRUD 服务 自动编写,因此您无需自己编写。

首先安装 、 和 包:@zenstackhq/server@tanstack/react-query@zenstackhq/tanstack-query

npm install @zenstackhq/server@latest @tanstack/react-query
npm install -D @zenstackhq/tanstack-query@latest

让我们将其挂载到终端节点。创建文件并填写以下内容:/api/model/[…path]/src/app/api/model/[…path]/route.ts

/src/app/api/model/[…path]/route.ts

import { enhance } from "@zenstackhq/runtime";
import { NextRequestHandler } from "@zenstackhq/server/next";
import { getServerAuthSession } from "~/server/auth";
import { db } from "~/server/db";// create an enhanced Prisma client with user context
async function getPrisma() {const session = await getServerAuthSession();return enhance(db, { user: session?.user });
}const handler = NextRequestHandler({ getPrisma, useAppDir: true });export {handler as DELETE,handler as GET,handler as PATCH,handler as POST,handler as PUT,
};

该路由现在已准备好访问数据库查询和更改请求。 但是,手动调用该服务将很繁琐。幸运的是,ZenStack 可以 自动生成 React 数据查询钩子。/api/model

让我们通过在顶层将以下代码段添加到 :schema.zmodel
加入post作为增删改查的模型,他的增删改查都是基于模型的API
/schema.zmodel

plugin hooks {provider = '@zenstackhq/tanstack-query'target = 'react'version = 'v5'output = "./src/lib/hooks"
}model Post {id Int @id @default(autoincrement())name StringcreatedAt DateTime @default(now())updatedAt DateTime @updatedAtpublished Boolean @default(false)createdBy User @relation(fields: [createdById], references: [id])createdById String @default(auth().id)@@index([name])// author has full access@@allow('all', auth() == createdBy)// logged-in users can view published posts@@allow('read', auth() != null && published)
}

现在再次运行;你会在 folder 下找到生成的钩子:zenstack generate/src/lib/hooks
注意:每次增加模型都要运行下面命令,让它生产API方法

npx zenstack generate

6、建立页面访问增删改查

现在让我们替换为下面的内容,并使用它来查看和管理帖子。/src/app/page.tsx

"use client";import type { Post } from "@prisma/client";
import { type NextPage } from "next";
import { signOut, useSession } from "next-auth/react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {useFindManyPost,useCreatePost,useUpdatePost,useDeletePost,
} from "../lib/hooks";type AuthUser = { id: string; email?: string | null };const Welcome = ({ user }: { user: AuthUser }) => {const router = useRouter();async function onSignout() {await signOut({ redirect: false });router.push("/signin");}return (<div className="flex gap-4"><h3 className="text-lg">Welcome back, {user?.email}</h3><buttonclassName="text-gray-300 underline"onClick={() => void onSignout()}>Signout</button></div>);
};const SigninSignup = () => {return (<div className="flex gap-4 text-2xl"><Link href="/signin" className="rounded-lg border px-4 py-2">Signin</Link><Link href="/signup" className="rounded-lg border px-4 py-2">Signup</Link></div>);
};const Posts = ({ user }: { user: AuthUser }) => {// Post crud hooksconst { mutateAsync: createPost } = useCreatePost();const { mutateAsync: updatePost } = useUpdatePost();const { mutateAsync: deletePost } = useDeletePost();// list all posts that're visible to the current user, together with their authorsconst { data: posts } = useFindManyPost({include: { createdBy: true },orderBy: { createdAt: "desc" },});async function onCreatePost() {const name = prompt("Enter post name");if (name) {await createPost({ data: { name } });}}async function onTogglePublished(post: Post) {await updatePost({where: { id: post.id },data: { published: !post.published },});}async function onDelete(post: Post) {await deletePost({ where: { id: post.id } });}return (<div className="container flex flex-col text-white"><buttonclassName="rounded border border-white p-2 text-lg"onClick={() => void onCreatePost()}>+ Create Post</button><ul className="container mt-8 flex flex-col gap-2">{posts?.map((post) => (<li key={post.id} className="flex items-end justify-between gap-4"><p className={`text-2xl ${!post.published ? "text-gray-400" : ""}`}>{post.name}<span className="text-lg"> by {post.createdBy.email}</span></p><div className="flex w-32 justify-end gap-1 text-left"><buttonclassName="underline"onClick={() => void onTogglePublished(post)}>{post.published ? "Unpublish" : "Publish"}</button><button className="underline" onClick={() => void onDelete(post)}>Delete</button></div></li>))}</ul></div>);
};const Home: NextPage = () => {const { data: session, status } = useSession();if (status === "loading") return <p>Loading ...</p>;return (<main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]"><div className="container flex flex-col items-center justify-center gap-12 px-4 py-16 text-white"><h1 className="text-5xl font-extrabold">My Awesome Blog</h1>{session?.user ? (// welcome & blog posts<div className="flex flex-col"><Welcome user={session.user} /><section className="mt-10"><Posts user={session.user} /></section></div>) : (// if not logged in<SigninSignup />)}</div></main>);
};export default Home;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/439039.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

记一次教学版内网渗透流程

信息收集 如果觉得文章写的不错可以共同交流 http://aertyxqdp1.target.yijinglab.com/dirsearch dirsearch -u "http://aertyxqdp1.target.yijinglab.com/"发现 http://aertyxqdp1.target.yijinglab.com/joomla/http://aertyxqdp1.target.yijinglab.com/phpMyA…

算法笔记(九)——栈

文章目录 删除字符串中的所有相邻重复项比较含退格的字符串基本计算机II字符串解码验证栈序列 栈是一种先进后出的数据结构&#xff0c;其操作主要有 进栈、压栈&#xff08;Push&#xff09; 出栈&#xff08;Pop&#xff09; 常见的使用栈的算法题 中缀转后缀逆波兰表达式求…

大学生就业市场:Spring Boot招聘系统的设计与实现

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

MySQL中NULL值是否会影响索引的使用

MySQL中NULL值是否会影响索引的使用 为何写这一篇文章 &#x1f42d;&#x1f42d;在面试的时候被问到NULL值是否会走索引的时候&#xff0c;感到有点不理解&#xff0c;于是事后就有了这篇文章 问题&#xff1a; 为name建立索引&#xff0c;name可以为空select * from user …

OpenHarmony标准系统上实现对rk系列芯片NPU的支持(npu使用)

在上篇文章中&#xff0c;我们学习了移植rk的npu驱动到OpenHarmony提供的内核。本文我们来学习如何在OpenHarmony标准系统rk系列芯片如何使用npu OpenHarmony RK系列芯片运行npu测试用例 在移植npu驱动到OpenHarmony之后&#xff0c;来运行npu样例进行简单测试 1.O 测试准备…

ModuleNotFoundError: No module named ‘package‘

报错&#xff1a; Traceback (most recent call last): File “”, line 198, in run_module_as_main File “”, line 88, in run_code File "D:\python\helloworld.venv\Scripts\pip.exe_main.py", line 4, in File "D:\python\helloworld.venv\Lib\site-pac…

昇思学习打卡营第32天|基于ResNet50的中药炮制饮片质量判断模型

背景介绍 中药炮制是根据中医药理论&#xff0c;依照临床用药需求&#xff0c;通过调剂和制剂要求&#xff0c;将中药材制备成中药饮片的过程。老百姓日常使用的中药饮片&#xff0c;是中药炮制技术的成果。中药炮制过程中&#xff0c;尤其是涉及到水火处理时&#xff0c;必须注…

电器自动化入门08:隔离变压器、行程开关介绍及选型

视频链接&#xff1a;3.4 电工知识&#xff1a;三相交流异步电动机自动往返行程控制及控制变压器选型_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1PJ41117PW?p8&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.隔离&#xff08;控制&#xff09;变压器 2.行程开…

【AI】AIOT简介

随着技术的快速发展&#xff0c;人工智能AI和物联网IoT已经成为当今最热门的技术领域。AIOT是人工智能和物联网的结合&#xff0c;使物联网设备更加智能化&#xff0c;能够进行自主决策和学习的技术。 通过物联网产生、收集来自不同维度的、海量的数据存储于云端、边缘端&#…

828华为云征文|部署个人文档管理系统 Docspell

828华为云征文&#xff5c;部署个人文档管理系统 Docspell 一、Flexus云服务器X实例介绍二、Flexus云服务器X实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置2.4 Docker 环境搭建 三、Flexus云服务器X实例部署 Docspell3.1 Docspell 介绍3.2 Docspell 部署3.3 Docspell 使用…

深度学习基础—目标定位与特征点检测

1.目标定位 &#xff08;1&#xff09;定义 目标定位就是在图片中&#xff0c;定位对象的位置&#xff0c;对于对象的位置可以用框圈住显示。如下图所示&#xff1a; 假设正在进行图片分类工作&#xff0c;那么这个汽车图片很有可能被分类为汽车类别。对于目标定位&#xff0c;…

螺蛳壳里做道场:老破机搭建的私人数据中心---Centos下Docker学习01(环境准备)

1 准备工作 由于创建数据中心需要安装很多服务器&#xff0c;这些服务器要耗费很所物理物理计算资源、存储资源、网络资源和软件资源&#xff0c;作为穷学生只有几百块的n手笔记本&#xff0c;不可能买十几台服务器来搭建数据中心&#xff0c;也不愿意跑实验室&#xff0c;想躺…

Pikachu-Cross-Site Scripting-xss之htmlspecialchars

首先输入各种字符 查看页面元素&#xff0c;可以看到这里对一些符号做了转换&#xff0c;但是 单引号等几个符号没处理&#xff1b; 从代码上看&#xff1b;使用单引号做闭合&#xff1b; 构造payload a onclickalert(11) 提交&#xff0c;得到xss攻击

网约班车升级手机端退票

背景 作为老古董程序员&#xff0c;不&#xff0c;应该叫互联网人员&#xff0c;因为我现在做的所有的事情&#xff0c;都是处于爱好&#xff0c;更多的时间是在和各行各业的朋友聊市场&#xff0c;聊需求&#xff0c;聊怎么通过IT互联网 改变实体行业的现状&#xff0c;准确的…

【Qt】控件概述(2)—— 按钮类控件

控件概述&#xff08;2&#xff09; 1. PushButton2. RadioButton——单选按钮2.1 使用2.2 区分信号 clicked&#xff0c;clicked(bool)&#xff0c;pressed&#xff0c;released&#xff0c;toggled(bool)2.3 QButtonGroup分组 3. CheckBox——复选按钮 1. PushButton QPushB…

《15分钟轻松学 Python》教程目录

为什么要写这个教程呢&#xff0c;主要是因为即使是AI技术突起的时代&#xff0c;想要用好AI做开发&#xff0c;那肯定离不开Python&#xff0c;就算最轻量级的智能体都有代码块要写&#xff0c;所以不一定要掌握完完整整的Python&#xff0c;只要掌握基础就能应对大部分场景。…

【机器学习】ID3、C4.5、CART 算法

目录 常见的决策树算法 1. ID3 2. C4.5 3. CART 决策树的优缺点 优点&#xff1a; 缺点&#xff1a; 决策树的优化 常见的决策树算法 1. ID3 ID3&#xff08;Iterative Dichotomiser 3&#xff09;算法使用信息增益作为特征选择的标准。它是一种贪心算法&#xff0c;信…

【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一部分 D3.js 基础知识 第一章 D3.js 简介&#xff08;已完结&#xff09; 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践&#xff08;上&#xff09;1.3 数据可…

python实现单例模式的常用三种方法-基于__new__/使用装饰器以及Python中的值类型、引用类型以及类的静态变量、读取进程和线程ID

一、python实现单例模式的常用三种方法-基于__new__,使用装饰器 涉及到类的使用就会有类的实例化&#xff0c;就会有类单例实现的需求&#xff0c;因为重复实例化会浪费资源。python中的单例模式与别的语言相比&#xff0c;单例实现的方法更丰富。虽然python实现单例的模式的方…

一文掌握Harbor镜像同步公有云镜像仓库实践

一文掌握Harbor镜像同步公有云镜像仓库实践 目录 1 引言2 概念 2.1 Harbor2.2 阿里云的镜像仓库ACR2.3 华为云的镜像仓库SWR2.4 Harbor复制管理同步镜像 2.4.1 复制管理的工作原理 2.5 Harbor同步镜像到公有云镜像仓库的优势 3 实验&#xff1a;通过Harbor 将容器镜像同步到公…