Project Examples
Let's look at some real-world TypeScript implementations and common use cases. These examples will help you understand how to structure and build full-scale applications.
REST API Server
Here's an example of a TypeScript Express.js API server:
// src/types/user.ts
export interface User {
id: string;
email: string;
name: string;
role: 'user' | 'admin';
}
// src/services/userService.ts
import { User } from '../types/user';
import { DatabaseError } from '../errors/DatabaseError';
export class UserService {
async findUser(id: string): Promise<Result<User>> {
try {
const user = await db.users.findUnique({ where: { id } });
return { success: true, data: user };
} catch (error) {
return { success: false, error: new DatabaseError('Failed to fetch user') };
}
}
}
// src/controllers/userController.ts
import { Request, Response } from 'express';
import { UserService } from '../services/userService';
export class UserController {
constructor(private userService: UserService) {}
async getUser(req: Request, res: Response): Promise<void> {
const result = await this.userService.findUser(req.params.id);
if (!result.success) {
res.status(500).json({ error: result.error.message });
return;
}
res.json(result.data);
}
}
React Frontend Application
A TypeScript React application with state management:
// src/types/store.ts
interface AppState {
user: User | null;
theme: 'light' | 'dark';
notifications: Notification[];
}
// src/hooks/useAuth.ts
function useAuth() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
auth.onAuthStateChanged((user) => {
setUser(user);
setLoading(false);
});
}, []);
return { user, loading };
}
// src/components/UserProfile.tsx
interface UserProfileProps {
user: User;
onUpdate: (updates: Partial<User>) => Promise<void>;
}
const UserProfile: React.FC<UserProfileProps> = ({ user, onUpdate }) => {
const [isEditing, setIsEditing] = useState(false);
const { theme } = useTheme();
return (
<Card theme={theme}>
<UserInfo user={user} isEditing={isEditing} />
<UserActions onEdit={() => setIsEditing(true)} onUpdate={onUpdate} />
</Card>
);
};
Full-Stack Integration Example
Here's how to connect frontend and backend with shared types:
// shared/types/api.ts
export interface ApiResponse<T> {
data: T;
meta: {
timestamp: string;
version: string;
};
}
// frontend/src/api/client.ts
import { ApiResponse, User } from 'shared/types/api';
export class ApiClient {
async getUser(id: string): Promise<ApiResponse<User>> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
}
// backend/src/controllers/api.ts
import { ApiResponse, User } from 'shared/types/api';
export function createApiResponse<T>(data: T): ApiResponse<T> {
return {
data,
meta: {
timestamp: new Date().toISOString(),
version: process.env.API_VERSION
}
};
}
Testing Examples
Unit Testing with Jest
// src/services/__tests__/userService.test.ts
import { UserService } from '../userService';
import { mockDb } from '../../testing/mockDb';
describe('UserService', () => {
let service: UserService;
beforeEach(() => {
service = new UserService(mockDb);
});
it('should find user by id', async () => {
const mockUser = createMockUser();
mockDb.users.findUnique.mockResolvedValue(mockUser);
const result = await service.findUser(mockUser.id);
expect(result.success).toBe(true);
expect(result.data).toEqual(mockUser);
});
it('should handle database errors', async () => {
mockDb.users.findUnique.mockRejectedValue(new Error('DB Error'));
const result = await service.findUser('123');
expect(result.success).toBe(false);
expect(result.error).toBeInstanceOf(DatabaseError);
});
});
Integration Testing
// src/tests/integration/api.test.ts
import { app } from '../../app';
import { createTestClient } from '../utils/testClient';
describe('API Integration', () => {
const client = createTestClient();
beforeAll(async () => {
await setupTestDatabase();
});
it('should create and retrieve user', async () => {
// Create user
const createResponse = await client.post('/users', {
name: 'Test User',
email: 'test@example.com'
});
expect(createResponse.status).toBe(201);
// Retrieve user
const { id } = createResponse.data;
const getResponse = await client.get(`/users/${id}`);
expect(getResponse.status).toBe(200);
expect(getResponse.data.email).toBe('test@example.com');
});
});
Project Structure
A typical TypeScript project structure:
project/
├── src/
│ ├── components/ # React components
│ ├── services/ # Business logic
│ ├── utils/ # Helper functions
│ ├── types/ # Type definitions
│ ├── config/ # Configuration
│ └── tests/ # Test files
├── shared/ # Shared types/utils
├── scripts/ # Build/deployment scripts
├── docs/ # Documentation
├── package.json
├── tsconfig.json
├── jest.config.js
└── README.md
Common Use Cases
- Form Handling
interface FormData {
email: string;
password: string;
}
function useForm<T>(initialData: T) {
const [data, setData] = useState<T>(initialData);
const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
const handleChange = (key: keyof T, value: T[keyof T]) => {
setData(prev => ({ ...prev, [key]: value }));
};
return { data, errors, handleChange };
}
- API Integration
class ApiService {
private baseUrl: string;
private headers: Record<string, string>;
constructor(config: ApiConfig) {
this.baseUrl = config.baseUrl;
this.headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${config.apiKey}`
};
}
async get<T>(endpoint: string): Promise<T> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
headers: this.headers
});
return response.json();
}
}
- State Management
type Action =
| { type: 'SET_USER'; payload: User }
| { type: 'CLEAR_USER' }
| { type: 'UPDATE_SETTINGS'; payload: Partial<Settings> };
function reducer(state: AppState, action: Action): AppState {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload };
case 'CLEAR_USER':
return { ...state, user: null };
case 'UPDATE_SETTINGS':
return {
...state,
settings: { ...state.settings, ...action.payload }
};
default:
return state;
}
}
Remember:
- Keep your project structure organized and consistent
- Use shared types between frontend and backend
- Write comprehensive tests
- Follow TypeScript best practices
- Document your code thoroughly
- Consider performance implications
- Use proper error handling
- Implement proper security measures