Using Drizzle ORM
Introduction
- This is a next-generation
TypeScript-first ORM(Object-Relational Mapping) library designed to interact with relational databases (PostgreSQL, MySQL, SQLite) in a lightweight,Type-safeand high-performance manner. - Unlike traditional ORMs (such as
Prisma, TypeORM, Hibernate) that hide SQL behind complex abstraction layers,Drizzlefollows the philosophyIf you know SQL, you know Drizzle. It serves as both anORMand a powerfulQuery Builder.
Advantages
- Absolute
TypeScript & Type-safestandard (100% Type-Safe): Schema is defined directly using TypeScript functions. When you write queries,Drizzleautomatically infers the return type without needing cumbersome code generation steps. - Extremely high performance & super lightweight (
Zero Overhead): Drizzle has no heavy dependencies and does not use background engines (such asRust binariesinPrisma). Its ultra-small package size helps optimize cold start times, making it ideal forServerlessandEdge Environments (Next.js, Vercel, Cloudflare Workers, Supabase). - Syntax close to native
SQL:DrizzleAPI mirrors the exact structure ofSQL (select(), from(), where(), leftJoin())statements. This helps developers easily control the exactSQLstatements generated without performance surprises (likeN+1 queryissues). - Supports both
SQL-like APIandRelational API: You can flexibly choose to write pureSQL-style queries or useRelational Queries (db.query.users.findMany(...))syntax when needing simple nested relational data retrieval. - Transparent Migration management (
Drizzle Kit): The includeddrizzle-kittool automatically comparesSchemasand generatesMigrationfiles as raw.sqlfiles instead of custom formats. This makes it easy to track, test and customizeSQLbefore applying it to theDatabase. - Predictable queries (Exactly 1 SQL Query): Each query in
Drizzlegenerates exactly one SQL statement sent to theDatabase, reducing round-trip costs and optimizing processing speed.
Use cases
- Projects built with
TypeScript / JavaScript(suitable forNextJS, Remix, NestJS). - Applications deployed on
Serverless / Edge Functionsrequiring fast startup. - Developers who like the flexibility of SQL while still wanting tight
IntelliSenseandType-safety.
Detail
First, install the following packages
bun add drizzle-orm drizzle-kit
Add the following scripts to package.json
{
"scripts": {
"db:push": "drizzle-kit push",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate"
}
}
Create a simple schema like this drizzle-orm/schema.ts
import {
bigserial,
integer,
pgTable,
text,
timestamp,
varchar,
} from 'drizzle-orm/pg-core'
export const products = pgTable('products', {
id: bigserial('id', {mode: 'number'}).primaryKey(),
name: varchar('name', {length: 255}).notNull(),
description: text('description'),
categoryId: integer('category_id').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
})
You can see that using Drizzle ORM makes defining table structures very simple, almost like working with a real Database
Next, define the database connection information from Drizzle ORM to Postgres as follows drizzle-orm/index.ts
import {drizzle, type NodePgDatabase} from 'drizzle-orm/node-postgres'
import {Pool} from 'pg'
import * as schema from './schema'
export type DrizzleDB = NodePgDatabase<typeof schema>
export const DRIZZLE_TOKEN = 'DRIZZLE_DEV'
export const createPool = () => {
return new Pool({connectionString: process.env.DATABASE_URL})
}
export const createDrizzle = () => {
const pool = createPool()
return drizzle(pool, {schema})
}
export const drizzleProvider = {
provide: DRIZZLE_TOKEN,
useFactory: createDrizzle,
}
Create service/drizzle.service.ts
import {Injectable, OnModuleDestroy} from '@nestjs/common'
import {Pool} from 'pg'
import {createDrizzle, createPool, type DrizzleDB} from 'src/drizzle-orm'
@Injectable()
export class DrizzleService implements OnModuleDestroy {
private readonly pool: Pool
public readonly db: DrizzleDB
constructor() {
this.pool = createPool()
this.db = createDrizzle()
}
async onModuleDestroy() {
await this.pool.end()
}
}
Create service/products.service.ts
import {Inject, Injectable, NotFoundException} from '@nestjs/common'
import {eq} from 'drizzle-orm'
import {DRIZZLE_TOKEN, type DrizzleDB} from 'src/drizzle-orm'
import * as schema from 'src/drizzle-orm/schema'
import {CreateProductDto, UpdateProductDto} from 'src/dto/product.dto'
import {DrizzleService} from './drizzle.service'
@Injectable()
export class ProductsService {
constructor(
@Inject(DRIZZLE_TOKEN) private readonly db: DrizzleDB,
private readonly drizzle: DrizzleService
) {}
async create(dto: CreateProductDto) {
const [result] = await this.db
.insert(schema.products)
.values(dto)
.returning()
return result
}
async findAll() {
return this.drizzle.db.select().from(schema.products)
}
async findOne(id: number) {
const [result] = await this.db
.select()
.from(schema.products)
.where(eq(schema.products.id, id))
if (!result) {
throw new NotFoundException(`Product with ID ${id} not found`)
}
return result
}
async update(id: number, dto: UpdateProductDto) {
const [updated] = await this.db
.update(schema.products)
.set(dto)
.where(eq(schema.products.id, id))
.returning()
if (!updated) {
throw new NotFoundException(`Product with ID ${id} not found`)
}
return updated
}
async remove(id: number) {
const [deleted] = await this.db
.delete(schema.products)
.where(eq(schema.products.id, id))
.returning()
if (!deleted) {
throw new NotFoundException(`Product with ID ${id} not found`)
}
return {success: true, deletedId: deleted.id}
}
}
I provide 2 ways to use Drizzle by using @Inject(DRIZZLE_TOKEN) and DrizzleService, both of which work similarly
Create file dto/product.dto.ts
import {PartialType} from '@nestjs/mapped-types'
import {
IsInt,
IsNotEmpty,
IsOptional,
IsString,
MaxLength,
} from 'class-validator'
export class CreateProductDto {
@IsString()
@IsNotEmpty()
@MaxLength(255)
name: string
@IsString()
@IsOptional()
description?: string
@IsInt()
@IsNotEmpty()
categoryId: number
}
export class UpdateProductDto extends PartialType(CreateProductDto) {}
Next is controller/products.controller.ts to define simple CRUD APIs
import {
Body,
Controller,
Delete,
Get,
Param,
ParseIntPipe,
Patch,
Post,
} from '@nestjs/common'
import {CreateProductDto, UpdateProductDto} from 'src/dto/product.dto'
import {ProductsService} from 'src/service/products.service'
@Controller('products')
export class ProductsController {
constructor(private productsService: ProductsService) {}
@Post()
create(@Body() createProductDto: CreateProductDto) {
return this.productsService.create(createProductDto)
}
@Get()
findAll() {
return this.productsService.findAll()
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.productsService.findOne(id)
}
@Patch(':id')
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateProductDto: UpdateProductDto
) {
return this.productsService.update(id, updateProductDto)
}
@Delete(':id')
remove(@Param('id', ParseIntPipe) id: number) {
return this.productsService.remove(id)
}
}
Modify app.module.ts
import {Module} from '@nestjs/common'
import {ConfigModule} from '@nestjs/config'
import {ProductsController} from './controller/products.controller'
import {drizzleProvider} from './drizzle-orm'
import {DrizzleService} from './service/drizzle.service'
import {ProductsService} from './service/products.service'
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.env',
}),
],
controllers: [
ProductsController,
],
providers: [
drizzleProvider,
DrizzleService,
ProductsService,
],
})
export class AppModule {}
Next, there are 2 ways to apply the schema from Drizzle to Postgres:
- Use the
bun db:pushcommand directly - Use the
bun db:generatecommand to create a.sqlfile, then usebun db:migrateto execute the migration into theDatabase
Option 1
$ bun db:push
$ drizzle-kit push
No config path provided, using default 'drizzle.config.ts'
Reading config file '~/drizzle.config.ts'
Using 'pg' driver for database querying
[✓] Pulling schema from database...
[✓] Changes applied
Option 2
After that, you can check directly in the Database that the table was created successfully
You can use this command to generate a migration file
$ bun db:generate
$ drizzle-kit generate
No config path provided, using default 'drizzle.config.ts'
Reading config file '~/drizzle.config.ts'
1 tables
products 5 columns 0 indexes 0 fks
[✓] Your SQL migration file ➜ drizzle/0000_slow_mephistopheles.sql 🚀
The result will generate the drizzle/0000_slow_mephistopheles.sql file containing table information to migrate as follows
CREATE TABLE "products" (
"id" bigserial PRIMARY KEY NOT NULL,
"name" varchar(255) NOT NULL,
"description" text,
"category_id" integer NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL
);
After checking that the script has no issues, you can use the bun db:migrate command to apply changes to the Database
Start the project to verify that the APIs are working properly
Happy coding!
Comments
Post a Comment