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

news2024/11/20 14:34:58

简介

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?   @unique
  emailVerified DateTime?
  password      String @password @omit
  image         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-assertion
        session.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 hash
    const 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 String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  published 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>
      <button
        className="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 hooks
  const { mutateAsync: createPost } = useCreatePost();
  const { mutateAsync: updatePost } = useUpdatePost();
  const { mutateAsync: deletePost } = useDeletePost();

  // list all posts that're visible to the current user, together with their authors
  const { 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">
      <button
        className="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">
              <button
                className="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.coloradmin.cn/o/2182884.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

目标检测技术的发展:从R-CNN、YOLO到DETR、DINO

“深度人工智能”是成都深度智谷科技旗下的人工智能教育机构订阅号&#xff0c;主要分享人工智能的基础知识、技术发展、学习经验等。此外&#xff0c;订阅号还为大家提供了人工智能的培训学习服务和人工智能证书的报考服务&#xff0c;欢迎大家前来咨询&#xff0c;实现自己的…

[FlareOn3]Challenge11

载入PE. 32 bit&#xff0c;无壳. 载入IDA&#xff08;32bit&#xff09;. 寻找main函数. int __cdecl main(int argc, const char **argv, const char **envp) {char Buffer[128]; // [esp0h] [ebp-94h] BYREFchar *Str1; // [esp80h] [ebp-14h]char *Str2; // [esp84h] [eb…

ROS理论与实践学习笔记——2 ROS通信机制之常用API

"API" 是 "Application Programming Interface" 的缩写&#xff0c;指的是应用程序编程接口。API是一组定义了不同软件组件如何互相通信的规范。它允许不同的软件系统之间共享功能&#xff0c;提供一种标准的方式来访问某个软件组件的功能或数据。 详细内…

JavaScript模块化-CommonJS规范和ESM规范

1 ES6模块化 1.1 ES6基本介绍 ES6 模块是 ECMAScript 2015&#xff08;ES6&#xff09;引入的标准模块系统&#xff0c;广泛应用于浏览器环境下的前端开发。Node.js环境主要使用CommonJS规范。ESM使用import和export来实现模块化开发从而解决了以下问题&#xff1a; 全局作用…

《安富莱嵌入式周报》第343期:雷电USB4开源示波器正式发布,卓越的模拟前端低噪便携示波器,自带100W电源的便携智能烙铁,NASA航空航天锂电池设计

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 更新一期视频教程 【授人以渔】CMSIS-RTOS V2封装层专题视频&#xff0c;一期视频将常用配置和用法梳理清楚&#xff0…

Win10系统插入带有麦克风的耳机_麦克风不起作用_解决方法_亲测成功---Windows运维工作笔记054

今天我在使用讯飞输入法的时候,想通过讯飞的语音输入法来提高自己的输入效率。 但是这个时候发现一个问题就是我插入我的台式机的是一个带有麦克风的耳机。 但是发现我这个耳机没有办法被电脑识别出麦克风来,所以说就没办法使用讯飞输入法的语音输入功能来直接输入文字了。…

Qt 窗口中鼠标点击事件的坐标探讨

// 鼠标点击事件 void Widget::mousePressEvent(QMouseEvent *event) {/*event->pos()、event->windowPos()和event->localPos()都表示鼠标点击位置在窗口中的位置&#xff0c;它们的值都是一样的&#xff0c;区别在于event->pos()是QPoint类型&#xff0c;event-&…

操作系统-磁盘管理

存储管理中的磁盘管理涉及到几个核心概念&#xff1a;磁道、扇区、磁头、盘面。 磁道&#xff1a;磁盘表面的同心圆&#xff0c;用于记录数据。每个磁道可以存储相同量的信息。 扇区&#xff1a;磁道被进一步划分的更小单元&#xff0c;通常是磁道的最小存储单位。一个常见的扇…

【新闻转载】Storm-0501:勒索软件攻击扩展到混合云环境

icrosoft发出警告&#xff0c;勒索软件团伙Storm-0501近期调整了攻击策略&#xff0c;目前正将目标瞄准混合云环境&#xff0c;旨在全面破坏受害者的资产。 该威胁行为者自2021年首次露面&#xff0c;起初作为Sabbath勒索软件行动的分支。随后&#xff0c;他们开始分发来自Hive…

C++发邮件:如何轻松实现邮件自动化发送?

C发邮件的详细步骤与教程指南&#xff1f;如何在C中发邮件&#xff1f; 无论是定期发送报告、通知客户还是管理内部沟通&#xff0c;自动化邮件系统都能显著提升工作效率。AokSend将详细介绍如何使用C发邮件&#xff0c;实现邮件自动化发送&#xff0c;帮助您轻松管理邮件通信…

10/1 力扣 49.字母异位词分组

基本知识&#xff1a; 关于字符串的排序&#xff1a; 1.多个字符串排序 1.1使用python内置的sorted() 使用该函数后原对象并不发生变化 1.2若多个字符串使用列表进行存储&#xff0c;使用列表的sort()方法 使用该函数后原对象原地变化 2.对单个字符串里的字母进行排序 使…

关于CSS 案例_新闻内容展示

新闻要求 标题:居中加粗发布日期: 右对齐分割线: 提示, 可以使用 hr 标签正文/段落: 左侧缩进插图: 居中显示 展示效果 审核过不了&#xff0c;内容没填大家将就着看吧。 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset&qu…

【异常数据检测】孤立森林算法异常数据检测算法(数据可视化 Matlab语言)

摘要 本文研究了基于孤立森林算法的异常数据检测方法&#xff0c;并在MATLAB中实现了该算法的可视化。孤立森林是一种无监督的异常检测算法&#xff0c;主要通过构建决策树来区分正常数据和异常数据。本文使用真实数据集&#xff0c;通过二维可视化展示了检测结果。实验结果表…

肝郁气滞有什么症状

在这个快节奏、高压力的时代&#xff0c;我们的身体往往承载着超负荷的情绪与压力&#xff0c;而“肝郁气滞”这一中医术语&#xff0c;正悄然成为许多现代人健康的隐形杀手。它如同体内的“情绪交通堵塞”&#xff0c;不仅影响心情&#xff0c;更波及全身健康。今天&#xff0…

计算机毕业设计 基于Python的新闻采集与订阅平台的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

【C++】多态,虚函数,重载,重写,重定义,final,override,抽象类,虚函数表,动态绑定,静态绑定详解

目录 1. 多态的定义 1.1 多态的构成条件 1.2 虚函数 1.3 虚函数重写 1.4 重载&#xff0c;重写&#xff0c;重定义 1.5 final 1.6 override 2. 抽象类 3. 多态的原理 3.1 虚函数表 3.2 子类的虚函数表 3.3 多态本质 3.4 动态绑定和静态绑定 4. 多继承关系的虚…

php语法学习

MySQL问题 如果外部mysql与内部mysql冲突&#xff0c;php连接如果已经打开mysql说明他启动的是外部的mysql8&#xff0c;单独点击服务器启动apache就不会冲突。 打开navicat 打开浏览器测试 1.单行和多行注释 2.中文乱码问题 <?php //echo "Hello World 你好&#…

Agr_Reader 1.7.11 极简优美的RSS阅读器,无广告

Agr Reader是一款简洁、优美、符合Material You风格的RSS阅读器。它不仅提供了强大的全文解析功能&#xff0c;默认支持离线阅读&#xff0c;还具备桌面小组件、自定义样式设置等功能。此外&#xff0c;它支持接入FreshRSS、Tiny Tiny RSS等多种RSS服务&#xff0c;并提供沉浸式…

Android studio配置AVD虚拟机

目录 设置虚拟设备参数 安装HAXM 找到HAXM安装包 安装 启动虚拟设备 设置虚拟设备参数 Tools->Devices Manager->Add a new divece一个加号符号的图标->Create Virtual Device 选择尺寸参数&#xff0c;没有合适的话选择New Hardware Profile&#xff0c;调整好…

Spring1

1.Spring系统架构图 (1)核心层 Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块 (2)AOP层 AOP:面向切面编程,它依赖核心层容器,目的是==在不改变原有代码的前提下对其进行功能增强== Aspects:AOP是思想,Aspects是对AOP思想的具体实现 (3)数据…