Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
LoginSign Up
menu
  1. Home
  2. /Web Development
  3. /Typescript
  4. /Modules
Your Progress0%
0 of 30 completed

TypeScript Topics

Getting Started

  • What is TypeScript?
  • TypeScript vs JavaScript
  • Setting Up TypeScript
  • Your First TypeScript Program

Basic Types

  • Primitive Types
  • Arrays and Tuples
  • Objects and Type Aliases
  • Enums
  • Any, Unknown, and Never

Functions & Interfaces

  • Function Types and Signatures
  • Interfaces
  • Optional and Default Parameters
  • Function Overloading

Advanced Types

  • Union and Intersection Types
  • Type Guards and Narrowing
  • Literal Types
  • Type Assertions and Casting

Generics

  • Introduction to Generics
  • Generic Constraints
  • Utility Types

Classes & OOP

  • Classes and Constructors
  • Access Modifiers
  • Inheritance and Polymorphism
  • Abstract Classes and Interfaces

Practical TypeScript

  • Working with Modules
  • TypeScript with React
  • Async/Await and Promises
  • Working with APIs and JSON
  • Type Declarations and DefinitelyTyped
  • TypeScript Best Practices

Working with Modules

Importing, exporting, and organizing TypeScript modules

Modules are the foundation of code organization in TypeScript. They let you split your code into separate files, export functionality from one file, and import it into another. TypeScript uses ES6 module syntax (import/export) which keeps your codebase maintainable and scalable. Let's master TypeScript modules!

Named Exports and Imports

Named exports allow you to export multiple items from a file. Each export must be imported using its exact name (or aliased).

user.ts
// Named exports - multiple exports from one file
export interface User {
  id: number;
  name: string;
  email: string;
}

export class UserService {
  private users: User[] = [];
  
  addUser(user: User): void {
    this.users.push(user);
  }
  
  getUser(id: number): User | undefined {
    return this.users.find(u => u.id === id);
  }
  
  getAllUsers(): User[] {
    return [...this.users];
  }
}

export function createUser(name: string, email: string): User {
  return {
    id: Date.now(),
    name,
    email
  };
}

export const ADMIN_ROLE = "admin";
export const USER_ROLE = "user";

// Private (not exported) - only available in this file
function validateEmail(email: string): boolean {
  return email.includes("@");
}
app.ts
// Importing named exports
import { User, UserService, createUser, ADMIN_ROLE } from './user';

const service = new UserService();

const user1 = createUser("Yusuf Ibrahim", "yusuf@example.com");
const user2 = createUser("Amina Hassan", "amina@example.com");

service.addUser(user1);
service.addUser(user2);

console.log(service.getAllUsers());

// Import with alias
import { ADMIN_ROLE as AdminRole, USER_ROLE as RegularUser } from './user';

console.log(AdminRole);      // "admin"
console.log(RegularUser);    // "user"

// Import all named exports as namespace
import * as UserModule from './user';

const user3 = UserModule.createUser("Chidi Okafor", "chidi@example.com");
const service2 = new UserModule.UserService();
service2.addUser(user3);

console.log(UserModule.ADMIN_ROLE);  // "admin"

Default Exports and Imports

Default exports allow you to export one main item from a file. It can be imported with any name (no curly braces needed).

logger.ts
// Default export - one main export per file
export default class Logger {
  private logs: string[] = [];
  
  log(message: string): void {
    const timestamp = new Date().toISOString();
    const logEntry = `[${timestamp}] ${message}`;
    this.logs.push(logEntry);
    console.log(logEntry);
  }
  
  error(message: string): void {
    this.log(`ERROR: ${message}`);
  }
  
  getLogs(): string[] {
    return [...this.logs];
  }
  
  clear(): void {
    this.logs = [];
  }
}
app.ts
// Importing default export (can use any name)
import Logger from './logger';

const logger = new Logger();
logger.log("Application started");
logger.error("Something went wrong");

