Skip to main content

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

  1. 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 };
}
  1. 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();
}
}
  1. 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:

  1. Keep your project structure organized and consistent
  2. Use shared types between frontend and backend
  3. Write comprehensive tests
  4. Follow TypeScript best practices
  5. Document your code thoroughly
  6. Consider performance implications
  7. Use proper error handling
  8. Implement proper security measures