We use cookies to enhance your experience on the site
CodeWorlds

TypeScript with Popular Frameworks

"Welcome back to Jurassic Park," says Dr. Ellie Sattler, paleo-botanist. "You've already learned TypeScript as a standalone language. But in the real world, we rarely use pure TypeScript — we usually integrate it with frameworks that help build complex applications. Come, I'll show you how TypeScript works with the most popular tools."

TypeScript with React - Typed Components

React is one of the most popular libraries for building user interfaces. TypeScript integrates wonderfully with it, providing strong typing for components, props, and hooks.

Basic Functional Components

1// Interface for component props
2interface DinoCardProps {
3  name: string;
4  species: string;
5  isDangerous: boolean;
6  age?: number; // Optional field
7  onEscape?: () => void; // Optional callback function
8}
9
10// Functional component with typed props
11const DinoCard: React.FC<DinoCardProps> = ({
12  name,
13  species,
14  isDangerous,
15  age,
16  onEscape
17}) => {
18  return (
19    <div className="dino-card">
20      <h2>{name}</h2>
21      <p>Species: {species}</p>
22      <p>Age: {age || 'Unknown'}</p>
23      <div className={isDangerous ? 'danger' : 'safe'}>
24        {isDangerous ? 'DANGEROUS' : 'Safe'}
25      </div>
26      {onEscape && (
27        <button onClick={onEscape}>
28          Report escape
29        </button>
30      )}
31    </div>
32  );
33};
34
35// Usage with full typing
36const App = () => {
37  const handleEscape = () => {
38    console.log('ALARM: Dinosaur escaped!');
39  };
40
41  return (
42    <DinoCard
43      name="Blue"
44      species="Velociraptor"
45      isDangerous={true}
46      age={4}
47      onEscape={handleEscape}
48    />
49  );
50};

Typing React Hooks

1import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
2
3interface Dinosaur {
4  id: number;
5  name: string;
6  species: string;
7  status: 'contained' | 'escaped' | 'feeding';
8}
9
10const DinosaurMonitor: React.FC = () => {
11  // useState with type
12  const [dinosaurs, setDinosaurs] = useState<Dinosaur[]>([]);
13  const [loading, setLoading] = useState<boolean>(false);
14  const [error, setError] = useState<Error | null>(null);
15
16  // useRef with DOM element type
17  const alertSound = useRef<HTMLAudioElement>(null);
18
19  // useEffect - fetching data
20  useEffect(() => {
21    const fetchDinosaurs = async () => {
22      setLoading(true);
23      try {
24        const response = await fetch('/api/dinosaurs');
25        const data: Dinosaur[] = await response.json();
26        setDinosaurs(data);
27      } catch (err) {
28        setError(err as Error);
29      } finally {
30        setLoading(false);
31      }
32    };
33
34    fetchDinosaurs();
35  }, []);
36
37  // useCallback with typed parameters
38  const handleDinoEscape = useCallback((dinoId: number) => {
39    setDinosaurs(prev =>
40      prev.map(dino =>
41        dino.id === dinoId
42          ? { ...dino, status: 'escaped' as const }
43          : dino
44      )
45    );
46
47    // Play alarm
48    if (alertSound.current) {
49      alertSound.current.play();
50    }
51  }, []);
52
53  // useMemo with return type
54  const dangerousDinos = useMemo<Dinosaur[]>(() => {
55    return dinosaurs.filter(dino => dino.status === 'escaped');
56  }, [dinosaurs]);
57
58  if (loading) return <div>Loading monitoring data...</div>;
59  if (error) return <div>Error: {error.message}</div>;
60
61  return (
62    <div>
63      <h1>Dinosaur Monitor</h1>
64      <audio ref={alertSound} src="/sounds/alarm.mp3" />
65
66      {dangerousDinos.length > 0 && (
67        <div className="alert">
68          WARNING: {dangerousDinos.length} dinosaurs at large!
69        </div>
70      )}
71
72      <div className="dino-grid">
73        {dinosaurs.map(dino => (
74          <DinoCard
75            key={dino.id}
76            name={dino.name}
77            species={dino.species}
78            isDangerous={dino.status === 'escaped'}
79            onEscape={() => handleDinoEscape(dino.id)}
80          />
81        ))}
82      </div>
83    </div>
84  );
85};

