shadrizz docs
AI

shadrizz docs

TypeScript Scaffolding Automation Web Framework

A CLI Tool That Generates Full Stack Apps Using Next.js, shadcn/ui, and Drizzle ORM.

Introduction

shadrizz is a full stack automation tool for building TypeScript web applications. This is an ephemeral web framework. You do not install it into your project as a dependency. It is a command line interface code generation tool. You use it to generate customizable code for full stack projects. You can scaffold database schemas and user interfaces to use as a reference to build your own full stack application.

Installation

Step 1: Create new project

Start by creating a new Next.js project using create-next-app.

npx create-next-app@latest my-app --typescript --eslint --tailwind --app --no-src-dir --no-import-alias --turbopack

Alternatively, use the new command to generate a new project using the above settings.

npx shadrizz@latest new my-app

Step 2: Run the CLI

UPDATE for Next.js 15 + React 19. If you're using npm, you may need to first run npm config set legacy-peer-deps true in order to work around a dependency error. Using pnpm instead of npm is recommended if you want to avoid this issue.

Run the shadrizz init command to setup your project.

cd my-app
npx shadrizz@latest init

Alternatively, you can also run the command non-interactively:

npx shadrizz@latest init -p npm --latest --db-dialect sqlite -pk cuid2 --auth-solution authjs --auth-providers github,google,credentials --admin

Step 3: Configure project

You will be asked a few questions to configure the app:

? Which package manager do you want to use? npm
? Do you want to install latest packages or pinned packages? pinned
? Which database dialect would you like to use? sqlite
? Which primary key generation strategy would you like to use? cuid2
? Which authentication solution do you want to use? authjs
? Which auth providers would you like to use? credentials
? Do you want to add an admin dashboard with role-based authorization? yes
? Do you want to enable the automatic pluralization of table and variable names? yes

Step 4: Project checklist

Generate and run the drizzle migrations:

npm run generate
npm run migrate

Create a test user and grant admin role:

npx tsx scripts/create-user.ts user@example.com password123
npx tsx scripts/grant-admin.ts user@example.com

Step 5: Scaffold an app

In this example, we'll create a simple blog application with a wysiwyg editor.

First, open a new terminal.

Install the tiptap add-on:

npx shadrizz@latest add tiptap

For the following scaffolds, make sure to choose admin as the authorization level.

Create a categories schema:

npx shadrizz@latest scaffold category -c name:text

Create a posts schema:

npx shadrizz@latest scaffold post -c title:text category_id:references content:text_tiptap is_draft:boolean published_at:timestamp

Run the drizzle migrations:

npm run generate
npm run migrate

Step 6: Log in to the app

Start the dev server:

npm run dev

Go to http://localhost:3000/admin-login and log in with the admin user.

Scaffold

After the initial configuration is completed, you can create full stack scaffolding with the scaffold command.

This command will generate the user interface, database migration and schema, server actions, server components, and client components of a full stack feature.

The -c option takes a space-separated string of column configurations in the following format: column_name:dataType. See Data Types for a list of supported data types.

The id, created_at, and updated_at fields are automatically generated by shadrizz, and should be omitted from the command.

After scaffolding, you can review the schema and make any necessary changes before running the database migrations.

Note that the table names will be transformed according to the Naming Conventions in this document.

Example:

npx shadrizz@latest scaffold products -c title:varchar price:decimal description:text stock_quantity:integer

Introspect

shadrizz will create an introspect.ts script that can be used to generate a scaffold command from an existing drizzle table.

For example:

npx tsx scripts/introspect.ts products

This would output something like:

npx shadrizz@latest scaffold products -c title:varchar price:decimal description:text stock_quantity:integer

You can make changes to the output as needed and run the command to generate a scaffold for the table. If the scaffold code already exists, it will override the existing scaffold.

Note: This command only accounts for the column name and underlying data type. Special data types like references and file will be generated as text. You'll have to manually changes these back to references or file as needed.

Data types

The following are data types that can be used with the scaffold command. Most of the data types in Drizzle ORM are also supported in shadrizz.

postgresql data types

integer, smallint, bigint, serial, smallserial, bigserial, boolean, text, varchar, char, numeric, decimal, real, doublePrecision, json, jsonb, time, timestamp, date, uuid

mysql data types

int, tinyint, smallint, mediumint, bigint, real, decimal, double, float, serial, binary, varbinary, char, varchar, text, boolean, date, datetime, time, year, timestamp, json

sqlite data types

integer, real, text, boolean, bigint, timestamp

Primary key generation strategy

shadrizz supports the following primary key generation strategies:

  • cuid2 - Uses the @paralleldrive/cuid2 package
  • uuidv7 - Uses the uuidv7 package
  • uuidv4 - Uses the crypto package
  • nanoid - Uses the nanoid package
  • auto_increment - Auto increment (This strategy is not compatible with the Drizzle Adapter for Auth.js)

The strategy that you choose during the init process will be saved in shadrizz.config.json. This will be used for the authentication, stripe, and scaffold schemas.

Foreign key constraints

shadrizz supports adding foreign key constraints using the following special data types:

  • references
  • references_select

This will set up the Drizzle relations and the UI form controls for managing the relations.

For example, a one to many relationship where a post belongs to a category can be set up using the following scaffolds.

First, scaffold the one side of the relationship.

npx shadrizz@latest scaffold category -c title:text

Second, scaffold the many side of the relationship using one of the references data types below:

References Input

The standard references data type will use an Input component that accepts a foreign key string.

npx shadrizz@latest scaffold post -c category_id:references title:text

References Select

The references_select data type will use a Select component where you can select from a dropdown list of items.

npx shadrizz@latest scaffold post -c category_id:references_select title:text

The component will initially show a list of ids, however it is easy to customize by changing the code in the form. For example, changing the react code from {category.id} to {category.title}.

File uploads

shadrizz supports a file data type. This creates a text db column to store the file path along with a basic ui for uploads to the file system

Example:

npx shadrizz@latest scaffold media -c title:text image:file video:file

Note: Next.js only generates routes for public files at compile time. If you need to serve the uploaded files, putting them into the public directory will not work in production without a new build every time.

If the uploaded files need to be served immediately after uploading, consider using a web server like nginx to serve the static files or an s3 compatible bucket instead.

In development, shadrizz will put the files in public/uploads, so that they can be served during development. This works in development because routes are compiled without running a new build.

In production, shadrizz will put the files in /var/www/uploads. You'll have to find a way to serve these files. For example, pointing an nginx location /uploads/ to the /var/www/uploads folder. Note: This won't work in serverless environments. If you're using serverless, consider using object storage like s3.

The file URI will be saved to the database. The upload paths can be changed in file-utils.ts.

Example nginx config:

server {
      listen 80;
      server_name www.example.com;

       location /uploads/ {
          alias /var/www/uploads/;
          autoindex off;
          try_files $uri $uri/ =404;
       }

       location / {
          proxy_pass http://127.0.0.1:3000/;
       }
}

Tip: The Next.js Image component performs automatic resizing of images. This works well for static images. However, uploaded images will not show up immediately unless you use the unoptimized attribute. Alternatively, you can use a regular img tag.

Auth

If auth was enabled during initialization, you will be able to scaffold using a private authorization level. These pages along with the server actions will require a user to be authenticated to access.

shadrizz provides a create-user.ts script to create test users.

shadrizz uses the jwt strategy of Auth.js. If you need database sessions, you will have to provide the implementation. Note: the credentials provider only supports the jwt strategy.

Private Dashboard

If auth was enabled, users will be able to sign in and access a user dashboard at /dashboard.

Any pages scaffolded with a private authorization level will be placed into the the (private) route group.

After running a private scaffold, a new link to the resource list page will be added to private-sidebar.tsx.

Users can sign in at /signin.

Admin Dashboard

If admin was enabled, users with the admin role will be able to access the admin dashboard at /admin. The admin login is at /admin-login.