console.log(logger.getLogs());

// Can name it anything
import MyLogger from './logger';

const customLogger = new MyLogger();
customLogger.log("Custom logger");

// Default export with function
// File: math.ts
export default function add(a: number, b: number): number {
  return a + b;
}

// File: app.ts
import add from './math';
// OR
import sum from './math';  // Different name, same function

console.log(add(5, 3));   // 8
console.log(sum(10, 20)); // 30

// Default export with arrow function
// File: multiply.ts
const multiply = (a: number, b: number): number => a * b;
export default multiply;

// File: app.ts
import multiply from './multiply';
console.log(multiply(4, 5));  // 20

Mixing Default and Named Exports

You can have both a default export and named exports in the same file.

config.ts
// Default export + named exports
export interface DatabaseConfig {
  host: string;
  port: number;
  database: string;
}

export interface AppConfig {
  name: string;
  version: string;
  database: DatabaseConfig;
}

// Default export
export default class Config {
  private static instance: Config;
  
  private constructor(
    public app: AppConfig
  ) {}
  
  static getInstance(app: AppConfig): Config {
    if (!Config.instance) {
      Config.instance = new Config(app);
    }
    return Config.instance;
  }
  
  getDatabaseConfig(): DatabaseConfig {
    return this.app.database;
  }
}

// Named exports
export const DEFAULT_PORT = 5432;
export const DEFAULT_HOST = "localhost";

export function createDefaultConfig(appName: string): AppConfig {
  return {
    name: appName,
    version: "1.0.0",
    database: {
      host: DEFAULT_HOST,
      port: DEFAULT_PORT,
      database: appName.toLowerCase()
    }
  };
}
app.ts
// Importing default and named exports
import Config, { 
  AppConfig, 
  DatabaseConfig, 
  DEFAULT_PORT,
  createDefaultConfig 
} from './config';

// Use named exports
const appConfig = createDefaultConfig("MyApp");
console.log(`Default port: ${DEFAULT_PORT}`);

// Use default export
const config = Config.getInstance(appConfig);
const dbConfig = config.getDatabaseConfig();

console.log(`Database: ${dbConfig.host}:${dbConfig.port}/${dbConfig.database}`);
// "Database: localhost:5432/myapp"

// Alternative: Import default separately
import ConfigClass from './config';
import { createDefaultConfig as createConfig } from './config';

const config2 = ConfigClass.getInstance(createConfig("AnotherApp"));

Re-exporting Modules

Re-exporting allows you to create a single entry point for multiple modules, making imports cleaner.

models/user.ts
export interface User {
  id: number;
  name: string;
  email: string;
}

export class UserModel {
  constructor(public user: User) {}
  
  getFullInfo(): string {
    return `${this.user.name} (${this.user.email})`;
  }
}
models/product.ts
export interface Product {
  id: number;
  name: string;
  price: number;
}

export class ProductModel {
  constructor(public product: Product) {}
  
  getFormattedPrice(): string {
    return `₦${this.product.price.toLocaleString()}`;
  }
}
models/order.ts
import { User } from './user';
import { Product } from './product';

export interface Order {
  id: number;
  user: User;
  products: Product[];
  total: number;
}

export class OrderModel {
  constructor(public order: Order) {}
  
  getOrderSummary(): string {
    return `Order #${this.order.id} - ₦${this.order.total}`;
  }
}
models/index.ts
// Re-export everything from each module
export * from './user';
export * from './product';
export * from './order';

// Or re-export specific items
export { User, UserModel } from './user';
export { Product, ProductModel } from './product';
export { Order, OrderModel } from './order';

// Can also add new exports
export const VERSION = "1.0.0";

export function createModels() {
  return {
    user: UserModel,
    product: ProductModel,
    order: OrderModel
  };
}
app.ts
// Clean imports from single entry point
import { 
  User, 
  UserModel, 
  Product, 
  ProductModel,
  Order,
  OrderModel,
  VERSION
} from './models';

