Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added docs/.nojekyll
Empty file.
1 change: 1 addition & 0 deletions docs/CNAME
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
zenstack.dev
28 changes: 28 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# ZenStack

> A toolkit for building secure CRUD apps with Next.js.

## What it is

ZenStack is a schema-first toolkit for defining data models, relations and access policies. It generates database schema, backend CRUD services and frontend React hooks for you automatically from the model. Our goal is to let you save time writing boilerplate code and focus on building real features!

_NOTE_: ZenStack is built above [Prisma ORM](https://www.prisma.io/) - the greatest ORM solution for Typescript. It extends Prisma's power from database handling to full-stack development.

See the [Quick start](quick-start.md) guide for more details.

## Features

- Intuitive data & authorization modeling language
- Generating RESTful CRUD services and React hooks
- End-to-end type safety
- Support for [all major relational databases](zmodel-data-source.md#supported-databases)
- Integration with authentication libraries (like [NextAuth](https://next-auth.js.org/ ':target=_blank'))
- [VSCode extension](https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack ':target=_blank') for model authoring

## Examples

Check out the [Collaborative Todo App](https://zenstack-todo.vercel.app/ ':target=_blank') for a running example. You can find the source code [here](https://github.com/zenstackhq/todo-demo-sqlite ':target=_blank').

## Community

Join our [discord server](https://go.zenstack.dev/chat ':target=_blank') for chat and updates!
12 changes: 12 additions & 0 deletions docs/_coverpage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
![cover-logo](_media/logo.png)

# ZenStack <small>0.4.0</small>

> A toolkit for building secure CRUD apps with Next.js + Typescript.

- Full-stack toolkit made for front-end developers
- Intuitive and flexible data modeling
- No more boilerplate CRUD code

[GitHub](https://github.com/zenstackhq/zenstack/)
[Get Started](#zenstack)
Binary file added docs/_media/cli-shot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_media/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_media/og-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_media/starter-shot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions docs/_navbar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- [EN](/)
- [中文](/zh-cn/)
- [Live Chat](https://go.zenstack.dev/chat ':target=_blank')
32 changes: 32 additions & 0 deletions docs/_sidebar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
- Getting started

- [Quick start](quick-start.md)
- [Modeling your app](modeling-your-app.md)
- [Code generation](code-generation.md)
- [Building your app](building-your-app.md)

- ZModel reference

- [Overview](zmodel-overview.md)
- [Data source](zmodel-data-source.md)
- [Enum](zmodel-enum.md)
- [Data model](zmodel-data-model.md)
- [Attribute](zmodel-attribute.md)
- [Field](zmodel-field.md)
- [Relation](zmodel-relation.md)
- [Access policy](zmodel-access-policy.md)
- [Field constraint](zmodel-field-constraint.md)
- [Referential action](zmodel-referential-action.md)

- CLI reference

- [Commands](cli-commands.md)

- Guide

- [Choosing a database](choosing-a-database.md)
- [Evolving model with migration](evolving-model-with-migration.md)
- [Integrating authentication](integrating-authentication.md)
- [Set up logging](setup-logging.md)

- [VSCode extension](vscode-extension.md)
169 changes: 169 additions & 0 deletions docs/building-your-app.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Building your app

The code generated from your model covers everything you need to implement CRUD, frontend and backend. This section illustrates the steps of using them when building your app.

## Mounting backend services

First you should mount the generated server-side code as a Next.js API endpoint. Here's an example:

```ts
// pages/api/zenstack/[...path].ts

import { authOptions } from '@api/auth/[...nextauth]';
import service from '@zenstackhq/runtime';
import {
requestHandler,
type RequestHandlerOptions,
} from '@zenstackhq/runtime/server';
import { NextApiRequest, NextApiResponse } from 'next';
import { unstable_getServerSession } from 'next-auth';

const options: RequestHandlerOptions = {
// a callback for getting the current login user
async getServerUser(req: NextApiRequest, res: NextApiResponse) {
// here we use NextAuth is used as an example, and you can change it to
// suit the authentication solution you use
const session = await unstable_getServerSession(req, res, authOptions);
return session?.user;
},
};
export default requestHandler(service, options);
```

Please note that the services need to be configured with a callback `getServerUser` for getting the current login user. The example above uses NextAuth to do it, but you can also hand-code it based on the authentication approach you use, as long as it returns a user object that represents the current session's user.

_TBD_ In the future we'll provide more samples for showing how to integrate with other libraries, like IronSession.

Make sure the services are mounted at route `/api/zenstack/` with a catch all parameter named `path`, as this is required by the generate React hooks.

## <small>_optional_</small> Integrating with NextAuth

If you use NextAuth for authentication, ZenStack also generates an adapter which you can use to configure NextAuth for persistence of user, session, etc.

```ts
// pages/api/auth/[...nextauth].ts

import service from '@zenstackhq/runtime';
import {
authorize,
NextAuthAdapter as Adapter,
} from '@zenstackhq/runtime/auth';
import NextAuth, { type NextAuthOptions } from 'next-auth';

export const authOptions: NextAuthOptions = {
// use ZenStack adapter for persistence
adapter: Adapter(service),

providers: [
CredentialsProvider({
credentials: { ... },
// use the generated "authorize" helper for credential based authentication
authorize: authorize(service),
}),
]

...
};

export default NextAuth(authOptions);
```

_TBD_ In the future we'll provide more samples for showing how to integrate with other libraries, like IronSession.

## Using React hooks

React hooks are generated for CRUD'ing each data model you defined. They save your time writing explicit HTTP requests to call the generated services. Internally the hooks use [SWR](https://swr.vercel.app/) for data fetching, so you'll also enjoy its built-in features, like caching, revalidation on interval, etc.

_NOTE_ The generated service code is injected with the access policies you defined in the model, so it's already secure, regardless called directly or via hooks. A read operation only returns data that's supposed to be visible to the current user, and a write operation is rejected if the policies verdict so.

### Read

Call `find` and `get` hooks for listing entities or loading a specific one. If your entity has relations, you can request related entities to be loaded together.

```ts
const { find } = usePost();
// lists unpublished posts with their author's data
const posts = find({
where: { published: false },
include: { author: true },
orderBy: { updatedAt: 'desc' },
});
```

```ts
const { get } = usePost();
// fetches a post with its author's data
const post = get(id, {
include: { author: true },
});
```

### Create

Call the async `create` method to create a new model entity. Note that if the model has relations, you can create related entities in a nested write. See example below:

```ts
const { create } = usePost();
// creating a new post for current user with a nested comment
const post = await create({
data: {
title: 'My New Post',
author: {
connect: { id: session.user.id },
},
comments: {
create: [{ content: 'First comment' }],
},
},
});
```

### Update

Similar to `create`, the update hook also allows nested write.

```ts
const { update } = usePost();
// updating a post's content and create a new comment
const post = await update(id, {
data: {
const: 'My post content',
comments: {
create: [{ content: 'A new comment' }],
},
},
});
```

### Delete

```ts
const { del } = usePost();
const post = await del(id);
```

## Server-side coding

Since doing CRUD with hooks is already secure, in many cases, you can implement your business logic right in the frontend code.

In case you need to do server-side coding, either through implementing an API endpoint or by using `getServerSideProps` for SSR, you can directly access the database client generated by Prisma:

```ts
import service from '@zenstackhq/runtime';

export const getServerSideProps: GetServerSideProps = async () => {
const posts = await service.db.post.findMany({
where: { published: true },
include: { author: true },
});
return {
props: { posts },
};
};
```

The Typescript types of data models, filters, sorting, etc., are all shared between the frontend and the backend.

**Note** that server-side database access is not protected by access policies. This is by-design so as to provide a way of bypassing the policies. Please make sure you implement authorization properly.

_TBD_ In the future we'll provide a utility for explicitly validating access policies in backend code, so that you can reuse your policy definitions in the model.
11 changes: 11 additions & 0 deletions docs/choosing-a-database.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Choosing a database

ZenStack is agnostic about where and how you deploy your web app, but hosting on serverless platforms like [Vercel](https://vercel.com/) is definitely a popular choice.

Serverless architecture has some implications on how you should care about your database hosting. Different from traditional architecture where you have a fixed number of long-running Node.js servers, in a serverless environment, a new Node.js context can potentially be created for each user request, and if traffic volume is high, this can quickly exhaust your database's connection limit, if you connect to the database directly without a proxy.

You'll likely be OK if your app has a low number of concurrent users, otherwise you should consider using a proxy in front of your database server. Here's a number of (incomplete) solutions you can consider:

- [Prisma Data Proxy](https://www.prisma.io/data-platform/proxy)
- [Supabase](https://supabase.com/)'s [connection pool](https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pool)
- [Deploy pgbouncer with Postgres on Heroku](https://devcenter.heroku.com/articles/postgres-connection-pooling)
123 changes: 123 additions & 0 deletions docs/cli-commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
## CLI commands

### `init`

Set up ZenStack for an existing Next.js + Typescript project.

```bash
npx zenstack init [dir]
```

### `generate`

Generates RESTful CRUD API and React hooks from your model.

```bash
npx zenstack generate [options]
```

_Options_:

```
--schema <file> schema file (with extension .zmodel) (default: "./zenstack/schema.zmodel")
```

### `migrate`

Update the database schema with migrations.

**Sub-commands**:

#### `migrate dev`

Create a migration from changes in Prisma schema, apply it to the database, trigger generation of database client. This command wraps `prisma migrate` command.

```bash
npx zenstack migrate dev [options]
```

_Options_:

```
--schema <file> schema file (with extension .zmodel) (default: "./zenstack/schema.zmodel")
```

#### `migrate reset`

Reset your database and apply all migrations.

```bash
npx zenstack migrate reset [options]
```

_Options_:

```
--schema <file> schema file (with extension .zmodel) (default: "./zenstack/schema.zmodel")
```

#### `migrate deploy`

Apply pending migrations to the database in production/staging.

```bash
npx zenstack migrate deploy [options]
```

_Options_:

```
--schema <file> schema file (with extension .zmodel) (default: "./zenstack/schema.zmodel")
```

#### `migrate status`

Check the status of migrations in the production/staging database.

```bash
npx zenstack migrate status [options]
```

_Options_:

```
--schema <file> schema file (with extension .zmodel) (default: "./zenstack/schema.zmodel")
```

### `db`

Manage your database schema and lifecycle during development. This command wraps `prisma db` command.

**Sub-commands**:

#### `db push`

Push the state from model to the database during prototyping.

```bash
npx zenstack db push [options]
```

_Options_:

```
--schema <file> schema file (with extension .zmodel) (default: "./zenstack/schema.zmodel")
--accept-data-loss Ignore data loss warnings
```

### `studio`

Browse your data with Prisma Studio. This command wraps `prisma studio` command.

```bash
npx zenstack studio [options]
```

_Options_:

```
--schema <file> schema file (with extension .zmodel) (default: "./zenstack/schema.zmodel")
-p --port <port> Port to start Studio in
-b --browser <browser> Browser to open Studio in
-n --hostname Hostname to bind the Express server to
```
Loading