SoftwareJanuary 13, 20258 min readEnextware Team

Building Secure and Scalable Web Applications with TypeScript

The role of TypeScript in modern web development, plus its contributions to type safety, code quality, and teamwork. Best practices for enterprise-grade projects.

Service scope

Quote and scope for typescript web development

This guide helps you decide. Visit the related solution page for project scope, the starter package, price range, and the quote flow.

View scope
Building Secure and Scalable Web Applications with TypeScript cover image

TypeScript has become an indispensable tool in modern web development. In this article, we'll take a detailed look at why TypeScript matters so much and how to use it effectively.

What Is TypeScript and Why Should We Use It?

Core Advantages

1. Type Safety

Catching runtime errors at compile time
Fewer production bugs
Smart code completion in the IDE
Safer refactoring

2. Code Quality

Self-documenting code
A more readable codebase
Less time spent on code review
Automatic documentation generation

3. Developer Experience

IntelliSense support
Warnings before errors are even caught
Easier navigation in large projects
Improved team collaboration

Why It Matters

TypeScript is one of the most preferred languages among developers
Its adoption has grown steadily year over year
A large share of major companies rely on it
It shortens bug-fixing cycles

TypeScript Fundamentals

1. Basic Types and Interfaces

// Primitive types
let username: string = "Erdenay";
let age: number = 25;
let isActive: boolean = true;

// Arrays
let tags: string[] = ["web", "design", "seo"];
let scores: Array<number> = [95, 87, 92];

// Tuples
let user: [string, number, boolean] = ["Erdenay", 25, true];

// Enums
enum UserRole {
  Admin = "ADMIN",
  User = "USER",
  Guest = "GUEST"
}

// Interfaces
interface Product {
  id: string;
  name: string;
  price: number;
  inStock: boolean;
  category?: string; // Optional
  readonly createdAt: Date; // Readonly
}

// Type aliases
type ID = string | number;
type Status = "pending" | "approved" | "rejected";

// Union types
function printId(id: string | number) {
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id.toFixed(2));
  }
}

2. Advanced Types

// Generics
function getFirstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

const firstNumber = getFirstElement([1, 2, 3]); // number
const firstString = getFirstElement(["a", "b"]); // string

// Utility Types
interface User {
  id: string;
  name: string;
  email: string;
  age: number;
}

// Partial - Makes all properties optional
type PartialUser = Partial<User>;

// Pick - Select specific properties
type UserPreview = Pick<User, "id" | "name">;

// Omit - Exclude specific properties
type UserWithoutEmail = Omit<User, "email">;

// Record - Create object type with specific keys
type UserRoles = Record<string, UserRole>;

// Mapped Types
type ReadonlyUser = {
  readonly [K in keyof User]: User[K];
};

// Conditional Types
type IsString<T> = T extends string ? "yes" : "no";
type Test1 = IsString<string>; // "yes"
type Test2 = IsString<number>; // "no"

3. Function Types

// Function type annotations
function add(a: number, b: number): number {
  return a + b;
}

// Arrow function with types
const multiply = (a: number, b: number): number => a * b;

// Optional and default parameters
function greet(name: string, greeting: string = "Hello"): string {
  return `${greeting}, ${name}!`;
}

// Rest parameters
function sum(...numbers: number[]): number {
  return numbers.reduce((acc, num) => acc + num, 0);
}

// Function overloads
function parseValue(value: string): string[];
function parseValue(value: number): number[];
function parseValue(value: string | number): string[] | number[] {
  if (typeof value === "string") {
    return value.split(",");
  }
  return [value];
}

TypeScript Best Practices with React

1. Component Types

import { FC, ReactNode } from 'react';

// Props interface
interface ButtonProps {
  children: ReactNode;
  onClick: () => void;
  variant?: "primary" | "secondary";
  disabled?: boolean;
}

// Functional Component with types
const Button: FC<ButtonProps> = ({
  children,
  onClick,
  variant = "primary",
  disabled = false
}) => {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={`btn btn-${variant}`}
    >
      {children}
    </button>
  );
};

// Generic component
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => ReactNode;
}

function List<T>({ items, renderItem }: ListProps<T>) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

2. Hooks with TypeScript

import { useState, useEffect, useCallback, useMemo } from 'react';

// useState with explicit type
const [user, setUser] = useState<User | null>(null);
const [count, setCount] = useState<number>(0);