// Instead of:
// import { User, UserModel } from './models/user';
// import { Product, ProductModel } from './models/product';
// import { Order, OrderModel } from './models/order';

const user: User = {
  id: 1,
  name: "Yusuf Ibrahim",
  email: "yusuf@example.com"
};

const product: Product = {
  id: 101,
  name: "Laptop",
  price: 450000
};

const userModel = new UserModel(user);
const productModel = new ProductModel(product);

console.log(userModel.getFullInfo());
console.log(productModel.getFormattedPrice());
console.log(`Models version: ${VERSION}`);

Type-Only Imports and Exports

TypeScript allows importing types without importing runtime code, improving tree-shaking and bundle size.

types.ts
export interface User {
  id: number;
  name: string;
  email: string;
}

export type UserRole = "admin" | "editor" | "viewer";

export interface AuthConfig {
  enabled: boolean;
  provider: string;
}

export function createUser(name: string, email: string): User {
  return {
    id: Date.now(),
    name,
    email
  };
}
app.ts
// Import only types (compile-time only, no runtime code)
import type { User, UserRole, AuthConfig } from './types';

// Import runtime function
import { createUser } from './types';

// Using types
const user: User = createUser("Yusuf", "yusuf@example.com");
const role: UserRole = "admin";
const config: AuthConfig = {
  enabled: true,
  provider: "oauth"
};

// Import type and value separately
import { type User as UserType, createUser as makeUser } from './types';

const user2: UserType = makeUser("Amina", "amina@example.com");

// Type-only export
export type { User, UserRole };  // Only exports types, not runtime code
export { createUser };            // Exports runtime function

Module Organization Patterns

Feature-Based Organization

project-structure.txt
src/
├── features/
│   ├── auth/
│   │   ├── index.ts          # Re-export everything
│   │   ├── auth.types.ts     # Types and interfaces
│   │   ├── auth.service.ts   # Business logic
│   │   ├── auth.utils.ts     # Helper functions
│   │   └── auth.constants.ts # Constants
│   │
│   ├── users/
│   │   ├── index.ts
│   │   ├── user.types.ts
│   │   ├── user.service.ts
│   │   ├── user.model.ts
│   │   └── user.utils.ts
│   │
│   └── products/
│       ├── index.ts
│       ├── product.types.ts
│       ├── product.service.ts
│       └── product.utils.ts
│
├── shared/
│   ├── types/
│   │   └── index.ts
│   ├── utils/
│   │   └── index.ts
│   └── constants/
│       └── index.ts
│
└── app.ts
features/auth/auth.types.ts
export interface LoginCredentials {
  email: string;
  password: string;
}

export interface AuthToken {
  accessToken: string;
  refreshToken: string;
  expiresIn: number;
}

export interface AuthUser {
  id: number;
  email: string;
  name: string;
  role: string;
}
features/auth/auth.service.ts
import { LoginCredentials, AuthToken, AuthUser } from './auth.types';

export class AuthService {
  async login(credentials: LoginCredentials): Promise<AuthToken> {
    // Simulate API call
    console.log(`Logging in: ${credentials.email}`);
    
    return {
      accessToken: "token_" + Date.now(),
      refreshToken: "refresh_" + Date.now(),
      expiresIn: 3600
    };
  }
  
  async getCurrentUser(token: string): Promise<AuthUser> {
    return {
      id: 1,
      email: "yusuf@example.com",
      name: "Yusuf Ibrahim",
      role: "admin"
    };
  }
  
  logout(): void {
    console.log("User logged out");
  }
}
features/auth/auth.constants.ts
export const TOKEN_KEY = "auth_token";
export const REFRESH_TOKEN_KEY = "refresh_token";
export const TOKEN_EXPIRY_KEY = "token_expiry";

export const ROLES = {
  ADMIN: "admin",
  EDITOR: "editor",
  VIEWER: "viewer"
} as const;

