@plazmodium/odin 0.3.2-beta → 0.3.4-beta
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.
- package/README.md +82 -11
- package/builtin/ODIN.md +1045 -0
- package/builtin/agent-definitions/README.md +170 -0
- package/builtin/agent-definitions/_shared-context.md +377 -0
- package/builtin/agent-definitions/architect.md +627 -0
- package/builtin/agent-definitions/builder.md +716 -0
- package/builtin/agent-definitions/discovery.md +293 -0
- package/builtin/agent-definitions/documenter.md +238 -0
- package/builtin/agent-definitions/guardian.md +1049 -0
- package/builtin/agent-definitions/integrator.md +363 -0
- package/builtin/agent-definitions/planning.md +236 -0
- package/builtin/agent-definitions/product.md +405 -0
- package/builtin/agent-definitions/release.md +430 -0
- package/builtin/agent-definitions/reviewer.md +447 -0
- package/builtin/agent-definitions/watcher.md +402 -0
- package/builtin/skills/api/graphql/SKILL.md +548 -0
- package/builtin/skills/api/grpc/SKILL.md +554 -0
- package/builtin/skills/api/rest-api/SKILL.md +469 -0
- package/builtin/skills/api/trpc/SKILL.md +503 -0
- package/builtin/skills/architecture/clean-architecture/SKILL.md +141 -0
- package/builtin/skills/architecture/domain-driven-design/SKILL.md +129 -0
- package/builtin/skills/architecture/event-driven/SKILL.md +145 -0
- package/builtin/skills/architecture/microservices/SKILL.md +143 -0
- package/builtin/skills/architecture/tla-precheck/SKILL.md +171 -0
- package/builtin/skills/backend/golang-gin/SKILL.md +141 -0
- package/builtin/skills/backend/nodejs-express/SKILL.md +277 -0
- package/builtin/skills/backend/nodejs-fastify/SKILL.md +152 -0
- package/builtin/skills/backend/python-django/SKILL.md +128 -0
- package/builtin/skills/backend/python-fastapi/SKILL.md +140 -0
- package/builtin/skills/database/mongodb/SKILL.md +132 -0
- package/builtin/skills/database/postgresql/SKILL.md +120 -0
- package/builtin/skills/database/prisma-orm/SKILL.md +366 -0
- package/builtin/skills/database/redis/SKILL.md +140 -0
- package/builtin/skills/database/supabase/SKILL.md +416 -0
- package/builtin/skills/devops/aws/SKILL.md +382 -0
- package/builtin/skills/devops/docker/SKILL.md +359 -0
- package/builtin/skills/devops/github-actions/SKILL.md +435 -0
- package/builtin/skills/devops/kubernetes/SKILL.md +459 -0
- package/builtin/skills/devops/terraform/SKILL.md +453 -0
- package/builtin/skills/frontend/alpine-dev/SKILL.md +27 -0
- package/builtin/skills/frontend/angular-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/astro-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/htmx-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/nextjs-dev/SKILL.md +470 -0
- package/builtin/skills/frontend/react-patterns/SKILL.md +166 -0
- package/builtin/skills/frontend/svelte-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/tailwindcss/SKILL.md +131 -0
- package/builtin/skills/frontend/vuejs-dev/SKILL.md +28 -0
- package/builtin/skills/generic-dev/SKILL.md +307 -0
- package/builtin/skills/testing/cypress/SKILL.md +372 -0
- package/builtin/skills/testing/jest/SKILL.md +176 -0
- package/builtin/skills/testing/playwright/SKILL.md +341 -0
- package/builtin/skills/testing/unit-tests-eval-sdd/SKILL.md +73 -0
- package/builtin/skills/testing/unit-tests-sdd/SKILL.md +83 -0
- package/builtin/skills/testing/vitest/SKILL.md +249 -0
- package/dist/adapters/skills/filesystem.d.ts.map +1 -1
- package/dist/adapters/skills/filesystem.js +2 -18
- package/dist/adapters/skills/filesystem.js.map +1 -1
- package/dist/builtin-assets.d.ts +8 -0
- package/dist/builtin-assets.d.ts.map +1 -0
- package/dist/builtin-assets.js +90 -0
- package/dist/builtin-assets.js.map +1 -0
- package/dist/init.js +69 -11
- package/dist/init.js.map +1 -1
- package/dist/schemas.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/prepare-phase-context.d.ts.map +1 -1
- package/dist/tools/prepare-phase-context.js +5 -0
- package/dist/tools/prepare-phase-context.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -3
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: golang-gin
|
|
3
|
+
description: Gin HTTP web framework for building high-performance Go APIs
|
|
4
|
+
category: backend
|
|
5
|
+
version: "1.9+"
|
|
6
|
+
compatible_with:
|
|
7
|
+
- postgresql
|
|
8
|
+
- mongodb
|
|
9
|
+
- redis
|
|
10
|
+
- rest-api
|
|
11
|
+
- grpc
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Go + Gin
|
|
15
|
+
|
|
16
|
+
## Overview
|
|
17
|
+
|
|
18
|
+
Gin is a high-performance HTTP web framework for Go. It provides routing, middleware, JSON validation, and rendering with minimal overhead.
|
|
19
|
+
|
|
20
|
+
## Project Structure
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
cmd/
|
|
24
|
+
├── server/
|
|
25
|
+
│ └── main.go # Entry point
|
|
26
|
+
internal/
|
|
27
|
+
├── config/
|
|
28
|
+
│ └── config.go # Configuration loading
|
|
29
|
+
├── handler/
|
|
30
|
+
│ ├── auth.go # Auth handlers
|
|
31
|
+
│ └── user.go # User handlers
|
|
32
|
+
├── middleware/
|
|
33
|
+
│ ├── auth.go # JWT middleware
|
|
34
|
+
│ └── cors.go # CORS middleware
|
|
35
|
+
├── model/
|
|
36
|
+
│ └── user.go # Domain models + DB structs
|
|
37
|
+
├── repository/
|
|
38
|
+
│ └── user_repo.go # Database access
|
|
39
|
+
├── service/
|
|
40
|
+
│ └── user_service.go # Business logic
|
|
41
|
+
└── router/
|
|
42
|
+
└── router.go # Route registration
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Core Patterns
|
|
46
|
+
|
|
47
|
+
### Router Setup
|
|
48
|
+
|
|
49
|
+
```go
|
|
50
|
+
func SetupRouter(userHandler *handler.UserHandler, authMW gin.HandlerFunc) *gin.Engine {
|
|
51
|
+
r := gin.Default()
|
|
52
|
+
|
|
53
|
+
r.Use(middleware.CORS())
|
|
54
|
+
|
|
55
|
+
api := r.Group("/api/v1")
|
|
56
|
+
{
|
|
57
|
+
auth := api.Group("/auth")
|
|
58
|
+
auth.POST("/login", userHandler.Login)
|
|
59
|
+
auth.POST("/register", userHandler.Register)
|
|
60
|
+
|
|
61
|
+
users := api.Group("/users")
|
|
62
|
+
users.Use(authMW)
|
|
63
|
+
users.GET("/:id", userHandler.GetByID)
|
|
64
|
+
users.PUT("/:id", userHandler.Update)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return r
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Handlers
|
|
72
|
+
|
|
73
|
+
```go
|
|
74
|
+
type UserHandler struct {
|
|
75
|
+
service service.UserService
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
func (h *UserHandler) GetByID(c *gin.Context) {
|
|
79
|
+
id := c.Param("id")
|
|
80
|
+
user, err := h.service.GetByID(c.Request.Context(), id)
|
|
81
|
+
if err != nil {
|
|
82
|
+
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
c.JSON(http.StatusOK, user)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Request binding with validation
|
|
89
|
+
type CreateUserRequest struct {
|
|
90
|
+
Email string `json:"email" binding:"required,email"`
|
|
91
|
+
Password string `json:"password" binding:"required,min=8"`
|
|
92
|
+
Name string `json:"name" binding:"required,max=100"`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func (h *UserHandler) Register(c *gin.Context) {
|
|
96
|
+
var req CreateUserRequest
|
|
97
|
+
if err := c.ShouldBindJSON(&req); err != nil {
|
|
98
|
+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
// ... create user
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Middleware
|
|
106
|
+
|
|
107
|
+
```go
|
|
108
|
+
func AuthMiddleware(secret string) gin.HandlerFunc {
|
|
109
|
+
return func(c *gin.Context) {
|
|
110
|
+
token := c.GetHeader("Authorization")
|
|
111
|
+
if token == "" {
|
|
112
|
+
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing token"})
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
claims, err := validateJWT(strings.TrimPrefix(token, "Bearer "), secret)
|
|
116
|
+
if err != nil {
|
|
117
|
+
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
c.Set("userID", claims.Subject)
|
|
121
|
+
c.Next()
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Best Practices
|
|
127
|
+
|
|
128
|
+
1. **Use `internal/`** — prevents external packages from importing your business logic
|
|
129
|
+
2. **Dependency injection** — pass dependencies via struct constructors, not globals
|
|
130
|
+
3. **Context propagation** — always pass `c.Request.Context()` to service/repo layers
|
|
131
|
+
4. **Graceful shutdown** — use `signal.NotifyContext` for clean shutdown
|
|
132
|
+
5. **Error wrapping** — use `fmt.Errorf("...: %w", err)` for error chains
|
|
133
|
+
6. **Structured logging** — use `slog` (Go 1.21+) or `zerolog`
|
|
134
|
+
7. **Interface-based repos** — define interfaces in the consumer package, not the provider
|
|
135
|
+
|
|
136
|
+
## Gotchas
|
|
137
|
+
|
|
138
|
+
- **Gin's `c.JSON` doesn't return** — always `return` after error responses
|
|
139
|
+
- **Binding validation tags** — `binding:"required"` only works with `ShouldBind*`
|
|
140
|
+
- **Goroutine safety** — don't share `gin.Context` across goroutines; copy needed values first
|
|
141
|
+
- **`c.Param` vs `c.Query`** — path params vs query strings are different methods
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nodejs-express
|
|
3
|
+
description: Node.js with Express.js framework expertise for building REST APIs, middleware patterns, and server-side applications
|
|
4
|
+
category: backend
|
|
5
|
+
version: "4.x"
|
|
6
|
+
compatible_with:
|
|
7
|
+
- postgresql
|
|
8
|
+
- prisma-orm
|
|
9
|
+
- supabase
|
|
10
|
+
- jest
|
|
11
|
+
- typescript
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Node.js + Express Development
|
|
15
|
+
|
|
16
|
+
## Overview
|
|
17
|
+
|
|
18
|
+
Express.js is a minimal, flexible Node.js web application framework providing robust features for building web and mobile applications.
|
|
19
|
+
|
|
20
|
+
## Project Structure
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
src/
|
|
24
|
+
├── index.ts # Entry point
|
|
25
|
+
├── app.ts # Express app setup
|
|
26
|
+
├── routes/
|
|
27
|
+
│ ├── index.ts # Route aggregator
|
|
28
|
+
│ ├── auth.routes.ts # Auth endpoints
|
|
29
|
+
│ └── user.routes.ts # User endpoints
|
|
30
|
+
├── controllers/
|
|
31
|
+
│ ├── auth.controller.ts
|
|
32
|
+
│ └── user.controller.ts
|
|
33
|
+
├── services/
|
|
34
|
+
│ ├── auth.service.ts
|
|
35
|
+
│ └── user.service.ts
|
|
36
|
+
├── middleware/
|
|
37
|
+
│ ├── auth.middleware.ts
|
|
38
|
+
│ ├── error.middleware.ts
|
|
39
|
+
│ └── validation.middleware.ts
|
|
40
|
+
├── models/ # Database models
|
|
41
|
+
├── utils/
|
|
42
|
+
│ └── asyncHandler.ts # Async error wrapper
|
|
43
|
+
└── types/
|
|
44
|
+
└── index.ts # TypeScript types
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Core Patterns
|
|
48
|
+
|
|
49
|
+
### App Setup
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// src/app.ts
|
|
53
|
+
import express from 'express';
|
|
54
|
+
import cors from 'cors';
|
|
55
|
+
import helmet from 'helmet';
|
|
56
|
+
import { errorHandler } from './middleware/error.middleware';
|
|
57
|
+
import routes from './routes';
|
|
58
|
+
|
|
59
|
+
const app = express();
|
|
60
|
+
|
|
61
|
+
// Security middleware
|
|
62
|
+
app.use(helmet());
|
|
63
|
+
app.use(cors());
|
|
64
|
+
|
|
65
|
+
// Body parsing
|
|
66
|
+
app.use(express.json());
|
|
67
|
+
app.use(express.urlencoded({ extended: true }));
|
|
68
|
+
|
|
69
|
+
// Routes
|
|
70
|
+
app.use('/api/v1', routes);
|
|
71
|
+
|
|
72
|
+
// Error handling (must be last)
|
|
73
|
+
app.use(errorHandler);
|
|
74
|
+
|
|
75
|
+
export default app;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Async Handler Pattern
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// src/utils/asyncHandler.ts
|
|
82
|
+
import { Request, Response, NextFunction } from 'express';
|
|
83
|
+
|
|
84
|
+
type AsyncFunction = (
|
|
85
|
+
req: Request,
|
|
86
|
+
res: Response,
|
|
87
|
+
next: NextFunction
|
|
88
|
+
) => Promise<any>;
|
|
89
|
+
|
|
90
|
+
export const asyncHandler = (fn: AsyncFunction) => {
|
|
91
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
92
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Controller Pattern
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// src/controllers/user.controller.ts
|
|
101
|
+
import { Request, Response } from 'express';
|
|
102
|
+
import { asyncHandler } from '../utils/asyncHandler';
|
|
103
|
+
import * as userService from '../services/user.service';
|
|
104
|
+
|
|
105
|
+
export const getUsers = asyncHandler(async (req: Request, res: Response) => {
|
|
106
|
+
const users = await userService.findAll();
|
|
107
|
+
res.json({ success: true, data: users });
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export const getUserById = asyncHandler(async (req: Request, res: Response) => {
|
|
111
|
+
const user = await userService.findById(req.params.id);
|
|
112
|
+
if (!user) {
|
|
113
|
+
res.status(404).json({ success: false, error: 'User not found' });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
res.json({ success: true, data: user });
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Route Definition
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// src/routes/user.routes.ts
|
|
124
|
+
import { Router } from 'express';
|
|
125
|
+
import { getUsers, getUserById, createUser } from '../controllers/user.controller';
|
|
126
|
+
import { authenticate } from '../middleware/auth.middleware';
|
|
127
|
+
import { validate } from '../middleware/validation.middleware';
|
|
128
|
+
import { createUserSchema } from '../validators/user.validator';
|
|
129
|
+
|
|
130
|
+
const router = Router();
|
|
131
|
+
|
|
132
|
+
router.get('/', authenticate, getUsers);
|
|
133
|
+
router.get('/:id', authenticate, getUserById);
|
|
134
|
+
router.post('/', authenticate, validate(createUserSchema), createUser);
|
|
135
|
+
|
|
136
|
+
export default router;
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Error Handling Middleware
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// src/middleware/error.middleware.ts
|
|
143
|
+
import { Request, Response, NextFunction } from 'express';
|
|
144
|
+
|
|
145
|
+
export class AppError extends Error {
|
|
146
|
+
constructor(
|
|
147
|
+
public statusCode: number,
|
|
148
|
+
public message: string,
|
|
149
|
+
public isOperational = true
|
|
150
|
+
) {
|
|
151
|
+
super(message);
|
|
152
|
+
Object.setPrototypeOf(this, AppError.prototype);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export const errorHandler = (
|
|
157
|
+
err: Error,
|
|
158
|
+
req: Request,
|
|
159
|
+
res: Response,
|
|
160
|
+
next: NextFunction
|
|
161
|
+
) => {
|
|
162
|
+
if (err instanceof AppError) {
|
|
163
|
+
return res.status(err.statusCode).json({
|
|
164
|
+
success: false,
|
|
165
|
+
error: err.message
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.error('Unexpected error:', err);
|
|
170
|
+
res.status(500).json({
|
|
171
|
+
success: false,
|
|
172
|
+
error: 'Internal server error'
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Auth Middleware
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// src/middleware/auth.middleware.ts
|
|
181
|
+
import { Request, Response, NextFunction } from 'express';
|
|
182
|
+
import jwt from 'jsonwebtoken';
|
|
183
|
+
import { AppError } from './error.middleware';
|
|
184
|
+
|
|
185
|
+
export interface AuthRequest extends Request {
|
|
186
|
+
user?: { id: string; email: string };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export const authenticate = (
|
|
190
|
+
req: AuthRequest,
|
|
191
|
+
res: Response,
|
|
192
|
+
next: NextFunction
|
|
193
|
+
) => {
|
|
194
|
+
const authHeader = req.headers.authorization;
|
|
195
|
+
|
|
196
|
+
if (!authHeader?.startsWith('Bearer ')) {
|
|
197
|
+
throw new AppError(401, 'No token provided');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const token = authHeader.split(' ')[1];
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as {
|
|
204
|
+
id: string;
|
|
205
|
+
email: string;
|
|
206
|
+
};
|
|
207
|
+
req.user = decoded;
|
|
208
|
+
next();
|
|
209
|
+
} catch {
|
|
210
|
+
throw new AppError(401, 'Invalid token');
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Best Practices
|
|
216
|
+
|
|
217
|
+
1. **Always use asyncHandler** - Prevents unhandled promise rejections
|
|
218
|
+
2. **Validate input** - Use Zod or Joi for request validation
|
|
219
|
+
3. **Layer your code** - Routes → Controllers → Services → Models
|
|
220
|
+
4. **Use TypeScript** - Type safety prevents runtime errors
|
|
221
|
+
5. **Environment variables** - Never hardcode secrets
|
|
222
|
+
6. **Error middleware last** - Must be registered after routes
|
|
223
|
+
7. **Use HTTP status codes correctly** - 200, 201, 400, 401, 403, 404, 500
|
|
224
|
+
|
|
225
|
+
## Common Response Format
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
// Success
|
|
229
|
+
{ success: true, data: {...} }
|
|
230
|
+
|
|
231
|
+
// Error
|
|
232
|
+
{ success: false, error: "Error message" }
|
|
233
|
+
|
|
234
|
+
// Paginated
|
|
235
|
+
{
|
|
236
|
+
success: true,
|
|
237
|
+
data: [...],
|
|
238
|
+
pagination: {
|
|
239
|
+
page: 1,
|
|
240
|
+
limit: 20,
|
|
241
|
+
total: 100,
|
|
242
|
+
totalPages: 5
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Gotchas & Pitfalls
|
|
248
|
+
|
|
249
|
+
- **Forgetting async error handling** - Always use asyncHandler or try-catch
|
|
250
|
+
- **Middleware order matters** - Auth before routes, error handler last
|
|
251
|
+
- **Not ending response** - Always call res.json(), res.send(), or next()
|
|
252
|
+
- **Memory leaks** - Clean up listeners, close DB connections on shutdown
|
|
253
|
+
- **CORS issues** - Configure cors() properly for your frontend domain
|
|
254
|
+
|
|
255
|
+
## Integration Notes
|
|
256
|
+
|
|
257
|
+
### With Prisma
|
|
258
|
+
```typescript
|
|
259
|
+
import { PrismaClient } from '@prisma/client';
|
|
260
|
+
const prisma = new PrismaClient();
|
|
261
|
+
|
|
262
|
+
// In service
|
|
263
|
+
export const findAll = () => prisma.user.findMany();
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### With Supabase
|
|
267
|
+
```typescript
|
|
268
|
+
import { createClient } from '@supabase/supabase-js';
|
|
269
|
+
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
|
270
|
+
|
|
271
|
+
// In service
|
|
272
|
+
export const findAll = async () => {
|
|
273
|
+
const { data, error } = await supabase.from('users').select('*');
|
|
274
|
+
if (error) throw new AppError(500, error.message);
|
|
275
|
+
return data;
|
|
276
|
+
};
|
|
277
|
+
```
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nodejs-fastify
|
|
3
|
+
description: Fastify framework for building high-performance Node.js APIs with schema-based validation
|
|
4
|
+
category: backend
|
|
5
|
+
version: "4.x"
|
|
6
|
+
compatible_with:
|
|
7
|
+
- postgresql
|
|
8
|
+
- prisma-orm
|
|
9
|
+
- mongodb
|
|
10
|
+
- redis
|
|
11
|
+
- rest-api
|
|
12
|
+
- typescript
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Node.js + Fastify
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
Fastify is a high-performance Node.js web framework focused on developer experience and low overhead. It uses JSON Schema for request/response validation and serialization.
|
|
20
|
+
|
|
21
|
+
## Project Structure
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
src/
|
|
25
|
+
├── app.ts # Fastify instance + plugin registration
|
|
26
|
+
├── server.ts # Entry point (start server)
|
|
27
|
+
├── plugins/
|
|
28
|
+
│ ├── auth.ts # Auth decorator/plugin
|
|
29
|
+
│ └── database.ts # DB connection plugin
|
|
30
|
+
├── routes/
|
|
31
|
+
│ ├── auth/
|
|
32
|
+
│ │ ├── index.ts # Route registration
|
|
33
|
+
│ │ └── schema.ts # JSON schemas
|
|
34
|
+
│ └── users/
|
|
35
|
+
│ ├── index.ts
|
|
36
|
+
│ └── schema.ts
|
|
37
|
+
├── services/
|
|
38
|
+
│ └── user.service.ts # Business logic
|
|
39
|
+
├── types/
|
|
40
|
+
│ └── index.ts # TypeScript types
|
|
41
|
+
└── tests/
|
|
42
|
+
└── routes/
|
|
43
|
+
└── auth.test.ts
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Core Patterns
|
|
47
|
+
|
|
48
|
+
### App Setup
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import Fastify from 'fastify';
|
|
52
|
+
|
|
53
|
+
const app = Fastify({
|
|
54
|
+
logger: true,
|
|
55
|
+
ajv: { customOptions: { removeAdditional: 'all' } },
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Register plugins
|
|
59
|
+
await app.register(import('./plugins/database'));
|
|
60
|
+
await app.register(import('./plugins/auth'));
|
|
61
|
+
|
|
62
|
+
// Register routes
|
|
63
|
+
await app.register(import('./routes/auth'), { prefix: '/auth' });
|
|
64
|
+
await app.register(import('./routes/users'), { prefix: '/users' });
|
|
65
|
+
|
|
66
|
+
export default app;
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Routes with Schema Validation
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { FastifyPluginAsync } from 'fastify';
|
|
73
|
+
|
|
74
|
+
const userRoutes: FastifyPluginAsync = async (fastify) => {
|
|
75
|
+
fastify.get('/:id', {
|
|
76
|
+
schema: {
|
|
77
|
+
params: { type: 'object', properties: { id: { type: 'string', format: 'uuid' } }, required: ['id'] },
|
|
78
|
+
response: {
|
|
79
|
+
200: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {
|
|
82
|
+
id: { type: 'string' },
|
|
83
|
+
email: { type: 'string' },
|
|
84
|
+
name: { type: 'string' },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
preHandler: [fastify.authenticate],
|
|
90
|
+
handler: async (request, reply) => {
|
|
91
|
+
const { id } = request.params as { id: string };
|
|
92
|
+
const user = await fastify.userService.getById(id);
|
|
93
|
+
if (!user) return reply.code(404).send({ error: 'Not found' });
|
|
94
|
+
return user;
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default userRoutes;
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Plugins (Decorators)
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import fp from 'fastify-plugin';
|
|
106
|
+
|
|
107
|
+
export default fp(async (fastify) => {
|
|
108
|
+
const db = await connectToDatabase(fastify.config.DATABASE_URL);
|
|
109
|
+
|
|
110
|
+
fastify.decorate('db', db);
|
|
111
|
+
fastify.addHook('onClose', async () => { await db.close(); });
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Type augmentation
|
|
115
|
+
declare module 'fastify' {
|
|
116
|
+
interface FastifyInstance {
|
|
117
|
+
db: Database;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Error Handling
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
app.setErrorHandler((error, request, reply) => {
|
|
126
|
+
request.log.error(error);
|
|
127
|
+
|
|
128
|
+
if (error.validation) {
|
|
129
|
+
return reply.code(400).send({ error: 'Validation failed', details: error.validation });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
reply.code(error.statusCode ?? 500).send({
|
|
133
|
+
error: error.message || 'Internal Server Error',
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Best Practices
|
|
139
|
+
|
|
140
|
+
1. **Schema-first** — define JSON Schema for all request/response; Fastify uses it for validation AND serialization (faster than manual serialization)
|
|
141
|
+
2. **Encapsulation** — use plugins for encapsulated contexts; `fastify-plugin` for shared decorators
|
|
142
|
+
3. **Type augmentation** — extend `FastifyInstance`/`FastifyRequest` for decorators
|
|
143
|
+
4. **Autoload** — use `@fastify/autoload` to auto-register routes and plugins
|
|
144
|
+
5. **Testing** — use `app.inject()` for integration tests (no real HTTP needed)
|
|
145
|
+
6. **Hooks** — prefer `onRequest`/`preHandler` hooks over middleware for auth
|
|
146
|
+
|
|
147
|
+
## Gotchas
|
|
148
|
+
|
|
149
|
+
- **Plugin encapsulation** — decorators are scoped to the plugin unless wrapped with `fastify-plugin`
|
|
150
|
+
- **Schema serialization** — response schemas strip undeclared properties (security feature, but surprising)
|
|
151
|
+
- **Async/await** — always return or `await`; unhandled promise rejections crash the server
|
|
152
|
+
- **Decorator timing** — decorators must be registered before routes that use them
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: python-django
|
|
3
|
+
description: Django web framework patterns for building full-stack Python applications with batteries included
|
|
4
|
+
category: backend
|
|
5
|
+
version: "5.x"
|
|
6
|
+
compatible_with:
|
|
7
|
+
- postgresql
|
|
8
|
+
- rest-api
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Python Django
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
Django is a high-level Python web framework that follows the "batteries included" philosophy. It provides ORM, admin, auth, forms, and templating out of the box.
|
|
16
|
+
|
|
17
|
+
## Project Structure
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
project/
|
|
21
|
+
├── manage.py
|
|
22
|
+
├── config/
|
|
23
|
+
│ ├── settings/
|
|
24
|
+
│ │ ├── base.py # Shared settings
|
|
25
|
+
│ │ ├── development.py # Dev overrides
|
|
26
|
+
│ │ └── production.py # Prod overrides
|
|
27
|
+
│ ├── urls.py # Root URL conf
|
|
28
|
+
│ └── wsgi.py
|
|
29
|
+
├── apps/
|
|
30
|
+
│ └── users/
|
|
31
|
+
│ ├── models.py # Database models
|
|
32
|
+
│ ├── views.py # View logic
|
|
33
|
+
│ ├── serializers.py # DRF serializers (if API)
|
|
34
|
+
│ ├── urls.py # App URL routes
|
|
35
|
+
│ ├── admin.py # Admin config
|
|
36
|
+
│ ├── services.py # Business logic
|
|
37
|
+
│ ├── tests/
|
|
38
|
+
│ │ ├── test_models.py
|
|
39
|
+
│ │ └── test_views.py
|
|
40
|
+
│ └── migrations/
|
|
41
|
+
└── requirements/
|
|
42
|
+
├── base.txt
|
|
43
|
+
└── dev.txt
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Core Patterns
|
|
47
|
+
|
|
48
|
+
### Models
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from django.db import models
|
|
52
|
+
from django.contrib.auth.models import AbstractUser
|
|
53
|
+
|
|
54
|
+
class User(AbstractUser):
|
|
55
|
+
email = models.EmailField(unique=True)
|
|
56
|
+
bio = models.TextField(blank=True, default="")
|
|
57
|
+
|
|
58
|
+
USERNAME_FIELD = "email"
|
|
59
|
+
REQUIRED_FIELDS = ["username"]
|
|
60
|
+
|
|
61
|
+
class Meta:
|
|
62
|
+
ordering = ["-date_joined"]
|
|
63
|
+
|
|
64
|
+
def __str__(self):
|
|
65
|
+
return self.email
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Views (DRF)
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from rest_framework import viewsets, permissions, status
|
|
72
|
+
from rest_framework.decorators import action
|
|
73
|
+
from rest_framework.response import Response
|
|
74
|
+
|
|
75
|
+
class UserViewSet(viewsets.ModelViewSet):
|
|
76
|
+
queryset = User.objects.all()
|
|
77
|
+
serializer_class = UserSerializer
|
|
78
|
+
permission_classes = [permissions.IsAuthenticated]
|
|
79
|
+
|
|
80
|
+
def get_queryset(self):
|
|
81
|
+
return self.queryset.filter(is_active=True)
|
|
82
|
+
|
|
83
|
+
@action(detail=False, methods=["get"])
|
|
84
|
+
def me(self, request):
|
|
85
|
+
serializer = self.get_serializer(request.user)
|
|
86
|
+
return Response(serializer.data)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Services Layer
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
# apps/users/services.py — keep business logic out of views
|
|
93
|
+
class UserService:
|
|
94
|
+
@staticmethod
|
|
95
|
+
def create_user(email: str, password: str, **kwargs) -> User:
|
|
96
|
+
user = User.objects.create_user(email=email, password=password, **kwargs)
|
|
97
|
+
send_welcome_email.delay(user.id) # Celery task
|
|
98
|
+
return user
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Querysets
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
# Avoid N+1 queries
|
|
105
|
+
users = User.objects.select_related("profile").prefetch_related("groups").filter(is_active=True)
|
|
106
|
+
|
|
107
|
+
# Use F() and Q() for complex queries
|
|
108
|
+
from django.db.models import F, Q
|
|
109
|
+
Product.objects.filter(Q(stock__gt=0) | Q(preorder=True)).order_by(F("price").desc())
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Best Practices
|
|
113
|
+
|
|
114
|
+
1. **Fat models, thin views** — or better: fat services, thin everything else
|
|
115
|
+
2. **Custom User model** — always define one from the start (`AbstractUser`)
|
|
116
|
+
3. **select_related / prefetch_related** — prevent N+1 queries
|
|
117
|
+
4. **Migrations** — never edit auto-generated migrations; create new ones
|
|
118
|
+
5. **Settings split** — separate base/dev/prod settings files
|
|
119
|
+
6. **Signals sparingly** — prefer explicit service calls over implicit signals
|
|
120
|
+
7. **Django REST Framework** — use for API projects; serializers for validation
|
|
121
|
+
|
|
122
|
+
## Gotchas
|
|
123
|
+
|
|
124
|
+
- **Circular imports** — use string references in ForeignKey (`"app.Model"`)
|
|
125
|
+
- **Migration conflicts** — in team settings, use `--merge` to resolve
|
|
126
|
+
- **QuerySet laziness** — querysets aren't evaluated until iterated; chain freely
|
|
127
|
+
- **SECRET_KEY exposure** — never commit to VCS; use environment variables
|
|
128
|
+
- **Timezone handling** — always use `USE_TZ = True` and `django.utils.timezone`
|