{"id":51152826,"url":"https://github.com/almeidazs/better-drizzle","last_synced_at":"2026-06-26T07:30:50.643Z","repository":{"id":366695349,"uuid":"1277144785","full_name":"almeidazs/better-drizzle","owner":"almeidazs","description":"ORM, but better","archived":false,"fork":false,"pushed_at":"2026-06-22T23:53:08.000Z","size":188,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-23T01:05:44.958Z","etag":null,"topics":["better","database","drizzle","drizzle-orm","mysql","nodejs","orm","postgresql","sqlite","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/almeidazs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/funding.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"github":"almeidazs"}},"created_at":"2026-06-22T16:12:45.000Z","updated_at":"2026-06-22T23:53:12.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/almeidazs/better-drizzle","commit_stats":null,"previous_names":["almeidazs/better-drizzle"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/almeidazs/better-drizzle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almeidazs%2Fbetter-drizzle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almeidazs%2Fbetter-drizzle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almeidazs%2Fbetter-drizzle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almeidazs%2Fbetter-drizzle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/almeidazs","download_url":"https://codeload.github.com/almeidazs/better-drizzle/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/almeidazs%2Fbetter-drizzle/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34808043,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-26T02:00:06.560Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["better","database","drizzle","drizzle-orm","mysql","nodejs","orm","postgresql","sqlite","typescript"],"created_at":"2026-06-26T07:30:50.558Z","updated_at":"2026-06-26T07:30:50.636Z","avatar_url":"https://github.com/almeidazs.png","language":"TypeScript","funding_links":["https://github.com/sponsors/almeidazs"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./assets/logo.png\" alt=\"better-drizzle\" width=\"520\" /\u003e\n\u003c/p\u003e\n\n\u003cbr/\u003e\n\n\u003ch3 align=\"center\"\u003eDrizzle ORM, but better 🚀\u003c/h3\u003e\n\n\u003cdiv align=\"center\"\u003e\n\nMinimal, type-safe repository helpers for [Drizzle ORM](https://orm.drizzle.team).\n\n[better-drizzle](https://npmjs.com/package/better-drizzle) wraps an existing Drizzle client and gives each table a small, consistent API for reads, writes, pagination, nested filters, relation loading, and optional hooks. The goal is simple: keep Drizzle's type-safety, remove repetitive query glue, and stay close enough to the metal that performance still matters.\n\n\u003c/div\u003e\n\n```bash\nnpm install better-drizzle drizzle-orm\n```\n\n\u003cdiv align=\"center\"\u003e\n\n## Why\n\nDrizzle is excellent when you want explicit SQL-first control.\n\nIt gets repetitive when every service ends up re-writing the same patterns:\n\n\u003c/div\u003e\n\n- point lookups\n- relation includes\n- pagination payloads\n- existence checks\n- count helpers\n- CRUD return shapes\n- nested `where` filters\n\n\u003cdiv align=\"center\"\u003e\n\n[better-drizzle](https://npmjs.com/package/better-drizzle) packages those patterns into a small repository-style API without trying to replace Drizzle itself.\n\n## What it improves\n\n\u003c/div\u003e\n\n- Less repeated query code for common CRUD flows\n- Nested relation filters with Drizzle-backed typing\n- `include` and `select` support with typed payload inference\n- Unified pagination return shape\n- Optional lifecycle hooks for cross-cutting behavior\n- First-class plugins with setup, transforms, and client/model extensions\n- Fast paths for simple reads and writes to reduce wrapper overhead\n- Consistent table delegates: `findMany`, `findFirst`, `create`, `update`, `delete`, `paginate`, `count`, `exists`, `upsert`\n\n\u003ch2 align=\"center\"\u003eQuerying your database with Better client\u003c/h2\u003e\n\n```ts\nimport { better } from 'better-drizzle';\nimport { drizzle } from 'drizzle-orm/better-sqlite3';\n\nconst db = drizzle(sqlite, { schema });\n\nconst client = better(db, { schema });\n\nconst user = await client.users.findFirst({\n\twhere: { id: 1 },\n});\n\nconst posts = await client.posts.findMany({\n\twhere: {\n\t\tpublished: true,\n\t\tauthor: {\n\t\t\tis: {\n\t\t\t\tactive: true,\n\t\t\t},\n\t\t},\n\t},\n\tinclude: {\n\t\tauthor: true,\n\t},\n\torderBy: [{ id: 'desc' }],\n\ttake: 20,\n});\n```\n\n\u003cdiv align=\"center\"\u003e\n\n**Check whether a user exists or not and count after it.**\n\n\u003c/div\u003e\n\n```ts\nconst exists = await client.users.exists({\n\twhere: { id: 123 },\n});\n\nconst count = await client.users.count({\n\twhere: {\n\t\tname: { contains: 'drizzle-orm' },\n\t},\n});\n```\n\n\u003cdiv align=\"center\"\u003e\n\n**Create and update the user.**\n\n\u003c/div\u003e\n\n```ts\nconst someUser = await client.users.create({\n\tdata: {\n\t\tid: 123,\n\t\tname: 'better',\n\t},\n});\n\nconst user = await client.users.update({\n\tdata: {\n\t\tname: 'better-drizzle',\n\t},\n\twhere: { id: someUser.id },\n});\n\nconst maybeCreated = await client.users.create({\n\tdata: {\n\t\temail: 'better@example.com',\n\t\tid: 124,\n\t\tname: 'better-again',\n\t},\n\tskipDuplicates: true,\n});\n\nif (!maybeCreated) {\n\tconsole.log('user already existed');\n}\n```\n\n\u003cdiv align=\"center\"\u003e\n\n**You can where queries like drizzle too**\n\n\u003c/div\u003e\n\n```ts\nconst { count } = await client.users.delete({\n\twhere: eq(users.id, 123),\n});\n```\n\n\u003cdiv align=\"center\"\u003e\n\n**You can also resolve repositories dynamically.**\n\n\u003c/div\u003e\n\n```ts\nconst users = client.repository('users');\n```\n\n\u003cdiv align=\"center\"\u003e\n\nThe repository name can be the TypeScript schema key or the database table name.\n\n## Transactions\n\nTransactions live on the client, not on individual models. The callback receives a full Better Drizzle client bound to the underlying transaction, so model delegates, plugin args, transforms, hooks, and nested transactions all keep working.\n\n\u003c/div\u003e\n\n```ts\nconst user = await client.transaction(async (tx) =\u003e {\n\tconst created = await tx.users.create({\n\t\tdata: {\n\t\t\temail: 'better@example.com',\n\t\t\tid: 123,\n\t\t\tname: 'better',\n\t\t},\n\t});\n\n\ttx.afterCommit(async () =\u003e {\n\t\tawait sendWelcomeEmail(created.email);\n\t});\n\n\treturn created;\n});\n```\n\n\u003cdiv align=\"center\"\u003e\n\n## Plugins\n\nPlugins let you package setup logic, query transforms, and reusable client/model extensions without wrapping `better(...)` yourself.\n\nPlugins can also extend the built-in operation args in a fully typed way through `operationArgs`, so custom fields like `deleted` or `mode` flow from the delegate call into plugin transforms and hooks.\n\n\u003c/div\u003e\n\n```ts\nimport { better } from 'better-drizzle';\nimport { timestamps } from '@better-drizzle/timestamps';\nimport { softDelete } from '@better-drizzle/soft-delete';\n\nconst client = better(drizzle, {\n\tschema,\n\tplugins: [\n\t\ttimestamps({\n\t\t\tcreatedAt: 'created_at',\n\t\t\tupdatedAt: 'updated_at',\n\t\t}),\n\t\tsoftDelete({\n\t\t\tcolumn: 'deletedAt',\n\t\t\tdeletedByColumn: 'deletedById',\n\t\t\tdefaults: {\n\t\t\t\tmode: 'soft',\n\t\t\t\tvisibility: 'without',\n\t\t\t},\n\t\t}),\n\t],\n});\n\nawait client.users.delete({\n\twhere: { id: 1 },\n});\n\nawait client.users.findMany({\n\tdeleted: 'only',\n});\n\nawait client.users.restore({\n\twhere: { id: 1 },\n});\n```\n\n\u003cdiv align=\"center\"\u003e\n\nNow you can soft delete rows easily and also have timestamps fields injected automatically.\n\n## Hooks\n\nThe client accepts optional hooks through `better(db, options)`. This is useful for auditing, tracing, metrics, authorization, and other cross-cutting concerns that you do not want duplicated in every repository call.\n\nThe hook layer is optional. If you do not need it, do not pass it.\n\n**Always assign a random UUID in the user before creating it.**\n\n\u003c/div\u003e\n\n```ts\nconst client = better(drizzle, {\n\tschema,\n\thooks: {\n\t\tbeforeCreate({ data: user }) {\n\t\t\tuser.organizationId = randomUUID();\n\t\t},\n\t},\n});\n```\n\n\u003cdiv align=\"center\"\u003e\n\n## Performance\n\nSee the full benchmark suite and results in [`benchmark/README.md`](/benchmark). The suite covers reads, writes, and transactions (including nested savepoints) with fair API-parity comparisons against raw Drizzle.\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falmeidazs%2Fbetter-drizzle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falmeidazs%2Fbetter-drizzle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falmeidazs%2Fbetter-drizzle/lists"}