typescript-best-practices
Comprehensive TypeScript best practices including type imports, security patterns, code structure, and early returns. Use when writing TypeScript code, reviewing for quality, implementing error handling, or ensuring type safety.
When & Why to Use This Skill
This Claude skill provides comprehensive, enterprise-grade TypeScript best practices designed to enhance code maintainability, type safety, and security. It focuses on enforcing high-quality coding standards, such as type-only imports for optimized builds, early return patterns for readability, and strict security protocols to prevent sensitive data exposure. By integrating these patterns, developers can ensure their TypeScript projects are robust, scalable, and follow industry-leading architectural principles.
Use Cases
- Automated Code Quality Audits: Use this skill to review pull requests and identify anti-patterns, such as improper type imports or deeply nested conditionals that hinder readability.
- Security Enforcement: Prevent critical security vulnerabilities by automatically checking for the exposure of sensitive fields like credential keys in database queries and API responses.
- Refactoring Legacy Code: Guide the transformation of loosely typed or poorly structured TypeScript code into a modern, type-safe architecture using discriminated unions and composition.
- Standardizing Error Handling: Implement consistent, predictable error management across a codebase using custom error classes and the Result type pattern for better debugging.
- Performance Optimization: Improve build times and enable better tree-shaking by systematically converting standard imports to type-only imports where applicable.
| name | typescript-best-practices |
|---|---|
| description | Comprehensive TypeScript best practices including type imports, security patterns, code structure, and early returns. Use when writing TypeScript code, reviewing for quality, implementing error handling, or ensuring type safety. |
| allowed-tools | Read, Grep, Glob |
TypeScript Best Practices
Enterprise-grade TypeScript patterns and practices for maintainable, type-safe, and secure code.
When to Use This Skill
- Writing new TypeScript code or modules
- Code reviews focusing on TypeScript quality
- Implementing type-safe patterns
- Ensuring security best practices
- Refactoring for better code structure
- Setting up imports and dependencies
Core Principles
1. Type Safety First
TypeScript's type system is your first line of defense against bugs. Use it fully:
- Always prefer explicit types over inference when it improves clarity
- Use
typeimports for TypeScript types - Leverage discriminated unions for type narrowing
- Use
nevertype for exhaustiveness checking
2. Security is Non-Negotiable
Security vulnerabilities can be prevented at the type level:
- NEVER expose sensitive credential fields
- Use proper type guards for user input validation
- Sanitize all external data at system boundaries
- Follow principle of least privilege in type definitions
3. Code Structure Matters
Well-structured code is easier to maintain and debug:
- Prefer early returns to reduce nesting
- Use composition over prop drilling
- Keep functions focused and single-purpose
- Organize imports consistently
Import Guidelines
Type Imports (CRITICAL)
Always use type imports for TypeScript types to improve build performance and clearly separate type-only imports from runtime imports.
✅ Good - Use type imports:
import type { User } from "@prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import type { ReactNode } from "react";
❌ Bad - Regular import for types:
import { User } from "@prisma/client";
import { NextApiRequest, NextApiResponse } from "next";
Why? Type imports:
- Are erased at compile time (no runtime overhead)
- Make it clear what's type-only vs runtime code
- Enable better tree-shaking
- Prevent circular dependency issues
Mixed Imports
When importing both types and values:
// ✅ Good - Separate type and value imports
import type { ComponentProps } from "react";
import { useState, useEffect } from "react";
// ✅ Also acceptable - inline type imports
import { useState, useEffect, type ComponentProps } from "react";
Security Rules
NEVER Expose Credential Keys
❌ CRITICAL ERROR - NEVER do this:
const user = await prisma.user.findFirst({
select: {
credentials: {
select: {
key: true, // ❌ SECURITY VIOLATION: Exposes sensitive data
}
}
}
});
✅ Good - Exclude sensitive fields:
const user = await prisma.user.findFirst({
select: {
id: true,
email: true,
credentials: {
select: {
id: true,
type: true,
// key field is intentionally excluded for security
}
}
}
});
Security checklist:
- ✅ Never select
credential.key,password,secret, or similar sensitive fields - ✅ Always use explicit
selectinstead of returning all fields - ✅ Create dedicated types for public-safe data structures
- ✅ Use type guards to validate external input
Type-Safe Sensitive Data Handling
// Define separate types for internal and external use
type UserCredential = {
id: string;
userId: string;
type: string;
key: string; // Only exists in internal type
};
type PublicUserCredential = Omit<UserCredential, 'key'>;
// Use utility type to enforce exclusion
function getPublicCredential(cred: UserCredential): PublicUserCredential {
const { key, ...publicData } = cred;
return publicData;
}
Code Structure
Early Returns
Reduce nesting and improve readability with early returns.
✅ Good - Early returns:
function processBooking(booking: Booking | null) {
if (!booking) return null;
if (!booking.isConfirmed) return null;
if (booking.isCancelled) return null;
return formatBooking(booking);
}
❌ Bad - Nested conditions:
function processBooking(booking: Booking | null) {
if (booking) {
if (booking.isConfirmed) {
if (!booking.isCancelled) {
return formatBooking(booking);
}
}
}
return null;
}
Composition Over Prop Drilling
✅ Good - Use React children and context:
// Instead of passing props through multiple layers
function App() {
return (
<ThemeProvider>
<UserProvider>
<Dashboard />
</UserProvider>
</ThemeProvider>
);
}
function Dashboard() {
const user = useUser(); // From context
const theme = useTheme(); // From context
return <div>...</div>;
}
❌ Bad - Prop drilling:
function App() {
const user = getUser();
const theme = getTheme();
return <Layout user={user} theme={theme} />;
}
function Layout({ user, theme }) {
return <Sidebar user={user} theme={theme} />;
}
function Sidebar({ user, theme }) {
return <UserMenu user={user} theme={theme} />;
}
Error Handling Patterns
Custom Error Classes
// ✅ Good - Typed error classes
export class AppError extends Error {
constructor(
public code: string,
public statusCode: number = 500,
message?: string
) {
super(message || code);
this.name = 'AppError';
Object.setPrototypeOf(this, AppError.prototype);
}
}
// Usage
function validateEmail(email: string): void {
if (!email.includes('@')) {
throw new AppError('INVALID_EMAIL', 400, 'Email must contain @');
}
}
Result Type Pattern
type Success<T> = { status: 'success'; data: T };
type Failure = { status: 'error'; error: string; code: string };
type Result<T> = Success<T> | Failure;
async function fetchUser(id: string): Promise<Result<User>> {
try {
const user = await db.user.findUnique({ where: { id } });
if (!user) {
return { status: 'error', error: 'User not found', code: 'NOT_FOUND' };
}
return { status: 'success', data: user };
} catch (error) {
return {
status: 'error',
error: error instanceof Error ? error.message : 'Unknown error',
code: 'FETCH_FAILED'
};
}
}
// Usage with discriminated union
const result = await fetchUser('123');
if (result.status === 'success') {
console.log(result.data); // TypeScript knows this is User
} else {
console.error(result.error); // TypeScript knows this is error
}
Type System Best Practices
Discriminated Unions
// ✅ Good - Type-safe state management
type RequestState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
function handleRequest<T>(state: RequestState<T>) {
switch (state.status) {
case 'idle':
return 'Not started';
case 'loading':
return 'Loading...';
case 'success':
return state.data; // TypeScript knows data exists
case 'error':
return state.error.message; // TypeScript knows error exists
}
}
Utility Types
// Omit sensitive fields
type PublicUser = Omit<User, 'password' | 'apiKey'>;
// Pick only needed fields
type UserSummary = Pick<User, 'id' | 'name' | 'email'>;
// Make all properties optional
type PartialUser = Partial<User>;
// Make all properties required
type RequiredUser = Required<User>;
// Make all properties readonly
type ImmutableUser = Readonly<User>;
Async/Promise Best Practices
Always Return Promises
// ✅ Good - Consistent promise return
async function fetchData(): Promise<Data> {
const response = await fetch('/api/data');
return response.json();
}
// ❌ Bad - Mixed sync/async
async function fetchData() {
if (cache.has('data')) {
return cache.get('data'); // Not wrapped in Promise
}
const response = await fetch('/api/data');
return response.json();
}
Error Propagation
// ✅ Good - Proper error handling
async function processData(id: string): Promise<Result<Data>> {
try {
const data = await fetchData(id);
const validated = validateData(data);
return { status: 'success', data: validated };
} catch (error) {
return {
status: 'error',
error: error instanceof Error ? error.message : 'Unknown error',
code: 'PROCESS_FAILED'
};
}
}
Examples Reference
See the examples/ directory for complete, working examples:
error-handling.ts- Custom error classes and Result typestype-safety.ts- Advanced type patternssecurity-patterns.ts- Secure data handlingasync-patterns.ts- Promise and async/await best practices
Templates Reference
See the templates/ directory for reusable code templates:
base-service.ts- Generic service class templateerror-handler.ts- Centralized error handling
AI Assistant Instructions
When this skill is activated:
Always:
- Use
typeimports for TypeScript types - Check for security violations (credential.key exposure)
- Suggest early returns for better code structure
- Provide complete, working examples
- Reference specific example files when relevant
- Explain the "why" behind each pattern
Never:
- Use regular imports for types
- Suggest exposing sensitive credential fields
- Skip error handling in examples
- Use
anywithout strong justification - Create deeply nested conditionals
Security Review Checklist:
- Are we selecting credential.key or other sensitive fields? ❌
- Are we using explicit
selectfor database queries? ✅ - Are we validating external input? ✅
- Are we using type guards for user data? ✅
Code Quality Checklist:
- Are we using
typeimports? ✅ - Are we using early returns? ✅
- Are we avoiding prop drilling? ✅
- Is error handling comprehensive? ✅
- Are types explicit and clear? ✅