Drizzle ORM + Postgres VPS: stack siêu nhanh cho prototype

Chia sẻ bài viết

Mục lục
TL;DR
  • Drizzle ORM = TypeScript first, schema-as-code, query SQL-like, không magic như Prisma.
  • Migration đơn giản: drizzle-kit generate + drizzle-kit migrate, không cần ORM service riêng.
  • Performance hơn Prisma 30-50% vì không cần spawn engine process Rust.
  • Cài Postgres trên VPS 2GB, kết nối qua DATABASE_URL, build app trong 30 phút.
  • Phù hợp prototype SaaS, indie hacker dev solo, side project muốn deploy nhanh.

Bạn đang code prototype SaaS bằng Next.js hoặc Hono trên VPS, muốn chọn ORM cho Postgres mà không bị Prisma kéo theo 200MB binary + Rust engine? Drizzle ORM là câu trả lời. Stack Drizzle + Postgres tự host trên Cloud VPS 2GB cho phép bạn ship MVP trong vài ngày, query type-safe, schema as code.

Drizzle khác Prisma ở triết lý: nó không generate client từ schema rồi gọi qua RPC. Drizzle là SQL builder kèm type inference, query chạy thẳng qua node-postgres driver. Không có engine process, không có generation step nặng, deploy ra Cloud Run hay edge runtime đều được.

Bài này hướng dẫn full flow: cài Postgres trên VPS, setup Drizzle trong Next.js project, viết schema, generate migration, query mẫu, và deploy production. Test trên Cloud VPS TND 2GB Ubuntu 24.04, tổng thời gian setup 30-45 phút.

1. Vì sao chọn Drizzle thay Prisma cho prototype?

Tiêu chíDrizzlePrisma
Bundle size~50KB gzipped~200MB binary engine
Cold start~50ms~500-1000ms (spawn engine)
Query syntaxSQL-like, gần nativeMagic chain methods
Edge runtimeHỗ trợ đầy đủCần Accelerate proxy
MigrationSQL file thuầnCustom format
Type inferenceTự động từ schemaCần prisma generate
Learning curveBiết SQL là đủCần học DSL riêng

Drizzle không tốt hơn Prisma trong mọi case. Prisma có UI Prisma Studio, ecosystem lớn, nhiều plugin. Nhưng cho prototype tốc độ cao, Drizzle thắng tuyệt đối về performance và simplicity.

2. Cài Postgres 16 trên VPS 2GB

sudo apt update
sudo apt install -y postgresql-16
sudo systemctl enable --now postgresql

# Tạo user và db
sudo -u postgres psql
CREATE USER drizzle_user WITH PASSWORD 'random_strong_password';
CREATE DATABASE drizzle_app OWNER drizzle_user;
GRANT ALL PRIVILEGES ON DATABASE drizzle_app TO drizzle_user;
q

# Mở port 5432 cho local only (an toàn)
sudo nano /etc/postgresql/16/main/postgresql.conf
# listen_addresses = 'localhost'

sudo systemctl restart postgresql

Test connection từ shell:

psql postgres://drizzle_user:[email protected]:5432/drizzle_app
# drizzle_app=> dt
# (no tables)

3. Khởi tạo Next.js + Drizzle project

npx create-next-app@latest my-app --typescript --app --no-src-dir
cd my-app

npm install drizzle-orm postgres
npm install -D drizzle-kit @types/node

Tạo file .env.local:

DATABASE_URL=postgres://drizzle_user:[email protected]:5432/drizzle_app

Tạo file drizzle.config.ts ở root:

import type { Config } from 'drizzle-kit';

export default {
  schema: './db/schema.ts',
  out: './db/migrations',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
} satisfies Config;

4. Viết schema first table với Drizzle

Tạo file db/schema.ts - đây là source of truth cho cả database structure và TypeScript types:

import { pgTable, serial, text, timestamp, integer, boolean } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  email: text('email').notNull().unique(),
  name: text('name'),
  passwordHash: text('password_hash').notNull(),
  isActive: boolean('is_active').default(true).notNull(),
  createdAt: timestamp('created_at').defaultNow().notNull(),
});