export type Role = typeof ROLES[keyof typeof ROLES];
features/auth/index.ts
// Re-export everything from auth feature
export * from './auth.types';
export * from './auth.service';
export * from './auth.constants';

// Can also create convenience exports
export { AuthService as default } from './auth.service';
app.ts
// Clean imports from feature modules
import { 
  AuthService, 
  LoginCredentials, 
  ROLES,
  TOKEN_KEY 
} from './features/auth';

import { UserService } from './features/users';
import { ProductService } from './features/products';

// Use the services
const authService = new AuthService();

const credentials: LoginCredentials = {
  email: "yusuf@example.com",
  password: "secret123"
};

authService.login(credentials).then(token => {
  console.log("Token:", token.accessToken);
  localStorage.setItem(TOKEN_KEY, token.accessToken);
});

console.log("Admin role:", ROLES.ADMIN);

Practical Examples

Example 1: API Client Module

api/types.ts
export interface ApiResponse<T> {
  success: boolean;
  data: T;
  message?: string;
  error?: string;
}

export interface PaginatedResponse<T> {
  data: T[];
  total: number;
  page: number;
  pageSize: number;
  totalPages: number;
}

export interface RequestConfig {
  headers?: Record<string, string>;
  params?: Record<string, any>;
  timeout?: number;
}
api/client.ts
import { ApiResponse, RequestConfig } from './types';

export class ApiClient {
  constructor(private baseUrl: string) {}
  
  private async request<T>(
    endpoint: string,
    method: string = "GET",
    config?: RequestConfig
  ): Promise<ApiResponse<T>> {
    const url = `${this.baseUrl}${endpoint}`;
    
    try {
      const response = await fetch(url, {
        method,
        headers: {
          "Content-Type": "application/json",
          ...config?.headers
        },
        signal: config?.timeout 
          ? AbortSignal.timeout(config.timeout)
          : undefined
      });
      
      const data = await response.json();
      
      return {
        success: response.ok,
        data,
        message: response.ok ? "Success" : "Request failed"
      };
    } catch (error) {
      return {
        success: false,
        data: null as any,
        error: (error as Error).message
      };
    }
  }
  
  async get<T>(endpoint: string, config?: RequestConfig): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, "GET", config);
  }
  
  async post<T>(
    endpoint: string, 
    data: any, 
    config?: RequestConfig
  ): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, "POST", {
      ...config,
      headers: {
        ...config?.headers,
        "Content-Type": "application/json"
      }
    });
  }
}
api/index.ts
// Re-export all API-related exports
export * from './types';
export * from './client';

// Create and export default client instance
import { ApiClient } from './client';

export const api = new ApiClient("https://api.example.com");

// Export convenience functions
export async function fetchUsers() {
  return api.get("/users");
}

export async function fetchProducts() {
  return api.get("/products");
}
app.ts
import { 
  api, 
  ApiResponse, 
  fetchUsers, 
  fetchProducts 
} from './api';

// Use default client
interface User {
  id: number;
  name: string;
}

api.get<User[]>("/users").then(response => {
  if (response.success) {
    console.log("Users:", response.data);
  }
});

// Use convenience functions
fetchUsers().then(response => {
  console.log("Fetched users:", response);
});

fetchProducts().then(response => {
  console.log("Fetched products:", response);
});

Example 2: Utility Module System