Custom Hooks with TypeScript

1// Custom hook for managing a list of dinosaurs
2interface UseDinosaursResult {
3  dinosaurs: Dinosaur[];
4  loading: boolean;
5  error: Error | null;
6  addDinosaur: (dino: Omit<Dinosaur, 'id'>) => Promise<void>;
7  removeDinosaur: (id: number) => Promise<void>;
8  updateStatus: (id: number, status: Dinosaur['status']) => void;
9}
10
11function useDinosaurs(): UseDinosaursResult {
12  const [dinosaurs, setDinosaurs] = useState<Dinosaur[]>([]);
13  const [loading, setLoading] = useState<boolean>(false);
14  const [error, setError] = useState<Error | null>(null);
15
16  const addDinosaur = useCallback(async (dino: Omit<Dinosaur, 'id'>) => {
17    setLoading(true);
18    try {
19      const response = await fetch('/api/dinosaurs', {
20        method: 'POST',
21        headers: { 'Content-Type': 'application/json' },
22        body: JSON.stringify(dino)
23      });
24      const newDino: Dinosaur = await response.json();
25      setDinosaurs(prev => [...prev, newDino]);
26    } catch (err) {
27      setError(err as Error);
28    } finally {
29      setLoading(false);
30    }
31  }, []);
32
33  const removeDinosaur = useCallback(async (id: number) => {
34    setLoading(true);
35    try {
36      await fetch(`/api/dinosaurs/${id}`, { method: 'DELETE' });
37      setDinosaurs(prev => prev.filter(d => d.id !== id));
38    } catch (err) {
39      setError(err as Error);
40    } finally {
41      setLoading(false);
42    }
43  }, []);
44
45  const updateStatus = useCallback((id: number, status: Dinosaur['status']) => {
46    setDinosaurs(prev =>
47      prev.map(dino =>
48        dino.id === id ? { ...dino, status } : dino
49      )
50    );
51  }, []);
52
53  return {
54    dinosaurs,
55    loading,
56    error,
57    addDinosaur,
58    removeDinosaur,
59    updateStatus
60  };
61}
62
63// Using the custom hook
64const ParkManager: React.FC = () => {
65  const {
66    dinosaurs,
67    loading,
68    error,
69    addDinosaur,
70    updateStatus
71  } = useDinosaurs();
72
73  const handleFeedDino = (id: number) => {
74    updateStatus(id, 'feeding');
75    setTimeout(() => updateStatus(id, 'contained'), 5000);
76  };
77
78  return (
79    <div>
80      {/* Component using the hook */}
81    </div>
82  );
83};

TypeScript with Node.js/Express - Backend API

Typing Express Applications

