TypeScript Configuration
Overviewβ
The application uses TypeScript 5.3 as the primary language for both backend (NestJS) and frontend (Next.js) applications. TypeScript provides static type checking and enhanced developer experience with strict configurations.
What is TypeScript?β
TypeScript is a superset of JavaScript that adds:
- Type Safety: Catch errors at compile time, not runtime
- Better IDE Support: IntelliSense and autocompletion
- Self-Documenting Code: Types serve as documentation
- Refactoring Confidence: Rename safely with type checking
- Modern Features: Supports latest JavaScript with transpilation
App TypeScript Setupβ
Root tsconfig.jsonβ
tsconfig.jsonβ
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist", "coverage"]
}
Backend (NestJS) Configurationβ
apps/api/tsconfig.jsonβ
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@qms/schemas": ["../../packages/schemas"],
"@qms/constants": ["../../packages/constants"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
}
apps/api/tsconfig.build.jsonβ
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": false
},
"exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
}
Frontend (Next.js) Configurationβ
apps/web/tsconfig.jsonβ
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "preserve",
"module": "ESNext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@qms/schemas": ["../../packages/schemas"],
"@qms/constants": ["../../packages/constants"]
},
"plugins": [
{
"name": "next"
}
]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
Shared Packages Configurationβ
packages/schemas/tsconfig.jsonβ
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"module": "ESNext",
"moduleResolution": "node",
"target": "ES2020"
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules", "dist"]
}
Type Definitionsβ
Global Typesβ
src/types.tsβ
// Type definitions shared across application
/**
* API Response wrapper
*/
export interface ApiResponse<T = unknown> {
data: T;
meta?: {
timestamp?: string;
version?: string;
};
error?: {
code: string;
message: string;
details?: Array<{ field: string; message: string }>;
};
}
/**
* Pagination metadata
*/
export interface PaginationMeta {
page: number;
limit: number;
total: number;
totalPages: number;
}
/**
* Authentication context
*/
export interface AuthContext {
userId: string;
email: string;
roles: string[];
permissions: string[];
expiresAt: Date;
}
/**
* Generic entity with timestamps
*/
export interface BaseEntity {
id: string;
createdAt: Date;
updatedAt: Date;
deletedAt?: Date;
}
Entity Typesβ
src/entities/employee.types.tsβ
import { BaseEntity } from '../types';
export interface Employee extends BaseEntity {
name: string;
email: string;
department: string;
role: string;
isActive: boolean;
phone?: string;
}
export interface CreateEmployeeRequest {
name: string;
email: string;
department: string;
role: string;
phone?: string;
}
export interface UpdateEmployeeRequest {
name?: string;
department?: string;
role?: string;
phone?: string;
isActive?: boolean;
}
export interface EmployeeList {
items: Employee[];
total: number;
page: number;
limit: number;
}
Path Aliasesβ
Configure path aliases for cleaner imports:
// β AVOID: Relative imports
import { UserService } from '../../../services/user.service';
import { UserSchema } from '../../../../schemas/user.schema';
// β
GOOD: Path aliases
import { UserService } from '@/services/user.service';
import { UserSchema } from '@qms/schemas/user.schema';
Configurationβ
In tsconfig.json:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@qms/schemas": ["../../packages/schemas"],
"@qms/constants": ["../../packages/constants"]
}
}
}
Strict Type Checkingβ
Enable All Strict Flagsβ
{
"compilerOptions": {
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"noImplicitThis": true
}
}
Strict Pattern Examplesβ
// β
GOOD: Explicit types
function processEmployee(employee: Employee | null): string {
if (!employee) {
return 'No employee';
}
return employee.name;
}
// β AVOID: Implicit any
function processEmployee(employee: any) {
return employee.name; // Could be undefined
}
// β
GOOD: Async return type
async function getEmployee(id: string): Promise<Employee> {
const response = await fetch(`/api/employees/${id}`);
return response.json();
}
// β AVOID: Missing return type
async function getEmployee(id: string) {
const response = await fetch(`/api/employees/${id}`);
return response.json(); // Type is any
}
Utility Typesβ
Common utility types used in QMS:
// Readonly properties
type ReadonlyEmployee = Readonly<Employee>;
// Partial properties (all optional)
type PartialEmployee = Partial<Employee>;
// Required properties (all required)
type RequiredEmployee = Required<Employee>;
// Pick specific properties
type EmployeePreview = Pick<Employee, 'id' | 'name' | 'email'>;
// Omit specific properties
type EmployeeWithoutId = Omit<Employee, 'id'>;
// Record type
type DepartmentStats = Record<string, number>;
// Union types
type UserRole = 'admin' | 'manager' | 'employee' | 'viewer';
// Discriminated unions
type EmployeeAction =
| { type: 'CREATE'; payload: CreateEmployeeRequest }
| { type: 'UPDATE'; payload: UpdateEmployeeRequest; id: string }
| { type: 'DELETE'; id: string };
Genericsβ
Write reusable types with generics:
/**
* Paginated response wrapper
*/
export interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
limit: number;
}
/**
* Async result with error handling
*/
export type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
/**
* Generic repository pattern
*/
export interface Repository<T> {
create(data: Omit<T, 'id'>): Promise<T>;
findById(id: string): Promise<T | null>;
update(id: string, data: Partial<T>): Promise<T>;
delete(id: string): Promise<void>;
list(page: number, limit: number): Promise<PaginatedResponse<T>>;
}
Declaration Filesβ
Create type definitions for JavaScript libraries:
// src/types/some-library.d.ts
declare module 'some-library' {
export interface Config {
apiKey: string;
timeout: number;
}
export function initialize(config: Config): void;
}
Type Checkingβ
IDE Integrationβ
Use TypeScript in your IDE for real-time checking:
# Generate type definitions
tsc --declaration --emitDeclarationOnly
# Type check without emitting
tsc --noEmit
# Watch mode for development
tsc --watch --noEmit
CI/CD Validationβ
# Check types in pipeline
tsc --noEmit --strict
# Generate coverage report
tsc --listFiles --listFilesOnly
Best Practicesβ
1. Be Explicit with Union Typesβ
// β
GOOD: Clear intent
function process(value: string | number): void {
if (typeof value === 'string') {
console.log(value.toUpperCase());
} else {
console.log(value.toFixed(2));
}
}
// β AVOID: Ambiguous
function process(value: any): void {
console.log(value.toUpperCase()); // Runtime error possible
}
2. Use Discriminated Unionsβ
// β
GOOD: Safe pattern matching
type Result = { status: 'success'; data: Employee } | { status: 'error'; error: string };
function handle(result: Result) {
if (result.status === 'success') {
console.log(result.data.name); // Type is narrowed
} else {
console.log(result.error);
}
}
3. Leverage Const Assertionsβ
// β
GOOD: Literal types
const ROLES = ['admin', 'user', 'guest'] as const;
type Role = (typeof ROLES)[number]; // 'admin' | 'user' | 'guest'
// β AVOID: String types
const ROLES = ['admin', 'user', 'guest']; // string[]
4. Use Type Guardsβ
// β
GOOD: Custom type guard
function isEmployee(value: unknown): value is Employee {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value &&
'email' in value
);
}
if (isEmployee(data)) {
console.log(data.name); // Type is narrowed
}
Troubleshootingβ
Type Error: Cannot Find Moduleβ
// Make sure paths are configured correctly
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
// Also ensure files export properly
// src/index.ts
export type Employee = { /* ... */ };
Type Error: Property Not Foundβ
# Generate type definitions
tsc --declaration
# Check for missing types
tsc --noEmit
# Update import paths if using aliases
Strict Mode Errorsβ
Gradual migration to strict mode:
{
"compilerOptions": {
"strict": false,
"strictNullChecks": true,
"noImplicitAny": true,
"noImplicitThis": true
}
}