// useEffect
useEffect(() => {
  const fetchUser = async () => {
    const response = await fetch('/api/user');
    const data: User = await response.json();
    setUser(data);
  };

  fetchUser();
}, []);

// useCallback
const handleClick = useCallback((id: string) => {
  console.log(`Clicked: ${id}`);
}, []);

// useMemo
const expensiveValue = useMemo(() => {
  return computeExpensiveValue(items);
}, [items]);

// Custom hook with types
function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue] as const;
}

3. Event Handlers

import { ChangeEvent, FormEvent, MouseEvent } from 'react';

function Form() {
  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    // Handle form submission
  };

  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };

  const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
    console.log("Button clicked");
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" onChange={handleChange} />
      <button onClick={handleClick}>Submit</button>
    </form>
  );
}

TypeScript with Next.js

1. Page Components

// app/products/[id]/page.tsx
import type { Metadata } from 'next';

interface PageProps {
  params: {
    id: string;
  };
  searchParams: {
    [key: string]: string | string[] | undefined;
  };
}

export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
  const product = await fetchProduct(params.id);

  return {
    title: product.name,
    description: product.description,
  };
}

export default async function ProductPage({ params, searchParams }: PageProps) {
  const product = await fetchProduct(params.id);

  return <div>{product.name}</div>;
}

2. API Routes

// app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server';

interface Product {
  id: string;
  name: string;
  price: number;
}

export async function GET(request: NextRequest) {
  const products: Product[] = await fetchProducts();

  return NextResponse.json(products);
}

export async function POST(request: NextRequest) {
  const body: Partial<Product> = await request.json();

  const newProduct = await createProduct(body);

  return NextResponse.json(newProduct, { status: 201 });
}

Enterprise Patterns

1. Domain-Driven Design

// types/domain/product.ts
export interface ProductEntity {
  id: ProductId;
  name: ProductName;
  price: Money;
  stock: Stock;
}

export type ProductId = string & { readonly __brand: "ProductId" };
export type ProductName = string & { readonly __brand: "ProductName" };

export class Money {
  private constructor(
    public readonly amount: number,
    public readonly currency: string
  ) {}

  static create(amount: number, currency: string): Money {
    if (amount < 0) throw new Error("Amount cannot be negative");
    return new Money(amount, currency);
  }
}

2. Repository Pattern

interface Repository<T> {
  findById(id: string): Promise<T | null>;
  findAll(): Promise<T[]>;
  create(entity: Omit<T, 'id'>): Promise<T>;
  update(id: string, entity: Partial<T>): Promise<T>;
  delete(id: string): Promise<void>;
}

class ProductRepository implements Repository<Product> {
  async findById(id: string): Promise<Product | null> {
    // Implementation
  }

  // ... other methods
}

Performance and Optimization

1. Strict Mode Configuration

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "moduleResolution": "bundler",
    "target": "ES2022",
    "lib": ["ES2022", "DOM"],
    "jsx": "preserve",
    "incremental": true
  }
}

2. Build Optimization

Compile Time:

Using project references for faster builds
Incremental compilation for quicker rebuilds
Type checking parallelization

Bundle Size:

Eliminating unused code with tree shaking
Type-only imports: `import type { User } from './types'`
No runtime overhead (types compile away)

Team Collaboration

1. Code Review Benefits

Type errors caught before runtime, reducing bugs
Self-documenting code shortens review time
Standardized patterns speed up onboarding

2. Documentation

/**
 * Calculates the total price of items in cart
 * @param items - Array of cart items
 * @param discountCode - Optional discount code
 * @returns Total price after discounts
 * @throws {Error} If discount code is invalid
 * @example
 * ```ts
 * const total = calculateTotal(cartItems, "SUMMER25");
 * ```
 */
function calculateTotal(
  items: CartItem[],
  discountCode?: string
): number {
  // Implementation
}

Enextware TypeScript Standards

We use TypeScript across all of our projects:

100% type coverage
Strict mode enabled
ESLint + Prettier configuration
Automated testing with Jest
CI/CD type checking

Results:

Noticeably fewer production bugs
Faster development cycles
Higher overall code quality

Contact

For professional web applications built with TypeScript:

Phone: +90 536 628 0007

Email: info@enextware.com

Related Articles

View all articles

Let’s design your custom software around real operations

We can bring your admin panel, integrations, API, and workflow logic into one custom software system.