framework-conventions-guide
This skill should be used when writing code in any opinionated framework's distinctive style. It applies when writing framework-based applications, creating models/controllers/views, or any framework code. Triggers on code generation, refactoring requests, code review, or when the user mentions framework conventions. Embodies the philosophy of embracing framework conventions, fighting complexity, and choosing simplicity over cleverness.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install jayteealao-blank-template-framework-conventions-guide
Repository
Skill path: .claude/skills/framework-conventions-guide
This skill should be used when writing code in any opinionated framework's distinctive style. It applies when writing framework-based applications, creating models/controllers/views, or any framework code. Triggers on code generation, refactoring requests, code review, or when the user mentions framework conventions. Embodies the philosophy of embracing framework conventions, fighting complexity, and choosing simplicity over cleverness.
Open repositoryBest for
Primary workflow: Write Technical Docs.
Technical facets: Full Stack, Tech Writer.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: jayteealao.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install framework-conventions-guide into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/jayteealao/blank-template before adding framework-conventions-guide to shared team environments
- Use framework-conventions-guide for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: framework-conventions-guide
description: This skill should be used when writing code in any opinionated framework's distinctive style. It applies when writing framework-based applications, creating models/controllers/views, or any framework code. Triggers on code generation, refactoring requests, code review, or when the user mentions framework conventions. Embodies the philosophy of embracing framework conventions, fighting complexity, and choosing simplicity over cleverness.
---
<objective>
Apply framework-native conventions to code in ANY opinionated framework. This skill teaches the universal principles that framework creators follow, regardless of language or framework.
</objective>
<essential_principles>
## Core Philosophy
"The best code is the code you don't write. The second best is the code that's obviously correct."
**Embrace the framework:**
- Rich domain models over service layers
- Framework routing over custom routing
- Built-in patterns over imported patterns
- Framework's database tools, not external ORMs
- Build solutions before reaching for packages
- Trust the framework's opinions - they exist for good reasons
**What to avoid (universal anti-patterns):**
- External auth libraries when framework auth exists
- Complex permission systems over simple role checks
- External job queues when framework has one
- External caching when framework provides it
- Component libraries when templates work
- GraphQL when REST is sufficient
- Factory patterns in tests when fixtures/seeds work
- Microservices when a monolith suffices
**Development Philosophy:**
- Ship, Validate, Refine - get to production to learn
- Fix root causes, not symptoms
- Write-time operations over read-time computations
- Database constraints over application validations
- Simple code that works > clever code that impresses
</essential_principles>
<intake>
What are you working on?
1. **Controllers/Views** - Request handling, routing, responses
2. **Models/Data** - Domain logic, state management, queries
3. **Frontend** - Templates, components, interactivity
4. **Architecture** - Routing, auth, jobs, caching
5. **Testing** - Unit tests, integration tests, fixtures
6. **Dependencies** - What to use vs avoid
7. **Code Review** - Review against framework conventions
8. **General Guidance** - Philosophy and conventions
**Specify a number or describe your task and your framework (Django, Laravel, Next.js, etc.)**
</intake>
<routing>
| Response | Reference to Read |
|----------|-------------------|
| 1, "controller", "view", "route" | [universal-patterns.md](./references/universal-patterns.md) - REST patterns section |
| 2, "model", "data", "query", "orm" | [universal-patterns.md](./references/universal-patterns.md) - State and models section |
| 3, "frontend", "template", "component" | [universal-patterns.md](./references/universal-patterns.md) - Frontend section |
| 4, "architecture", "auth", "job", "cache" | [universal-patterns.md](./references/universal-patterns.md) |
| 5, "test", "testing" | [universal-patterns.md](./references/universal-patterns.md) - Testing section |
| 6, "dependency", "package", "library" | [anti-patterns.md](./references/anti-patterns.md) |
| 7, "review" | Read all references, then review code |
| 8, general task | Read relevant references based on context |
**After reading relevant references, apply patterns to the user's code.**
</routing>
<framework_detection>
Before providing guidance, identify the framework and load its specific conventions:
| Framework | Core Conventions | What to Embrace |
|-----------|------------------|-----------------|
| **Django** | Fat models, thin views, ORM queries | Admin, class-based views, forms, signals |
| **Laravel** | Eloquent, Blade, Facades, Artisan | Resource controllers, form requests, events |
| **Next.js** | Server components, file routing, API routes | App router, server actions, middleware |
| **Spring Boot** | Auto-config, dependency injection, JPA | Starters, repositories, @Transactional |
| **Phoenix** | Contexts, Ecto, LiveView, channels | Generators, PubSub, presence |
| **FastAPI** | Pydantic, async, dependency injection | OpenAPI, background tasks, middleware |
| **Rails** | Fat models, thin controllers, conventions | Hotwire, concerns, Active* libraries |
| **Remix** | Loaders, actions, nested routes | Form handling, progressive enhancement |
| **NestJS** | Decorators, modules, providers | Guards, interceptors, pipes |
</framework_detection>
<quick_reference>
## Universal Naming Conventions
**Actions:** Domain verbs that describe what happens
- Good: `card.close()`, `order.ship()`, `user.activate()`
- Bad: `card.setStatus('closed')`, `order.updateShipped(true)`
**Predicates:** Boolean queries derived from state
- Good: `card.closed?`, `order.shipped?`, `user.active?`
- Bad: `card.isClosed`, `order.getIsShipped()`
**Collections:** Descriptive scope names
- Good: `chronologically`, `alphabetically`, `recent`, `active`, `pending`
- Bad: `sortByDate`, `filterActive`, `getRecent`
## REST Mapping (Universal)
Instead of custom actions, create new resources:
```
Custom Action → RESTful Resource
POST /cards/:id/close → POST /cards/:id/closure
DELETE /cards/:id/close → DELETE /cards/:id/closure
POST /orders/:id/ship → POST /orders/:id/shipment
POST /users/:id/ban → POST /users/:id/ban
```
## State as Data (Universal)
Instead of boolean flags, use related records or enums:
```
Boolean Flag → State Record/Enum
card.closed = true → card.closure (record exists)
order.shipped = true → order.shipment (record exists)
user.status = 'banned' → user.ban (record exists)
Query with joins/relations, not boolean comparisons:
- Cards with closures = closed cards
- Cards without closures = open cards
```
## The Framework Way
Before writing custom code, ask:
1. Does the framework have a built-in way to do this?
2. Is there a convention I should follow?
3. Will this surprise other developers familiar with the framework?
4. Am I fighting the framework or working with it?
</quick_reference>
<reference_index>
## Domain Knowledge
All detailed patterns in `references/`:
| File | Topics |
|------|--------|
| [universal-patterns.md](./references/universal-patterns.md) | REST mapping, state as data, naming, testing, frontend |
| [anti-patterns.md](./references/anti-patterns.md) | What to avoid, complexity traps, over-engineering signs |
</reference_index>
<success_criteria>
Code follows framework conventions when:
- Routes map to CRUD verbs on resources
- Models use framework's built-in composition patterns
- State is tracked via records/relations, not booleans
- No unnecessary service objects or abstractions
- Framework solutions preferred over external packages
- Tests use framework's built-in test utilities
- Interactivity uses framework's frontend solution
- Authorization logic lives close to domain
- Jobs are shallow wrappers calling domain methods
- Configuration uses framework patterns, not custom solutions
</success_criteria>
<philosophy>
## Why Convention Over Configuration?
1. **Shared vocabulary** - Everyone knows what to expect
2. **Faster onboarding** - New devs understand the codebase immediately
3. **Less decision fatigue** - Spend energy on business logic, not architecture
4. **Community support** - Problems are already solved and documented
5. **Upgrades are easier** - Framework updates don't break custom patterns
## The 90/10 Rule
The framework solves 90% of your problems elegantly. The remaining 10% often feels like it needs custom solutions, but usually:
- You're overcomplicating the requirement
- There's a framework pattern you haven't discovered
- The "limitation" is actually protecting you from a bad idea
When you truly need to go custom, document why and keep it isolated.
</philosophy>
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/universal-patterns.md
```markdown
# Universal Framework Patterns
These patterns apply across all opinionated frameworks. The syntax differs, but the concepts are universal.
## REST Resource Mapping
### The Problem with Custom Actions
Custom actions like `POST /cards/123/close` or `POST /orders/456/ship` seem convenient but create problems:
- Non-standard HTTP semantics
- Harder to cache
- Inconsistent API design
- More routes to maintain
### The Solution: Resources for Everything
Map actions to resources:
| Custom Action | RESTful Alternative | HTTP Verb |
|--------------|---------------------|-----------|
| Close a card | Create a closure | POST /cards/:id/closure |
| Reopen a card | Delete the closure | DELETE /cards/:id/closure |
| Ship an order | Create a shipment | POST /orders/:id/shipment |
| Archive a post | Create an archival | POST /posts/:id/archival |
| Ban a user | Create a ban | POST /users/:id/ban |
| Unban a user | Delete the ban | DELETE /users/:id/ban |
### Controller Structure
Each resource gets its own controller with standard CRUD actions:
```
ClosuresController
create - Close the card
destroy - Reopen the card
ShipmentsController
create - Ship the order
show - View shipment details
BansController
create - Ban the user
destroy - Unban the user
```
## State as Data
### The Problem with Boolean Flags
Boolean columns seem simple but cause issues:
- No history of when state changed
- No record of who changed it
- Hard to query complex state combinations
- State explosion with multiple booleans
### The Solution: State Records
Instead of `card.closed: boolean`, create a `closures` table:
```
closures
id
card_id
closed_by_id
closed_at
reason
```
Benefits:
- Full audit trail
- Query with joins: "cards with closures" = closed cards
- Additional metadata (who, when, why)
- Easy to add more states without schema changes
### Querying State
```
# Closed cards (have a closure record)
cards.joins(:closure)
Card.objects.filter(closure__isnull=False)
# Open cards (no closure record)
cards.where.missing(:closure)
Card.objects.filter(closure__isnull=True)
```
## Naming Conventions
### Domain Verbs
Use verbs that describe what happens in the domain:
```
Good:
card.close()
order.ship()
user.activate()
post.publish()
comment.flag()
Bad:
card.setStatus('closed')
order.updateShippingStatus(true)
user.setActive(true)
post.setPublished(true)
```
### Predicates
Boolean methods should read naturally:
```
Good:
card.closed? # Ruby
card.is_closed # Python (property)
card.isClosed() # Java/TypeScript
order.shipped?
user.active?
Bad:
card.getIsClosed()
card.checkIfClosed()
order.isShippedStatus()
```
### Scopes and Queries
Name scopes/querysets by what they return:
```
Good:
.chronologically # Ordered by date
.alphabetically # Ordered by name
.recent # Last N items
.active # Currently active
.pending # Awaiting action
.by_author(user) # Filtered by author
Bad:
.sortByCreatedAt()
.filterByActive()
.getRecentItems()
.whereStatusActive()
```
## Frontend Patterns
### Server-First
Modern frameworks embrace server-rendered HTML with progressive enhancement:
| Framework | Server-First Solution |
|-----------|----------------------|
| Rails | Hotwire (Turbo + Stimulus) |
| Laravel | Livewire |
| Phoenix | LiveView |
| Django | HTMX or Turbo |
| Next.js | Server Components |
| Remix | Loaders + Actions |
### When to Use Client-Side JS
Only reach for heavy client-side JavaScript when you need:
- Real-time collaboration (multiple cursors, live editing)
- Complex drag-and-drop interfaces
- Offline-first functionality
- Heavy data visualization
For everything else, server-rendered HTML with progressive enhancement is simpler and more maintainable.
### Component Patterns
```
Good:
- Small, focused components
- Props/attributes for configuration
- Server-rendered by default
- JS enhancement for interactivity
Bad:
- Giant components doing everything
- Client-side state management for server data
- SPAs for content sites
- Rebuilding browser features in JS
```
## Testing Patterns
### Use Framework Tools
Every framework has built-in testing support. Use it.
| Framework | Test Framework | Fixtures/Factories |
|-----------|---------------|-------------------|
| Rails | Minitest | Fixtures |
| Django | unittest/pytest | Fixtures |
| Laravel | PHPUnit | Factories |
| Phoenix | ExUnit | Fixtures |
| Next.js | Jest/Vitest | - |
| Spring | JUnit | @DataJpaTest |
### Test Types
```
Unit Tests
- Test models/domain logic in isolation
- Fast, no database/network
- Mock external dependencies
Integration Tests
- Test controllers/views with database
- Verify request/response cycle
- Use real (test) database
System/E2E Tests
- Test full user flows in browser
- Slow, use sparingly
- Cover critical paths only
```
### Fixture vs Factory Philosophy
**Fixtures** (static test data):
- Defined once, reused everywhere
- Fast to load
- Relationships explicit
- Great for stable domain models
**Factories** (generated test data):
- Dynamic, create what you need
- More flexible
- Can be slower
- Great for complex scenarios
Most framework creators prefer fixtures for simplicity. Factories add complexity that's often unnecessary.
## Configuration Patterns
### Environment-Based Config
```
Good:
- Config from environment variables
- Sensible defaults
- Framework config files
- 12-factor app principles
Bad:
- Config objects with dependency injection
- XML configuration
- Config spread across many files
- Runtime configuration changes
```
### Simple Accessors
```
Good:
MyLib.api_key = ENV['API_KEY']
MyLib.timeout = 30
Bad:
MyLib.configure do |config|
config.api_key = ENV['API_KEY']
config.timeout = 30
end
```
The simpler pattern (direct assignment) is often sufficient. Only use configuration blocks when you have many related settings.
## Authorization Patterns
### Keep It Simple
```
Good:
# On the User model
def can_edit?(post)
post.author == self || admin?
end
# In controller
authorize! if current_user.can_edit?(@post)
Bad:
# Separate policy classes
class PostPolicy
def edit?
user.admin? || record.author == user
end
end
# Complex authorization framework
authorize @post, :edit?
```
For most applications, simple methods on User are enough. Only introduce policy objects when authorization becomes genuinely complex.
## Job/Background Task Patterns
### Shallow Wrappers
Jobs should be thin wrappers that call model methods:
```
Good:
class ShipOrderJob
def perform(order_id)
Order.find(order_id).ship!
end
end
Bad:
class ShipOrderJob
def perform(order_id)
order = Order.find(order_id)
order.update!(status: 'shipping')
ShippingService.new(order).create_label
NotificationService.new(order.user).send_shipped_email
AnalyticsService.track('order_shipped', order_id: order.id)
end
end
```
The logic belongs in the model. The job just triggers it.
```
### references/anti-patterns.md
```markdown
# Framework Anti-Patterns
Patterns to avoid when working with opinionated frameworks. These add complexity without proportional benefit.
## The Complexity Trap
### Signs You're Over-Engineering
1. **More files than features** - If adding a simple feature requires touching 10+ files, something is wrong
2. **Can't explain it simply** - If you can't explain the architecture to a new developer in 5 minutes, it's too complex
3. **Framework within a framework** - Building abstractions on top of framework abstractions
4. **Solving problems you don't have** - "We might need this someday" code
5. **Copy-paste from enterprise tutorials** - Patterns designed for 1000-person teams don't fit 5-person teams
### The Service Object Trap
**When NOT to use service objects:**
- Single model operations
- Simple validations
- Basic CRUD
- Anything the model can do
**When service objects might be justified:**
- Orchestrating multiple models in a transaction
- Complex external API interactions
- Business processes that span multiple domains
```
Bad: UserRegistrationService that just creates a User
Good: Put User.create logic in the User model
Bad: EmailSendingService that wraps the mailer
Good: Use the mailer directly
Bad: OrderCalculationService for totaling an order
Good: Order#total method on the model
```
## Imported Pattern Problems
### Patterns That Don't Belong
| Pattern | Origin | Why It's Wrong Here |
|---------|--------|-------------------|
| Repository Pattern | Java/C# | ORMs already abstract database access |
| Dependency Injection Containers | Java/C# | Dynamic languages don't need them |
| Command/Query Separation | DDD | Overkill for most CRUD apps |
| Event Sourcing | Financial systems | You probably don't need an audit log of everything |
| Hexagonal Architecture | Enterprise Java | Framework IS your architecture |
| Microservices | Netflix scale | You're not Netflix |
### The "Enterprise" Smell
If your codebase has any of these, question why:
- `src/Domain/`, `src/Infrastructure/`, `src/Application/` folders
- Interface files for every class
- Abstract factories
- Builder patterns for simple objects
- Strategy patterns for one strategy
- More than 2 levels of inheritance
## Dependency Anti-Patterns
### Packages You Probably Don't Need
| Package Type | Framework Alternative |
|--------------|----------------------|
| Authentication gems/packages | Framework's built-in auth (150 lines of custom code) |
| Authorization frameworks | Simple `can_do_thing?` methods on User |
| Form builders | Framework's form helpers |
| API serializers | Framework's JSON rendering |
| State machines | Enum + transition methods |
| Soft delete gems | Simple `deleted_at` column |
| Pagination gems | Framework's built-in pagination |
| Search gems (simple cases) | SQL LIKE queries or full-text search |
### The Gem/Package Evaluation Checklist
Before adding a dependency, ask:
1. Can I write this in < 100 lines?
2. Is the framework going to add this soon?
3. Will this be maintained in 2 years?
4. What's the performance overhead?
5. Does it fight the framework or complement it?
## Testing Anti-Patterns
### Over-Mocking
```
Bad:
# Mocking everything
mock_user = Mock(User)
mock_order = Mock(Order)
mock_payment = Mock(Payment)
# What are you even testing at this point?
Good:
# Use real objects, mock only external services
user = users(:john)
order = user.orders.create!(...)
# Mock only the payment gateway
```
### Wrong Test Types
```
Bad:
# Unit testing controllers (integration test territory)
# E2E testing every edge case (too slow)
# Mocking the database in integration tests
Good:
# Unit tests for complex model logic
# Integration tests for request/response
# E2E tests for critical user flows only
```
### Factory Overuse
```
Bad:
# Factory with 50 traits
create(:user, :admin, :verified, :with_subscription, :premium, ...)
# Factory that creates half the database
create(:order) # creates user, products, addresses, payments...
Good:
# Simple fixtures with known state
users(:admin)
orders(:pending_shipment)
```
## Architecture Anti-Patterns
### Premature Abstraction
```
Bad:
# Creating PaymentGateway interface before you have 2 gateways
# Building a "plugin system" for one plugin
# Abstracting database queries "in case we switch databases"
Good:
# Use Stripe directly until you need another gateway
# Build the feature, abstract later if needed
# PostgreSQL isn't going anywhere
```
### Configuration Object Disease
```
Bad:
class AppConfig
attr_accessor :database_url
attr_accessor :redis_url
attr_accessor :api_key
attr_accessor :timeout
attr_accessor :feature_flags
# 50 more settings...
end
CONFIG = AppConfig.new
CONFIG.timeout = 30
Good:
# Environment variables + framework config
ENV['DATABASE_URL']
Rails.application.config.timeout = 30
```
### Namespace Explosion
```
Bad:
App::Services::Users::Registration::CreateUserService
App::Repositories::Users::UserRepository
App::ValueObjects::Users::UserEmail
Good:
User # Model with registration logic
# That's it. You don't need the rest.
```
## Frontend Anti-Patterns
### SPA Everything
```
Bad:
# React/Vue SPA for a blog
# Client-side routing for content sites
# Redux for server data
# Building your own loading states
Good:
# Server-rendered HTML
# Turbo/HTMX/Livewire for interactivity
# Let the browser handle navigation
# Progressive enhancement
```
### Rebuilding the Browser
```
Bad:
# Custom tooltip library
# Custom modal system
# Custom form validation
# Custom routing
Good:
# Native tooltips (title attribute)
# Native dialogs (<dialog> element)
# Native validation (HTML5 validation)
# Native navigation (links)
```
## The Golden Rule
**Before adding complexity, ask:**
1. Does the framework already solve this?
2. Have I read the framework documentation?
3. Is a more experienced developer using this pattern successfully?
4. Will a new hire understand this in their first week?
5. What's the simplest thing that could possibly work?
If you can't answer "yes" to most of these, you're probably over-engineering.
## Recovery Patterns
### If You're Already Over-Engineered
1. **Stop adding more abstraction** - Don't abstract your way out of abstraction
2. **Identify the 20% causing 80% of pain** - Focus removal efforts there
3. **Inline aggressively** - Copy code from abstractions back to call sites
4. **Delete unused code** - If it's not called, it doesn't need to exist
5. **Trust the framework** - Let it do what it was designed to do
### Healthy Codebase Signs
- New developers productive in days, not weeks
- Features ship in hours, not days
- Most files under 200 lines
- Tests run in under a minute
- Deployment is boring
- Framework upgrades are straightforward
```