@girardmedia/bootspring 3.3.2 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/assets/agents/accessibility-auditor.md +39 -0
  2. package/assets/agents/api-designer.md +40 -0
  3. package/assets/agents/auth-implementer.md +64 -0
  4. package/assets/agents/bug-hunter.md +42 -0
  5. package/assets/agents/bundle-analyzer.md +40 -0
  6. package/assets/agents/cache-optimizer.md +55 -0
  7. package/assets/agents/changelog-writer.md +55 -0
  8. package/assets/agents/ci-cd-builder.md +40 -0
  9. package/assets/agents/code-explainer.md +39 -0
  10. package/assets/agents/code-reviewer.md +39 -0
  11. package/assets/agents/cost-optimizer.md +57 -0
  12. package/assets/agents/cron-scheduler.md +51 -0
  13. package/assets/agents/data-seeder.md +56 -0
  14. package/assets/agents/database-architect.md +40 -0
  15. package/assets/agents/dependency-updater.md +40 -0
  16. package/assets/agents/deploy-checker.md +40 -0
  17. package/assets/agents/docker-optimizer.md +40 -0
  18. package/assets/agents/documentation-writer.md +40 -0
  19. package/assets/agents/email-builder.md +55 -0
  20. package/assets/agents/env-setup.md +40 -0
  21. package/assets/agents/error-handler.md +40 -0
  22. package/assets/agents/eslint-fixer.md +46 -0
  23. package/assets/agents/feature-flagger.md +69 -0
  24. package/assets/agents/git-detective.md +39 -0
  25. package/assets/agents/graphql-builder.md +60 -0
  26. package/assets/agents/incident-responder.md +59 -0
  27. package/assets/agents/log-analyzer.md +39 -0
  28. package/assets/agents/migration-planner.md +41 -0
  29. package/assets/agents/monorepo-navigator.md +39 -0
  30. package/assets/agents/nextjs-expert.md +57 -0
  31. package/assets/agents/notification-builder.md +56 -0
  32. package/assets/agents/onboarding-guide.md +39 -0
  33. package/assets/agents/performance-profiler.md +40 -0
  34. package/assets/agents/prisma-expert.md +57 -0
  35. package/assets/agents/rate-limiter.md +58 -0
  36. package/assets/agents/react-expert.md +58 -0
  37. package/assets/agents/refactorer.md +42 -0
  38. package/assets/agents/regex-builder.md +46 -0
  39. package/assets/agents/release-manager.md +40 -0
  40. package/assets/agents/s3-manager.md +58 -0
  41. package/assets/agents/schema-validator.md +40 -0
  42. package/assets/agents/search-builder.md +62 -0
  43. package/assets/agents/security-auditor.md +39 -0
  44. package/assets/agents/sitemap-generator.md +53 -0
  45. package/assets/agents/stripe-integrator.md +59 -0
  46. package/assets/agents/tailwind-expert.md +55 -0
  47. package/assets/agents/tech-debt-tracker.md +39 -0
  48. package/assets/agents/test-writer.md +42 -0
  49. package/assets/agents/type-fixer.md +45 -0
  50. package/assets/agents/webhook-builder.md +54 -0
  51. package/assets/rules/cpp.md +53 -0
  52. package/assets/rules/css.md +52 -0
  53. package/assets/rules/go.md +50 -0
  54. package/assets/rules/html.md +52 -0
  55. package/assets/rules/java.md +51 -0
  56. package/assets/rules/kotlin.md +50 -0
  57. package/assets/rules/php.md +51 -0
  58. package/assets/rules/python.md +51 -0
  59. package/assets/rules/ruby.md +51 -0
  60. package/assets/rules/rust.md +49 -0
  61. package/assets/rules/shell.md +52 -0
  62. package/assets/rules/sql.md +49 -0
  63. package/assets/rules/swift.md +50 -0
  64. package/assets/rules/typescript.md +52 -0
  65. package/assets/rules/yaml-json.md +51 -0
  66. package/assets/skills/accessibility.md +210 -0
  67. package/assets/skills/agent-patterns.md +387 -0
  68. package/assets/skills/ai-integration.md +263 -0
  69. package/assets/skills/animation-patterns.md +224 -0
  70. package/assets/skills/api-design.md +218 -0
  71. package/assets/skills/api-gateway.md +341 -0
  72. package/assets/skills/api-versioning.md +226 -0
  73. package/assets/skills/astro-patterns.md +233 -0
  74. package/assets/skills/auth-patterns.md +248 -0
  75. package/assets/skills/aws-patterns.md +171 -0
  76. package/assets/skills/background-jobs.md +162 -0
  77. package/assets/skills/browser-extensions.md +309 -0
  78. package/assets/skills/caching-patterns.md +253 -0
  79. package/assets/skills/ci-cd.md +251 -0
  80. package/assets/skills/cli-development.md +296 -0
  81. package/assets/skills/code-review.md +185 -0
  82. package/assets/skills/cron-patterns.md +327 -0
  83. package/assets/skills/data-fetching.md +231 -0
  84. package/assets/skills/database-migrations.md +346 -0
  85. package/assets/skills/database-patterns.md +219 -0
  86. package/assets/skills/debugging.md +281 -0
  87. package/assets/skills/design-system.md +289 -0
  88. package/assets/skills/django-patterns.md +182 -0
  89. package/assets/skills/docker-patterns.md +235 -0
  90. package/assets/skills/e2e-testing.md +287 -0
  91. package/assets/skills/edge-computing.md +268 -0
  92. package/assets/skills/electron-patterns.md +266 -0
  93. package/assets/skills/email-templates.md +206 -0
  94. package/assets/skills/error-handling.md +265 -0
  95. package/assets/skills/event-driven.md +232 -0
  96. package/assets/skills/express-patterns.md +239 -0
  97. package/assets/skills/fastapi-patterns.md +198 -0
  98. package/assets/skills/feature-flags.md +212 -0
  99. package/assets/skills/figma-to-code.md +298 -0
  100. package/assets/skills/file-upload.md +228 -0
  101. package/assets/skills/forms-patterns.md +264 -0
  102. package/assets/skills/gcp-patterns.md +189 -0
  103. package/assets/skills/git-workflow.md +187 -0
  104. package/assets/skills/golang-patterns.md +185 -0
  105. package/assets/skills/graphql-patterns.md +244 -0
  106. package/assets/skills/i18n-patterns.md +172 -0
  107. package/assets/skills/image-processing.md +350 -0
  108. package/assets/skills/java-springboot.md +226 -0
  109. package/assets/skills/kotlin-patterns.md +207 -0
  110. package/assets/skills/kubernetes-patterns.md +326 -0
  111. package/assets/skills/laravel-patterns.md +261 -0
  112. package/assets/skills/llm-fine-tuning.md +335 -0
  113. package/assets/skills/load-testing.md +303 -0
  114. package/assets/skills/logging-observability.md +228 -0
  115. package/assets/skills/markdown-processing.md +318 -0
  116. package/assets/skills/mcp-server-patterns.md +292 -0
  117. package/assets/skills/microservices.md +272 -0
  118. package/assets/skills/migration-patterns.md +239 -0
  119. package/assets/skills/mongodb-patterns.md +189 -0
  120. package/assets/skills/monorepo-patterns.md +287 -0
  121. package/assets/skills/nextjs-app-router.md +237 -0
  122. package/assets/skills/notification-patterns.md +348 -0
  123. package/assets/skills/oauth-patterns.md +246 -0
  124. package/assets/skills/payment-integration.md +222 -0
  125. package/assets/skills/pdf-generation.md +307 -0
  126. package/assets/skills/performance-optimization.md +277 -0
  127. package/assets/skills/php-patterns.md +210 -0
  128. package/assets/skills/prisma-patterns.md +241 -0
  129. package/assets/skills/prompt-engineering.md +193 -0
  130. package/assets/skills/pwa-patterns.md +247 -0
  131. package/assets/skills/python-patterns.md +158 -0
  132. package/assets/skills/python-testing.md +172 -0
  133. package/assets/skills/queue-patterns.md +295 -0
  134. package/assets/skills/rag-patterns.md +159 -0
  135. package/assets/skills/rate-limiting.md +319 -0
  136. package/assets/skills/react-components.md +201 -0
  137. package/assets/skills/react-native-patterns.md +299 -0
  138. package/assets/skills/real-time-patterns.md +181 -0
  139. package/assets/skills/redis-patterns.md +188 -0
  140. package/assets/skills/refactoring.md +218 -0
  141. package/assets/skills/regex-patterns.md +191 -0
  142. package/assets/skills/remix-patterns.md +262 -0
  143. package/assets/skills/responsive-design.md +199 -0
  144. package/assets/skills/ruby-rails-patterns.md +178 -0
  145. package/assets/skills/rust-patterns.md +211 -0
  146. package/assets/skills/search-patterns.md +227 -0
  147. package/assets/skills/security-hardening.md +237 -0
  148. package/assets/skills/seo-patterns.md +179 -0
  149. package/assets/skills/serverless-patterns.md +223 -0
  150. package/assets/skills/sql-optimization.md +154 -0
  151. package/assets/skills/state-management.md +254 -0
  152. package/assets/skills/storybook-patterns.md +330 -0
  153. package/assets/skills/svelte-patterns.md +258 -0
  154. package/assets/skills/swift-patterns.md +227 -0
  155. package/assets/skills/tailwind-patterns.md +272 -0
  156. package/assets/skills/tdd-workflow.md +199 -0
  157. package/assets/skills/terraform-patterns.md +270 -0
  158. package/assets/skills/testing-react.md +240 -0
  159. package/assets/skills/testing-vitest.md +232 -0
  160. package/assets/skills/typescript-strict.md +159 -0
  161. package/assets/skills/video-processing.md +340 -0
  162. package/assets/skills/vue-patterns.md +247 -0
  163. package/assets/skills/web-workers.md +327 -0
  164. package/assets/skills/webhooks-patterns.md +283 -0
  165. package/assets/skills/websocket-patterns.md +306 -0
  166. package/dist/cli/index.js +941 -958
  167. package/dist/core/index.d.ts +341 -11
  168. package/dist/core.js +58 -95
  169. package/dist/mcp/index.d.ts +33 -1
  170. package/dist/mcp-server.js +177 -255
  171. package/package.json +4 -1