export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull().references(() => users.id),
  title: text('title').notNull(),
  slug: text('slug').notNull().unique(),
  content: text('content'),
  published: boolean('published').default(false).notNull(),
  createdAt: timestamp('created_at').defaultNow().notNull(),
  updatedAt: timestamp('updated_at').defaultNow().notNull(),
});

export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Post = typeof posts.$inferSelect;
export type NewPost = typeof posts.$inferInsert;

5. Generate và apply migration

# Thêm script vào package.json
"scripts": {
  "db:generate": "drizzle-kit generate",
  "db:migrate": "drizzle-kit migrate",
  "db:push": "drizzle-kit push",
  "db:studio": "drizzle-kit studio"
}

npm run db:generate
# Tạo file db/migrations/0000_xxxxx.sql với CREATE TABLE

npm run db:migrate
# Apply migration vào Postgres

File migration là SQL thuần, bạn có thể edit tay nếu cần tùy chỉnh trước khi apply. Khác với Prisma, không có hidden state.

# Xem cấu trúc DB
npm run db:studio
# Mở https://local.drizzle.studio - UI quản lý DB

6. Setup Drizzle client cho query

// db/client.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';

const connectionString = process.env.DATABASE_URL!;

const client = postgres(connectionString, {
  max: 10,           // connection pool size
  idle_timeout: 20,
  connect_timeout: 10,
});

export const db = drizzle(client, { schema });

Lưu ý: ở Next.js edge runtime, dùng @vercel/postgres hoặc neon-serverless thay postgres package (HTTP-based, không cần TCP).

7. CRUD query đầu tiên

// app/api/users/route.ts
import { db } from '@/db/client';
import { users } from '@/db/schema';
import { eq } from 'drizzle-orm';
import { NextResponse } from 'next/server';

// GET /api/users
export async function GET() {
  const allUsers = await db.select().from(users);
  return NextResponse.json(allUsers);
}

// POST /api/users
export async function POST(req: Request) {
  const body = await req.json();
  const [newUser] = await db
    .insert(users)
    .values({
      email: body.email,
      name: body.name,
      passwordHash: body.passwordHash,
    })
    .returning();
  return NextResponse.json(newUser, { status: 201 });
}

Đẹp ở chỗ: newUser tự động được infer type User từ schema. Không cần generate, không cần annotation tay.

8. Join, where, order - query phức tạp hơn

import { db } from '@/db/client';
import { users, posts } from '@/db/schema';
import { eq, desc, and, like } from 'drizzle-orm';

// Lấy tất cả post của user kèm thông tin user
const result = await db
  .select({
    postId: posts.id,
    title: posts.title,
    authorName: users.name,
    authorEmail: users.email,
  })
  .from(posts)
  .innerJoin(users, eq(posts.userId, users.id))
  .where(and(
    eq(posts.published, true),
    like(posts.title, '%nextjs%')
  ))
  .orderBy(desc(posts.createdAt))
  .limit(20);

// Drizzle generate SQL gần như native, dễ debug
console.log(result);

Khác Prisma findMany({ where: { posts: { some: ... } } }) magic, Drizzle viết gần SQL thuần - dev biết SQL học trong 10 phút.

9. Relations và query builder advanced

Drizzle có 2 cách query: select chain (như SQL) và relational query (như Prisma). Cả 2 đều type-safe.

// db/schema.ts - thêm relations
import { relations } from 'drizzle-orm';

export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, {
    fields: [posts.userId],
    references: [users.id],
  }),
}));

// Query với relational API
const userWithPosts = await db.query.users.findFirst({
  where: eq(users.id, 1),
  with: {
    posts: {
      orderBy: [desc(posts.createdAt)],
      limit: 5,
    },
  },
});

10. Transaction và batch insert

// Transaction
const result = await db.transaction(async (tx) => {
  const [user] = await tx.insert(users).values({
    email: '[email protected]',
    passwordHash: 'hashed',
  }).returning();

  await tx.insert(posts).values({
    userId: user.id,
    title: 'First post',
    slug: 'first-post',
  });

  return user;
});

