autoworkflow 3.1.5 → 3.6.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 (124) hide show
  1. package/.claude/commands/analyze.md +19 -0
  2. package/.claude/commands/audit.md +26 -0
  3. package/.claude/commands/build.md +39 -0
  4. package/.claude/commands/commit.md +25 -0
  5. package/.claude/commands/fix.md +23 -0
  6. package/.claude/commands/plan.md +18 -0
  7. package/.claude/commands/suggest.md +23 -0
  8. package/.claude/commands/verify.md +18 -0
  9. package/.claude/hooks/post-bash-router.sh +20 -0
  10. package/.claude/hooks/post-commit.sh +140 -0
  11. package/.claude/hooks/post-edit.sh +190 -17
  12. package/.claude/hooks/pre-edit.sh +221 -0
  13. package/.claude/hooks/session-check.sh +90 -0
  14. package/.claude/settings.json +56 -6
  15. package/.claude/settings.local.json +5 -1
  16. package/.claude/skills/actix.md +337 -0
  17. package/.claude/skills/alembic.md +504 -0
  18. package/.claude/skills/angular.md +237 -0
  19. package/.claude/skills/api-design.md +187 -0
  20. package/.claude/skills/aspnet-core.md +377 -0
  21. package/.claude/skills/astro.md +245 -0
  22. package/.claude/skills/auth-clerk.md +327 -0
  23. package/.claude/skills/auth-firebase.md +367 -0
  24. package/.claude/skills/auth-nextauth.md +359 -0
  25. package/.claude/skills/auth-supabase.md +368 -0
  26. package/.claude/skills/axum.md +386 -0
  27. package/.claude/skills/blazor.md +456 -0
  28. package/.claude/skills/chi.md +348 -0
  29. package/.claude/skills/code-review.md +133 -0
  30. package/.claude/skills/csharp.md +296 -0
  31. package/.claude/skills/css-modules.md +325 -0
  32. package/.claude/skills/cypress.md +343 -0
  33. package/.claude/skills/debugging.md +133 -0
  34. package/.claude/skills/diesel.md +392 -0
  35. package/.claude/skills/django.md +301 -0
  36. package/.claude/skills/docker.md +319 -0
  37. package/.claude/skills/doctrine.md +473 -0
  38. package/.claude/skills/documentation.md +182 -0
  39. package/.claude/skills/dotnet.md +409 -0
  40. package/.claude/skills/drizzle.md +293 -0
  41. package/.claude/skills/echo.md +321 -0
  42. package/.claude/skills/eloquent.md +256 -0
  43. package/.claude/skills/emotion.md +426 -0
  44. package/.claude/skills/entity-framework.md +370 -0
  45. package/.claude/skills/express.md +316 -0
  46. package/.claude/skills/fastapi.md +329 -0
  47. package/.claude/skills/fastify.md +299 -0
  48. package/.claude/skills/fiber.md +315 -0
  49. package/.claude/skills/flask.md +322 -0
  50. package/.claude/skills/gin.md +342 -0
  51. package/.claude/skills/git.md +116 -0
  52. package/.claude/skills/github-actions.md +353 -0
  53. package/.claude/skills/go.md +377 -0
  54. package/.claude/skills/gorm.md +409 -0
  55. package/.claude/skills/graphql.md +478 -0
  56. package/.claude/skills/hibernate.md +379 -0
  57. package/.claude/skills/hono.md +306 -0
  58. package/.claude/skills/java.md +400 -0
  59. package/.claude/skills/jest.md +313 -0
  60. package/.claude/skills/jpa.md +282 -0
  61. package/.claude/skills/kotlin.md +347 -0
  62. package/.claude/skills/kubernetes.md +363 -0
  63. package/.claude/skills/laravel.md +414 -0
  64. package/.claude/skills/mcp-browser.md +320 -0
  65. package/.claude/skills/mcp-database.md +219 -0
  66. package/.claude/skills/mcp-fetch.md +241 -0
  67. package/.claude/skills/mcp-filesystem.md +204 -0
  68. package/.claude/skills/mcp-github.md +217 -0
  69. package/.claude/skills/mcp-memory.md +240 -0
  70. package/.claude/skills/mcp-search.md +218 -0
  71. package/.claude/skills/mcp-slack.md +262 -0
  72. package/.claude/skills/micronaut.md +388 -0
  73. package/.claude/skills/mongodb.md +319 -0
  74. package/.claude/skills/mongoose.md +355 -0
  75. package/.claude/skills/mysql.md +281 -0
  76. package/.claude/skills/nestjs.md +335 -0
  77. package/.claude/skills/nextjs-app-router.md +260 -0
  78. package/.claude/skills/nextjs-pages.md +172 -0
  79. package/.claude/skills/nuxt.md +202 -0
  80. package/.claude/skills/openapi.md +489 -0
  81. package/.claude/skills/performance.md +199 -0
  82. package/.claude/skills/php.md +398 -0
  83. package/.claude/skills/playwright.md +371 -0
  84. package/.claude/skills/postgresql.md +257 -0
  85. package/.claude/skills/prisma.md +293 -0
  86. package/.claude/skills/pydantic.md +304 -0
  87. package/.claude/skills/pytest.md +313 -0
  88. package/.claude/skills/python.md +272 -0
  89. package/.claude/skills/quarkus.md +377 -0
  90. package/.claude/skills/react.md +230 -0
  91. package/.claude/skills/redis.md +391 -0
  92. package/.claude/skills/refactoring.md +143 -0
  93. package/.claude/skills/remix.md +246 -0
  94. package/.claude/skills/rest-api.md +490 -0
  95. package/.claude/skills/rocket.md +366 -0
  96. package/.claude/skills/rust.md +341 -0
  97. package/.claude/skills/sass.md +380 -0
  98. package/.claude/skills/sea-orm.md +382 -0
  99. package/.claude/skills/security.md +167 -0
  100. package/.claude/skills/sequelize.md +395 -0
  101. package/.claude/skills/spring-boot.md +416 -0
  102. package/.claude/skills/sqlalchemy.md +269 -0
  103. package/.claude/skills/sqlx-rust.md +408 -0
  104. package/.claude/skills/state-jotai.md +346 -0
  105. package/.claude/skills/state-mobx.md +353 -0
  106. package/.claude/skills/state-pinia.md +431 -0
  107. package/.claude/skills/state-redux.md +337 -0
  108. package/.claude/skills/state-tanstack-query.md +434 -0
  109. package/.claude/skills/state-zustand.md +340 -0
  110. package/.claude/skills/styled-components.md +403 -0
  111. package/.claude/skills/svelte.md +238 -0
  112. package/.claude/skills/sveltekit.md +207 -0
  113. package/.claude/skills/symfony.md +437 -0
  114. package/.claude/skills/tailwind.md +279 -0
  115. package/.claude/skills/terraform.md +394 -0
  116. package/.claude/skills/testing-library.md +371 -0
  117. package/.claude/skills/trpc.md +426 -0
  118. package/.claude/skills/typeorm.md +368 -0
  119. package/.claude/skills/vitest.md +330 -0
  120. package/.claude/skills/vue.md +202 -0
  121. package/.claude/skills/warp.md +365 -0
  122. package/README.md +163 -52
  123. package/package.json +1 -1
  124. package/system/triggers.md +256 -17
