Best Practices
Learn the coding standards, patterns, and best practices used in Ever Works.
π― Objectivesβ
By the end of this module, you will:
- β Understand TypeScript best practices
- β Follow React patterns and optimization techniques
- β Apply database best practices
- β Implement security guidelines
- β Optimize for performance
- β Write clean, maintainable code
Estimated time: 1-2 days
TypeScript Best Practicesβ
Use Explicit Typesβ
// β Avoid implicit any
function processData(data) {
return data.map(item => item.value);
}
// β
Use explicit types
function processData(data: Array<{ value: number }>): number[] {
return data.map(item => item.value);
}
Avoid anyβ
// β Don't use any
const fetchData = async (): Promise<any> => {
// ...
};
// β
Define proper types
interface UserData {
id: string;
name: string;
email: string;
}
const fetchData = async (): Promise<UserData> => {
// ...
};
Use Type Guardsβ
// β
Type guards for runtime safety
function isUser(obj: unknown): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
'id' in obj &&
'email' in obj
);
}
if (isUser(data)) {
// TypeScript knows data is User here
console.log(data.email);
}
Use Discriminated Unionsβ
// β
Discriminated unions for state management
type RequestState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: User }
| { status: 'error'; error: string };
function handleState(state: RequestState) {
switch (state.status) {
case 'idle':
return 'Not started';
case 'loading':
return 'Loading...';
case 'success':
return state.data.name; // TypeScript knows data exists
case 'error':
return state.error; // TypeScript knows error exists
}
}
Learn more about TypeScript β
React Best Practicesβ
Use Memoizationβ
// β
Memoize expensive computations
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
// β
Memoize callbacks
const handleClick = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// β
Memoize components
const MemoizedComponent = memo(({ data }) => {
return <div>{data}</div>;
});
Create Custom Hooksβ
// β
Extract reusable logic into custom hooks
function useUser(userId: string) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchUser(userId)
.then(setUser)
.catch(err => setError(err.message))
.finally(() => setLoading(false));
}, [userId]);
return { user, loading, error };
}
// Usage
function UserProfile({ userId }: { userId: string }) {
const { user, loading, error } = useUser(userId);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{user?.name}</div>;
}
Avoid Unnecessary Re-rendersβ
// β Creates new object on every render
<Component style={{ margin: 10 }} />
// β
Define outside component or use useMemo
const style = { margin: 10 };
<Component style={style} />
// β Inline function creates new reference
<button onClick={() => handleClick(id)}>Click</button>
// β
Use useCallback
const handleButtonClick = useCallback(() => {
handleClick(id);
}, [id]);
<button onClick={handleButtonClick}>Click</button>
Component Structureβ
// β
Consistent component structure
export function UserCard({ user }: { user: User }) {
// 1. Hooks
const [isExpanded, setIsExpanded] = useState(false);
const { theme } = useTheme();
// 2. Derived state
const displayName = user.name || user.email;
// 3. Event handlers
const handleToggle = useCallback(() => {
setIsExpanded(prev => !prev);
}, []);
// 4. Effects
useEffect(() => {
// Side effects
}, []);
// 5. Render
return (
<div>
{/* JSX */}
</div>
);
}
Database Best Practicesβ
Use Transactionsβ
// β
Use transactions for related operations
await db.transaction(async (tx) => {
await tx.insert(users).values(newUser);
await tx.insert(profiles).values(newProfile);
await tx.insert(settings).values(defaultSettings);
});
Use Prepared Statementsβ
// β
Drizzle ORM uses prepared statements automatically
const user = await db
.select()
.from(users)
.where(eq(users.id, userId))
.limit(1);
Add Proper Indexingβ
// β
Add indexes for frequently queried fields
export const users = pgTable('users', {
id: text('id').primaryKey(),
email: text('email').notNull().unique(), // Automatically indexed
createdAt: timestamp('created_at').notNull(),
}, (table) => ({
emailIdx: index('email_idx').on(table.email),
createdAtIdx: index('created_at_idx').on(table.createdAt),
}));
Validate Dataβ
// β
Validate before insertion
const userSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(50),
age: z.number().min(18).max(120),
});
const validatedData = userSchema.parse(userData);
await db.insert(users).values(validatedData);
Learn more about Drizzle ORM β
Security Best Practicesβ
Validate All Inputβ
// β
Always validate user input
const schema = z.object({
email: z.string().email(),
password: z.string().min(8).max(100),
});
const result = schema.safeParse(req.body);
if (!result.success) {
return NextResponse.json(
{ error: 'Validation failed', details: result.error },
{ status: 400 }
);
}
Use Parameterized Queriesβ
// β
Drizzle ORM prevents SQL injection
const user = await db
.select()
.from(users)
.where(eq(users.email, userEmail)); // Safe
// β Never use raw SQL with user input
// const user = await db.execute(`SELECT * FROM users WHERE email = '${userEmail}'`);
Sanitize Outputβ
// β
Sanitize HTML content
import DOMPurify from 'isomorphic-dompurify';
const sanitizedContent = DOMPurify.sanitize(userContent);
Implement Rate Limitingβ
// β
Rate limit sensitive endpoints
import { rateLimit } from '@/lib/rate-limit';
export async function POST(request: NextRequest) {
const identifier = request.ip ?? 'anonymous';
const { success } = await rateLimit.limit(identifier);
if (!success) {
return NextResponse.json(
{ error: 'Too many requests' },
{ status: 429 }
);
}
// Process request
}
Performance Optimizationβ
Code Splittingβ
// β
Dynamic imports for code splitting
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <div>Loading...</div>,
ssr: false, // Disable SSR if not needed
});
Image Optimizationβ
// β
Use Next.js Image component
import Image from 'next/image';
<Image
src="/hero.jpg"
alt="Hero image"
width={1200}
height={600}
priority // For above-the-fold images
placeholder="blur"
/>
Virtualizationβ
// β
Virtualize long lists
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
{virtualizer.getVirtualItems().map(virtualItem => (
<div key={virtualItem.index}>
{items[virtualItem.index].name}
</div>
))}
</div>
);
}
Caching Strategiesβ
// β
Use React Query for server state
const { data, isLoading } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
});
Learn more about performance β
Code Qualityβ
DRY (Don't Repeat Yourself)β
// β Repetitive code
function getUserEmail(userId: string) {
const user = await db.select().from(users).where(eq(users.id, userId));
return user?.email;
}
function getUserName(userId: string) {
const user = await db.select().from(users).where(eq(users.id, userId));
return user?.name;
}
// β
Extract common logic
async function getUser(userId: string) {
return await db.select().from(users).where(eq(users.id, userId)).limit(1);
}
function getUserEmail(userId: string) {
const user = await getUser(userId);
return user?.email;
}
function getUserName(userId: string) {
const user = await getUser(userId);
return user?.name;
}
SOLID Principlesβ
Follow SOLID principles for maintainable code:
- Single Responsibility: Each function/component does one thing
- Open/Closed: Open for extension, closed for modification
- Liskov Substitution: Subtypes must be substitutable for their base types
- Interface Segregation: Many specific interfaces better than one general
- Dependency Inversion: Depend on abstractions, not concretions
Learn more about SOLID in React β
Consistent Namingβ
// β
Consistent naming conventions
// Components: PascalCase
export function UserProfile() {}
// Hooks: camelCase with 'use' prefix
export function useUser() {}
// Constants: UPPER_SNAKE_CASE
export const MAX_RETRIES = 3;
// Functions: camelCase
export function fetchUserData() {}
// Types/Interfaces: PascalCase
export interface User {}
export type UserRole = 'admin' | 'user';
Additional Resourcesβ
Internal Documentationβ
- Tech Stack - Technologies and versions
- Testing Guide - Testing strategies
- Production Checklist - Production readiness
External Resourcesβ
Next Stepsβ
After learning best practices:
- Exercises - Practice with real tasks
- Apply these practices in your daily work
- Share knowledge with the team
Code Reviews
The best way to learn and enforce best practices is through code reviews. Always request reviews and learn from feedback.
Ready to practice? Move on to Exercises! π