Skip to content

Commit

Permalink
docs: new introduction section (#135)
Browse files Browse the repository at this point in the history
* docs: new introduction section

* update vercel.json

* update  api part

* update imagery

* addressing review comments

* simplify intro page

* update copy writing

* fix
  • Loading branch information
ymc9 authored Jun 20, 2023
1 parent 6f18b38 commit a66d7f6
Show file tree
Hide file tree
Showing 42 changed files with 661 additions and 69 deletions.
2 changes: 1 addition & 1 deletion blog/custom-attributes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ model User {
Actually, that’s exactly what the ZModel would be. You can find it in our example code:
[https://zenstack.dev/docs/get-started/nextjs#3-preparing-the-user-model-for-authentication](https://zenstack.dev/docs/get-started/nextjs#3-preparing-the-user-model-for-authentication)
[https://zenstack.dev/docs/quick-start/nextjs#3-preparing-the-user-model-for-authentication](https://zenstack.dev/docs/quick-start/nextjs#3-preparing-the-user-model-for-authentication)
The above attributes are already predefined in our standard library as below:
Expand Down
2 changes: 1 addition & 1 deletion blog/postgrest-alternative/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,4 @@ The `/api/data` endpoint will then provide a full set of Prisma operations for e
## Wrap Up
I hope you find the Prisma + ZenStack combination a useful alternative to PostgREST. Check out the [Get Started](/docs/category/get-started) and [Guides](/docs/category/guides) pages for more details, and join our [Discord](https://discord.com/invite/Ykhr738dUe) for questions and updates!
I hope you find the Prisma + ZenStack combination a useful alternative to PostgREST. Check out the [Get Started](/docs/category/quick-start) and [Guides](/docs/category/guides) pages for more details, and join our [Discord](https://discord.com/invite/Ykhr738dUe) for questions and updates!
8 changes: 8 additions & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,11 @@ Using a separate DSL gives us the flexibility to add more extensions in the futu
In practice, you may run into problems that the `schema.prisma` generated by ZenStack triggers validation errors in `prisma` CLI. This is because Prisma CLI has many validation rules, some quite subtle. We try to replicate those rules in `zenstack` CLI, but it's more of a best-effort approach. Fortunately, the errors reported by `prisma` CLI usually give pretty good hints on what to change in `schema.zmodel`.

We will continue improving the parity between ZModel and Prisma Schema regarding validation rules.

### Does ZenStack require a specific Prisma version?

No. ZenStack references Prisma as a peer dependency and should work with Prisma 4.0 and above.

### Does ZenStack work with Prisma migration?

Yes. When you run `zenstack generate` it generates a standard Prisma schema file that can be used with Prisma migration.
2 changes: 1 addition & 1 deletion docs/guides/trpc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Guide for using ZenStack with tRPC.
sidebar_position: 7
---

import InitTips from '../get-started/_zenstack-init-tips.md';
import InitTips from '../quick-start/_zenstack-init-tips.md';

# Using With tRPC

Expand Down
112 changes: 112 additions & 0 deletions docs/intro/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
description: Automatic CRUD API
sidebar_label: 4. Automatic CRUD API
sidebar_position: 4
---

# Automatic CRUD API

Many backend services have big chunks of code wrapping around the database and providing access-controlled CRUD APIs. These boring boilerplate codes are both tedious to write and error-prone to maintain.

With the access-policy-enhanced Prisma Client, ZenStack can automatically provide a full-fledged CRUD API for your data models through a set of framework-specific server adapters. Currently, we have adapters for [Next.js](https://nextjs.org), [SvelteKit](https://kit.svelte.dev/), [Express](https://expressjs.com/), and [Fastify](https://www.fastify.io/).

Let's see how it works using Express as an example.

```ts title='app.ts'

import { PrismaClient } from '@prisma/client';
import { withPolicy } from '@zenstackhq/runtime';
import { ZenStackMiddleware } from '@zenstackhq/server/express';
import express from 'express';
import { getSessionUser } from './auth'

const prisma = new PrismaClient();
const app = express();

app.use(express.json());

// options for creating the Express middleware that provides the CRUD API
const options = {
// called for every request to get a Prisma Client instance
getPrisma: (request) => {
// getSessionUser extracts the current session user from the request,
// its implementation depends on your auth solution
const user = getSessionUser(request);

// return a policy-enhanced Prisma Client
return withPolicy(prisma, { user });
}
}

// mount the middleware to "/api/model" route
app.use(
'/api/model',
ZenStackMiddleware(options)
);
```

:::info

There's no hard requirement to use an enhanced Prisma Client with the API, but you should always do it to ensure the APIs are protected when exposed to the Internet.

:::

With the code above, CRUD APIs are mounted at route "/api/model". By default, the APIs provide "RPC" style endpoints that mirror the Prisma Client APIs. For example, you can consume it like the following:

```ts
// Create a post for user#1
POST /api/model/post
{
"data": {
"title": "Post 1",
"author": { "connect": { "id": 1 } }
}
}

// List all published posts with their authors
GET /api/model/post/findMany?q={"where":{"published":true},"include":{"author":true}}
```
You can also choose to provide a more "REST" style API by initializing the middleware with a RESTful API handler:
```ts title='app.ts'
import RestApiHandler from '@zenstackhq/server/api/rest';

const options = {
getPrisma: (request) => {...},
// use RESTful-style API handler
handler: RestApiHandler({ endpoint: 'http://myhost/api' })
}

// mount the middleware to "/api/model" route
app.use(
'/api/model',
ZenStackMiddleware(options)
);
```
Now the API endpoints follow the RESTful conventions (using [JSON:API](https://jsonapi.org/) as transport):
```ts
// Create a post for user#1
POST /api/model/post
{
"data": {
"type": 'post',
"attributes": {
"title": "Post 1"
},
relationships: {
author: {
data: { type: 'user', id: 1 }
}
}
}
}

// List all published posts with their authors
GET /api/model/post?filter[published]=true&include=author
```
As you can see, with a few lines of code, you can get a full-fledged CRUD API for your data models. See [here](/docs/category/server-adapters) for details on using the server adapter specific to your framework. You may also be interested in generating an [OpenAPI](https://www.openapis.org/) specification using the [`@zenstackhq/openapi`](/docs/reference/plugins/openapi) plugin.
With the APIs in place, you can now use them to build the user interface. In the next section, let's see how ZenStack simplifies this part by generating data-access hooks for the frontend.
35 changes: 35 additions & 0 deletions docs/intro/conclusion.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
description: Conclusion
sidebar_label: 6. Conclusion
sidebar_position: 6
---

import useBaseUrl from '@docusaurus/useBaseUrl';
import ThemedImage from '@theme/ThemedImage';

# Conclusion

Thank you for following through on the introduction, and I hope you enjoyed the reading.

One of the design goals of ZenStack is to package its features into loosely coupled components and let the developers choose what to use and how to mix and match them. For example, if your project is a monorepo one using one of the "meta-frameworks" like Next.js, you can use ZenStack across your entire stack like the following:

<ThemedImage
alt="ZenStack Architecture"
sources={{
light: useBaseUrl('/img/intro/zenstack-nextjs-light.png'),
dark: useBaseUrl('/img/intro/zenstack-nextjs-dark.png'),
}}
/>

However, you can also only pick the parts that suit your needs best:

- Improved Prisma schema with features like [multi-file support and model inheritance](/docs/guides/multiple-schema).
- Implementing [multi-tenancy](/blog/multi-tenant).
- A better way to achieve [soft delete](/blog/soft-delete).
- Headless [RESTful API](/blog/rest-api-on-vercel) over the Node.js framework of your choice, with Swagger documentation.

Next steps:

- Check out the [Quick Start](/docs/category/quick-start) docs to get started with the framework of choice.
- Learn to accomplish certain goals with ZenStack in the [Guides](/docs/category/guides).
- Check out [Reference](/docs/category/reference) for detailed schema language and API documentation.
97 changes: 97 additions & 0 deletions docs/intro/enhancement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
description: Enhanced Prisma Client
sidebar_label: 3. Enhanced Prisma Client
sidebar_position: 3
---

# Enhanced Prisma Client

The ZModel language allows us to enrich our data models with semantics that couldn't be done with Prisma. Similarly, at runtime, ZenStack provides APIs that ***enhance*** Prisma Client instances. These enhancements are transparent proxies, so they have exactly the same APIs as the regular Prisma Client but add additional behaviors.

The most interesting enhancement is the enforcement of access policies. Let's say we have the following ZModel:

```prisma
model User {
id Int @id
posts Post[]
// user-related access policies are omitted
// ...
}
model Post {
id Int @id
title String @length(5, 255)
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
// 🔐 author has full access
@@allow('all', auth() == author)
// 🔐 logged-in users can view published posts
@@allow('read', auth() != null && published)
}
```

You can see how the enhancement works in the following code snippet:

```ts

// create a regular Prisma Client first
const prisma = new PrismaClient();

// create two users and a post for each

// user#1 => post#1
await prisma.user.create({
data: {
id: 1,
posts: { create: [{ id: 1, title: 'Post 1' }] }
}
})

// user#2 => post#2
await prisma.user.create({
data: {
id: 2,
posts: { create: [{ id: 2, title: 'Post 2' }] }
}
})


// the call below returns all posts since there's no filtering
const posts = await prisma.post.findMany();
assert(posts.length == 2, 'should return all posts');

// create a policy-enhanced wrapper with a user context for user#1
import { withPolicy } from '@zenstackhq/runtime';
const enhanced = withPolicy(prisma, { user: { id: 1 }});

// even without any filtering, the call below only returns
// posts that're readable by user#1, i.e., [post#1]
const userPosts = await enhanced.post.findMany();
assert(userPosts.length == 1 && userPosts[0].id == 1], 'should return only post#1');

// ❌ the call below fails because user#1 is not allowed to update post#2
await enhanced.post.update({
where: { id: 2 },
data: { published: true }
});

// ❌ the call below fails because "title" field violates the `@length` constraint
await enhanced.post.create({
data: { title: 'Hi' }
});

```

When building a backend service, you can centralize authorization concerns into the schema using access policies and then use the enhanced Prisma Client across your service code. This practice can bring three clear benefits:

- A smaller code base.
- A more secure and reliable result compared to manually writing authorization logic.
- Better maintainability since when authorization rules evolve, the schema is the only place where you need to make changes.

You can find more information about access policies [here](/docs/guides/understanding-access-policy).

In fact, you may not need to implement a backend service at all if the service is mainly CRUD. With an access-control-enhanced Prisma Client, a full-fledged CRUD service can be generated automatically. Let's see how it works in the next section.
63 changes: 63 additions & 0 deletions docs/intro/frontend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
description: Frontend Data Access
sidebar_label: 5. Frontend Data Access
sidebar_position: 5
---

# Frontend Data Access

ZenStack has a small core, and many of its functionalities are implemented as plugins. Generating a frontend data access library is an excellent example of such a plugin. For example, you can enable the generation of TanStack Query hooks (for React) as follows:

```prisma
plugin hooks {
provider = "@zenstackhq/tanstack-query"
output = "./src/lib/hooks"
target = "react"
}
```

The generated hooks provide a set of APIs that closely mirror that of Prisma Client and call into the automatic CRUD API introduced in the previous section. Here're a few examples of using them:

```tsx

import type { Post } from '@prisma/client';
import { useFindManyPost, useCreatePost } from '../lib/hooks';

// post list component
const Posts = ({ userId }: { userId: string }) => {
const create = useCreatePost();

// list all posts that're visible to the current user, together with their authors
const { data: posts } = useFindManyPost({
include: { author: true },
orderBy: { createdAt: 'desc' },
});

async function onCreatePost() {
create.mutate({
data: {
title: 'My awesome post',
authorId: userId,
},
});
}

return (
<>
<button onClick={onCreatePost}>Create</button>
<ul>
{posts?.map((post) => (
<li key={post.id}>
{post.title} by {post.author.email}
</li>
))}
</ul>
</>
);
};

```

ZenStack currently provides plugins for generating client hooks targeting [TanStack Query](/docs/reference/plugins/tanstack-query) (which supports React and Vue) and [SWR](/docs/reference/plugins/swr) (React only).
31 changes: 31 additions & 0 deletions docs/intro/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
description: ZenStack is a Node.js/TypeScript toolkit that simplifies the development of a web app's backend. It supercharges Prisma ORM with a powerful access control layer and unleashes its full potential for full-stack development.
sidebar_position: 1
---

# Introduction

ZenStack is a Node.js/TypeScript toolkit that simplifies the development of a web app's backend. It supercharges [Prisma ORM](https://prisma.io) with a powerful access control layer and unleashes its full potential for full-stack development. We created it with the belief that developers can build better applications by:

- **Keeping a single source of truth for core business logic**
- **Being more declarative**
- **Writing less code**

To achieve these goals, ZenStack progressively enhances Prisma at multiple levels:

<ul>

🛠️ An extended schema language that supports custom attributes

🔐 Access policies and data validation rules

🚀 Automatic CRUD APIs - RESTful, tRPC

🤖 Generating client-side data access libraries (aka hooks) - SWR, TanStack Query

🧩 A plugin system for great extensibility

</ul>

In the following sections, we'll guide you through understanding each layer of the extensions, and you'll see how ZenStack can help you build a full-stack web app much faster.

Loading

1 comment on commit a66d7f6

@vercel
Copy link

@vercel vercel bot commented on a66d7f6 Jun 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.