@@ -0,0 +1,370 @@
1
+ # Entity Framework Core Skill
2
+
3
+ ## DbContext Configuration
4
+ \`\`\`csharp
5
+ public class AppDbContext : DbContext
6
+ {
7
+ public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
8
+
9
+ public DbSet<User> Users => Set<User>();
10
+ public DbSet<Post> Posts => Set<Post>();
11
+ public DbSet<Comment> Comments => Set<Comment>();
12
+
13
+ protected override void OnModelCreating(ModelBuilder builder)
14
+ {
15
+ // Apply all configurations from assembly
16
+ builder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
17
+ }
18
+
19
+ // Override SaveChanges for auditing
20
+ public override async Task<int> SaveChangesAsync(CancellationToken ct = default)
21
+ {
22
+ foreach (var entry in ChangeTracker.Entries<IAuditable>())
23
+ {
24
+ if (entry.State == EntityState.Added)
25
+ {
26
+ entry.Entity.CreatedAt = DateTime.UtcNow;
27
+ }
28
+ entry.Entity.UpdatedAt = DateTime.UtcNow;
29
+ }
30
+ return await base.SaveChangesAsync(ct);
31
+ }
32
+ }
33
+
34
+ // Registration in Program.cs
35
+ builder.Services.AddDbContext<AppDbContext>(options =>
36
+ options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))
37
+ .EnableSensitiveDataLogging(builder.Environment.IsDevelopment())
38
+ .EnableDetailedErrors(builder.Environment.IsDevelopment()));
39
+ \`\`\`
40
+
41
+ ## Entity Configuration (Fluent API)
42
+ \`\`\`csharp
43
+ // User entity
44
+ public class User
45
+ {
46
+ public string Id { get; set; } = Guid.NewGuid().ToString();
47
+ public string Email { get; set; } = null!;
48
+ public string Name { get; set; } = null!;
49
+ public string PasswordHash { get; set; } = null!;
50
+ public bool IsActive { get; set; } = true;
51
+ public DateTime CreatedAt { get; set; }
52
+ public DateTime UpdatedAt { get; set; }
53
+
54
+ // Navigation properties
55
+ public Profile? Profile { get; set; }
56
+ public ICollection<Post> Posts { get; set; } = new List<Post>();
57
+ public ICollection<Role> Roles { get; set; } = new List<Role>();
58
+ }
59
+
60
+ // Configuration class
61
+ public class UserConfiguration : IEntityTypeConfiguration<User>
62
+ {
63
+ public void Configure(EntityTypeBuilder<User> builder)
64
+ {
65
+ builder.ToTable("users");
66
+
67
+ builder.HasKey(u => u.Id);
68
+
69
+ builder.Property(u => u.Email)
70
+ .HasMaxLength(255)
71
+ .IsRequired();
72
+
73
+ builder.HasIndex(u => u.Email)
74
+ .IsUnique();
75
+
76
+ builder.Property(u => u.Name)
77
+ .HasMaxLength(100)
78
+ .IsRequired();
79
+
80
+ builder.Property(u => u.PasswordHash)
81
+ .HasColumnName("password_hash")
82
+ .IsRequired();
83
+
84
+ // One-to-one
85
+ builder.HasOne(u => u.Profile)
86
+ .WithOne(p => p.User)
87
+ .HasForeignKey<Profile>(p => p.UserId)
88
+ .OnDelete(DeleteBehavior.Cascade);
89
+
90
+ // One-to-many
91
+ builder.HasMany(u => u.Posts)
92
+ .WithOne(p => p.Author)
93
+ .HasForeignKey(p => p.AuthorId)
94
+ .OnDelete(DeleteBehavior.Cascade);
95
+
96
+ // Many-to-many
97
+ builder.HasMany(u => u.Roles)
98
+ .WithMany(r => r.Users)
99
+ .UsingEntity<Dictionary<string, object>>(
100
+ "user_roles",
101
+ j => j.HasOne<Role>().WithMany().HasForeignKey("role_id"),
102
+ j => j.HasOne<User>().WithMany().HasForeignKey("user_id"));
103
+ }
104
+ }
105
+ \`\`\`
106
+
107
+ ## Migrations
108
+ \`\`\`bash
109
+ # Add migration
110
+ dotnet ef migrations add CreateUsersTable
111
+
112
+ # Update database
113
+ dotnet ef database update
114
+
115
+ # Generate SQL script
116
+ dotnet ef migrations script -o migrations.sql
117
+
118
+ # Revert to specific migration
119
+ dotnet ef database update PreviousMigration
120
+
121
+ # Remove last migration (if not applied)
122
+ dotnet ef migrations remove
123
+ \`\`\`
124
+
125
+ ## Queries
126
+ \`\`\`csharp
127
+ public class UserRepository : IUserRepository
128
+ {
129
+ private readonly AppDbContext _context;
130
+
131
+ public UserRepository(AppDbContext context)
132
+ {
133
+ _context = context;
134
+ }
135
+
136
+ // Basic queries
137
+ public async Task<User?> GetByIdAsync(string id, CancellationToken ct = default)
138
+ {
139
+ return await _context.Users.FindAsync(new object[] { id }, ct);
140
+ }
141
+
142
+ public async Task<User?> GetByEmailAsync(string email, CancellationToken ct = default)
143
+ {
144
+ return await _context.Users
145
+ .FirstOrDefaultAsync(u => u.Email == email, ct);
146
+ }
147
+
148
+ // Eager loading
149
+ public async Task<User?> GetWithPostsAsync(string id, CancellationToken ct = default)
150
+ {
151
+ return await _context.Users
152
+ .Include(u => u.Posts)
153
+ .Include(u => u.Profile)
154
+ .FirstOrDefaultAsync(u => u.Id == id, ct);
155
+ }
156
+
157
+ // Filtered include
158
+ public async Task<User?> GetWithActivePostsAsync(string id, CancellationToken ct = default)
159
+ {
160
+ return await _context.Users
161
+ .Include(u => u.Posts.Where(p => p.Published))
162
+ .FirstOrDefaultAsync(u => u.Id == id, ct);
163
+ }
164
+
165
+ // Pagination
166
+ public async Task<(List<User> Users, int Total)> GetPagedAsync(
167
+ int page, int pageSize, CancellationToken ct = default)
168
+ {
169
+ var query = _context.Users.Where(u => u.IsActive);
170
+
171
+ var total = await query.CountAsync(ct);
172
+ var users = await query
173
+ .OrderByDescending(u => u.CreatedAt)
174
+ .Skip((page - 1) * pageSize)
175
+ .Take(pageSize)
176
+ .ToListAsync(ct);
177
+
178
+ return (users, total);
179
+ }
180
+
181
+ // Projection
182
+ public async Task<List<UserSummary>> GetSummariesAsync(CancellationToken ct = default)
183
+ {
184
+ return await _context.Users
185
+ .Where(u => u.IsActive)
186
+ .Select(u => new UserSummary(u.Id, u.Email, u.Name))
187
+ .ToListAsync(ct);
188
+ }
189
+
190
+ // Raw SQL
191
+ public async Task<List<User>> SearchAsync(string query, CancellationToken ct = default)
192
+ {
193
+ return await _context.Users
194
+ .FromSqlInterpolated($"SELECT * FROM users WHERE name ILIKE {'%' + query + '%'}")
195
+ .ToListAsync(ct);
196
+ }
197
+ }
198
+
199
+ public record UserSummary(string Id, string Email, string Name);
200
+ \`\`\`
201
+
202
+ ## CRUD Operations
203
+ \`\`\`csharp
204
+ // Create
205
+ public async Task<User> CreateAsync(CreateUserRequest request, CancellationToken ct = default)
206
+ {
207
+ var user = new User
208
+ {
209
+ Email = request.Email,
210
+ Name = request.Name,
211
+ PasswordHash = HashPassword(request.Password)
212
+ };
213
+
214
+ _context.Users.Add(user);
215
+ await _context.SaveChangesAsync(ct);
216
+
217
+ return user;
218
+ }
219
+
220
+ // Update
221
+ public async Task<User?> UpdateAsync(
222
+ string id, UpdateUserRequest request, CancellationToken ct = default)
223
+ {
224
+ var user = await _context.Users.FindAsync(new object[] { id }, ct);
225
+ if (user is null) return null;
226
+
227
+ if (request.Name is not null)
228
+ user.Name = request.Name;
229
+ if (request.Email is not null)
230
+ user.Email = request.Email;
231
+
232
+ await _context.SaveChangesAsync(ct);
233
+ return user;
234
+ }
235
+
236
+ // Delete
237
+ public async Task<bool> DeleteAsync(string id, CancellationToken ct = default)
238
+ {
239
+ var user = await _context.Users.FindAsync(new object[] { id }, ct);
240
+ if (user is null) return false;
241
+
242
+ _context.Users.Remove(user);
243
+ await _context.SaveChangesAsync(ct);
244
+ return true;
245
+ }
246
+
247
+ // Bulk update
248
+ public async Task DeactivateOldUsersAsync(DateTime before, CancellationToken ct = default)
249
+ {
250
+ await _context.Users
251
+ .Where(u => u.CreatedAt < before && u.IsActive)
252
+ .ExecuteUpdateAsync(s => s.SetProperty(u => u.IsActive, false), ct);
253
+ }
254
+
255
+ // Bulk delete
256
+ public async Task DeleteInactiveAsync(CancellationToken ct = default)
257
+ {
258
+ await _context.Users
259
+ .Where(u => !u.IsActive)
260
+ .ExecuteDeleteAsync(ct);
261
+ }
262
+ \`\`\`
263
+
264
+ ## Transactions
265
+ \`\`\`csharp
266
+ public async Task CreateUserWithProfileAsync(
267
+ CreateUserRequest request, CreateProfileRequest profileRequest, CancellationToken ct = default)
268
+ {
269
+ await using var transaction = await _context.Database.BeginTransactionAsync(ct);
270
+
271
+ try
272
+ {
273
+ var user = new User
274
+ {
275
+ Email = request.Email,
276
+ Name = request.Name,
277
+ PasswordHash = HashPassword(request.Password)
278
+ };
279
+ _context.Users.Add(user);
280
+ await _context.SaveChangesAsync(ct);
281
+
282
+ var profile = new Profile
283
+ {
284
+ UserId = user.Id,
285
+ Bio = profileRequest.Bio
286
+ };
287
+ _context.Profiles.Add(profile);
288
+ await _context.SaveChangesAsync(ct);
289
+
290
+ await transaction.CommitAsync(ct);
291
+ }
292
+ catch
293
+ {
294
+ await transaction.RollbackAsync(ct);
295
+ throw;
296
+ }
297
+ }
298
+
299
+ // With execution strategy (for retries)
300
+ public async Task CreateWithRetryAsync(User user, CancellationToken ct = default)
301
+ {
302
+ var strategy = _context.Database.CreateExecutionStrategy();
303
+
304
+ await strategy.ExecuteAsync(async () =>
305
+ {
306
+ await using var transaction = await _context.Database.BeginTransactionAsync(ct);
307
+
308
+ _context.Users.Add(user);
309
+ await _context.SaveChangesAsync(ct);
310
+ // More operations...
311
+
312
+ await transaction.CommitAsync(ct);
313
+ });
314
+ }
315
+ \`\`\`
316
+
317
+ ## Change Tracking
318
+ \`\`\`csharp
319
+ // No-tracking queries (read-only, better performance)
320
+ public async Task<List<User>> GetAllReadOnlyAsync(CancellationToken ct = default)
321
+ {
322
+ return await _context.Users
323
+ .AsNoTracking()
324
+ .ToListAsync(ct);
325
+ }
326
+
327
+ // Disable tracking globally for read-heavy scenarios
328
+ services.AddDbContext<AppDbContext>(options =>
329
+ options.UseNpgsql(connectionString)
330
+ .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
331
+
332
+ // Check entity state
333
+ var entry = _context.Entry(user);
334
+ var state = entry.State; // Added, Modified, Deleted, Unchanged, Detached
335
+
336
+ // Explicit state change
337
+ _context.Entry(user).State = EntityState.Modified;
338
+
339
+ // Attach detached entity
340
+ _context.Users.Attach(user);
341
+ \`\`\`
342
+
343
+ ## Compiled Queries
344
+ \`\`\`csharp
345
+ // For frequently executed queries
346
+ private static readonly Func<AppDbContext, string, Task<User?>> GetByEmailQuery =
347
+ EF.CompileAsyncQuery((AppDbContext ctx, string email) =>
348
+ ctx.Users.FirstOrDefault(u => u.Email == email));
349
+
350
+ public async Task<User?> GetByEmailOptimizedAsync(string email)
351
+ {
352
+ return await GetByEmailQuery(_context, email);
353
+ }
354
+ \`\`\`
355
+
356
+ ## ✅ DO
357
+ - Use \`IEntityTypeConfiguration<T>\` for entity configuration
358
+ - Use \`AsNoTracking()\` for read-only queries
359
+ - Use projections (\`Select\`) to fetch only needed fields
360
+ - Use \`Include\` for eager loading to avoid N+1
361
+ - Use \`ExecuteUpdateAsync\`/\`ExecuteDeleteAsync\` for bulk operations
362
+ - Pass \`CancellationToken\` to all async methods
363
+
364
+ ## ❌ DON'T
365
+ - Don't call \`ToList()\` before \`Where()\` (fetches all rows)
366
+ - Don't use lazy loading (enabled by default in some versions)
367
+ - Don't forget \`AsNoTracking()\` for read-only queries
368
+ - Don't use \`Find()\` with includes (use \`FirstOrDefault\`)
369
+ - Don't expose \`DbContext\` outside repository layer
370
+ - Don't ignore migration conflicts - resolve them properly
@@ -0,0 +1,316 @@
1
+ # Express.js Skill
2
+
3
+ ## Project Structure
4
+ \`\`\`
5
+ src/
6
+ ├── index.ts # App entry
7
+ ├── app.ts # Express app setup
8
+ ├── routes/
9
+ │ ├── index.ts # Route aggregator
10
+ │ └── users.ts # User routes
11
+ ├── controllers/
12
+ │ └── users.ts # Route handlers
13
+ ├── middleware/
14
+ │ ├── auth.ts # Authentication
15
+ │ ├── validate.ts # Validation
16
+ │ └── errorHandler.ts # Error handling
17
+ ├── services/
18
+ │ └── users.ts # Business logic
19
+ └── types/
20
+ └── express.d.ts # Type extensions
21
+ \`\`\`
22
+
23
+ ## App Setup
24
+ \`\`\`typescript
25
+ import express from 'express';
26
+ import cors from 'cors';
27
+ import helmet from 'helmet';
28
+ import { router } from './routes';
29
+ import { errorHandler } from './middleware/errorHandler';
30
+
31
+ const app = express();
32
+
33
+ // Security middleware
34
+ app.use(helmet());
35
+ app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') }));
36
+
37
+ // Body parsing
38
+ app.use(express.json({ limit: '10kb' }));
39
+ app.use(express.urlencoded({ extended: true }));
40
+
41
+ // Request logging
42
+ app.use((req, res, next) => {
43
+ console.log(\`\${req.method} \${req.path}\`);
44
+ next();
45
+ });
46
+
47
+ // Routes
48
+ app.use('/api', router);
49
+
50
+ // 404 handler
51
+ app.use((req, res) => {
52
+ res.status(404).json({ error: 'Not found' });
53
+ });
54
+
55
+ // Error handler (must be last)
56
+ app.use(errorHandler);
57
+
58
+ export { app };
59
+ \`\`\`
60
+
61
+ ## Router Pattern
62
+ \`\`\`typescript
63
+ // routes/users.ts
64
+ import { Router } from 'express';
65
+ import { auth } from '../middleware/auth';
66
+ import { validate } from '../middleware/validate';
67
+ import { createUserSchema, updateUserSchema } from '../schemas/user';
68
+ import * as userController from '../controllers/users';
69
+
70
+ const router = Router();
71
+
72
+ router.get('/', userController.getUsers);
73
+ router.get('/:id', userController.getUser);
74
+ router.post('/', validate(createUserSchema), userController.createUser);
75
+ router.put('/:id', auth, validate(updateUserSchema), userController.updateUser);
76
+ router.delete('/:id', auth, userController.deleteUser);
77
+
78
+ export { router as userRouter };
79
+
80
+ // routes/index.ts
81
+ import { Router } from 'express';
82
+ import { userRouter } from './users';
83
+
84
+ const router = Router();
85
+
86
+ router.use('/users', userRouter);
87
+
88
+ export { router };
89
+ \`\`\`
90
+
91
+ ## Controller Pattern
92
+ \`\`\`typescript
93
+ // controllers/users.ts
94
+ import type { Request, Response, NextFunction } from 'express';
95
+ import { userService } from '../services/users';
96
+
97
+ export const getUsers = async (req: Request, res: Response, next: NextFunction) => {
98
+ try {
99
+ const { page = 1, limit = 10 } = req.query;
100
+ const users = await userService.findAll({ page: +page, limit: +limit });
101
+ res.json(users);
102
+ } catch (error) {
103
+ next(error);
104
+ }
105
+ };
106
+
107
+ export const getUser = async (req: Request, res: Response, next: NextFunction) => {
108
+ try {
109
+ const user = await userService.findById(req.params.id);
110
+ if (!user) {
111
+ return res.status(404).json({ error: 'User not found' });
112
+ }
113
+ res.json(user);
114
+ } catch (error) {
115
+ next(error);
116
+ }
117
+ };
118
+
119
+ export const createUser = async (req: Request, res: Response, next: NextFunction) => {
120
+ try {
121
+ const user = await userService.create(req.body);
122
+ res.status(201).json(user);
123
+ } catch (error) {
124
+ next(error);
125
+ }
126
+ };
127
+ \`\`\`
128
+
129
+ ## Validation Middleware
130
+ \`\`\`typescript
131
+ // middleware/validate.ts
132
+ import { z, ZodSchema } from 'zod';
133
+ import type { Request, Response, NextFunction } from 'express';
134
+
135
+ export const validate = (schema: ZodSchema) => {
136
+ return (req: Request, res: Response, next: NextFunction) => {
137
+ const result = schema.safeParse({
138
+ body: req.body,
139
+ query: req.query,
140
+ params: req.params,
141
+ });
142
+
143
+ if (!result.success) {
144
+ return res.status(400).json({
145
+ error: 'Validation failed',
146
+ details: result.error.flatten(),
147
+ });
148
+ }
149
+
150
+ req.body = result.data.body;
151
+ next();
152
+ };
153
+ };
154
+
155
+ // schemas/user.ts
156
+ export const createUserSchema = z.object({
157
+ body: z.object({
158
+ email: z.string().email(),
159
+ name: z.string().min(1).max(100),
160
+ password: z.string().min(8),
161
+ }),
162
+ });
163
+ \`\`\`
164
+
165
+ ## Authentication Middleware
166
+ \`\`\`typescript
167
+ // middleware/auth.ts
168
+ import type { Request, Response, NextFunction } from 'express';
169
+ import jwt from 'jsonwebtoken';
170
+
171
+ // Extend Express Request type
172
+ declare global {
173
+ namespace Express {
174
+ interface Request {
175
+ user?: { id: string; email: string };
176
+ }
177
+ }
178
+ }
179
+
180
+ export const auth = async (req: Request, res: Response, next: NextFunction) => {
181
+ const authHeader = req.headers.authorization;
182
+
183
+ if (!authHeader?.startsWith('Bearer ')) {
184
+ return res.status(401).json({ error: 'No token provided' });
185
+ }
186
+
187
+ const token = authHeader.split(' ')[1];
188
+
189
+ try {
190
+ const payload = jwt.verify(token, process.env.JWT_SECRET!) as { id: string; email: string };
191
+ req.user = payload;
192
+ next();
193
+ } catch {
194
+ return res.status(401).json({ error: 'Invalid token' });
195
+ }
196
+ };
197
+
198
+ // Optional auth (doesn't fail if no token)
199
+ export const optionalAuth = async (req: Request, res: Response, next: NextFunction) => {
200
+ const authHeader = req.headers.authorization;
201
+
202
+ if (authHeader?.startsWith('Bearer ')) {
203
+ try {
204
+ const token = authHeader.split(' ')[1];
205
+ req.user = jwt.verify(token, process.env.JWT_SECRET!) as any;
206
+ } catch {
207
+ // Ignore invalid tokens for optional auth
208
+ }
209
+ }
210
+
211
+ next();
212
+ };
213
+ \`\`\`
214
+
215
+ ## Error Handler
216
+ \`\`\`typescript
217
+ // middleware/errorHandler.ts
218
+ import type { Request, Response, NextFunction } from 'express';
219
+
220
+ class AppError extends Error {
221
+ constructor(
222
+ public statusCode: number,
223
+ public message: string,
224
+ public isOperational = true
225
+ ) {
226
+ super(message);
227
+ }
228
+ }
229
+
230
+ export const errorHandler = (
231
+ err: Error,
232
+ req: Request,
233
+ res: Response,
234
+ next: NextFunction
235
+ ) => {
236
+ // Log error
237
+ console.error(err);
238
+
239
+ if (err instanceof AppError) {
240
+ return res.status(err.statusCode).json({
241
+ error: err.message,
242
+ });
243
+ }
244
+
245
+ // Don't leak error details in production
246
+ res.status(500).json({
247
+ error: process.env.NODE_ENV === 'production'
248
+ ? 'Internal server error'
249
+ : err.message,
250
+ });
251
+ };
252
+
253
+ export { AppError };
254
+ \`\`\`
255
+
256
+ ## Async Handler Wrapper
257
+ \`\`\`typescript
258
+ // utils/asyncHandler.ts
259
+ import type { Request, Response, NextFunction, RequestHandler } from 'express';
260
+
261
+ type AsyncHandler = (
262
+ req: Request,
263
+ res: Response,
264
+ next: NextFunction
265
+ ) => Promise<any>;
266
+
267
+ export const asyncHandler = (fn: AsyncHandler): RequestHandler => {
268
+ return (req, res, next) => {
269
+ Promise.resolve(fn(req, res, next)).catch(next);
270
+ };
271
+ };
272
+
273
+ // Usage
274
+ router.get('/users', asyncHandler(async (req, res) => {
275
+ const users = await userService.findAll();
276
+ res.json(users);
277
+ }));
278
+ \`\`\`
279
+
280
+ ## File Upload
281
+ \`\`\`typescript
282
+ import multer from 'multer';
283
+
284
+ const upload = multer({
285
+ storage: multer.memoryStorage(),
286
+ limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
287
+ fileFilter: (req, file, cb) => {
288
+ if (file.mimetype.startsWith('image/')) {
289
+ cb(null, true);
290
+ } else {
291
+ cb(new Error('Only images allowed'));
292
+ }
293
+ },
294
+ });
295
+
296
+ router.post('/upload', upload.single('image'), async (req, res) => {
297
+ const file = req.file;
298
+ // Process file...
299
+ res.json({ url: uploadedUrl });
300
+ });
301
+ \`\`\`
302
+
303
+ ## ❌ DON'T
304
+ - Use synchronous error handling (use async/await + next)
305
+ - Forget to call next(error) in catch blocks
306
+ - Put business logic in controllers
307
+ - Trust req.body without validation
308
+ - Expose stack traces in production
309
+
310
+ ## ✅ DO
311
+ - Use middleware for cross-cutting concerns
312
+ - Validate all request bodies with Zod
313
+ - Handle errors centrally with error middleware
314
+ - Use async wrapper or try/catch with next(error)
315
+ - Extend Request type for custom properties
316
+ - Separate routes, controllers, and services