@@ -0,0 +1,346 @@
1
+ ---
2
+ name: database-migrations
3
+ description: Database migration patterns for Prisma, Knex, zero-downtime deploys, rollback strategies, and seed data management.
4
+ ---
5
+
6
+ # Database Migration Patterns
7
+
8
+ ## When to Use
9
+ Apply migration patterns whenever you change database schema: adding tables, modifying columns, creating indexes, or altering constraints. Migrations provide version-controlled, repeatable, and reversible schema changes. Use these patterns from the first database change to prevent drift between environments. Zero-downtime techniques are essential for production deployments with active traffic.
10
+
11
+ ## How It Works
12
+
13
+ ### Prisma Migrate Workflow
14
+
15
+ ```prisma
16
+ // prisma/schema.prisma
17
+ generator client {
18
+ provider = "prisma-client-js"
19
+ }
20
+
21
+ datasource db {
22
+ provider = "postgresql"
23
+ url = env("DATABASE_URL")
24
+ }
25
+
26
+ model User {
27
+ id String @id @default(cuid())
28
+ email String @unique
29
+ name String?
30
+ role Role @default(USER)
31
+ posts Post[]
32
+ createdAt DateTime @default(now())
33
+ updatedAt DateTime @updatedAt
34
+
35
+ @@index([email])
36
+ @@index([createdAt])
37
+ }
38
+
39
+ model Post {
40
+ id String @id @default(cuid())
41
+ title String @db.VarChar(255)
42
+ slug String @unique
43
+ body String
44
+ published Boolean @default(false)
45
+ author User @relation(fields: [authorId], references: [id])
46
+ authorId String
47
+ tags Tag[]
48
+ createdAt DateTime @default(now())
49
+
50
+ @@index([authorId])
51
+ @@index([slug])
52
+ @@index([published, createdAt])
53
+ }
54
+
55
+ model Tag {
56
+ id String @id @default(cuid())
57
+ name String @unique
58
+ posts Post[]
59
+ }
60
+
61
+ enum Role {
62
+ USER
63
+ ADMIN
64
+ }
65
+ ```
66
+
67
+ ```bash
68
+ # Create a new migration
69
+ npx prisma migrate dev --name add_tags_table
70
+
71
+ # Apply migrations in production (no prompt)
72
+ npx prisma migrate deploy
73
+
74
+ # Reset database (dev only)
75
+ npx prisma migrate reset
76
+
77
+ # Check migration status
78
+ npx prisma migrate status
79
+ ```
80
+
81
+ ### Knex Migration Pattern
82
+
83
+ ```typescript
84
+ // migrations/20260526120000_create_users.ts
85
+ import { Knex } from 'knex';
86
+
87
+ export async function up(knex: Knex): Promise<void> {
88
+ await knex.schema.createTable('users', (table) => {
89
+ table.uuid('id').primary().defaultTo(knex.fn.uuid());
90
+ table.string('email', 255).notNullable().unique();
91
+ table.string('name', 255);
92
+ table.enum('role', ['user', 'admin']).defaultTo('user');
93
+ table.timestamps(true, true); // created_at, updated_at with defaults
94
+
95
+ table.index(['email']);
96
+ table.index(['created_at']);
97
+ });
98
+ }
99
+
100
+ export async function down(knex: Knex): Promise<void> {
101
+ await knex.schema.dropTableIfExists('users');
102
+ }
103
+ ```
104
+
105
+ ```typescript
106
+ // migrations/20260526130000_add_posts_table.ts
107
+ import { Knex } from 'knex';
108
+
109
+ export async function up(knex: Knex): Promise<void> {
110
+ await knex.schema.createTable('posts', (table) => {
111
+ table.uuid('id').primary().defaultTo(knex.fn.uuid());
112
+ table.string('title', 255).notNullable();
113
+ table.string('slug', 255).notNullable().unique();
114
+ table.text('body').notNullable();
115
+ table.boolean('published').defaultTo(false);
116
+ table.uuid('author_id').notNullable()
117
+ .references('id').inTable('users').onDelete('CASCADE');
118
+ table.timestamps(true, true);
119
+
120
+ table.index(['author_id']);
121
+ table.index(['slug']);
122
+ table.index(['published', 'created_at']);
123
+ });
124
+ }
125
+
126
+ export async function down(knex: Knex): Promise<void> {
127
+ await knex.schema.dropTableIfExists('posts');
128
+ }
129
+ ```
130
+
131
+ ### Zero-Downtime Column Addition
132
+
133
+ ```typescript
134
+ // Step 1: Add nullable column (non-breaking)
135
+ export async function up(knex: Knex): Promise<void> {
136
+ await knex.schema.alterTable('users', (table) => {
137
+ table.string('avatar_url', 500).nullable();
138
+ });
139
+ }
140
+
141
+ // Step 2: Backfill data (separate migration, can run while app serves traffic)
142
+ export async function up(knex: Knex): Promise<void> {
143
+ const batchSize = 1000;
144
+ let offset = 0;
145
+ let rows;
146
+
147
+ do {
148
+ rows = await knex('users')
149
+ .whereNull('avatar_url')
150
+ .limit(batchSize)
151
+ .offset(offset)
152
+ .select('id');
153
+
154
+ if (rows.length > 0) {
155
+ await knex('users')
156
+ .whereIn('id', rows.map((r: any) => r.id))
157
+ .update({ avatar_url: 'https://cdn.example.com/default-avatar.png' });
158
+ }
159
+
160
+ offset += batchSize;
161
+ } while (rows.length === batchSize);
162
+ }
163
+
164
+ // Step 3: Add NOT NULL constraint (after backfill verified)
165
+ export async function up(knex: Knex): Promise<void> {
166
+ await knex.schema.alterTable('users', (table) => {
167
+ table.string('avatar_url', 500).notNullable().defaultTo('https://cdn.example.com/default-avatar.png').alter();
168
+ });
169
+ }
170
+ ```
171
+
172
+ ### Zero-Downtime Column Rename (Expand-Contract)
173
+
174
+ ```typescript
175
+ // Phase 1: Add new column, copy data
176
+ export async function up(knex: Knex): Promise<void> {
177
+ // Add new column
178
+ await knex.schema.alterTable('users', (table) => {
179
+ table.string('display_name', 255);
180
+ });
181
+
182
+ // Copy data
183
+ await knex.raw('UPDATE users SET display_name = name');
184
+
185
+ // Add trigger to keep columns in sync during transition
186
+ await knex.raw(`
187
+ CREATE OR REPLACE FUNCTION sync_user_name() RETURNS TRIGGER AS $$
188
+ BEGIN
189
+ IF TG_OP = 'INSERT' OR NEW.name IS DISTINCT FROM OLD.name THEN
190
+ NEW.display_name = NEW.name;
191
+ END IF;
192
+ IF NEW.display_name IS DISTINCT FROM OLD.display_name THEN
193
+ NEW.name = NEW.display_name;
194
+ END IF;
195
+ RETURN NEW;
196
+ END;
197
+ $$ LANGUAGE plpgsql;
198
+
199
+ CREATE TRIGGER sync_name_trigger BEFORE INSERT OR UPDATE ON users
200
+ FOR EACH ROW EXECUTE FUNCTION sync_user_name();
201
+ `);
202
+ }
203
+
204
+ // Phase 2: Deploy app code reading from display_name
205
+ // Phase 3: Remove old column and trigger
206
+ export async function up(knex: Knex): Promise<void> {
207
+ await knex.raw('DROP TRIGGER IF EXISTS sync_name_trigger ON users');
208
+ await knex.raw('DROP FUNCTION IF EXISTS sync_user_name()');
209
+ await knex.schema.alterTable('users', (table) => {
210
+ table.dropColumn('name');
211
+ });
212
+ }
213
+ ```
214
+
215
+ ### Seed Data Management
216
+
217
+ ```typescript
218
+ // prisma/seed.ts
219
+ import { PrismaClient } from '@prisma/client';
220
+
221
+ const prisma = new PrismaClient();
222
+
223
+ async function main() {
224
+ // Upsert to make seeds idempotent
225
+ const admin = await prisma.user.upsert({
226
+ where: { email: 'admin@example.com' },
227
+ update: {},
228
+ create: {
229
+ email: 'admin@example.com',
230
+ name: 'Admin',
231
+ role: 'ADMIN',
232
+ },
233
+ });
234
+
235
+ const tags = ['typescript', 'react', 'node', 'database'];
236
+ for (const name of tags) {
237
+ await prisma.tag.upsert({
238
+ where: { name },
239
+ update: {},
240
+ create: { name },
241
+ });
242
+ }
243
+
244
+ console.log(`Seeded: ${admin.email}, ${tags.length} tags`);
245
+ }
246
+
247
+ main()
248
+ .then(() => prisma.$disconnect())
249
+ .catch((e) => {
250
+ console.error(e);
251
+ prisma.$disconnect();
252
+ process.exit(1);
253
+ });
254
+ ```
255
+
256
+ ### Rollback Strategy
257
+
258
+ ```typescript
259
+ // scripts/rollback-migration.ts
260
+ import { execSync } from 'child_process';
261
+
262
+ async function rollback(steps: number = 1) {
263
+ console.log(`Rolling back ${steps} migration(s)...`);
264
+
265
+ // For Knex
266
+ execSync(`npx knex migrate:rollback --knexfile knexfile.ts --step ${steps}`, {
267
+ stdio: 'inherit',
268
+ });
269
+
270
+ // Verify state
271
+ execSync('npx knex migrate:status --knexfile knexfile.ts', { stdio: 'inherit' });
272
+ }
273
+
274
+ // For Prisma, rollback requires resolving migration:
275
+ // npx prisma migrate resolve --rolled-back MIGRATION_NAME
276
+ // Then manually reverse the changes
277
+ ```
278
+
279
+ ### Migration Testing
280
+
281
+ ```typescript
282
+ // __tests__/migrations.test.ts
283
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
284
+ import { Knex, knex } from 'knex';
285
+
286
+ describe('database migrations', () => {
287
+ let db: Knex;
288
+
289
+ beforeAll(async () => {
290
+ db = knex({ client: 'pg', connection: process.env.TEST_DATABASE_URL });
291
+ });
292
+
293
+ afterAll(async () => { await db.destroy(); });
294
+
295
+ it('applies all migrations without error', async () => {
296
+ await db.migrate.latest();
297
+ const [, pending] = await db.migrate.status();
298
+ expect(pending).toHaveLength(0);
299
+ });
300
+
301
+ it('rolls back without error', async () => {
302
+ await db.migrate.rollback(undefined, true); // rollback all
303
+ await db.migrate.latest(); // reapply
304
+ });
305
+
306
+ it('creates expected tables', async () => {
307
+ const tables = await db.raw(`
308
+ SELECT table_name FROM information_schema.tables
309
+ WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
310
+ `);
311
+ const names = tables.rows.map((r: any) => r.table_name);
312
+ expect(names).toContain('users');
313
+ expect(names).toContain('posts');
314
+ });
315
+
316
+ it('creates expected indexes', async () => {
317
+ const indexes = await db.raw(`
318
+ SELECT indexname FROM pg_indexes WHERE tablename = 'posts'
319
+ `);
320
+ const names = indexes.rows.map((r: any) => r.indexname);
321
+ expect(names).toContain('posts_slug_unique');
322
+ expect(names).toContain('posts_author_id_index');
323
+ });
324
+ });
325
+ ```
326
+
327
+ ## Examples
328
+
329
+ | Change | Strategy | Downtime Risk |
330
+ |--------|----------|---------------|
331
+ | Add nullable column | Single migration | None |
332
+ | Add NOT NULL column | 3-step: add nullable, backfill, add constraint | None |
333
+ | Rename column | Expand-contract with sync trigger | None |
334
+ | Drop column | Remove from app code first, then drop | None if app deployed first |
335
+ | Add index | `CREATE INDEX CONCURRENTLY` (PG) | None |
336
+ | Change column type | Add new column, migrate, swap | None |
337
+
338
+ ## Checklist
339
+ - [ ] Every schema change has an up and down migration
340
+ - [ ] Migrations are idempotent (re-running does not fail)
341
+ - [ ] Seeds use upsert to be safely re-runnable
342
+ - [ ] Column additions are nullable or have defaults (non-breaking)
343
+ - [ ] Destructive changes use expand-contract pattern over multiple deploys
344
+ - [ ] Indexes created with `CONCURRENTLY` option on production databases
345
+ - [ ] Migration tests verify apply, rollback, and expected schema
346
+ - [ ] Backfill migrations process data in batches to avoid lock contention
@@ -0,0 +1,219 @@
1
+ ---
2
+ name: database-patterns
3
+ description: Design databases for performance and safety — indexes, N+1 prevention, pooling, migrations, and row-level security.
4
+ ---
5
+
6
+ # Database Patterns
7
+
8
+ ## When to Use
9
+
10
+ Apply these patterns when designing schemas, writing queries, or operating
11
+ databases in production. Most performance problems are database problems — a
12
+ missing index or N+1 query can make a 10ms page take 10 seconds. Fix these
13
+ at design time, not after users complain.
14
+
15
+ ## How It Works
16
+
17
+ ### 1. Indexing Strategy
18
+
19
+ Create indexes for columns in WHERE, JOIN, and ORDER BY clauses.
20
+
21
+ ```sql
22
+ -- Single column: filter by status
23
+ CREATE INDEX idx_orders_status ON orders(status);
24
+
25
+ -- Composite: filter by user + sort by date
26
+ CREATE INDEX idx_orders_user_created ON orders(user_id, created_at DESC);
27
+
28
+ -- Partial: only index active rows
29
+ CREATE INDEX idx_active_users ON users(email) WHERE deleted_at IS NULL;
30
+
31
+ -- Covering: index includes all selected columns (index-only scan)
32
+ CREATE INDEX idx_orders_cover ON orders(user_id, status) INCLUDE (total, created_at);
33
+ ```
34
+
35
+ Column order in composite indexes matters. The leftmost column must appear in
36
+ the query for the index to be used. `(user_id, status)` works for queries
37
+ filtering by `user_id` alone or `user_id + status`, but not `status` alone.
38
+
39
+ Always verify with EXPLAIN:
40
+
41
+ ```sql
42
+ EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 'usr_123' AND status = 'pending';
43
+ -- Look for: Index Scan, not Seq Scan
44
+ -- Check: actual time, rows examined vs returned
45
+ ```
46
+
47
+ ### 2. N+1 Query Prevention
48
+
49
+ The N+1 problem: 1 query to get 50 users, then 50 queries to get each user's
50
+ orders.
51
+
52
+ ```typescript
53
+ // Bad — N+1
54
+ const users = await db.user.findMany();
55
+ for (const user of users) {
56
+ user.orders = await db.order.findMany({ where: { userId: user.id } });
57
+ }
58
+
59
+ // Good — eager load (2 queries total)
60
+ const users = await db.user.findMany({
61
+ include: { orders: { where: { status: 'active' } } },
62
+ });
63
+
64
+ // Good — manual batch with IN clause
65
+ const users = await db.user.findMany();
66
+ const userIds = users.map((u) => u.id);
67
+ const orders = await db.order.findMany({
68
+ where: { userId: { in: userIds } },
69
+ });
70
+ const ordersByUser = groupBy(orders, 'userId');
71
+ ```
72
+
73
+ For GraphQL, use DataLoader to batch and deduplicate within a request:
74
+
75
+ ```typescript
76
+ const userLoader = new DataLoader(async (ids: string[]) => {
77
+ const users = await db.user.findMany({ where: { id: { in: ids } } });
78
+ const map = new Map(users.map((u) => [u.id, u]));
79
+ return ids.map((id) => map.get(id) ?? null);
80
+ });
81
+ ```
82
+
83
+ ### 3. Connection Pooling
84
+
85
+ Every database connection consumes memory. Pool and limit them.
86
+
87
+ ```typescript
88
+ // Prisma — configure in connection string
89
+ // postgresql://user:pass@host:5432/db?connection_limit=10&pool_timeout=30
90
+
91
+ // Node pg pool
92
+ const pool = new Pool({
93
+ max: 20, // max connections
94
+ idleTimeoutMillis: 30000,
95
+ connectionTimeoutMillis: 5000,
96
+ });
97
+ ```
98
+
99
+ Production sizing rule: start with `max = (2 * CPU cores) + disk spindles`.
100
+ For a 2-core VPS with SSD, 5-10 connections is often optimal.
101
+
102
+ ### 4. Migration Best Practices
103
+
104
+ ```sql
105
+ -- Always use IF NOT EXISTS / IF EXISTS for safety
106
+ CREATE TABLE IF NOT EXISTS audit_logs (
107
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
108
+ action TEXT NOT NULL,
109
+ user_id UUID REFERENCES users(id),
110
+ payload JSONB,
111
+ created_at TIMESTAMPTZ DEFAULT now()
112
+ );
113
+
114
+ -- Add columns as nullable first, backfill, then add NOT NULL
115
+ ALTER TABLE users ADD COLUMN phone TEXT;
116
+ -- Backfill: UPDATE users SET phone = '' WHERE phone IS NULL;
117
+ -- Then: ALTER TABLE users ALTER COLUMN phone SET NOT NULL;
118
+ ```
119
+
120
+ Rules:
121
+ - Never rename or drop a column in a single migration — it breaks running code
122
+ - Instead: add new column, deploy code that reads both, migrate data, deploy
123
+ code that reads only new, drop old column
124
+ - Keep migrations small and reversible
125
+ - Test migrations against a production-size dataset before deploying
126
+
127
+ ### 5. Read Replicas
128
+
129
+ Route read-heavy queries to replicas, writes to the primary.
130
+
131
+ ```typescript
132
+ const primary = new Pool({ connectionString: process.env.DATABASE_PRIMARY });
133
+ const replica = new Pool({ connectionString: process.env.DATABASE_REPLICA });
134
+
135
+ function getPool(operation: 'read' | 'write'): Pool {
136
+ return operation === 'write' ? primary : replica;
137
+ }
138
+
139
+ // Be aware of replication lag — after a write, read from primary briefly
140
+ async function createAndReturn(data: CreateInput) {
141
+ const created = await primary.query('INSERT INTO ... RETURNING *', [data]);
142
+ return created.rows[0]; // read from primary, not replica
143
+ }
144
+ ```
145
+
146
+ ### 6. Row-Level Security (PostgreSQL)
147
+
148
+ Let the database enforce access control, not just the application.
149
+
150
+ ```sql
151
+ -- Enable RLS on the table
152
+ ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
153
+
154
+ -- Users can only see their own documents
155
+ CREATE POLICY user_documents ON documents
156
+ USING (owner_id = current_setting('app.current_user_id')::uuid);
157
+
158
+ -- Admins can see everything
159
+ CREATE POLICY admin_all ON documents
160
+ USING (current_setting('app.current_role') = 'admin');
161
+ ```
162
+
163
+ Set the session variable before queries:
164
+
165
+ ```typescript
166
+ await pool.query(`SET app.current_user_id = $1`, [userId]);
167
+ await pool.query(`SET app.current_role = $1`, [userRole]);
168
+ const docs = await pool.query('SELECT * FROM documents'); // RLS filters automatically
169
+ ```
170
+
171
+ ### 7. Soft Deletes and Archival
172
+
173
+ ```sql
174
+ ALTER TABLE orders ADD COLUMN deleted_at TIMESTAMPTZ;
175
+
176
+ -- Partial index for non-deleted rows (most queries)
177
+ CREATE INDEX idx_orders_active ON orders(user_id, created_at)
178
+ WHERE deleted_at IS NULL;
179
+
180
+ -- View for convenience
181
+ CREATE VIEW active_orders AS
182
+ SELECT * FROM orders WHERE deleted_at IS NULL;
183
+ ```
184
+
185
+ ### 8. JSONB for Flexible Data
186
+
187
+ ```sql
188
+ -- Store semi-structured metadata
189
+ ALTER TABLE events ADD COLUMN metadata JSONB DEFAULT '{}';
190
+
191
+ -- Index for fast key lookups
192
+ CREATE INDEX idx_events_metadata ON events USING GIN (metadata);
193
+
194
+ -- Query nested fields
195
+ SELECT * FROM events WHERE metadata->>'source' = 'api';
196
+ SELECT * FROM events WHERE metadata @> '{"tags": ["urgent"]}';
197
+ ```
198
+
199
+ ## Examples
200
+
201
+ | Problem | Solution |
202
+ |---------|----------|
203
+ | Slow list queries | Composite index on filter + sort columns |
204
+ | 50 queries per page load | Eager loading or DataLoader batching |
205
+ | 100 idle connections | Connection pooling with max 10-20 |
206
+ | Column rename breaks prod | Two-phase migration (add, migrate, drop) |
207
+ | Multi-tenant data leaks | Row-level security policies |
208
+
209
+ ## Checklist
210
+
211
+ - [ ] Every foreign key column has an index
212
+ - [ ] Composite indexes match the most common query patterns
213
+ - [ ] `EXPLAIN ANALYZE` is run for new queries touching large tables
214
+ - [ ] No N+1 queries — verified with query logging in development
215
+ - [ ] Connection pool max is tuned to server capacity (not default 100)
216
+ - [ ] Migrations are backward-compatible — no column drops or renames in one step
217
+ - [ ] Read replicas handle read-heavy workloads with replication lag awareness
218
+ - [ ] Sensitive tables use row-level security or application-level tenant filtering
219
+ - [ ] JSONB columns have GIN indexes when queried frequently