-
-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: new introduction section (#135)
* 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
Showing
42 changed files
with
661 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
|
Oops, something went wrong.
a66d7f6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
zenstack-new-site – ./
zenstack.dev
zenstack-new-site.vercel.app
www.zenstack.dev
zenstack-new-site-git-main-zenstack.vercel.app
zenstack-new-site-zenstack.vercel.app