@stackguide/mcp-server 1.0.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.
- package/LICENSE +674 -0
- package/README.md +453 -0
- package/data/knowledge/python-django/architecture/architecture-patterns.md +201 -0
- package/data/knowledge/python-django/common-issues/common-issues.md +181 -0
- package/data/knowledge/python-django/patterns/drf-patterns.md +133 -0
- package/data/knowledge/react-node/architecture/node-architecture.md +257 -0
- package/data/knowledge/react-node/common-issues/common-issues.md +262 -0
- package/data/knowledge/react-node/patterns/react-patterns.md +244 -0
- package/data/rules/python-django/best-practices/django-best-practices.md +120 -0
- package/data/rules/python-django/coding-standards/django-standards.md +104 -0
- package/data/rules/python-django/security/security-guidelines.md +146 -0
- package/data/rules/react-node/best-practices/react-best-practices.md +195 -0
- package/data/rules/react-node/coding-standards/node-standards.md +192 -0
- package/data/rules/react-node/coding-standards/react-standards.md +155 -0
- package/data/rules/react-node/security/security-guidelines.md +228 -0
- package/dist/config/persistence.d.ts +15 -0
- package/dist/config/persistence.d.ts.map +1 -0
- package/dist/config/persistence.js +171 -0
- package/dist/config/persistence.js.map +1 -0
- package/dist/config/types.d.ts +47 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +116 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1799 -0
- package/dist/index.js.map +1 -0
- package/dist/resources/knowledgeProvider.d.ts +10 -0
- package/dist/resources/knowledgeProvider.d.ts.map +1 -0
- package/dist/resources/knowledgeProvider.js +130 -0
- package/dist/resources/knowledgeProvider.js.map +1 -0
- package/dist/resources/rulesProvider.d.ts +10 -0
- package/dist/resources/rulesProvider.d.ts.map +1 -0
- package/dist/resources/rulesProvider.js +135 -0
- package/dist/resources/rulesProvider.js.map +1 -0
- package/dist/services/cursorDirectory.d.ts +55 -0
- package/dist/services/cursorDirectory.d.ts.map +1 -0
- package/dist/services/cursorDirectory.js +367 -0
- package/dist/services/cursorDirectory.js.map +1 -0
- package/dist/services/ruleManager.d.ts +18 -0
- package/dist/services/ruleManager.d.ts.map +1 -0
- package/dist/services/ruleManager.js +382 -0
- package/dist/services/ruleManager.js.map +1 -0
- package/dist/services/webDocumentation.d.ts +41 -0
- package/dist/services/webDocumentation.d.ts.map +1 -0
- package/dist/services/webDocumentation.js +237 -0
- package/dist/services/webDocumentation.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Django Coding Standards
|
|
2
|
+
|
|
3
|
+
Follow these coding standards when developing Django applications.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
project/
|
|
9
|
+
├── config/ # Project settings
|
|
10
|
+
│ ├── settings/
|
|
11
|
+
│ │ ├── base.py
|
|
12
|
+
│ │ ├── development.py
|
|
13
|
+
│ │ └── production.py
|
|
14
|
+
│ ├── urls.py
|
|
15
|
+
│ └── wsgi.py
|
|
16
|
+
├── apps/ # Django applications
|
|
17
|
+
│ └── app_name/
|
|
18
|
+
│ ├── models.py
|
|
19
|
+
│ ├── views.py
|
|
20
|
+
│ ├── serializers.py
|
|
21
|
+
│ ├── urls.py
|
|
22
|
+
│ └── tests/
|
|
23
|
+
├── templates/
|
|
24
|
+
├── static/
|
|
25
|
+
└── manage.py
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Naming Conventions
|
|
29
|
+
|
|
30
|
+
- **Models**: Use singular, PascalCase (e.g., `User`, `BlogPost`)
|
|
31
|
+
- **Views**: Suffix with purpose (e.g., `UserListView`, `create_user`)
|
|
32
|
+
- **URLs**: Use lowercase, hyphens (e.g., `/user-profile/`)
|
|
33
|
+
- **Templates**: Use lowercase, underscores (e.g., `user_detail.html`)
|
|
34
|
+
|
|
35
|
+
## Model Best Practices
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from django.db import models
|
|
39
|
+
from django.utils.translation import gettext_lazy as _
|
|
40
|
+
|
|
41
|
+
class BaseModel(models.Model):
|
|
42
|
+
"""Abstract base model with common fields."""
|
|
43
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
44
|
+
updated_at = models.DateTimeField(auto_now=True)
|
|
45
|
+
|
|
46
|
+
class Meta:
|
|
47
|
+
abstract = True
|
|
48
|
+
|
|
49
|
+
class Article(BaseModel):
|
|
50
|
+
title = models.CharField(_("title"), max_length=200)
|
|
51
|
+
slug = models.SlugField(_("slug"), unique=True)
|
|
52
|
+
content = models.TextField(_("content"))
|
|
53
|
+
author = models.ForeignKey(
|
|
54
|
+
"users.User",
|
|
55
|
+
on_delete=models.CASCADE,
|
|
56
|
+
related_name="articles",
|
|
57
|
+
verbose_name=_("author")
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
class Meta:
|
|
61
|
+
verbose_name = _("article")
|
|
62
|
+
verbose_name_plural = _("articles")
|
|
63
|
+
ordering = ["-created_at"]
|
|
64
|
+
|
|
65
|
+
def __str__(self):
|
|
66
|
+
return self.title
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## View Patterns
|
|
70
|
+
|
|
71
|
+
Prefer class-based views for complex logic:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from django.views.generic import ListView, DetailView
|
|
75
|
+
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
76
|
+
|
|
77
|
+
class ArticleListView(LoginRequiredMixin, ListView):
|
|
78
|
+
model = Article
|
|
79
|
+
template_name = "articles/list.html"
|
|
80
|
+
context_object_name = "articles"
|
|
81
|
+
paginate_by = 10
|
|
82
|
+
|
|
83
|
+
def get_queryset(self):
|
|
84
|
+
return super().get_queryset().select_related("author")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Import Order
|
|
88
|
+
|
|
89
|
+
1. Standard library imports
|
|
90
|
+
2. Django imports
|
|
91
|
+
3. Third-party imports
|
|
92
|
+
4. Local application imports
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import json
|
|
96
|
+
from datetime import datetime
|
|
97
|
+
|
|
98
|
+
from django.db import models
|
|
99
|
+
from django.utils import timezone
|
|
100
|
+
|
|
101
|
+
from rest_framework import serializers
|
|
102
|
+
|
|
103
|
+
from .models import Article
|
|
104
|
+
```
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Django Security Guidelines
|
|
2
|
+
|
|
3
|
+
Critical security practices for Django applications.
|
|
4
|
+
|
|
5
|
+
## CRITICAL: Security Checklist
|
|
6
|
+
|
|
7
|
+
- [ ] SECRET_KEY is kept secret and rotated
|
|
8
|
+
- [ ] DEBUG is False in production
|
|
9
|
+
- [ ] ALLOWED_HOSTS is properly configured
|
|
10
|
+
- [ ] HTTPS is enforced
|
|
11
|
+
- [ ] CSRF protection is enabled
|
|
12
|
+
- [ ] SQL injection is prevented (use ORM)
|
|
13
|
+
- [ ] XSS protection is enabled
|
|
14
|
+
|
|
15
|
+
## Settings for Production
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
# config/settings/production.py
|
|
19
|
+
|
|
20
|
+
DEBUG = False
|
|
21
|
+
ALLOWED_HOSTS = ["yourdomain.com", "www.yourdomain.com"]
|
|
22
|
+
|
|
23
|
+
# Security settings
|
|
24
|
+
SECURE_BROWSER_XSS_FILTER = True
|
|
25
|
+
SECURE_CONTENT_TYPE_NOSNIFF = True
|
|
26
|
+
X_FRAME_OPTIONS = "DENY"
|
|
27
|
+
|
|
28
|
+
# HTTPS settings
|
|
29
|
+
SECURE_SSL_REDIRECT = True
|
|
30
|
+
SESSION_COOKIE_SECURE = True
|
|
31
|
+
CSRF_COOKIE_SECURE = True
|
|
32
|
+
SECURE_HSTS_SECONDS = 31536000
|
|
33
|
+
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
|
34
|
+
SECURE_HSTS_PRELOAD = True
|
|
35
|
+
|
|
36
|
+
# Cookie settings
|
|
37
|
+
SESSION_COOKIE_HTTPONLY = True
|
|
38
|
+
CSRF_COOKIE_HTTPONLY = True
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Authentication Security
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
# Strong password validation
|
|
45
|
+
AUTH_PASSWORD_VALIDATORS = [
|
|
46
|
+
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
|
|
47
|
+
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
|
48
|
+
"OPTIONS": {"min_length": 12}},
|
|
49
|
+
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
|
|
50
|
+
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
# Rate limiting login attempts
|
|
54
|
+
AXES_FAILURE_LIMIT = 5
|
|
55
|
+
AXES_COOLOFF_TIME = timedelta(minutes=15)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## SQL Injection Prevention
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
# ALWAYS use ORM or parameterized queries
|
|
62
|
+
|
|
63
|
+
# DANGEROUS - Never do this!
|
|
64
|
+
query = f"SELECT * FROM articles WHERE id = {user_input}"
|
|
65
|
+
|
|
66
|
+
# SAFE - Use ORM
|
|
67
|
+
Article.objects.filter(id=user_input)
|
|
68
|
+
|
|
69
|
+
# SAFE - Parameterized raw query if needed
|
|
70
|
+
Article.objects.raw("SELECT * FROM articles WHERE id = %s", [user_input])
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## XSS Prevention
|
|
74
|
+
|
|
75
|
+
```html
|
|
76
|
+
<!-- Django auto-escapes by default -->
|
|
77
|
+
{{ user_input }}
|
|
78
|
+
|
|
79
|
+
<!-- Be careful with safe filter -->
|
|
80
|
+
{{ user_input|safe }} <!-- Only use with trusted content -->
|
|
81
|
+
|
|
82
|
+
<!-- Use bleach for user HTML -->
|
|
83
|
+
{{ user_input|bleach }}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## CSRF Protection
|
|
87
|
+
|
|
88
|
+
```html
|
|
89
|
+
<!-- Always include in forms -->
|
|
90
|
+
<form method="post">
|
|
91
|
+
{% csrf_token %}
|
|
92
|
+
...
|
|
93
|
+
</form>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
# For AJAX requests
|
|
98
|
+
from django.views.decorators.csrf import ensure_csrf_cookie
|
|
99
|
+
|
|
100
|
+
@ensure_csrf_cookie
|
|
101
|
+
def get_csrf_token(request):
|
|
102
|
+
return JsonResponse({"status": "ok"})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## File Upload Security
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
import os
|
|
109
|
+
from django.core.exceptions import ValidationError
|
|
110
|
+
|
|
111
|
+
def validate_file_extension(value):
|
|
112
|
+
ext = os.path.splitext(value.name)[1].lower()
|
|
113
|
+
valid_extensions = [".pdf", ".doc", ".docx"]
|
|
114
|
+
if ext not in valid_extensions:
|
|
115
|
+
raise ValidationError("Unsupported file extension.")
|
|
116
|
+
|
|
117
|
+
def validate_file_size(value):
|
|
118
|
+
limit = 5 * 1024 * 1024 # 5MB
|
|
119
|
+
if value.size > limit:
|
|
120
|
+
raise ValidationError("File too large.")
|
|
121
|
+
|
|
122
|
+
class Document(models.Model):
|
|
123
|
+
file = models.FileField(
|
|
124
|
+
upload_to="documents/",
|
|
125
|
+
validators=[validate_file_extension, validate_file_size]
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Sensitive Data
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
# Never log sensitive data
|
|
133
|
+
import logging
|
|
134
|
+
logger = logging.getLogger(__name__)
|
|
135
|
+
|
|
136
|
+
# BAD
|
|
137
|
+
logger.info(f"User {user.email} logged in with password {password}")
|
|
138
|
+
|
|
139
|
+
# GOOD
|
|
140
|
+
logger.info(f"User {user.id} logged in successfully")
|
|
141
|
+
|
|
142
|
+
# Use Django's signing for tokens
|
|
143
|
+
from django.core.signing import Signer
|
|
144
|
+
signer = Signer()
|
|
145
|
+
signed_value = signer.sign("my-data")
|
|
146
|
+
```
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# React Best Practices
|
|
2
|
+
|
|
3
|
+
Essential best practices for building robust React applications.
|
|
4
|
+
|
|
5
|
+
## State Management
|
|
6
|
+
|
|
7
|
+
### Local State First
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// Start with local state
|
|
11
|
+
const [count, setCount] = useState(0);
|
|
12
|
+
|
|
13
|
+
// Lift state only when necessary
|
|
14
|
+
// Move to context/store when multiple components need it
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Derived State
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
// Bad - Storing derived state
|
|
21
|
+
const [items, setItems] = useState([]);
|
|
22
|
+
const [filteredItems, setFilteredItems] = useState([]);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
setFilteredItems(items.filter(i => i.active));
|
|
26
|
+
}, [items]);
|
|
27
|
+
|
|
28
|
+
// Good - Compute during render
|
|
29
|
+
const [items, setItems] = useState([]);
|
|
30
|
+
const filteredItems = useMemo(
|
|
31
|
+
() => items.filter(i => i.active),
|
|
32
|
+
[items]
|
|
33
|
+
);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Performance Optimization
|
|
37
|
+
|
|
38
|
+
### Memoization
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { memo, useMemo, useCallback } from 'react';
|
|
42
|
+
|
|
43
|
+
// Memoize expensive calculations
|
|
44
|
+
const sortedItems = useMemo(() => {
|
|
45
|
+
return [...items].sort((a, b) => a.name.localeCompare(b.name));
|
|
46
|
+
}, [items]);
|
|
47
|
+
|
|
48
|
+
// Memoize callbacks passed to children
|
|
49
|
+
const handleClick = useCallback((id: string) => {
|
|
50
|
+
setSelected(id);
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
// Memoize components that receive object/array props
|
|
54
|
+
export const UserList = memo(function UserList({ users }: Props) {
|
|
55
|
+
return users.map(user => <UserCard key={user.id} user={user} />);
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Code Splitting
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { lazy, Suspense } from 'react';
|
|
63
|
+
|
|
64
|
+
// Lazy load heavy components
|
|
65
|
+
const Dashboard = lazy(() => import('./pages/Dashboard'));
|
|
66
|
+
const Analytics = lazy(() => import('./pages/Analytics'));
|
|
67
|
+
|
|
68
|
+
function App() {
|
|
69
|
+
return (
|
|
70
|
+
<Suspense fallback={<LoadingSpinner />}>
|
|
71
|
+
<Routes>
|
|
72
|
+
<Route path="/dashboard" element={<Dashboard />} />
|
|
73
|
+
<Route path="/analytics" element={<Analytics />} />
|
|
74
|
+
</Routes>
|
|
75
|
+
</Suspense>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Error Handling
|
|
81
|
+
|
|
82
|
+
### Error Boundaries
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { Component, ErrorInfo, ReactNode } from 'react';
|
|
86
|
+
|
|
87
|
+
interface Props {
|
|
88
|
+
children: ReactNode;
|
|
89
|
+
fallback?: ReactNode;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface State {
|
|
93
|
+
hasError: boolean;
|
|
94
|
+
error?: Error;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export class ErrorBoundary extends Component<Props, State> {
|
|
98
|
+
state: State = { hasError: false };
|
|
99
|
+
|
|
100
|
+
static getDerivedStateFromError(error: Error): State {
|
|
101
|
+
return { hasError: true, error };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
105
|
+
console.error('Error caught:', error, errorInfo);
|
|
106
|
+
// Log to error tracking service
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
render() {
|
|
110
|
+
if (this.state.hasError) {
|
|
111
|
+
return this.props.fallback || <ErrorFallback error={this.state.error} />;
|
|
112
|
+
}
|
|
113
|
+
return this.props.children;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Custom Hooks
|
|
119
|
+
|
|
120
|
+
### Reusable Logic
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
// hooks/useLocalStorage.ts
|
|
124
|
+
function useLocalStorage<T>(key: string, initialValue: T) {
|
|
125
|
+
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
126
|
+
try {
|
|
127
|
+
const item = window.localStorage.getItem(key);
|
|
128
|
+
return item ? JSON.parse(item) : initialValue;
|
|
129
|
+
} catch {
|
|
130
|
+
return initialValue;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const setValue = useCallback((value: T | ((val: T) => T)) => {
|
|
135
|
+
try {
|
|
136
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
137
|
+
setStoredValue(valueToStore);
|
|
138
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error('Error saving to localStorage:', error);
|
|
141
|
+
}
|
|
142
|
+
}, [key, storedValue]);
|
|
143
|
+
|
|
144
|
+
return [storedValue, setValue] as const;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## API Integration
|
|
149
|
+
|
|
150
|
+
### React Query Pattern
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
154
|
+
|
|
155
|
+
// Fetch data
|
|
156
|
+
function useUsers() {
|
|
157
|
+
return useQuery({
|
|
158
|
+
queryKey: ['users'],
|
|
159
|
+
queryFn: () => api.getUsers(),
|
|
160
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Mutate data
|
|
165
|
+
function useCreateUser() {
|
|
166
|
+
const queryClient = useQueryClient();
|
|
167
|
+
|
|
168
|
+
return useMutation({
|
|
169
|
+
mutationFn: (data: CreateUserDTO) => api.createUser(data),
|
|
170
|
+
onSuccess: () => {
|
|
171
|
+
queryClient.invalidateQueries({ queryKey: ['users'] });
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Testing Patterns
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
181
|
+
import userEvent from '@testing-library/user-event';
|
|
182
|
+
|
|
183
|
+
describe('UserCard', () => {
|
|
184
|
+
it('calls onSelect when clicked', async () => {
|
|
185
|
+
const onSelect = jest.fn();
|
|
186
|
+
const user = { id: '1', name: 'John' };
|
|
187
|
+
|
|
188
|
+
render(<UserCard user={user} onSelect={onSelect} />);
|
|
189
|
+
|
|
190
|
+
await userEvent.click(screen.getByRole('button'));
|
|
191
|
+
|
|
192
|
+
expect(onSelect).toHaveBeenCalledWith('1');
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
```
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Node.js Coding Standards
|
|
2
|
+
|
|
3
|
+
Follow these coding standards when developing Node.js applications.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
├── config/ # Configuration files
|
|
10
|
+
│ ├── index.ts
|
|
11
|
+
│ └── database.ts
|
|
12
|
+
├── controllers/ # Request handlers
|
|
13
|
+
├── services/ # Business logic
|
|
14
|
+
├── repositories/ # Data access layer
|
|
15
|
+
├── models/ # Data models/entities
|
|
16
|
+
├── middlewares/ # Express middlewares
|
|
17
|
+
├── routes/ # Route definitions
|
|
18
|
+
├── utils/ # Utility functions
|
|
19
|
+
├── types/ # TypeScript types
|
|
20
|
+
├── validators/ # Request validation
|
|
21
|
+
└── app.ts # Application entry
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Naming Conventions
|
|
25
|
+
|
|
26
|
+
- **Files**: camelCase or kebab-case (e.g., `userService.ts` or `user-service.ts`)
|
|
27
|
+
- **Classes**: PascalCase (e.g., `UserService`)
|
|
28
|
+
- **Functions/Variables**: camelCase (e.g., `getUserById`)
|
|
29
|
+
- **Constants**: SCREAMING_SNAKE_CASE (e.g., `MAX_RETRIES`)
|
|
30
|
+
- **Interfaces**: PascalCase (e.g., `IUserRepository`)
|
|
31
|
+
|
|
32
|
+
## Error Handling
|
|
33
|
+
|
|
34
|
+
### Custom Error Classes
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// errors/AppError.ts
|
|
38
|
+
export class AppError extends Error {
|
|
39
|
+
constructor(
|
|
40
|
+
public message: string,
|
|
41
|
+
public statusCode: number = 500,
|
|
42
|
+
public code?: string,
|
|
43
|
+
public isOperational: boolean = true
|
|
44
|
+
) {
|
|
45
|
+
super(message);
|
|
46
|
+
Error.captureStackTrace(this, this.constructor);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class NotFoundError extends AppError {
|
|
51
|
+
constructor(resource: string = 'Resource') {
|
|
52
|
+
super(`${resource} not found`, 404, 'NOT_FOUND');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class ValidationError extends AppError {
|
|
57
|
+
constructor(message: string, public errors?: Record<string, string>) {
|
|
58
|
+
super(message, 400, 'VALIDATION_ERROR');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Error Middleware
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// middlewares/errorHandler.ts
|
|
67
|
+
import { Request, Response, NextFunction } from 'express';
|
|
68
|
+
import { AppError } from '../errors/AppError';
|
|
69
|
+
|
|
70
|
+
export const errorHandler = (
|
|
71
|
+
err: Error,
|
|
72
|
+
req: Request,
|
|
73
|
+
res: Response,
|
|
74
|
+
next: NextFunction
|
|
75
|
+
) => {
|
|
76
|
+
if (err instanceof AppError) {
|
|
77
|
+
return res.status(err.statusCode).json({
|
|
78
|
+
status: 'error',
|
|
79
|
+
code: err.code,
|
|
80
|
+
message: err.message,
|
|
81
|
+
...(err.errors && { errors: err.errors })
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Log unexpected errors
|
|
86
|
+
console.error('Unexpected error:', err);
|
|
87
|
+
|
|
88
|
+
res.status(500).json({
|
|
89
|
+
status: 'error',
|
|
90
|
+
code: 'INTERNAL_ERROR',
|
|
91
|
+
message: 'An unexpected error occurred'
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Async Handling
|
|
97
|
+
|
|
98
|
+
### Async Wrapper
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// utils/asyncHandler.ts
|
|
102
|
+
import { Request, Response, NextFunction, RequestHandler } from 'express';
|
|
103
|
+
|
|
104
|
+
export const asyncHandler = (fn: RequestHandler): RequestHandler => {
|
|
105
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
106
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Usage
|
|
111
|
+
router.get('/users/:id', asyncHandler(async (req, res) => {
|
|
112
|
+
const user = await userService.getById(req.params.id);
|
|
113
|
+
res.json(user);
|
|
114
|
+
}));
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Configuration
|
|
118
|
+
|
|
119
|
+
### Environment Variables
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// config/index.ts
|
|
123
|
+
import dotenv from 'dotenv';
|
|
124
|
+
import { z } from 'zod';
|
|
125
|
+
|
|
126
|
+
dotenv.config();
|
|
127
|
+
|
|
128
|
+
const envSchema = z.object({
|
|
129
|
+
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
|
130
|
+
PORT: z.string().transform(Number).default('3000'),
|
|
131
|
+
DATABASE_URL: z.string(),
|
|
132
|
+
JWT_SECRET: z.string().min(32),
|
|
133
|
+
JWT_EXPIRES_IN: z.string().default('7d'),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const parsed = envSchema.safeParse(process.env);
|
|
137
|
+
|
|
138
|
+
if (!parsed.success) {
|
|
139
|
+
console.error('❌ Invalid environment variables:', parsed.error.format());
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export const config = parsed.data;
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Logging
|
|
147
|
+
|
|
148
|
+
### Structured Logging
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// utils/logger.ts
|
|
152
|
+
import pino from 'pino';
|
|
153
|
+
|
|
154
|
+
export const logger = pino({
|
|
155
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
156
|
+
transport: process.env.NODE_ENV === 'development'
|
|
157
|
+
? { target: 'pino-pretty' }
|
|
158
|
+
: undefined,
|
|
159
|
+
base: {
|
|
160
|
+
pid: process.pid,
|
|
161
|
+
env: process.env.NODE_ENV,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Usage
|
|
166
|
+
logger.info({ userId: user.id }, 'User logged in');
|
|
167
|
+
logger.error({ err, requestId }, 'Failed to process request');
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Import Order
|
|
171
|
+
|
|
172
|
+
1. Node.js built-in modules
|
|
173
|
+
2. External dependencies
|
|
174
|
+
3. Internal modules
|
|
175
|
+
4. Relative imports
|
|
176
|
+
5. Types (if separate)
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { readFile } from 'fs/promises';
|
|
180
|
+
import path from 'path';
|
|
181
|
+
|
|
182
|
+
import express from 'express';
|
|
183
|
+
import { z } from 'zod';
|
|
184
|
+
|
|
185
|
+
import { config } from '@/config';
|
|
186
|
+
import { logger } from '@/utils/logger';
|
|
187
|
+
|
|
188
|
+
import { UserService } from './userService';
|
|
189
|
+
import { validateUser } from './validators';
|
|
190
|
+
|
|
191
|
+
import type { User, CreateUserDTO } from './types';
|
|
192
|
+
```
|