Using PostgreSQL/MySQL with Sequelize/Prisma/Drizzle · learncode.live

Choosing an ORM

Three popular ORMs for Node.js + SQL databases:

ORMDatabase SupportStyleType SafetyLearning Curve
SequelizePostgreSQL, MySQL, SQLite, MSSQLPromise-based, class modelsLowMedium
PrismaPostgreSQL, MySQL, SQLite, MongoDB, CockroachDBDeclarative schema, auto-generated clientExcellentLow
DrizzlePostgreSQL, MySQL, SQLiteSQL-like, lightweight, zero-abstractionExcellentMedium
Express App
    │
    ├─── Sequelize ───→ PostgreSQL / MySQL
    ├─── Prisma ───────→ PostgreSQL / MySQL
    └─── Drizzle ──────→ PostgreSQL / MySQL

1. Sequelize

Installation

npm install sequelize pg pg-hstore   # PostgreSQL
npm install sequelize mysql2         # MySQL

Connection Setup

const { Sequelize } = require('sequelize');

const sequelize = new Sequelize(
  process.env.DB_NAME || 'myapp',
  process.env.DB_USER || 'postgres',
  process.env.DB_PASSWORD,
  {
    host: process.env.DB_HOST || 'localhost',
    dialect: 'postgres', // or 'mysql'
    logging: process.env.NODE_ENV === 'development' ? console.log : false,
    pool: {
      max: 5,
      min: 0,
      acquire: 30000,
      idle: 10000
    }
  }
);

async function connectDB() {
  try {
    await sequelize.authenticate();
    console.log('Database connected');
  } catch (err) {
    console.error('Connection failed:', err);
  }
}

module.exports = { sequelize, connectDB };

Defining Models

// models/User.js
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');

const User = sequelize.define('User', {
  id: {
    type: DataTypes.UUID,
    defaultValue: DataTypes.UUIDV4,
    primaryKey: true
  },
  name: {
    type: DataTypes.STRING(50),
    allowNull: false,
    validate: {
      len: [2, 50]
    }
  },
  email: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true,
    validate: {
      isEmail: true
    }
  },
  age: {
    type: DataTypes.INTEGER,
    validate: {
      min: 13,
      max: 120
    }
  },
  role: {
    type: DataTypes.ENUM('user', 'admin', 'moderator'),
    defaultValue: 'user'
  },
  isActive: {
    type: DataTypes.BOOLEAN,
    defaultValue: true
  }
}, {
  timestamps: true, // Adds createdAt, updatedAt
  tableName: 'users'
});

module.exports = User;

Model Relationships

// models/Post.js
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const User = require('./User');

const Post = sequelize.define('Post', {
  title: { type: DataTypes.STRING, allowNull: false },
  body: { type: DataTypes.TEXT, allowNull: false }
}, { timestamps: true });

// Associations
User.hasMany(Post, { foreignKey: 'userId' });
Post.belongsTo(User, { foreignKey: 'userId' });

module.exports = Post;

CRUD with Sequelize

const User = require('./models/User');
const Post = require('./models/Post');

// CREATE
const user = await User.create({ name: 'Alice', email: 'alice@test.com' });

// READ
const users = await User.findAll();
const user = await User.findByPk(id);
const adults = await User.findAll({ where: { age: { [Op.gte]: 18 } } });
const user = await User.findOne({ where: { email: 'alice@test.com' } });

// READ with includes (JOIN)
const posts = await Post.findAll({
  include: [{ model: User, attributes: ['name', 'email'] }],
  order: [['createdAt', 'DESC']],
  limit: 10,
  offset: 0
});

// UPDATE
await User.update({ age: 31 }, { where: { id } });

// DELETE
await User.destroy({ where: { id } });

Syncing Models

// In development - sync all models (creates tables)
async function initDB() {
  await sequelize.sync({ alter: true });
  //   alter: true - adjusts tables to match model changes
  //   force: true - drops and recreates (loses data!)
}

// In production - use migrations instead

2. Prisma

Installation

npm install prisma @prisma/client
npx prisma init

Schema Definition (prisma/schema.prisma)

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql" // or "mysql"
  url      = env("DATABASE_URL")
}

model User {
  id        String   @id @default(uuid())
  name      String
  email     String   @unique
  age       Int?
  role      Role     @default(USER)
  isActive  Boolean  @default(true)
  posts     Post[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("users")
}

model Post {
  id        String   @id @default(uuid())
  title     String
  body      String
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("posts")
}

enum Role {
  USER
  ADMIN
  MODERATOR
}

Run Migrations

npx prisma migrate dev --name init
npx prisma generate

CRUD with Prisma

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

// CREATE
const user = await prisma.user.create({
  data: {
    name: 'Alice',
    email: 'alice@test.com',
    age: 30
  }
});

// READ
const users = await prisma.user.findMany({
  where: { age: { gte: 18 } },
  orderBy: { createdAt: 'desc' },
  take: 10,
  skip: 0,
  select: { id: true, name: true, email: true }
});

const user = await prisma.user.findUnique({
  where: { email: 'alice@test.com' },
  include: { posts: true }
});

// UPDATE
const user = await prisma.user.update({
  where: { id },
  data: { age: 31 }
});

// DELETE
await prisma.user.delete({ where: { id } });

Prisma Client Setup

// config/prisma.js
const { PrismaClient } = require('@prisma/client');

const prisma = new PrismaClient({
  log: process.env.NODE_ENV === 'development'
    ? ['query', 'info', 'warn']
    : ['error']
});

module.exports = prisma;

3. Drizzle

Installation

npm install drizzle-orm pg
npm install -D drizzle-kit @types/pg
npx drizzle-kit init

Schema Definition

// db/schema.js
const { pgTable, serial, text, integer, boolean, timestamp } = require('drizzle-orm/pg-core');

const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  email: text('email').notNull().unique(),
  age: integer('age'),
  role: text('role').default('user'),
  isActive: boolean('is_active').default(true),
  createdAt: timestamp('created_at').defaultNow(),
  updatedAt: timestamp('updated_at').defaultNow()
});

module.exports = { users };

CRUD with Drizzle

const { drizzle } = require('drizzle-orm/node-postgres');
const { Pool } = require('pg');
const { users } = require('./db/schema');
const { eq, and, gte, desc } = require('drizzle-orm');

const pool = new Pool({
  connectionString: process.env.DATABASE_URL
});
const db = drizzle(pool);

// CREATE
const [user] = await db.insert(users).values({
  name: 'Alice',
  email: 'alice@test.com',
  age: 30
}).returning();

// READ
const allUsers = await db.select().from(users);
const adults = await db.select()
  .from(users)
  .where(gte(users.age, 18));
const user = await db.select()
  .from(users)
  .where(eq(users.email, 'alice@test.com'))
  .limit(1);

// UPDATE
await db.update(users)
  .set({ age: 31 })
  .where(eq(users.id, id));

// DELETE
await db.delete(users).where(eq(users.id, id));

Comparison: Same Route with Each ORM

// Sequelize
app.get('/api/users', async (req, res) => {
  const users = await User.findAll({
    include: { model: Post, attributes: ['title'] }
  });
  res.json(users);
});

// Prisma
app.get('/api/users', async (req, res) => {
  const users = await prisma.user.findMany({
    include: { posts: { select: { title: true } } }
  });
  res.json(users);
});

// Drizzle (raw SQL-like)
app.get('/api/users', async (req, res) => {
  const users = await db.select({
    id: users.id,
    name: users.name,
    posts: {
      title: posts.title
    }
  })
  .from(users)
  .leftJoin(posts, eq(users.id, posts.authorId));
  res.json(users);
});

Key Takeaways

  • Sequelize is the most mature ORM with rich model associations and hooks
  • Prisma provides the best developer experience with auto-generated type-safe client
  • Drizzle is the lightest - you write SQL-like queries with full type safety
  • All support PostgreSQL, MySQL, and SQLite
  • Use migrations to manage schema changes in production
  • Store database URL in environment variable (DATABASE_URL)
  • Always handle connection errors and configure connection pooling
  • Choose based on your team’s needs: features (Sequelize), DX (Prisma), or performance (Drizzle)
Courses