Drizzle Next Guide
This guide covers the usage details and features of Drizzle Next.
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. With scaffolding, you spend less time writing boilerplate code and looking things up in documentation 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.
After the initial configuration is completed from running the init
command, you can create full stack scaffolding with the scaffold
command.
Given the table and column parameters, this command will generate the Next.js user interface, server actions, server components and client components. And it will also generate the Drizzle database table definition.
The -c
option takes a space-separated string of column configurations in the following format: column_name:dataType
. The data types map directly to most of the available data types in Drizzle ORM. See Data Types for a list of supported data types.
The id
, created_at
, and updated_at
fields are automatically generated by Drizzle Next, and should be omitted from the command.
The scaffolded code will be placed into the (development)
route group. By default, the pages and actions in this group are only accessible in the development environment. This behavior can be changed in app/(development)/layout.tsx
. The recommended workflow is to change the authentication and authorization behavior of these pages and actions before moving the code into a different route group, such as (public)
or (private)
.
After scaffolding, you can review the schema and make any necessary changes before running the database migrations.
Example:
npx drizzle-next@latest scaffold products -c title:text price:integer description:text
INFO
The table names will be transformed according to the Naming Conventions in this document.
Scaffolding Modes
During the init
command, you have the option to choose the --framework
. This is used to control what type of scaffolding Drizzle Next will generate.
next
- This is the default option. It will generate everything you need to start building full stack Next.js apps. Use case: If you plan to build full stack Next.js apps using the server components and server actions pattern with Drizzle ORM.express
- This mode will generate only Express.js API routes along with the Drizzle ORM schemas. Use case: If you need to quickly generate CRUD APIs that integrates both Express.js and Drizzle ORM.drizzle
- This mode will only setup Drizzle ORM. When you run scaffolds, it will only generate the Drizzle tables. Use case: If you want to use Drizzle Next's drizzle automation with other frameworks.all
- This option includes all of the above. This mode will generate full stack Next.js code, Drizzle tables, and Express.js API routes. Use case: If you want to build a full stack Next.js app, along with a separate Express API that leverages the same Drizzle tables.
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 Drizzle Next.
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 strategy
Drizzle Next supports the following primary key generation strategies:
uuidv4
- Uses thecrypto
packagecuid2
- Uses the@paralleldrive/cuid2
packageuuidv7
- Uses theuuidv7
packagenanoid
- Uses thenanoid
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 drizzle-next.config.ts
. This will be used for the authentication and scaffold schemas.
Foreign key constraints
Drizzle Next 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 drizzle-next@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 drizzle-next@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 drizzle-next@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}
.
Auth
During initialization of Drizzle Next, you can optionally choose to install Auth.js.
Drizzle Next provides a create-user.ts
script to create test users.
The Auth.js configuration is located in lib/auth.ts
. By default, Drizzle Next includes the following providers: Credential, GitHub, Google, Postmark, and Nodemailer. You can add or remove providers as needed.
After initialization, Drizzle Next will display a checklist of things to complete setup, such as adding client ids and secrets.
Drizzle Next 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 at /signin
and access a user dashboard at /dashboard
.
Any pages in the (private)
route group will require the user to have a user
role. This behavior can be changed in app/(private)/layout.tsx
.
Authorization
If admin was enabled, Drizzle Next will install Drizzle Admin and provide the all of the initial setup required to start using the Drizzle Admin Dashboard.
Users with the admin
role will be able to access the admin dashboard at /admin
. The admin signin is at /admin-signin
.
An authorization check happens at the admin layout.tsx
. The lib/authorize.ts
module contains an authorize
utility function. The function can be customized and used in pages, layouts, and server actions.
A grant-admin.ts
script is provided to grant users the admin role.
Users will be assigned a user
role when created. This behavior can be changed in auth.ts
.
File uploads
Drizzle Next 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 drizzle-next@latest scaffold media -c title:text image:file video:file
By default, file uploads are placed in a git ignored uploads
directory at the root of the project.
An uploads
route handler is used for serving the static files.
TIP
Do not use the public
directory for uploads because Next.js only generates routes for public files at compile time.
For better performance, consider using a web server like nginx to serve the uploaded files or an s3 compatible bucket.
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 upload.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.
Add-on Extensions
experimentalAdd-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 drizzle-next@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.
Tiptap Editor
npx drizzle-next@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 drizzle-next@latest scaffold posts -c title:text content:text_tiptap
Project Structure
Drizzle Next project structure.
- app
- (admin) - route group for the drizzle admin engine
- (auth) - route group for signin and signout feature
- (development) - route group where scaffolded code is placed
- (private) - route group requiring logged in user
- (public) - route group that is publicly accessible
- api - api routes
- uploads - upload route handler for serving uploaded files
- components
- ui - customizable ui components
- drizzle - sql migrations
- lib - configuration and utilities
- public - static assets
- schema - drizzle schemas
- scripts - executable scripts
- styles - css
- types - module augmentation and types
Scaffold structure
Drizzle Next uses a feature based, instead of type based, structure for scaffolding. The actions, components, and utilities are colocated with the routes. This convention reduces the number of directories that need to be generated. The scaffolded code is meant to be changed and moved around by the developer.
- app
- (development)
- posts
- _components
- _lib
- [id]
- new
TIP
All routes in the (development)
route group are by default only accessible when the NODE_ENV
environment variable is set to development
. This prevents accidental exposure of scaffolded code in production.
The scaffolded code can be changed, moved around, deleted, or just saved as a reference.
Awaited Return Types
The _lib
folder for each scaffold contains reusable queries and return types.
There are two main advantages to extracting queries into a separate module.
- Extracting the query functions makes it reusable in other parts of the code.
- It allows us to create a reusable Awaited ReturnType which reduces type boilerplate.
For example, here is a getPostById
function:
import { eq } from "drizzle-orm";
import { db } from "@/lib/db";
import { posts } from "@/schema/posts";
export type PostObj = Awaited<ReturnType<typeof getPostById>>;
export async function getPostById(id: string) {
return await db.query.posts.findFirst({
where: eq(posts.id, id),
with: { category: true },
});
}
The PostObj
type is automatically defined by whatever is returned from the getPostById
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 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 were to write it by hand:
type PostObj = {
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 { PostObj } from "../_lib/get-post-by-id";
export function PostDetail({ postObj }: { postObj: PostObj }) {
// ...
}
This makes it easier to achieve full stack type safety across the front end and back end. Note that Awaited ReturnTypes is a feature of TypeScript and not specific to any library.
Naming conventions
Number and case transformations will be automatically applied to the generated code.
Drizzle Next 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 drizzle-next.config.ts
by setting the pluralizeEnabled
boolean.
Pluralize Enabled
Pluralized Enabled is the default setting. With pluralize enabled, Drizzle Next 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, Drizzle Next 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 |
This mode was added to support non-English projects where pluralization may not apply.
Dependency Strategy
Drizzle Next provides a --latest
option to install latest dependencies during the init
command. This means you'll get the latest cutting edge versions of Drizzle ORM, Auth.js, TailwindCSS, Zod, and other packages. However, you may need to resolve any unexpected issues.
By default, you'll get the pinned versions of each top-level dependency during the init
command. The pinned versions can be found in package-pinned.json
in the Drizzle Next GitHub repo. Each build is tested before latest dependencies are merged as the pinned dependencies.