@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,350 @@
1
+ ---
2
+ name: image-processing
3
+ description: Image processing patterns with Sharp for resizing, format conversion, CDN optimization, responsive images, and lazy loading.
4
+ ---
5
+
6
+ # Image Processing Patterns
7
+
8
+ ## When to Use
9
+ Apply image processing when your application handles user uploads, serves responsive images, or needs to optimize assets for web delivery. Sharp is the go-to library for server-side processing due to its speed (libvips-based). These patterns cover resizing, format conversion (WebP/AVIF), CDN integration, responsive srcset generation, and lazy loading strategies. Implement these patterns at upload time and at build time to minimize runtime processing.
10
+
11
+ ## How It Works
12
+
13
+ ### Sharp Processing Pipeline
14
+
15
+ ```typescript
16
+ // src/images/processor.ts
17
+ import sharp from 'sharp';
18
+ import path from 'path';
19
+
20
+ interface ProcessOptions {
21
+ width?: number;
22
+ height?: number;
23
+ quality?: number;
24
+ format?: 'webp' | 'avif' | 'jpeg' | 'png';
25
+ fit?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
26
+ }
27
+
28
+ export async function processImage(input: Buffer | string, options: ProcessOptions): Promise<Buffer> {
29
+ let pipeline = sharp(input);
30
+
31
+ // Auto-rotate based on EXIF
32
+ pipeline = pipeline.rotate();
33
+
34
+ // Resize
35
+ if (options.width || options.height) {
36
+ pipeline = pipeline.resize({
37
+ width: options.width,
38
+ height: options.height,
39
+ fit: options.fit ?? 'cover',
40
+ withoutEnlargement: true,
41
+ });
42
+ }
43
+
44
+ // Format conversion
45
+ const quality = options.quality ?? 80;
46
+ switch (options.format ?? 'webp') {
47
+ case 'webp':
48
+ pipeline = pipeline.webp({ quality, effort: 4 });
49
+ break;
50
+ case 'avif':
51
+ pipeline = pipeline.avif({ quality, effort: 4 });
52
+ break;
53
+ case 'jpeg':
54
+ pipeline = pipeline.jpeg({ quality, progressive: true, mozjpeg: true });
55
+ break;
56
+ case 'png':
57
+ pipeline = pipeline.png({ quality, compressionLevel: 9, palette: true });
58
+ break;
59
+ }
60
+
61
+ return pipeline.toBuffer();
62
+ }
63
+
64
+ export async function getImageMetadata(input: Buffer | string) {
65
+ const metadata = await sharp(input).metadata();
66
+ return {
67
+ width: metadata.width ?? 0,
68
+ height: metadata.height ?? 0,
69
+ format: metadata.format ?? 'unknown',
70
+ size: metadata.size ?? 0,
71
+ hasAlpha: metadata.hasAlpha ?? false,
72
+ };
73
+ }
74
+ ```
75
+
76
+ ### Responsive Image Generation
77
+
78
+ ```typescript
79
+ // src/images/responsive.ts
80
+ import sharp from 'sharp';
81
+ import path from 'path';
82
+ import fs from 'fs/promises';
83
+
84
+ interface ResponsiveConfig {
85
+ widths: number[];
86
+ formats: Array<'webp' | 'avif' | 'jpeg'>;
87
+ quality: number;
88
+ }
89
+
90
+ const defaultConfig: ResponsiveConfig = {
91
+ widths: [320, 640, 768, 1024, 1280, 1920],
92
+ formats: ['avif', 'webp', 'jpeg'],
93
+ quality: 80,
94
+ };
95
+
96
+ export async function generateResponsiveSet(
97
+ inputPath: string,
98
+ outputDir: string,
99
+ config: ResponsiveConfig = defaultConfig
100
+ ): Promise<Array<{ width: number; format: string; path: string; size: number }>> {
101
+ const results: Array<{ width: number; format: string; path: string; size: number }> = [];
102
+ const baseName = path.basename(inputPath, path.extname(inputPath));
103
+ const input = sharp(inputPath).rotate();
104
+ const metadata = await input.metadata();
105
+
106
+ await fs.mkdir(outputDir, { recursive: true });
107
+
108
+ for (const width of config.widths) {
109
+ if (width > (metadata.width ?? 0)) continue; // skip upscaling
110
+
111
+ for (const format of config.formats) {
112
+ const fileName = `${baseName}-${width}w.${format}`;
113
+ const outputPath = path.join(outputDir, fileName);
114
+
115
+ const buffer = await sharp(inputPath)
116
+ .rotate()
117
+ .resize({ width, withoutEnlargement: true })
118
+ [format]({ quality: config.quality })
119
+ .toBuffer();
120
+
121
+ await fs.writeFile(outputPath, buffer);
122
+
123
+ results.push({
124
+ width,
125
+ format,
126
+ path: outputPath,
127
+ size: buffer.length,
128
+ });
129
+ }
130
+ }
131
+
132
+ return results;
133
+ }
134
+
135
+ // Generate HTML picture element
136
+ export function generatePictureElement(
137
+ images: Array<{ width: number; format: string; path: string }>,
138
+ alt: string,
139
+ sizes: string = '(max-width: 768px) 100vw, 50vw'
140
+ ): string {
141
+ const byFormat = new Map<string, typeof images>();
142
+ for (const img of images) {
143
+ const list = byFormat.get(img.format) ?? [];
144
+ list.push(img);
145
+ byFormat.set(img.format, list);
146
+ }
147
+
148
+ const sources = ['avif', 'webp'].map((format) => {
149
+ const formatImages = byFormat.get(format);
150
+ if (!formatImages) return '';
151
+ const srcset = formatImages.map((i) => `${i.path} ${i.width}w`).join(', ');
152
+ return `<source type="image/${format}" srcset="${srcset}" sizes="${sizes}" />`;
153
+ }).filter(Boolean).join('\n ');
154
+
155
+ const fallback = byFormat.get('jpeg')?.[0];
156
+
157
+ return `<picture>
158
+ ${sources}
159
+ <img src="${fallback?.path}" alt="${alt}" loading="lazy" decoding="async" sizes="${sizes}" />
160
+ </picture>`;
161
+ }
162
+ ```
163
+
164
+ ### Upload Processing Pipeline
165
+
166
+ ```typescript
167
+ // src/images/upload-handler.ts
168
+ import { Router } from 'express';
169
+ import multer from 'multer';
170
+ import { processImage, getImageMetadata } from './processor';
171
+
172
+ const upload = multer({
173
+ limits: { fileSize: 10 * 1024 * 1024 }, // 10MB max
174
+ fileFilter: (_req, file, cb) => {
175
+ const allowed = ['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/avif'];
176
+ cb(null, allowed.includes(file.mimetype));
177
+ },
178
+ });
179
+
180
+ const router = Router();
181
+
182
+ router.post('/api/images/upload', upload.single('image'), async (req, res) => {
183
+ if (!req.file) return res.status(400).json({ error: 'No image file provided' });
184
+
185
+ const metadata = await getImageMetadata(req.file.buffer);
186
+
187
+ // Generate multiple sizes
188
+ const variants = await Promise.all([
189
+ processImage(req.file.buffer, { width: 150, height: 150, format: 'webp', fit: 'cover' })
190
+ .then((buf) => ({ name: 'thumbnail', buffer: buf })),
191
+ processImage(req.file.buffer, { width: 800, format: 'webp' })
192
+ .then((buf) => ({ name: 'medium', buffer: buf })),
193
+ processImage(req.file.buffer, { width: 1920, format: 'webp' })
194
+ .then((buf) => ({ name: 'large', buffer: buf })),
195
+ processImage(req.file.buffer, { width: 1920, format: 'avif' })
196
+ .then((buf) => ({ name: 'large-avif', buffer: buf })),
197
+ ]);
198
+
199
+ // Upload all variants to storage
200
+ const urls: Record<string, string> = {};
201
+ for (const variant of variants) {
202
+ const key = `images/${req.userId}/${Date.now()}-${variant.name}`;
203
+ urls[variant.name] = await uploadToStorage(key, variant.buffer);
204
+ }
205
+
206
+ // Generate blur placeholder
207
+ const placeholder = await sharp(req.file.buffer)
208
+ .resize(20, 20, { fit: 'inside' })
209
+ .blur()
210
+ .toBuffer();
211
+ const blurDataURL = `data:image/jpeg;base64,${placeholder.toString('base64')}`;
212
+
213
+ res.json({
214
+ urls,
215
+ metadata: { width: metadata.width, height: metadata.height, format: metadata.format },
216
+ placeholder: blurDataURL,
217
+ });
218
+ });
219
+
220
+ export default router;
221
+ ```
222
+
223
+ ### CDN-Optimized Image Serving
224
+
225
+ ```typescript
226
+ // src/images/cdn-handler.ts
227
+ import { Router } from 'express';
228
+ import sharp from 'sharp';
229
+
230
+ const router = Router();
231
+
232
+ // On-demand image transformation: /images/:key?w=800&f=webp&q=80
233
+ router.get('/images/:key(*)', async (req, res) => {
234
+ const { w, h, f, q, fit } = req.query;
235
+ const width = w ? parseInt(w as string) : undefined;
236
+ const height = h ? parseInt(h as string) : undefined;
237
+ const format = (f as 'webp' | 'avif' | 'jpeg') ?? 'webp';
238
+ const quality = q ? parseInt(q as string) : 80;
239
+
240
+ // Check cache first
241
+ const cacheKey = `img:${req.params.key}:${width}:${height}:${format}:${quality}`;
242
+ const cached = await cache.get(cacheKey);
243
+ if (cached) {
244
+ res.set({
245
+ 'Content-Type': `image/${format}`,
246
+ 'Cache-Control': 'public, max-age=31536000, immutable',
247
+ });
248
+ return res.send(cached);
249
+ }
250
+
251
+ // Fetch original
252
+ const original = await fetchFromStorage(req.params.key);
253
+ if (!original) return res.status(404).send('Image not found');
254
+
255
+ // Transform
256
+ const result = await processImage(original, {
257
+ width, height, format, quality,
258
+ fit: (fit as any) ?? 'cover',
259
+ });
260
+
261
+ // Cache transformed result
262
+ await cache.set(cacheKey, result, 86400);
263
+
264
+ res.set({
265
+ 'Content-Type': `image/${format}`,
266
+ 'Cache-Control': 'public, max-age=31536000, immutable',
267
+ 'Vary': 'Accept',
268
+ });
269
+
270
+ res.send(result);
271
+ });
272
+
273
+ export default router;
274
+ ```
275
+
276
+ ### React Lazy Loading Component
277
+
278
+ ```tsx
279
+ // src/components/OptimizedImage.tsx
280
+ import { useState, useRef, useEffect } from 'react';
281
+
282
+ interface OptimizedImageProps {
283
+ src: string;
284
+ alt: string;
285
+ width: number;
286
+ height: number;
287
+ placeholder?: string; // blur data URL
288
+ sizes?: string;
289
+ className?: string;
290
+ priority?: boolean;
291
+ }
292
+
293
+ export function OptimizedImage({
294
+ src, alt, width, height, placeholder, sizes, className, priority,
295
+ }: OptimizedImageProps) {
296
+ const [loaded, setLoaded] = useState(false);
297
+ const imgRef = useRef<HTMLImageElement>(null);
298
+
299
+ useEffect(() => {
300
+ if (imgRef.current?.complete) setLoaded(true);
301
+ }, []);
302
+
303
+ const srcSet = [320, 640, 1024, 1920]
304
+ .map((w) => `${src}?w=${w}&f=webp ${w}w`)
305
+ .join(', ');
306
+
307
+ return (
308
+ <div className="relative overflow-hidden" style={{ aspectRatio: `${width}/${height}` }}>
309
+ {placeholder && !loaded && (
310
+ <img src={placeholder} alt="" aria-hidden className="absolute inset-0 w-full h-full object-cover blur-lg scale-110" />
311
+ )}
312
+ <picture>
313
+ <source type="image/avif" srcSet={srcSet.replace(/f=webp/g, 'f=avif')} sizes={sizes} />
314
+ <source type="image/webp" srcSet={srcSet} sizes={sizes} />
315
+ <img
316
+ ref={imgRef}
317
+ src={`${src}?w=1024&f=jpeg`}
318
+ alt={alt}
319
+ width={width}
320
+ height={height}
321
+ sizes={sizes ?? '100vw'}
322
+ loading={priority ? 'eager' : 'lazy'}
323
+ decoding={priority ? 'sync' : 'async'}
324
+ onLoad={() => setLoaded(true)}
325
+ className={`${className ?? ''} transition-opacity duration-300 ${loaded ? 'opacity-100' : 'opacity-0'}`}
326
+ />
327
+ </picture>
328
+ </div>
329
+ );
330
+ }
331
+ ```
332
+
333
+ ## Examples
334
+
335
+ | Format | Quality 80 Size | Browser Support | Use Case |
336
+ |--------|----------------|-----------------|----------|
337
+ | AVIF | ~40% of JPEG | Chrome, Firefox | Best compression, modern browsers |
338
+ | WebP | ~60% of JPEG | All modern | Good balance of quality and support |
339
+ | JPEG | Baseline | Universal | Fallback for all browsers |
340
+ | PNG | Lossless | Universal | Screenshots, graphics with transparency |
341
+
342
+ ## Checklist
343
+ - [ ] Images auto-rotated based on EXIF orientation before processing
344
+ - [ ] Multiple sizes generated at upload time (thumbnail, medium, large)
345
+ - [ ] AVIF and WebP variants served with JPEG fallback via `<picture>`
346
+ - [ ] Blur placeholder generated for progressive loading experience
347
+ - [ ] `loading="lazy"` and `decoding="async"` on below-fold images
348
+ - [ ] Width and height attributes set to prevent layout shift (CLS)
349
+ - [ ] CDN Cache-Control headers set with long TTL and immutable
350
+ - [ ] Upload handler validates file type and enforces size limits
@@ -0,0 +1,226 @@
1
+ ---
2
+ name: java-springboot
3
+ description: Spring Boot patterns for dependency injection, JPA, security, actuator, profiles, and testing.
4
+ ---
5
+
6
+ # Spring Boot Patterns
7
+
8
+ ## When to Use
9
+
10
+ Apply these patterns when building Spring Boot 3.x applications with Java 17+.
11
+ Use this skill for structuring services with dependency injection, mapping JPA
12
+ entities, configuring Spring Security, exposing health checks via Actuator, and
13
+ writing integration tests.
14
+
15
+ ## How It Works
16
+
17
+ ### Dependency Injection
18
+
19
+ Use constructor injection exclusively. Avoid `@Autowired` on fields. Spring
20
+ auto-detects single-constructor beans. Use `@Qualifier` only when multiple
21
+ implementations exist.
22
+
23
+ ```java
24
+ @Service
25
+ public class OrderService {
26
+ private final OrderRepository orderRepo;
27
+ private final PaymentGateway paymentGateway;
28
+
29
+ // No @Autowired needed with single constructor
30
+ public OrderService(OrderRepository orderRepo, PaymentGateway paymentGateway) {
31
+ this.orderRepo = orderRepo;
32
+ this.paymentGateway = paymentGateway;
33
+ }
34
+
35
+ public Order place(CreateOrderRequest req) {
36
+ Order order = Order.from(req);
37
+ paymentGateway.charge(order.total());
38
+ return orderRepo.save(order);
39
+ }
40
+ }
41
+ ```
42
+
43
+ ### JPA Entities
44
+
45
+ Use `@Entity` with explicit table and column names. Prefer `IDENTITY` generation
46
+ for PostgreSQL. Always override `equals`/`hashCode` based on the business key,
47
+ not the generated ID.
48
+
49
+ ```java
50
+ @Entity
51
+ @Table(name = "orders")
52
+ public class Order {
53
+ @Id
54
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
55
+ private Long id;
56
+
57
+ @Column(nullable = false, unique = true, length = 36)
58
+ private String orderNumber;
59
+
60
+ @Enumerated(EnumType.STRING)
61
+ @Column(nullable = false)
62
+ private OrderStatus status;
63
+
64
+ @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
65
+ private List<OrderItem> items = new ArrayList<>();
66
+
67
+ @Override
68
+ public boolean equals(Object o) {
69
+ if (this == o) return true;
70
+ if (!(o instanceof Order other)) return false;
71
+ return orderNumber != null && orderNumber.equals(other.orderNumber);
72
+ }
73
+
74
+ @Override
75
+ public int hashCode() {
76
+ return Objects.hash(orderNumber);
77
+ }
78
+ }
79
+ ```
80
+
81
+ ### Repository Layer
82
+
83
+ Use Spring Data JPA interfaces. Add custom queries with `@Query` for complex
84
+ lookups. Use projections for read-only DTOs.
85
+
86
+ ```java
87
+ public interface OrderRepository extends JpaRepository<Order, Long> {
88
+ Optional<Order> findByOrderNumber(String orderNumber);
89
+
90
+ @Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.status = :status")
91
+ List<Order> findByStatusWithItems(@Param("status") OrderStatus status);
92
+
93
+ @Query("SELECT new com.example.dto.OrderSummary(o.orderNumber, o.status, o.createdAt) FROM Order o")
94
+ Page<OrderSummary> findSummaries(Pageable pageable);
95
+ }
96
+ ```
97
+
98
+ ### Spring Security
99
+
100
+ Use `SecurityFilterChain` bean configuration (not `WebSecurityConfigurerAdapter`).
101
+ Extract JWT claims into a custom `UserDetails`.
102
+
103
+ ```java
104
+ @Configuration
105
+ @EnableMethodSecurity
106
+ public class SecurityConfig {
107
+ @Bean
108
+ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
109
+ return http
110
+ .csrf(csrf -> csrf.disable())
111
+ .sessionManagement(sm -> sm.sessionCreationPolicy(STATELESS))
112
+ .authorizeHttpRequests(auth -> auth
113
+ .requestMatchers("/api/public/**").permitAll()
114
+ .requestMatchers("/api/admin/**").hasRole("ADMIN")
115
+ .anyRequest().authenticated()
116
+ )
117
+ .oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults()))
118
+ .build();
119
+ }
120
+ }
121
+ ```
122
+
123
+ ### Actuator and Health Checks
124
+
125
+ Expose `/actuator/health` for load balancers. Add custom health indicators for
126
+ downstream dependencies.
127
+
128
+ ```java
129
+ @Component
130
+ public class DatabaseHealthIndicator implements HealthIndicator {
131
+ private final DataSource dataSource;
132
+
133
+ public DatabaseHealthIndicator(DataSource dataSource) {
134
+ this.dataSource = dataSource;
135
+ }
136
+
137
+ @Override
138
+ public Health health() {
139
+ try (Connection conn = dataSource.getConnection()) {
140
+ if (conn.isValid(2)) {
141
+ return Health.up().withDetail("database", "reachable").build();
142
+ }
143
+ } catch (SQLException e) {
144
+ return Health.down(e).build();
145
+ }
146
+ return Health.down().build();
147
+ }
148
+ }
149
+ ```
150
+
151
+ ### Profiles
152
+
153
+ Use `application-{profile}.yml` for environment-specific config. Activate with
154
+ `SPRING_PROFILES_ACTIVE=prod`. Keep `application.yml` for defaults.
155
+
156
+ ```yaml
157
+ # application.yml (defaults)
158
+ spring:
159
+ jpa:
160
+ open-in-view: false
161
+
162
+ # application-prod.yml
163
+ spring:
164
+ datasource:
165
+ url: ${DATABASE_URL}
166
+ hikari:
167
+ maximum-pool-size: 20
168
+ ```
169
+
170
+ ### Testing
171
+
172
+ Use `@SpringBootTest` sparingly (slow). Prefer `@WebMvcTest` for controllers,
173
+ `@DataJpaTest` for repositories. Use `@MockBean` to replace dependencies.
174
+
175
+ ```java
176
+ @WebMvcTest(OrderController.class)
177
+ class OrderControllerTest {
178
+ @Autowired MockMvc mockMvc;
179
+ @MockBean OrderService orderService;
180
+
181
+ @Test
182
+ void getOrder_returnsOrder() throws Exception {
183
+ when(orderService.findByNumber("ORD-001"))
184
+ .thenReturn(Optional.of(testOrder()));
185
+
186
+ mockMvc.perform(get("/api/orders/ORD-001"))
187
+ .andExpect(status().isOk())
188
+ .andExpect(jsonPath("$.orderNumber").value("ORD-001"));
189
+ }
190
+
191
+ @Test
192
+ void getOrder_notFound_returns404() throws Exception {
193
+ when(orderService.findByNumber("MISSING"))
194
+ .thenReturn(Optional.empty());
195
+
196
+ mockMvc.perform(get("/api/orders/MISSING"))
197
+ .andExpect(status().isNotFound());
198
+ }
199
+ }
200
+ ```
201
+
202
+ ## Examples
203
+
204
+ **Pattern: Global exception handler**
205
+ ```java
206
+ @RestControllerAdvice
207
+ public class GlobalExceptionHandler {
208
+ @ExceptionHandler(EntityNotFoundException.class)
209
+ ResponseEntity<ErrorResponse> handleNotFound(EntityNotFoundException ex) {
210
+ return ResponseEntity.status(404).body(new ErrorResponse("NOT_FOUND", ex.getMessage()));
211
+ }
212
+ }
213
+ ```
214
+
215
+ ## Checklist
216
+
217
+ - [ ] Constructor injection only (no `@Autowired` on fields)
218
+ - [ ] JPA entities have `equals`/`hashCode` on business key, not generated ID
219
+ - [ ] `@Query` with `JOIN FETCH` to prevent N+1 queries
220
+ - [ ] `open-in-view: false` in application config
221
+ - [ ] Security config uses `SecurityFilterChain` bean, not deprecated adapter
222
+ - [ ] Actuator health endpoint exposed; custom indicators for critical dependencies
223
+ - [ ] Profile-specific configs in `application-{profile}.yml`
224
+ - [ ] `@WebMvcTest` / `@DataJpaTest` over `@SpringBootTest` where possible
225
+ - [ ] `@Transactional` on service methods that do multiple writes
226
+ - [ ] `@RestControllerAdvice` for centralized exception handling