lovable-boilerplate / rules
chihebnabil/lovable-boilerplate/.cursor/rules/services.mdc
Service layer architecture, API patterns, and data management with TanStack Query
Cursor rule65 starsChanged 14 months ago
- Sends data out
What's in it
- Service Layer & Data Management Rules
- Service Layer Organization
- File Structure
- Service Layer Pattern
- Shared Types (lib/types.ts)
- Constants (lib/constants.ts)
- Validation Schemas
- TanStack Query Integration
- Query Hooks Pattern
- Mutation Hooks Pattern
- NEVER in Components
- ALWAYS Use
---
description: Service layer architecture, API patterns, and data management with TanStack Query
globs: ["src/lib/**/*.ts", "src/services/**/*.ts", "src/hooks/use*.ts"]
alwaysApply: false
---
# Service Layer & Data Management Rules
## Service Layer Organization
### File Structure
```
src/lib/
├── utils.ts # General utilities (keep cn function)
├── types.ts # Shared TypeScript interfaces
├── constants.ts # App-wide constants
├── validations/ # Zod schemas
│ ├── user.ts
│ ├── product.ts
│ └── common.ts
└── services/ # API service layer
├── api.ts # Base API client
├── userService.ts
└── productService.ts
```
### Service Layer Pattern
```tsx
// services/userService.ts
export const userService = {
async getAll(): Promise<User[]> {
const response = await fetch('/api/users')
if (!response.ok) throw new Error('Failed to fetch users')
return response.json()
},
async getById(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`)
if (!response.ok) throw new Error('Failed to fetch user')
return response.json()
},
async create(data: CreateUserData): Promise<User> {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
if (!response.ok) throw new Error('Failed to create user')
return response.json()
},
async update(id: string, data: Partial<User>): Promise<User> {
const response = await fetch(`/api/users/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
if (!response.ok) throw new Error('Failed to update user')
return response.json()
}
}
```
### Shared Types (lib/types.ts)
```tsx
export interface User {
id: string
email: string
name: string
avatar?: string
role: UserRole
createdAt: string
updatedAt: string
}
export interface ApiResponse<T> {
data: T
message: string
success: boolean
meta?: {
total: number
page: number
limit: number
}
}
export type UserRole = 'admin' | 'user' | 'guest'
export type Theme = 'light' | 'dark' | 'system'
export type LoadingState = 'idle' | 'loading' | 'success' | 'error'
```
### Constants (lib/constants.ts)
```tsx
export const API_ENDPOINTS = {
USERS: '/api/users',
PRODUCTS: '/api/products',
AUTH: '/api/auth',
} as const
export const QUERY_KEYS = {
USERS: 'users',
PRODUCTS: 'products',
USER_PROFILE: 'user-profile',
} as const
export const ROUTES = {
HOME: '/',
DASHBOARD: '/dashboard',
USERS: '/users',
PROFILE: '/profile',
} as const
```
### Validation Schemas
```tsx
// lib/validations/common.ts
export const emailSchema = z.string().email('Invalid email address')
export const phoneSchema = z.string().regex(/^\+?[\d\s-()]+$/, 'Invalid phone number')
// lib/validations/user.ts
export const userSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: emailSchema,
phone: phoneSchema.optional(),
role: z.enum(['admin', 'user', 'guest'])
})
export const createUserSchema = userSchema.omit({ role: true })
export const updateUserSchema = userSchema.partial()
export type UserFormData = z.infer<typeof userSchema>
export type CreateUserData = z.infer<typeof createUserSchema>
export type UpdateUserData = z.infer<typeof updateUserSchema>
```
## TanStack Query Integration
### Query Hooks Pattern
```tsx
// hooks/useUsers.ts
export const useUsers = (params?: UserQueryParams) => {
return useQuery({
queryKey: [QUERY_KEYS.USERS, params],
queryFn: () => userService.getAll(params),
staleTime: 5 * 60 * 1000, // 5 minutes
})
}
export const useUser = (id: string) => {
return useQuery({
queryKey: [QUERY_KEYS.USERS, id],
queryFn: () => userService.getById(id),
enabled: !!id,
})
}
```
### Mutation Hooks Pattern
```tsx
// hooks/useUserMutations.ts
export const useCreateUser = () => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: userService.create,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.USERS] })
toast.success('User created successfully')
},
onError: (error) => {
toast.error(error.message)
}
})
}
```
## NEVER in Components
- Direct API calls (`fetch`, `axios` inline)
- Business logic mixed with UI
- Hardcoded API URLs
- Duplicate validation schemas
## ALWAYS Use
- Service layer for all API calls
- Shared types for consistent interfaces
- Custom hooks for data fetching
- Centralized constants for URLs and keys
More agent context in chihebnabil/lovable-boilerplate
8 other files this repository gives its agents.
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
No reports yet. Be the first to say whether it worked.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool public_context_discussion, action report. How to connect one.