1import express, { Request, Response, NextFunction } from 'express';
2
3// Interfaces for data models
4interface Dinosaur {
5  id: number;
6  name: string;
7  species: string;
8  paddock: number;
9  feedingSchedule: string[];
10}
11
12interface CreateDinosaurDTO {
13  name: string;
14  species: string;
15  paddock: number;
16}
17
18// Extending Request with custom fields
19interface AuthRequest extends Request {
20  user?: {
21    id: number;
22    role: 'admin' | 'keeper' | 'visitor';
23  };
24}
25
26// Controller with typing
27class DinosaurController {
28  // GET /api/dinosaurs
29  async getAll(req: Request, res: Response<Dinosaur[]>, next: NextFunction) {
30    try {
31      const dinosaurs = await this.dinosaurService.findAll();
32      res.json(dinosaurs);
33    } catch (error) {
34      next(error);
35    }
36  }
37
38  // GET /api/dinosaurs/:id
39  async getById(
40    req: Request<{ id: string }>,
41    res: Response<Dinosaur>,
42    next: NextFunction
43  ) {
44    try {
45      const id = parseInt(req.params.id);
46      const dinosaur = await this.dinosaurService.findById(id);
47
48      if (!dinosaur) {
49        res.status(404).json({ error: 'Dinosaur not found' } as any);
50        return;
51      }
52
53      res.json(dinosaur);
54    } catch (error) {
55      next(error);
56    }
57  }
58
59  // POST /api/dinosaurs
60  async create(
61    req: Request<{}, {}, CreateDinosaurDTO>,
62    res: Response<Dinosaur>,
63    next: NextFunction
64  ) {
65    try {
66      const newDino = await this.dinosaurService.create(req.body);
67      res.status(201).json(newDino);
68    } catch (error) {
69      next(error);
70    }
71  }
72}
73
74// Middleware with typing
75const authenticateUser = (
76  req: AuthRequest,
77  res: Response,
78  next: NextFunction
79): void => {
80  const token = req.headers.authorization?.split(' ')[1];
81
82  if (!token) {
83    res.status(401).json({ error: 'No token provided' });
84    return;
85  }
86
87  try {
88    const decoded = verifyToken(token);
89    req.user = decoded;
90    next();
91  } catch (error) {
92    res.status(401).json({ error: 'Invalid token' });
93  }
94};
95
96// Routing
97const app = express();
98
99app.use(express.json());
100
101const dinoController = new DinosaurController();
102
103app.get('/api/dinosaurs', dinoController.getAll.bind(dinoController));
104app.get('/api/dinosaurs/:id', dinoController.getById.bind(dinoController));
105app.post('/api/dinosaurs', authenticateUser, dinoController.create.bind(dinoController));

Typed Services and Repositories

1// Repository Pattern with TypeScript
2interface Repository<T> {
3  findAll(): Promise<T[]>;
4  findById(id: number): Promise<T | null>;
5  create(data: Omit<T, 'id'>): Promise<T>;
6  update(id: number, data: Partial<T>): Promise<T>;
7  delete(id: number): Promise<void>;
8}
9
10class DinosaurRepository implements Repository<Dinosaur> {
11  async findAll(): Promise<Dinosaur[]> {
12    // Database implementation
13    return [];
14  }
15
16  async findById(id: number): Promise<Dinosaur | null> {
17    // Implementation
18    return null;
19  }
20
21  async create(data: Omit<Dinosaur, 'id'>): Promise<Dinosaur> {
22    // Implementation
23    return { id: 1, ...data } as Dinosaur;
24  }
25
26  async update(id: number, data: Partial<Dinosaur>): Promise<Dinosaur> {
27    // Implementation
28    return {} as Dinosaur;
29  }
30
31  async delete(id: number): Promise<void> {
32    // Implementation
33  }
34}
35
36// Service Layer
37class DinosaurService {
38  constructor(private repository: DinosaurRepository) {}
39
40  async getAllDinosaurs(): Promise<Dinosaur[]> {
41    return this.repository.findAll();
42  }
43
44  async getDinosaurById(id: number): Promise<Dinosaur> {
45    const dino = await this.repository.findById(id);
46    if (!dino) {
47      throw new Error(`Dinosaur with id ${id} not found`);
48    }
49    return dino;
50  }
51
52  async createDinosaur(data: CreateDinosaurDTO): Promise<Dinosaur> {
53    // Validation
54    this.validateDinosaurData(data);
55
56    // Creation
57    return this.repository.create({
58      ...data,
59      feedingSchedule: this.generateFeedingSchedule(data.species)
60    });
61  }
62
63  private validateDinosaurData(data: CreateDinosaurDTO): void {
64    if (!data.name || data.name.length < 2) {
65      throw new Error('Invalid dinosaur name');
66    }
67    if (data.paddock < 1 || data.paddock > 10) {
68      throw new Error('Invalid paddock number');
69    }
70  }
71
72  private generateFeedingSchedule(species: string): string[] {
73    // Schedule generation logic
74    return ['08:00', '14:00', '20:00'];
75  }
76}

TypeScript with Next.js - Full-stack with Native Support

Next.js has built-in TypeScript support — just create a

.ts
or
.tsx
file and Next.js will automatically configure TypeScript!

Pages and API Routes

1// pages/dinosaurs/[id].tsx - Dynamic route with typing
2import { GetServerSideProps, NextPage } from 'next';
3
4interface DinosaurPageProps {
5  dinosaur: Dinosaur;
6  lastUpdated: string;
7}
8
9// Page component
10const DinosaurPage: NextPage<DinosaurPageProps> = ({ dinosaur, lastUpdated }) => {
11  return (
12    <div>
13      <h1>{dinosaur.name}</h1>
14      <p>Species: {dinosaur.species}</p>
15      <p>Paddock: {dinosaur.paddock}</p>
16      <small>Last updated: {lastUpdated}</small>
17    </div>
18  );
19};
20
21// Server-side rendering with typing
22export const getServerSideProps: GetServerSideProps<DinosaurPageProps> = async (context) => {
23  const { id } = context.params as { id: string };
24
25  try {
26    const response = await fetch(`https://api.park.com/dinosaurs/${id}`);
27    const dinosaur: Dinosaur = await response.json();
28
29    return {
30      props: {
31        dinosaur,
32        lastUpdated: new Date().toISOString()
33      }
34    };
35  } catch (error) {
36    return {
37      notFound: true
38    };
39  }
40};
41
42export default DinosaurPage;

API Routes in Next.js

1// pages/api/dinosaurs/[id].ts
2import type { NextApiRequest, NextApiResponse } from 'next';
3
4type ResponseData = {
5  dinosaur?: Dinosaur;
6  error?: string;
7};
8
9export default async function handler(
10  req: NextApiRequest,
11  res: NextApiResponse<ResponseData>
12) {
13  const { id } = req.query;
14
15  if (req.method === 'GET') {
16    try {
17      const dinosaur = await getDinosaurById(Number(id));
18      res.status(200).json({ dinosaur });
19    } catch (error) {
20      res.status(404).json({ error: 'Dinosaur not found' });
21    }
22  } else if (req.method === 'PUT') {
23    try {
24      const updatedDino = await updateDinosaur(Number(id), req.body);
25      res.status(200).json({ dinosaur: updatedDino });
26    } catch (error) {
27      res.status(400).json({ error: 'Update failed' });
28    }
29  } else {
30    res.status(405).json({ error: 'Method not allowed' });
31  }
32}

App Router (Next.js 13+) with Server Components

1// app/dinosaurs/[id]/page.tsx
2interface PageProps {
3  params: { id: string };
4  searchParams: { [key: string]: string | string[] | undefined };
5}
6
7// Server Component
8export default async function DinosaurDetailPage({ params }: PageProps) {
9  const dinosaur = await getDinosaurById(params.id);
10
11  return (
12    <div>
13      <h1>{dinosaur.name}</h1>
14      <DinosaurDetails dino={dinosaur} />
15    </div>
16  );
17}
18
19// Client Component
20'use client';
21
22interface DinosaurDetailsProps {
23  dino: Dinosaur;
24}
25
26export function DinosaurDetails({ dino }: DinosaurDetailsProps) {
27  const [isFavorite, setIsFavorite] = useState(false);
28
29  return (
30    <div>
31      <button onClick={() => setIsFavorite(!isFavorite)}>
32        {isFavorite ? 'Favorited' : 'Add to favorites'}
33      </button>
34      {/* Rest of the component */}
35    </div>
36  );
37}

Typing Patterns in Frameworks

Generic Components

