Skip to main content

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
}
}

Resources​