// Batch insert nhiều rows
await db.insert(posts).values([
  { userId: 1, title: 'Post 1', slug: 'p1' },
  { userId: 1, title: 'Post 2', slug: 'p2' },
  { userId: 2, title: 'Post 3', slug: 'p3' },
]);

11. Deploy lên VPS production

# Build trên VPS
cd /var/app
git pull origin main
npm ci --production=false
npm run db:migrate    # apply pending migrations
npm run build
NODE_ENV=production pm2 restart web

# Hoặc systemd service
sudo systemctl restart app.service

Drizzle không cần generate client trước build, không có postinstall hook nặng. Deploy nhanh hơn Prisma 2-3x.

12. So sánh performance với Prisma

MetricDrizzlePrismaChênh lệch
Cold start API route80ms650msDrizzle nhanh 8x
Simple SELECT 1 row3ms8msDrizzle nhanh 2.5x
JOIN 3 table12ms22msDrizzle nhanh 1.8x
RAM dùng (Next.js prod)180MB320MBDrizzle ít hơn 140MB
Bundle size client50KB0 (server only)tương đương

Test trên Cloud VPS TND 2GB, dataset 10000 rows, lặp 100 lần lấy trung bình.

13. Limitations và khi nào KHÔNG nên dùng Drizzle

  • Cần Prisma Studio UI đẹp - Drizzle Studio ổn nhưng ít tính năng hơn.
  • Team đã quen Prisma 2+ năm - chi phí switch cao.
  • Cần plugin ecosystem rộng như nestjs-prisma, prisma-trpc-generator.
  • Cần support cho 10+ database (Drizzle hỗ trợ Postgres, MySQL, SQLite tốt nhất).
Cloud VPS cho vibe coder

Stack Drizzle + Postgres VPS 2GB - prototype trong 1 ngày

Cloud VPS TND sẵn AlmaLinux 9, Ubuntu 22/24, Debian 12/13. SSD CEPH, snapshot 1-click, backup hằng ngày, network 200Mbps trong nước. Cài Postgres + Node.js trong 5 phút, deploy Next.js + Drizzle ORM ngay.

Xem 8 cấu hình Cloud VPS →

FAQ

Drizzle có thay được Prisma hoàn toàn không?

Cho prototype và project mới, có. Drizzle thiếu vài tính năng nhỏ như Prisma Migrate dev (resetable migration). Nhưng query, type-safety, performance đều tốt hơn Prisma trên VPS 2GB.

Drizzle có chạy được trên edge runtime Vercel hoặc Cloudflare Workers không?

Có. Drizzle hỗ trợ neon-serverless cho Neon, @vercel/postgres cho Vercel, và Cloudflare D1. Bundle nhỏ 50KB, deploy edge thoải mái.

Migration của Drizzle có rollback được không?

Drizzle không có auto rollback như Prisma. Cách backup: dùng pg_dump trước migration. Hoặc viết down migration tay nếu cần. Cho prototype thì khôi phục từ snapshot Cloud VPS nhanh hơn.

Drizzle Studio có thay được pgAdmin không?

Cho task xem/edit data đơn giản thì có. Drizzle Studio UI đẹp, type-aware. pgAdmin mạnh hơn cho DBA task như EXPLAIN, partition, replication.

Schema Drizzle có support enum và JSON column không?

Có. pgEnum cho enum, jsonb cho JSON column với type inference từ TypeScript interface. Cú pháp: const status = pgEnum(status, [active, inactive]).

Có nên migrate từ Prisma sang Drizzle giữa chừng project không?

Chỉ migrate khi gặp pain point thực tế: cold start chậm, RAM cao trên VPS nhỏ, edge runtime không chạy. Nếu Prisma đang ổn thì không cần switch chỉ vì hype.

2009
15+ năm vận hành liên tục
10+
tập đoàn lớn tin dùng
100+
doanh nghiệp SMB Việt
30 ngày
đổi key lỗi miễn phí
Phần mềm bản quyền chính hãng chúng tôi cung cấp
Bản quyền chính hãng Hóa đơn VAT đầy đủ Đổi key lỗi 30 ngày Vận hành từ 2009 MST 0200994870 Hotline 0225.999.6666