1// Universal list component with typing
2interface ListProps<T> {
3  items: T[];
4  renderItem: (item: T, index: number) => React.ReactNode;
5  keyExtractor: (item: T) => string | number;
6  emptyMessage?: string;
7}
8
9function List<T>({ items, renderItem, keyExtractor, emptyMessage }: ListProps<T>) {
10  if (items.length === 0) {
11    return <div>{emptyMessage || 'No items'}</div>;
12  }
13
14  return (
15    <ul>
16      {items.map((item, index) => (
17        <li key={keyExtractor(item)}>
18          {renderItem(item, index)}
19        </li>
20      ))}
21    </ul>
22  );
23}
24
25// Usage
26const DinosaurList = () => {
27  const dinosaurs: Dinosaur[] = [
28    { id: 1, name: 'Rex', species: 'T-Rex', paddock: 1, feedingSchedule: [] },
29    { id: 2, name: 'Blue', species: 'Velociraptor', paddock: 3, feedingSchedule: [] }
30  ];
31
32  return (
33    <List<Dinosaur>
34      items={dinosaurs}
35      keyExtractor={(dino) => dino.id}
36      renderItem={(dino) => (
37        <div>
38          <strong>{dino.name}</strong> - {dino.species}
39        </div>
40      )}
41      emptyMessage="No dinosaurs in the system"
42    />
43  );
44};

Form Handling with TypeScript

1import { useForm, SubmitHandler } from 'react-hook-form';
2
3interface DinosaurFormData {
4  name: string;
5  species: string;
6  paddock: number;
7  isDangerous: boolean;
8  feedingTimes: string[];
9}
10
11const DinosaurForm: React.FC = () => {
12  const {
13    register,
14    handleSubmit,
15    formState: { errors }
16  } = useForm<DinosaurFormData>();
17
18  const onSubmit: SubmitHandler<DinosaurFormData> = async (data) => {
19    try {
20      const response = await fetch('/api/dinosaurs', {
21        method: 'POST',
22        headers: { 'Content-Type': 'application/json' },
23        body: JSON.stringify(data)
24      });
25
26      if (response.ok) {
27        alert('Dinosaur added!');
28      }
29    } catch (error) {
30      console.error('Error adding dinosaur:', error);
31    }
32  };
33
34  return (
35    <form onSubmit={handleSubmit(onSubmit)}>
36      <input
37        {...register('name', { required: 'Name is required' })}
38        placeholder="Dinosaur name"
39      />
40      {errors.name && <span>{errors.name.message}</span>}
41
42      <input
43        {...register('species', { required: true })}
44        placeholder="Species"
45      />
46
47      <input
48        type="number"
49        {...register('paddock', {
50          required: true,
51          min: 1,
52          max: 10
53        })}
54        placeholder="Paddock number"
55      />
56
57      <label>
58        <input type="checkbox" {...register('isDangerous')} />
59        Dangerous species
60      </label>
61
62      <button type="submit">Add dinosaur</button>
63    </form>
64  );
65};

Summary

"You see?" Dr. Sattler smiles. "TypeScript integrates beautifully with all popular frameworks:"

React

  • Strong component typing via
    React.FC<Props>
  • Typed hooks (
    useState<T>
    ,
    useRef<HTMLElement>
    )
  • Custom hooks with full type safety
  • Generic components for reusability

Node.js/Express

  • Typing Request, Response, NextFunction
  • Repository and Service patterns with interfaces
  • Middleware with custom Request types
  • DTO (Data Transfer Objects) for validation

Next.js

  • Native TypeScript support - zero configuration
  • Typed
    getServerSideProps
    ,
    getStaticProps
  • API Routes with typing
  • Server and Client Components in App Router

Key benefits:

  • Catching errors while writing code
  • Autocomplete and IntelliSense
  • Refactoring without fear
  • Self-documenting code
  • Easier team collaboration

"TypeScript," Dr. Sattler concludes, "is like adding security systems to our Jurassic Park infrastructure. It requires a bit more work upfront, but in the long run, it saves us tons of problems!"

Go to CodeWorlds