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 UI, 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.
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
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 configuration
Drizzle Next will create an id generation utility in lib/id.ts
:
export function createId() {
return crypto.randomUUID();
}
It is easy to change the implementation to a different id generator. For example: uuid v7, cuid2, nanoid, or a library of your choice.
By default, Drizzle Next will use the following primary key data types:
- postgresql:
uuid
- mysql:
varchar
- sqlite:
text
You can override this in drizzle-next.config.ts
.
const drizzleNextConfig = {
// ...
pkDataType: "text",
pkFunctionTemplate: "text()",
};
export default drizzleNextConfig;
The pkDataType
controls what gets imported into the generated schemas.
The pkFunctionTemplate
controls what gets rendered as the value of the generated schema id columns.
For example:
import {
pgTable,
timestamp,
text, // <-- pkDataType
} from "drizzle-orm/pg-core";
import { createId } from "@/lib/id";
export const users = pgTable("users", {
id: text() // <-- pkFunctionTemplate
.primaryKey()
.$defaultFn(() => createId()),
name: text(),
// ...
});
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}
.
Authentication and Authorization
Drizzle Next ships with a simple JWT authentication implementation. This is a basic starting point that can be extended for more advanced use cases.
Drizzle Next provides a create-user.ts
script to create test users.
A grant-admin.ts
script is provided to grant users the admin role.
Users will be able to sign in at /login
and access a user dashboard at /dashboard
.
Any pages in the (private)
route group will require the user to be logged in. This behavior can be changed in app/(private)/layout.tsx
.
Users with the admin
role will be able to access the admin dashboard at /admin
. The admin login is at /admin-login
. An authorization check happens at the admin layout.tsx
.
The lib/auth.ts
module contains various utility functions, such as requireAuth
and requireAdmin
. The function can be customized and used in pages, layouts, and server actions. getUserSession
can be used for optional authentication.
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.
Project Structure
Drizzle Next project structure.
- actions
- admin - admin actions
- auth - auth actions
- app
- (admin) - route group for admin dashboard and scaffolding
- (auth) - route group for login and logout feature
- (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
- admin - scaffolded forms and components
- auth - auth forms
- layouts - admin, private, and public layouts
- ui - customizable ui components
- db
- queries - scaffolded drizzle queries
- schema - scaffolded drizzle schemas
- drizzle - sql migrations
- lib - utilities and configuration
- public - static assets
- scripts - executable scripts
- types - types
Scaffold structure
Drizzle Next uses a type based instead of feature based, structure for scaffolding. The actions, components, and queries are NOT colocated with the routes. This convention is preferred to keep the app router free of clutter and easy to scan.
All scaffolded code from the scaffold
command is secure by default. The actions and routes all require authentication and admin authorization.
Awaited Return Types
The db/queries
folder contains reusable queries from the scaffolding.
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 "@/db/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 "@/db/queries/posts-queries";
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.