
repository-pattern
by allenlin90
SKILL.md
name: repository-pattern description: Provides general guidance for implementing data access layers in backend applications. This skill should be used when designing repositories, implementing query methods, or managing database access patterns.
Repository Pattern Skill
Provides general guidelines for implementing repository (data access) layers in backend applications.
Core Responsibilities
Repositories act as data access abstraction. They should:
- Encapsulate queries - All database operations go through repositories
- Hide database details - Services don't know SQL or ORM specifics
- Provide typed interfaces - Strong typing for queries and results
- Handle errors - Low-level errors are handled here
- Implement soft deletes - Consistent deletion strategy
- Support common patterns - Find, create, update, delete operations
- Optimize queries - Eager loading, indexes, pagination
Repository Architecture
Layered Pattern:
Service (Business logic)
↓
Repository (Data access)
├── Find operations
├── Create/Update operations
├── Delete operations
├── Bulk operations
└── Count operations
↓
Database (PostgreSQL, MongoDB, etc.)
Core Operations
Find Operations
findById(id) → Entity | null
findBy*(key) → Entity | null
findMany(params) → Entity[]
findOne(criteria) → Entity | null
findFirstOrThrow(criteria) → Entity | throws
Key Patterns:
- ✅ Always include soft delete filter (deletedAt: null)
- ✅ Support multiple find methods (by id, by uid, by email, etc.)
- ✅ Return null when not found (or throw if OrThrow variant)
- ✅ Support filtering and ordering
- ✅ Support eager loading (includes/relations)
Create Operations
create(data) → Entity
createMany(data[]) → BatchPayload
Key Patterns:
- ✅ Validate data before creating
- ✅ Return created entity with all fields
- ✅ Use bulk operations for multiple creates
- ✅ Handle unique constraint violations
- ✅ Generate required fields (id, timestamps)
Update Operations
update(where, data) → Entity
updateMany(where, data) → BatchPayload
Key Patterns:
- ✅ Only update specified fields
- ✅ Return updated entity
- ✅ Use bulk operations for multiple updates
- ✅ Handle concurrent updates (optimistic locking)
- ✅ Update timestamps automatically
Delete Operations
softDelete(where) → void
hardDelete(where) → void (rarely used)
Key Patterns:
- ✅ Use soft delete by default (set deletedAt)
- ✅ Never expose hard delete publicly
- ✅ Preserve data for audit trails
- ✅ Create separate method for permanent deletion (if needed)
Bulk Operations
createMany(data[]) → BatchPayload
updateMany(where, data) → BatchPayload
deleteMany(where) → BatchPayload
Key Patterns:
- ✅ Handle multiple records in single operation
- ✅ Support partial success (skipDuplicates)
- ✅ Return operation results
- ✅ Use for performance-critical operations
Soft Delete Pattern
Always filter out deleted records by default:
✅ CORRECT: Filter deleted
findMany({ where: { deletedAt: null } })
✅ CORRECT: Soft delete (set timestamp)
softDelete({ uid: 'user_123' })
✅ CORRECT: Query deleted (explicit)
findMany({ where: { deletedAt: { not: null } } })
❌ WRONG: No soft delete filter
findMany() // Includes deleted records
❌ WRONG: Hard delete
delete({ uid: 'user_123' }) // Loses data
Key Rules:
- ✅ All find operations include
deletedAt: null - ✅ Use soft delete (update deletedAt) instead of hard delete
- ✅ Create separate methods for querying deleted records
- ❌ Never expose hard delete without careful consideration
- ❌ Never forget the soft delete filter in queries
Query Patterns
Finding by Single Identifier
Common patterns:
- findById(id) - By database ID
- findByUid(uid) - By external UID
- findByEmail(email) - By email
- findByUsername(username) - By username
- findByExternalId(extId) - By external system ID
Implementation:
- ✅ Create specialized methods for common lookups
- ✅ Include soft delete filter
- ✅ Return null if not found (or throw)
- ✅ Type-safe return values
Finding Multiple Records
Params:
- where: Filter criteria
- skip: Pagination offset
- take: Pagination limit
- orderBy: Sort order
- include: Relationships to load
Key Patterns:
- ✅ Support filtering (where clause)
- ✅ Support pagination (skip/take)
- ✅ Support sorting (orderBy)
- ✅ Support eager loading (include)
- ✅ Always filter out soft-deleted records
Type-Safe Queries
Use framework's type system to ensure type safety:
✅ Typed includes
findMany({
include: { studio: true, roles: true }
}) → User & { studio: Studio, roles: Role[] }
✅ Typed where clauses
findMany({
where: { status: 'active' }
})
✅ Typed results
result.id → number
result.uid → string
result.deletedAt → Date | null
Error Handling
Repositories handle low-level errors:
❌ Don't throw HTTP exceptions
throw new NotFoundException('User not found')
✅ Throw domain errors or let ORM handle
throw Prisma errors (converted by global filter)
✅ Use OrThrow variants
findFirstOrThrow() // Throws if not found
Key Rules:
- ✅ Let ORM throw low-level errors
- ✅ Global filter converts to HTTP responses
- ✅ Use
findFirstOrThrow()for not-found cases - ✅ Never throw HTTP exceptions in repositories
- ❌ Never expose database errors to clients
Performance Considerations
N+1 Query Prevention
❌ WRONG: N+1 queries
users.forEach(user => {
user.studio = getStudio(user.studioId)
})
✅ CORRECT: Eager loading
findMany({
include: { studio: true }
})
Bulk Operations
❌ WRONG: Loop creates
users.forEach(user => create(user))
✅ CORRECT: Bulk create
createMany(users)
Parallel Queries
❌ WRONG: Sequential
const users = findMany()
const count = count()
✅ CORRECT: Parallel
const [users, count] = Promise.all([
findMany(),
count()
])
Pagination Implementation
Input:
- page: number (1-based)
- limit: number
Calculate:
- skip = (page - 1) * limit
- take = limit
Output:
- data: Entity[]
- total: number (call count() separately)
Key Rules:
- ✅ Support page and limit parameters
- ✅ Calculate skip correctly
- ✅ Query data and count in parallel (from service layer)
- ✅ Return pagination metadata from service
Testing Repositories
Unit Tests: In-memory database or mocks
✅ Test find operations
✅ Test create/update/delete
✅ Test soft delete filtering
✅ Test bulk operations
✅ Test pagination
Integration Tests: Real database
✅ Test with real ORM
✅ Test transaction behavior
✅ Test constraints and relationships
✅ Test concurrent updates
Best Practices Checklist
- All find operations filter
deletedAt: null - Soft delete used instead of hard delete
- Specialized
findBy*methods for common queries - Bulk operations used instead of loops
- Type-safe queries using framework's type system
- Error handling delegates to global filters
- No business logic in repositories
- No HTTP exceptions thrown
- Pagination supported
- Eager loading (includes) supported
- Repositories testable and mockable
- Clear method signatures with return types
- No N+1 query patterns
- Proper indexing on filtered columns
Related Skills
- service-pattern/SKILL.md - Service layer using repositories
- backend-controller-pattern/SKILL.md - Repository consumption
- database-patterns/SKILL.md - ORM-specific patterns
Decision Tree
Getting single record?
→ Use findById() or specialized findBy*() method
- Include soft delete filter
- Return null if not found
- Support eager loading via includes
Getting multiple records?
→ Use findMany() with filtering/pagination
- Include soft delete filter
- Support skip/take for pagination
- Support where clauses for filtering
- Support orderBy for sorting
Creating/Updating multiple records? → Use bulk operations
- Use
createMany()orupdateMany() - Avoid loops
- Return operation results
Unsure about approach? → Check similar repositories in codebase
- Look for established patterns
- Follow existing conventions
- Ask in code review
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon