Proposal for Extending schema.ts
in Drizzle ORM with Runtime Middlewares
#1513
Replies: 9 comments 16 replies
-
Thank you for taking the time to write this. The following opinions are my own, and mine only. I personally think that the core drizzle-orm package should stay dependency-free. That is one of the selling point that brought me to it. I think the approach we should follow is improve the experience of the plug-ins we have. |
Beta Was this translation helpful? Give feedback.
-
I agree with you about keeping drizzle dependency-free, and I think that it can still be achieved with the API example I provided. What I miss from |
Beta Was this translation helpful? Give feedback.
-
Your clarification just changed my mind! I think this is a great idea!
Having said this, I think we should convert this discussion to a feature request. I think it is a nice idea. |
Beta Was this translation helpful? Give feedback.
-
Hello again, After further reflection on the proposed enhancements for Drizzle ORM, I've developed an additional perspective that might further expand our capabilities: replacing the This concept of middleware aligns more closely with the pattern seen in many modern web frameworks. It opens up a plethora of possibilities, going beyond mere validation. Here's how it could look: export const mySchemaUsers = mySchema.table('users', {
id: serial('id').primaryKey().use(myIdMiddleware),
name: text('name').use(myNameMiddleware),
}); Expanded Capabilities with Middleware:
By conceptualizing these functions as middleware, we not only retain the initial idea of enhanced validation but also open the door to a more powerful and flexible data-handling paradigm within Drizzle ORM. I'm curious to know what the community and maintainers think about this expanded idea. Does this middleware approach align with the goals and architecture of Drizzle ORM? Looking forward to hearing your thoughts and suggestions! |
Beta Was this translation helpful? Give feedback.
-
The functionality that developers can extend their drizzle client like prisma client extension. Scenarios that I have faced:
Sorry for spamming. |
Beta Was this translation helpful? Give feedback.
-
I've been thinking about this. There is a lot to consider. I think the middleware should be at the table level only to avoid any race condition/complications around the order of operations. To be able to make it useful enough, const usersMiddleware = {
beforeSelect: ({table: CurrentTable, selectedFields: SelectFields, where: SQL, logger: CustomLogger}) => ({ SelectedFields, where }),
afterSelect: ({result: DependsOnTheSelect[], logger: CustomLogger }) => CustomReturn,
beforeInsert: ({ table: CurrentTable, insertedFields: Partial<InsertedFields>, logger: CustomLogger }) => ({ insertedFields, returnedObj }),
afterInsert: ({result: ResultTypeFromTheDriver, returnedObject: WhateverTheUserWants, logger: CustomLogger}) => unknown,
beforeDelete: ({table: CurrentTable, where: SQL, logger: CustomLoger}) => boolean | ((db: Database) => unknown),
afterDelete: ({result: ResultTypeFromTheDriver, logger: CustomLogger}) => unknown,
beforeUpdate: ({ table: CurrentTable, insertedFields: InsertedFields, logger: CustomLogger }) => InsertedFields,
afterUpdate: ({result: ResultTypeFromTheDriver, logger: CustomLogger}) => unknown,
} Not sure if logger is a good fit here, or the user should have it in the scope. As you might see, the names are very descriptive and make the intent very easy to guess. This avoid the if-else nightmare of an event-like api like Here is an annotated example of what this could allow: const users = pgTable('users', {
id: serial('id').primaryKey(),
tenantId: bigint('tenant_id').notNull(),
name: varchar('name', { length: 150 }).notNull(),
password: varchar('password', { length: 300 }).notNull(),
lastName: varchar('last_name', { length: 150 }).notNull(),
fullName: varchar('full_name', { length: 300 }).notNull(),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
deletedAt: timestamp('deleted_at'),
randomId: varchar('random_id').notNull()
}).$use({
beforeSelect({table, where, selectedFields }) {
// Here we make sure that if the query is db.select().from(users), the password won't come out.
// But if we do db.select({ password: users.password }).from(users), it would work.
if (!selectedFields) {
const { password, rest: selectedFields } = getTableColumns(table);
}
// You could implement multi tenant applications like this
return { selectedFields, where: and(where, eq(table.tenantId, getTenantId()), isNull(table.deletedAt)) };
},
beforeInsert({table, insertedFields, logger}) {
// Here you can modify the data, validate. The sky is the limit.
if (Array.isArray(insertedFields) {
insertedFields = insertedFields.map((field) => userSchema.parse({
...field,
randomId: generateRandomId(),
fullName: `${field.name} ${field.lastName}`
})
} else {
insertedFields.randomId = generateRandomId();
insertedFields.fullName = `${insertedFields.name} ${insertedFields.lastName}`
insertedFields = userSchema.parse(insertedFields);
}
// Here we can define a custom object to return after the insert:
return {
insertedFields,
generatedRandomId: Array.isArray(insertedFields) ? insertedFields.map(field => field.randomId) : insertedFields.randomId
}
},
afterInsert({ result, returnedObject, logger }) {
logger("inserted random id(s): ", returnedObject.generatedRandomId)
return { result, randomId: returnedObject.generatedRandomId }
},
beforeDelete({table, where }) {
// Here we can do soft deletes:
return async (db) => await db.update(users).set({ deletedAt: new Date() }).where(and(where, eq(table.tenantId, getTenantId()))
}
} This are just the examples I came up with and that I see people asking about. |
Beta Was this translation helpful? Give feedback.
-
Middleware is a must have in my opinion. It's actually preventing me from migrating a current project to drizzle. I think Sequelize offers a strong solution. Some important aspects for me:
|
Beta Was this translation helpful? Give feedback.
-
For anyone needing to plug into Drizzle's lifecycle, check this gist. It's not perfect but it's a good workaround until this is officially supported. |
Beta Was this translation helpful? Give feedback.
-
Is there an implementation plan? I really need this feature. |
Beta Was this translation helpful? Give feedback.
-
Hello Drizzle Maintainers and Community,
I am excited to propose an enhancement to the
schema.ts
in Drizzle ORM, aiming to integrate runtime validations using libraries like Zod or Joi. This addition is poised to bring a new level of data integrity and flexibility to Drizzle ORM.Currently,
schema.ts
is structured as follows:While effective in defining the table structure, it lacks capabilities for in-depth data validation. Here's how I envision enhancing this:
Column-Level Validation:
This approach allows each column to have bespoke validation logic, offering precise control over the constraints of each field.
Example:
Table-Level Validation:
This holistic approach enables validation of the entire data structure, accommodating complex inter-field validations and business rules.
Example:
Advantages of This Approach:
Enhanced Validation Beyond DB Capabilities: This feature surpasses the typical database-level validations, enabling more sophisticated checks like string lengths, array validations, and other complex data integrity checks not typically available at the database level.
Robust Defaults: With libraries like Zod, we can define strong defaults, such as using nanoid, uuid, or even namespaced IDs (e.g.,
post_abcd12345
), providing more flexibility and robustness in data handling.Shared Validation Logic with Frontend: The proposed validation functions can be exported and reused in the frontend. This consistency ensures that both backend and frontend validations are aligned, reducing redundancy and enhancing overall application integrity.
I believe these enhancements will significantly benefit Drizzle ORM users by offering more powerful and flexible validation mechanisms, ultimately leading to more robust and reliable applications.
I'm eager to hear the community's thoughts on this proposal and am ready to actively contribute towards the implementation of this feature. Your feedback will be invaluable in shaping this potential addition to Drizzle ORM.
Thank you for considering this suggestion, and I look forward to a fruitful discussion.
Beta Was this translation helpful? Give feedback.
All reactions