DrizzleORM
DrizzleORM is an ORM framework for
TypeScript.
It offers you several levels of Database communication:
- Typesafe Table View approach
- Typesafe Query Builder
- Simple SQL query execution
Drizzle ORM is highly influenced by Exposed and Jetbrains development methodology
Supported Databases
Links
In Progress
Installing
npm install drizzle-orm drizzle-kit
In Progress
yarn add drizzle-orm drizzle-kit
bower install drizzle-orm drizzle-kit
Connecting to database
import { DbConnector } from "drizzle-orm";
const db = await new DbConnector()
.connectionString("postgres://user:password@host:port/db")
.connect();
const db = await new DbConnector()
.params({
host: '0.0.0.0',
port: 5432,
user: 'user',
password: 'password',
db: 'optional_db_name'
}).connect();
Project structure
- tables folder
- migrations folder
Create tables
Users Table
export const rolesEnum = createEnum({ alias: 'test-enum', values: ['user', 'guest', 'admin'] });
export default class UsersTable extends AbstractTable<UsersTable> {
public id = this.serial('id').primaryKey();
public fullName = this.text('full_name');
public phone = this.varchar('phone', { size: 256 });
public media = this.jsonb<string[]>('media');
public decimalField = this.decimal('test', { precision: 100, scale: 2 }).notNull();
public bigIntField = this.bigint('test1', 'max_bytes_53');
public role = this.type(rolesEnum, 'name_in_table').notNull();
public createdAt = this.timestamp('created_at').notNull();
public createdAtWithTimezone = this.timestamptz('created_at_time_zone');
public updatedAt = this.timestamp('updated_at').defaultValue(Defaults.CURRENT_TIMESTAMP);
public isArchived = this.bool('is_archived').defaultValue(false);
public phoneFullNameIndex = this.index([this.phone, this.fullName]);
public phoneIndex = this.uniqueIndex(this.phone);
public tableName(): string {
return 'users';
}
}
Cities Table
interface CityMeta {
population: number,
connection: string,
}
export default class CitiesTable extends AbstractTable<CitiesTable> {
public id = this.serial('id').primaryKey();
public foundationDate = this.timestamp('name').notNull();
public location = this.varchar('page', { size: 256 });
public userId = this.int('user_id').foreignKey(UsersTable, (table) => table.id, { onUpdate: 'CASCADE' });
public metadata = this.jsonb<CityMeta>('metadata');
public tableName(): string {
return 'cities';
}
}
User Groups Table
export default class UserGroupsTable extends AbstractTable<UserGroupsTable> {
public id = this.serial('id').primaryKey();
public name = this.varchar('name');
public description = this.varchar('description');
public tableName(): string {
return 'user_groups';
}
}
User to User Groups Table
Many to many connection between Users and User Groups
export default class UsersToUserGroupsTable extends AbstractTable<UsersToUserGroupsTable> {
public groupId = this.int('city_id').foreignKey(UserGroupsTable, (table) => table.id, { onDelete: 'CASCADE' });
public userId = this.int('user_id').foreignKey(UsersTable, (table) => table.id, { onDelete: 'CASCADE' });
public manyToManyIndex = this.index([this.groupId, this.userId]);
public tableName(): string {
return 'users_to_user_groups';
}
}
CRUD
SELECT
const db = await new DbConnector()
.connectionString('postgresql://postgres@127.0.0.1/drizzle')
.connect();
const usersTable = new UsersTable(db);
const allSelect = await usersTable.select().all();
const firstSelect = await usersTable.select().findOne();
Sorting and Filtering
Select all records from Users
where phone is "hello"
const eqSelect = await usersTable.select().where(
eq(usersTable.phone, 'hello')
).all();
Select all records from Users
where both phone is "hello"
and phone is "hello"
const andSelect = await usersTable.select().where(
and([
eq(usersTable.phone, 'hello'),
eq(usersTable.phone, 'hello')
]),
).all();
Select all records from Users
where either phone is "hello"
or phone is "hello"
const orSelect = await usersTable.select().where(
or([eq(usersTable.phone, 'hello')]),
).all();
Select all records from Users
using LIMIT and OFFSET
const limitOffsetSelect = await usersTable.select().limit(10).offset(10).all();
Select all records from Users
where phone
contains "hello"
const likeSelect = await usersTable.select().where(
like(usersTable.phone, '%hello%')
).all();
Select all records from Users
where phone
equals to some of values from array
const inArraySelect = usersTable.select().where(
inArray(usersTable.phone, ['hello'])
).all();
Select all records from Users
where phone
greater(>) than "hello"
const greaterSelect = usersTable.select().where(
greater(usersTable.phone, 'hello')
).all();
Select all records from Users
where phone
less(<) than "hello"
const lessSelect = usersTable.select().where(
less(usersTable.phone, 'hello')
).all();
Select all records from Users
where phone
greater or equals(>=) than "hello"
const greaterEqSelect = usersTable.select().where(
greaterEq(usersTable.phone, 'hello')
).all();
Select all records from Users
where phone
less or equals(<=)
const lessEqSelect = usersTable.select().where(
lessEq(usersTable.phone, 'hello')
).all();
Select all records from Users
where phone
is NULL
const isNullSelect = usersTable.select().where(
isNull(usersTable.phone)
).all();
Select all records from Users
where phone
not equals to "hello"
const notEqSelect = usersTable.select().where(
notEq(usersTable.phone, 'hello')
).all();
Select all records from Users
ordered by phone
in ascending order
const ordered = await usersTable.select().orderBy((table) => table.phone, Order.ASC).all();
Partial Selecting
const partialSelect = await usersTable.select({
id: usersTable.id,
phone: usersTable.phone,
}).all();
const { mappedId, mappedPhone } = partialSelect;
Update
Update fullName
to newName
in Users
where phone is "hello"
await usersTable.update()
.where(eq(usersTable.phone, 'hello'))
.set({ fullName: 'newName' })
.execute();
Update fullName
to newName
in Users
where phone is "hello"
returning updated User
model
await usersTable.update()
.where(eq(usersTable.phone, 'hello'))
.set({ fullName: 'newName' })
.all();
Update fullName
to newName
in Users
where phone is "hello"
returning updated User
model
await usersTable.update()
.where(eq(usersTable.phone, 'hello'))
.set({ fullName: 'newName' })
.findOne();
Delete
Delete user
where phone is "hello"
await usersTable.delete()
.where(eq(usersTable.phone, 'hello'))
.execute();
Delete user
where phone is "hello"
returning updated User
model
await usersTable.delete()
.where(eq(usersTable.phone, 'hello'))
.all();
Delete user
where phone is "hello"
returning updated User
model
await usersTable.delete()
.where(eq(usersTable.phone, 'hello'))
.findOne();
Insert
Insert user
with required fields
await usersTable.insert({
test: 1,
createdAt: new Date(),
}).execute();
Insert user
with required fields and get all rows as array
const user = await usersTable.insert({
test: 1,
createdAt: new Date(),
}).all();
Insert user
with required fields and get inserted entity
const user = await usersTable.insert({
test: 1,
createdAt: new Date(),
}).findOne();
Insert many users
with required fields and get all inserted entities
const users = await usersTable.insertMany([{
test: 1,
createdAt: new Date(),
}, {
test: 2,
createdAt: new Date(),
}]).all();
Insert many users
with required fields and get all inserted entities. If such user already exists - update phone
field
await usersTable.insertMany([{
test: 1,
createdAt: new Date(),
}, {
test: 2,
createdAt: new Date(),
}])
.onConflict(
(table) => table.phoneIndex,
{ phone: 'confilctUpdate' },
).all();
Joins
Join One-To-Many Tables
Join Cities with Users and map to city object with full user
const usersTable = new UsersTable(db);
const citiesTable = new CitiesTable(db);
const userWithCities = await citiesTable.select()
.where(eq(citiesTable.id, 1))
.leftJoin(UsersTable,
(city) => city.userId,
(users) => users.id)
.execute();
const citiesWithUserObject = userWithCities.map((city, user) => ({ ...city, user }));
Join Many-To-Many Tables
Join User Groups with Users, using many-to-many table and map response to get user object with groups array
const usersWithUserGroups = await usersToUserGroupsTable.select()
.where(eq(userGroupsTable.id, 1))
.leftJoin(UsersTable,
(userToGroup) => userToGroup.userId,
(users) => users.id)
.leftJoin(UsersToUserGroupsTable, UserGroupsTable,
(userToGroup) => userToGroup.groupId,
(users) => users.id)
.execute();
const userGroupWithUsers = usersWithUserGroups.group({
one: (_, dbUser, dbUserGroup) => dbUser!,
many: (_, dbUser, dbUserGroup) => dbUserGroup!,
});
const userWithGroups: ExtractModel<UsersTable> & { groups: ExtractModel<UserGroupsTable>[] } = {
...userGroupWithUsers.one,
groups: userGroupWithUsers.many,
};
Join User Groups with Users, using many-to-many table and map response to get user group object with users array
const usersWithUserGroups = await usersToUserGroupsTable.select()
.where(eq(userGroupsTable.id, 1))
.leftJoin(UsersTable,
(userToGroup) => userToGroup.userId,
(users) => users.id)
.leftJoin(UsersToUserGroupsTable, UserGroupsTable,
(userToGroup) => userToGroup.groupId,
(users) => users.id)
.execute();
const userGroupWithUsers = usersWithUserGroups.group({
one: (_, dbUser, dbUserGroup) => dbUserGroup!,
many: (_, dbUser, dbUserGroup) => dbUser!,
});
const userWithGroups: ExtractModel<UserGroupsTable> & { users: ExtractModel<UsersTable>[] } = {
...userGroupWithUsers.one,
users: userGroupWithUsers.many,
};
Join using partial field select
Join Cities with Users getting only needed fields form request
await citiesTable.select({
id: citiesTable.id,
userId: citiesTable.userId,
})
.where(eq(citiesTable.id, 1))
.leftJoin(UsersTable,
(city) => city.userId,
(users) => users.id,
{
id: usersTable.id,
})
.execute();
const citiesWithUserObject = userWithCities.map((city, user) => ({ ...city, user }));
Migrations
To run migrations generated by drizzle-kit you could use Migrator
class
Provide drizzle-kit config path
await drizzle.migrator(db).migrate('src/drizzle.config.yaml');
Another possibility is to provide object with path to folder with migrations
await drizzle.migrator(db).migrate({ migrationFolder: 'drizzle' });