You will be able to scaffold using an admin authorization level. The pages will be put into the (admin) route group. These pages along with the server actions will require a user with the admin role to access.

An authorization check happens at the admin layout.tsx. The authorization-service.ts contains an isAdmin utility function that checks the current session for the admin role.

After running an admin scaffold, a new link to the resource list page will be added to admin-sidebar.tsx.

A grant-admin.ts script is provided to grant users the admin role.

The application uses a role text field to determine the user's role. Any user with an admin role will have admin access.

Users will be assigned a user role when created. This behavior can be changed in auth.ts.

Add-on Extensions

Add-ons are full stack components that can be added after a project has been initialized.

An add-on extension can be added using the add command.

To see a list of available add-ons, run npx shadrizz@latest add -h.

The add command will install all necessary UI components, npm dependencies and dev dependencies for the add-on.

Then it will write all of the necessary code for the add-on to your project.

Stripe

npx shadrizz@latest add stripe

Code will be generated for a one-time purchase, monthly subscription plan, and a dynamic pricing checkout. This includes the webhook, checkout session, and customer portal api endpoints.

A pricing page will be generated. The buttons will initiate a stripe checkout if the user is logged in. If the user is not logged in, they will redirect to the sign in page.

A create-price.ts script is provided to create the initial products on Stripe and on the local database. This is required before using the one-time purchase and subscription plan. You must provide the implementation for fulfulling one-time purchases or provisioning subscriptions. That is marked as TODO in the webhook route.

The dynamic pricing does not require creating and mapping to products on Stripe. Dynamic pricing is useful if you need to generate a custom price, manage products, and manage orders in your own database. You must provide the implementation for creating and processing dynamic orders. That is also marked as TODO in the dynamic checkout session and webhook route.

The checkout sessions will be created with the user's email on first checkout. The user's stripe customer id will be saved in the checkout completed webhook. Subsequent checkouts will use the user's stripe customer id. This ensures that the Stripe invoice records are associated to the same Stripe customer, and allows the user to see the full invoice history in the customer portal.

Any of the code and content can be changed to fit your business model. The goal of this Stripe automation is to provide some minimal integration patterns to use as a starting point.

Tiptap Editor

npx shadrizz@latest add tiptap

This add-on will install several tiptap dependencies and write the code for a generic tiptap editor component.

After installing the add-on, you'll be able to scaffold with a text_tiptap data type.

For example:

npx shadrizz@latest scaffold posts -c title:text content:text_tiptap

Project Structure

This is the shadrizz project structure. The scaffolding automations will write to specific folders and files, so it's recommended to not move things around if you plan to continue using shadrizz automations.

- actions
    - admin - server actions requiring admin authorization
    - private - server actions requiring logged in user
    - public - server actions that are publicly accessible
- app
    - (admin) - route group requiring admin authorization
    - (private) - route group requiring logged in user
    - (public) - route group that is publicly accessible
- components
    - admin - components for admin routes
    - private - components for private routes
    - public - components for public routes
    - ui - shadcn components
- drizzle - sql migrations
- lib - configuration and utilities
- public - static assets
- repositories - reusable queries and return types
- schema - drizzle schemas
- scripts - executable scripts
- services - reusable business logic
- styles - css
- types - module augmentation

Repositories

Repositories are modules containing reusable queries.

Why use the repository pattern if you can write the Drizzle query directly in the server component?

First, it makes the query reusable by putting it in a shared module.

Second, the other major benefit is that it allows us to create a reusable Awaited ReturnType. For example:

export type PostsWithRelations = Awaited<
  ReturnType<typeof getPostsWithRelations>
>;

export async function getPostsWithRelations() {
  return await db.query.posts.findMany({
    with: {
      category: true,
    },
  });
}

The PostsWithRelations type is automatically defined by whatever is returned from the getPostsWithRelations function.

This becomes more relevant as your project grows in size and you must deal with more nested relations. Without an automatic return type, you would spend a large amount of time writing interfaces and types by hand to annotate your React component props.

With the awaited return type, we get a type that might look something like this if we wrote it by hand:

type PostsWithRelations = {
  id: string;
  title: string;
  content: string;
  category: {
    id: string;
    name: string;
  };
}[];

Now we can annotate our components as needed without having to write the type ourself:

import { PostsWithRelations } from "@/repositories/post-repository";

export function PostTable({ postList }: { postList: PostsWithRelations }) {
...
}

Services

The services directory is the place for reusable business logic. Initially, you may write the logic in the server components and actions, but eventually you might want to extract that logic and put it somewhere to be shared with other parts of the app.

For example, shadrizz generates an authorization service with reusable authorization logic that is used in multiple places, such as the admin layout and admin server actions.

Naming conventions

Number and case transformations will be automatically applied to the generated code.

shadrizz has two options when it comes to naming conventions. Plurize Enabled and Pluralize Disabled.

Case transformations (camel case, snake case, etc) will always be applied, however number transformations (singular/plural/original) will be applied depending on the pluralize mode used.

Original, in this context, means no transformations are applied.

You can change the mode in shadrizz.config.json by setting the pluralizeEnabled boolean.

Pluralize Enabled

With pluralize enabled, shadrizz uses naming conventions as described in the table below.

Regardless of whether you pass in foo_bar or foo_bars as the table name, the number transformations will be applied to each part of the code, along with the case transformation.

Generated Code Number Case Example
Class names singular pascal case FooBar
Database table names plural snake case foo_bars
Database column names original snake case foo_bar
Database foreign keys singular snake case foo_bar_id
Drizzle table variable names plural camel case fooBars
Drizzle column property names original camel case fooBar
Drizzle foreign key property names singular camel case fooBarId
Drizzle findMany variable names singular camel case fooBarList
Drizzle findFirst variable names singular camel case fooBarObj
File names any kebab case foo-bar.ts
Form input names original camel case fooBar
React array props singular camel case fooBarList
React object props singular camel case fooBar
URL pathnames any kebab case /foo-bar
Query string parameters original camel case ?fooBar=baz
UI table and column names any capital case Foo Bar

Pluralize Disabled

With pluralize disabled, shadrizz will not apply any number transformations to the generated code.

If you pass in foo_bar as the table name, it will always use the singular form.

If you pass in foo_bars as the table name, it will always use the plural form.

Generated Code Number Case Singular Plural
Class names original pascal case FooBar FooBars
Database table names original snake case foo_bar foo_bars
Database column names original snake case foo_bar foo_bars
Database foreign keys original snake case foo_bar_id foo_bars_id
Drizzle table variable names original camel case fooBar fooBars
Drizzle column property names original camel case fooBar fooBars
Drizzle foreign key property names original camel case fooBarId fooBarsId
Drizzle findMany variable names original camel case fooBarList fooBarsList
Drizzle findFirst variable names original camel case fooBarObj fooBarsObj
File names any kebab case foo-bar.ts foo-bars.ts
Form input names original camel case fooBar fooBars
React array props original camel case fooBarList fooBarsList
React object props original camel case fooBar fooBars
URL pathnames any kebab case /foo-bar /foo-bars
Query string parameters original camel case ?fooBar=baz ?fooBars=baz
UI table and column names any capital case Foo Bar Foo Bars

Philosophy

Technology curation

Many decisions happen at the beginnings of projects. A developer must decide on: a web framework, a database, UI component library, object relational mapper (ORM), CSS framework, authentication solution, validation library, payment solution, and other technologies relevant to the project. This can be time consuming and lead to decision fatigue. In the JavaScript world, this is known as JavaScript fatigue. It is a phenomenon describing the overwhelming array of technology choices in the JavaScript ecosystem. shadrizz offers a preferred list of technologies to be used as a foundation for web app projects.

Configuration automation

Once the technologies are decided on, the next step is to wire everything together such that the application works as a cohesive whole. This means making sure the database connection is working, the UI component library is installed, and that integrations with external services are working.

Developers will often use libraries to add capabilities such as authentication, authorization, and data validations. However, setting these up can be time consuming. shadrizz provides an init command which allows you to choose from a short menu of features that you can add to your Next.js app. shadrizz will write all the code necessary for the selected features.

By having a simple working example, you'll save time not having to build it entirely from scratch. You can customize the generated code to fit your project requirements. Additionally, shadrizz will display a checklist of tasks to complete the initial configuration. The init command is intended to be run once at the beginning of a new project.

Scaffold automation

Once the technologies are selected and configured, the next step is to begin building the interesting parts of the app. Typically, this involves a number of tasks including creating the database tables, API endpoints or server actions, user interface, layouts, pages, and web forms. This process may involve referencing documentation and writing the "boilerplate" code that the rest of the app will be built upon. This too is a time consuming process.

shadrizz provides a scaffold command to automate the entire process of setting up the initial "boilerplate" code. You only have to provide the table name along with the columns and data types. shadrizz will generate the database migration files, back end code, and front end code for the provided database schema.

What is the purpose of the scaffolded code? It is to provide a fully working full stack Create Read Update Delete (CRUD) feature that you can use as a reference to build the rest of your app. The scaffold command is intended to be run as many times as you need to generate full stack scaffolding. This automation is heavily inspired by the Ruby on Rails scaffold command.

Technologies

Next.js

Many of the full stack patterns used in shadrizz are based on the official Next.js documentation. Next.js provides many conveniences out of the box, such as file system routing, server side rendering, code bundling, and more.

shadcn/ui

shadcn/ui is the tool that copies and pastes beautifully styled components into your projects. Similarly, shadrizz generates full stack components into your Next.js project. You have full control of the code that is generated instead of the code being hidden behind an external package.

Drizzle ORM

A Headless TypeScript ORM that provides both SQL-like and relational queries, as well as schema generation and database migrations. If you know SQL, you know Drizzle. If you prefer an ORM, you can use their query API. Drizzle always outputs 1 query.

TypeScript

TypeScript adds additional syntax for types to JavaScript. This enables the early catching of errors in your editor and at compile time.

TailwindCSS

TailwindCSS is a CSS framework which provides reusable utility classes. TailwindCSS simplifies and improves scalability of styling by coupling markup with style.

Auth.js

shadrizz favors proven, open-source solutions that let you maintain control over your data. Similarly, with shadrizz, you own not only your data but also your code.

Zod

shadrizz uses zod and drizzle-zod for data validations. Each server action that is generated by the scaffolding tool will also contain zod validations to check for the correctness of data being submitted. Zod also provides tools for the parsing of form data.

Other Inspirations

Ruby on Rails

Nostalgia for Ruby on Rails style development is one motivation that led to the creation of shadrizz. The shadrizz scaffold command was modeled after the rails scaffold command. With a predefined set of conventions, you'll spend less time configuring things, and more time building.

Django

Django has a built-in admin interface as part of the core framework. It works out of the box with your custom data models. This was the main inspiration for the auth and admin dashboard of shadrizz.

WordPress

The content management dashboard of WordPress was also an inspiration for the design of the shadrizz dashboard.

FAQ

What can I build with shadrizz?

shadrizz is suitable for full stack monolithic server side rendered web applications. It is a full stack tool kit that automates away the time consuming things you need to do at the start of a new full stack Next.js project, saving you days worth of boilerplate coding.

What is a scaffold?

A scaffold is all of the starter code, including the UI and data layer, that is required to have a fully functional CRUD application. Scaffolding was popular in MVC frameworks such as Ruby on Rails. With scaffolding, you spend less time looking things up because there is a point of reference to build upon. This frees up time and energy to focus on building the interesting parts of the app.

Why not a boilerplate?

Boilerplates go obsolete fast. shadrizz offers a latest option to install latest dependencies. This means you'll get the latest version of Drizzle ORM, shadcn/ui components, Auth.js, TailwindCSS, Zod, and more. If you prefer a more stable version, choose the pinned option during initialization and you'll get the pinned versions of each top-level dependency. The pinned versions can be found in package-shadrizz.json in the shadrizz GitHub repo.

Author

Built by travisluong.

License

MIT