Full Stack TypeScript development has become the go-to choice for building robust, type-safe applications. Let's explore how to create a complete application using TypeScript for both frontend and backend. 🚀
Setting Up Your Development Environment
First, let's set up a basic project structure:
mkdir full-stack-typescript
cd full-stack-typescript
npm init -y
Install essential dependencies:
npm install typescript @types/node ts-node nodemon express @types/express
npm install -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
Project Configuration
Create a TypeScript configuration file:
// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"baseUrl": "./src",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Backend Development with Express 🔧
Let's create a type-safe Express server:
// src/types/express/index.d.ts
declare namespace Express {
export interface Request {
user?: {
id: string;
email: string;
}
}
}
// src/server.ts
import express, { Request, Response } from 'express';
import { UserService } from './services/UserService';
interface CreateUserRequest {
email: string;
password: string;
name: string;
}
const app = express();
app.use(express.json());
app.post('/users', async (req: Request<{}, {}, CreateUserRequest>, res: Response) => {
try {
const userService = new UserService();
const user = await userService.createUser(req.body);
res.json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
Creating Type-Safe Services
// src/services/UserService.ts
interface User {
id: string;
email: string;
name: string;
createdAt: Date;
}
export class UserService {
async createUser(data: Omit<User, 'id' | 'createdAt'>): Promise<User> {
// Implementation
return {
id: crypto.randomUUID(),
...data,
createdAt: new Date()
};
}
}
Frontend Development with React and TypeScript 🎨
Set up a React frontend with TypeScript:
npx create-react-app client --template typescript
cd client
npm install @tanstack/react-query axios
Create type-safe API calls:
// src/api/types.ts
export interface User {
id: string;
email: string;
name: string;
createdAt: Date;
}
export interface CreateUserDTO {
email: string;
password: string;
name: string;
}
// src/api/users.ts
import axios from 'axios';
import { User, CreateUserDTO } from './types';
export const createUser = async (data: CreateUserDTO): Promise<User> => {
const response = await axios.post<User>('/api/users', data);
return response.data;
};
Type-Safe React Components
// src/components/UserForm.tsx
import React from 'react';
import { useMutation } from '@tanstack/react-query';
import { createUser } from '../api/users';
import type { CreateUserDTO } from '../api/types';
export const UserForm: React.FC = () => {
const mutation = useMutation({
mutationFn: (data: CreateUserDTO) => createUser(data)
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
mutation.mutate({
email: formData.get('email') as string,
password: formData.get('password') as string,
name: formData.get('name') as string
});
};
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<input name="password" type="password" required />
<input name="name" type="text" required />
<button type="submit">Create User</button>
</form>
);
};
Shared Types Between Frontend and Backend
Create a shared types package:
// shared/types/index.ts
export interface User {
id: string;
email: string;
name: string;
createdAt: Date;
}
export interface CreateUserDTO {
email: string;
password: string;
name: string;
}
Error Handling with Type Safety
// src/utils/errors.ts
export class AppError extends Error {
constructor(
message: string,
public statusCode: number = 400
) {
super(message);
this.name = 'AppError';
}
}
// Usage in API endpoints
app.get('/users/:id', async (req: Request<{ id: string }>, res: Response) => {
try {
const user = await userService.findById(req.params.id);
if (!user) {
throw new AppError('User not found', 404);
}
res.json(user);
} catch (error) {
if (error instanceof AppError) {
res.status(error.statusCode).json({ error: error.message });
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
});
Database Integration with Type Safety
Using Prisma as an example:
// prisma/schema.prisma
model User {
id String @id @default(uuid())
email String @unique
name String
password String
createdAt DateTime @default(now())
}
// src/services/UserService.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export class UserService {
async createUser(data: CreateUserDTO): Promise<User> {
return prisma.user.create({
data
});
}
}
Testing with TypeScript
// src/services/__tests__/UserService.test.ts
import { UserService } from '../UserService';
import { CreateUserDTO } from '../../types';
describe('UserService', () => {
const userService = new UserService();
it('should create a user', async () => {
const userData: CreateUserDTO = {
email: 'test@example.com',
password: 'password123',
name: 'Test User'
};
const user = await userService.createUser(userData);
expect(user).toHaveProperty('id');
expect(user.email).toBe(userData.email);
});
});
Best Practices and Tips 💡
- Always use strict TypeScript configuration
- Leverage utility types for better type safety
- Use zod or io-ts for runtime type validation
- Share types between frontend and backend
- Implement proper error handling
- Use TypeScript-aware linting
- Write tests with type coverage
Conclusion
Full Stack TypeScript development provides an excellent developer experience with end-to-end type safety. By following these patterns and best practices, you can build robust, maintainable applications with confidence. Remember to:
- Keep your types DRY
- Leverage TypeScript's powerful type system
- Use modern tools and frameworks
- Maintain consistent coding patterns
Start small and gradually add more type safety as your application grows. Happy coding! 🚀