A CLI Tool That Generates Full Stack Apps Using Next.js, shadcn/ui, and Drizzle ORM.
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.
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
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
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
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
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
Start the dev server:
npm run dev
Go to http://localhost:3000/admin-login and log in with the admin user.
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
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.
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
shadrizz supports the following primary key generation strategies:
cuid2
- Uses the @paralleldrive/cuid2
packageuuidv7
- Uses the uuidv7
packageuuidv4
- Uses the crypto
packagenanoid
- Uses the nanoid
packageauto_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.
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:
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
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}
.
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.
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.
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
.
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-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.
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.
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
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 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 }) {
...
}
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.
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.
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 |
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 |
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.
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.
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.
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 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.
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 adds additional syntax for types to JavaScript. This enables the early catching of errors in your editor and at compile time.
TailwindCSS is a CSS framework which provides reusable utility classes. TailwindCSS simplifies and improves scalability of styling by coupling markup with style.
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.
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.
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 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.
The content management dashboard of WordPress was also an inspiration for the design of the shadrizz dashboard.
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.
Built by travisluong.
MIT