utils/string.ts
export function capitalize(str: string): string {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

export function truncate(str: string, maxLength: number): string {
  if (str.length <= maxLength) return str;
  return str.slice(0, maxLength - 3) + "...";
}

export function slugify(str: string): string {
  return str
    .toLowerCase()
    .replace(/s+/g, "-")
    .replace(/[^a-z0-9-]/g, "");
}
utils/number.ts
export function formatCurrency(amount: number, currency: string = "NGN"): string {
  const symbol = currency === "NGN" ? "₦" : currency;
  return `${symbol}${amount.toLocaleString()}`;
}

export function clamp(value: number, min: number, max: number): number {
  return Math.min(Math.max(value, min), max);
}

export function randomInt(min: number, max: number): number {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
utils/date.ts
export function formatDate(date: Date, format: string = "YYYY-MM-DD"): string {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  
  return format
    .replace("YYYY", String(year))
    .replace("MM", month)
    .replace("DD", day);
}

export function daysFromNow(days: number): Date {
  const date = new Date();
  date.setDate(date.getDate() + days);
  return date;
}

export function isToday(date: Date): boolean {
  const today = new Date();
  return date.toDateString() === today.toDateString();
}
utils/index.ts
// Re-export all utilities with namespace
export * as StringUtils from './string';
export * as NumberUtils from './number';
export * as DateUtils from './date';

// Or re-export individually
export { capitalize, truncate, slugify } from './string';
export { formatCurrency, clamp, randomInt } from './number';
export { formatDate, daysFromNow, isToday } from './date';
app.ts
// Option 1: Import namespaced utilities
import { StringUtils, NumberUtils, DateUtils } from './utils';

console.log(StringUtils.capitalize("hello"));        // "Hello"
console.log(NumberUtils.formatCurrency(50000));      // "₦50,000"
console.log(DateUtils.formatDate(new Date()));       // "2025-12-26"

// Option 2: Import specific functions
import { capitalize, formatCurrency, formatDate } from './utils';

console.log(capitalize("typescript"));     // "Typescript"
console.log(formatCurrency(100000));       // "₦100,000"
console.log(formatDate(new Date()));       // "2025-12-26"

// Option 3: Import all from specific module
import * as string from './utils/string';

console.log(string.capitalize("modules"));
console.log(string.slugify("TypeScript Modules"));  // "typescript-modules"

Test Your Knowledge

Type Safety Check

Which module export/import is correct?

// File: user.ts
// Option A
export class User {
  constructor(public name: string) {}
}

export function createUser(name: string): User {
  return new User(name);
}

// File: app.ts (importing from user.ts)
import { User, createUser } from './user';

// Option B
// File: math.ts
const add = (a: number, b: number) => a + b;
export default add;

// File: app.ts
import subtract from './math';  // Wrong name

// Option C
// File: config.ts
export default class Config {
  version = "1.0.0";
}

// File: app.ts
import Config from './config';
const config = new Config();

// Option D
// File: utils.ts
export const PI = 3.14159;
export const E = 2.71828;

// File: app.ts
import { PI } from './utils';
import { E as EulerNumber } from './utils';

Common TypeScript Error

Code with Error
// File: utils.ts
export function formatPrice(amount: number): string {
  return `₦${amount.toLocaleString()}`;
}

function helperFunction(): void {
  // Private function - not exported
  console.log("Helper");
}

// File: app.ts
import { formatPrice, helperFunction } from './utils';

// Trying to use non-exported function
helperFunction();  // ❌ Error!

// Also trying to import something that doesn't exist
import { calculateTax } from './utils';  // ❌ Error!

❌ Module './utils' has no exported member 'helperFunction'. Module './utils' has no exported member 'calculateTax'.

What's Wrong?

You can only import members that are explicitly exported. Private (non-exported) functions and non-existent exports cannot be imported.

Corrected Code
// File: utils.ts
export function formatPrice(amount: number): string {
  return `₦${amount.toLocaleString()}`;
}

// Export the helper if you need it elsewhere
export function helperFunction(): void {
  console.log("Helper");
}

// Add the missing export
export function calculateTax(amount: number, rate: number): number {
  return amount * rate;
}

// File: app.ts
import { formatPrice, helperFunction, calculateTax } from './utils';

// ✅ Now all imports work
console.log(formatPrice(50000));     // "₦50,000"
helperFunction();                     // "Helper"
console.log(calculateTax(100000, 0.075));  // 7500

// Or only import what you need
import { formatPrice } from './utils';

console.log(formatPrice(25000));  // "₦25,000"

Solution: Only import exported members or export the functions you need

Best Practices

  1. Use named exports for multiple exports - clearer dependencies
  2. Use default exports for main functionality - one primary export
  3. Create index.ts barrel files - single entry point for features
  4. Use type-only imports when importing only types - better tree-shaking
  5. Organize by feature, not by type - group related code together
  6. Avoid circular dependencies - refactor shared code
  7. Keep modules focused - single responsibility
  8. Use absolute imports with path aliases when possible
best-practices-example.ts
// ✅ Good: Feature-based organization
// features/users/index.ts
export * from './user.types';
export * from './user.service';
export { UserService as default } from './user.service';

// ✅ Good: Type-only imports
import type { User, UserRole } from './types';
import { createUser } from './utils';

// ✅ Good: Named exports for utilities
export function formatName(name: string): string {
  return name.trim();
}

export function validateEmail(email: string): boolean {
  return email.includes("@");
}

// ✅ Good: Default export for main class
export default class UserManager {
  // Main functionality
}

// ✅ Good: Re-exporting with namespace
export * as Utils from './utils';
export * as Types from './types';

// ❌ Avoid: Circular dependencies
// file-a.ts
import { B } from './file-b';
export class A {
  b = new B();
}

// file-b.ts
import { A } from './file-a';  // Circular!
export class B {
  a = new A();
}

// ❌ Avoid: Mixing default and named exports unnecessarily
export default function main() {}
export const helper1 = () => {};
export const helper2 = () => {};
// Better: All named or one clear default

// ❌ Avoid: Too many exports from one file
export const thing1 = 1;
export const thing2 = 2;
// ... 50 more exports
// Better: Split into multiple focused modules

Key Takeaways

  • Named exports use export keyword and import with { }
  • Default exports use export default and import without { }
  • A file can have both default and named exports
  • Use import * as Name to import all exports as namespace
  • Type-only imports use import type for better tree-shaking
  • Barrel files (index.ts) create clean entry points
  • Re-exporting consolidates multiple modules
  • Organize code by feature, not file type
  • Avoid circular dependencies between modules
  • Modules enable code splitting and reusability

What's Next?

Excellent work! You've mastered TypeScript modules and understand how to organize and structure your code effectively. Next, we'll explore TypeScript with React—how to type React components, props, state, hooks, and build type-safe React applications. This is essential for modern web development!

Get ready to combine TypeScript with React!

Mastering TypeScript modules! Check this out!

Previous
Abstract Classes and Interfaces
Next
TypeScript with React

Never Miss a New TypeScript Tutorial

Join 1,000+ developers learning TypeScript step-by-step. Get new tutorials, type safety tips, and exclusive resources delivered to your inbox - completely FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

TypeScript Tutorials

0 of 30 completed

Your Progress0%

Topics

Getting Started

  • What is TypeScript?
  • TypeScript vs JavaScript
  • Setting Up TypeScript
  • Your First TypeScript Program

Basic Types

  • Primitive Types
  • Arrays and Tuples
  • Objects and Type Aliases
  • Enums
  • Any, Unknown, and Never

Functions & Interfaces

  • Function Types and Signatures
  • Interfaces
  • Optional and Default Parameters
  • Function Overloading

Advanced Types

  • Union and Intersection Types
  • Type Guards and Narrowing
  • Literal Types
  • Type Assertions and Casting

Generics

  • Introduction to Generics
  • Generic Constraints
  • Utility Types

Classes & OOP

  • Classes and Constructors
  • Access Modifiers
  • Inheritance and Polymorphism
  • Abstract Classes and Interfaces

Practical TypeScript

  • Working with Modules
  • TypeScript with React
  • Async/Await and Promises
  • Working with APIs and JSON
  • Type Declarations and DefinitelyTyped
  • TypeScript Best Practices
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo