Skip to main content

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>
);
}

Learn more about React β†’


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
}

Learn more about security β†’


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​

External Resources​


Next Steps​

After learning best practices:

  1. Exercises - Practice with real tasks
  2. Apply these practices in your daily work
  3. 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! πŸš€