TypeScript Project Structure and Organization

In this chapter, we'll explore TypeScript project structure and organization in detail, from the foundational setup to advanced structuring techniques. You'll learn how to configure folders, manage files, set up tsconfig.json, and follow best practices for scalable and maintainable TypeScript projects. By the end, you'll have a solid grasp of how to efficiently structure and organize a TypeScript project.

Introduction to TypeScript Project Structure

A TypeScript project structure helps in maintaining code quality, scalability, and ease of collaboration. The project structure defines how the files and directories are organized, how dependencies are managed, and how the code is compiled and tested.

Initializing a TypeScript Project

Setting Up the Project

To start a new TypeScript project, you need to have Node.js and npm (Node Package Manager) installed. Once you have these installed, follow these steps:

Key Components of Project Structure

  • Directory Layout: Clear separation of concerns within the project.
  • Configuration Files: Centralized settings for TypeScript, build tools, and linters.
  • Dependencies Management: Efficient handling of libraries and tools.

Create a project directory:

Key Components of Project Structure, Typescript Project Structure and Organization

Create a project directory:

				
					mkdir my-typescript-project
cd my-typescript-project

				
			

Initialize the project:

				
					npm init -y

				
			

Install TypeScript:

				
					npm install typescript --save-dev

				
			

Create a tsconfig.json file:

				
					npx tsc --init

				
			

The tsconfig.json file is essential as it configures the TypeScript compiler.

Understanding the tsconfig.json File

The tsconfig.json file is the configuration file for TypeScript. It allows you to define how TypeScript code is compiled.

 Key Configuration Options

compilerOptions: This section specifies compiler options

				
					{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}

				
			
  • target: Specifies the ECMAScript target version.
  • module: Specifies the module system (e.g., commonjs for Node.js).
  • strict: Enables all strict type-checking options.
  • outDir: Directory for compiled output.
  • rootDir: Root directory of the source files.
  • esModuleInterop: Enables interoperability between CommonJS and ES Modules.
  • skipLibCheck: Skips type checking of all declaration files (.d.ts).

Additional Configuration Options

include: Specifies the files or directories to be included.

				
					{
  "include": ["src/**/*"]
}

				
			

exclude: Specifies the files or directories to be excluded.

				
					{
  "exclude": ["node_modules", "dist"]
}

				
			

Organizing Source Code

A typical TypeScript project is organized in a way that separates source code, configuration files, and built output.

Directory Structure

Here’s a recommended directory structure:

				
					my-typescript-project/
├── src/
│   ├── models/
│   ├── services/
│   ├── utils/
│   └── index.ts
├── tests/
├── dist/
├── node_modules/
├── package.json
├── tsconfig.json
└── README.md

				
			
  • src/: Contains the source code.
    • models/: Contains TypeScript interfaces and types.
    • services/: Contains service classes and functions.
    • utils/: Contains utility functions.
    • index.ts: Entry point of the application.
  • tests/: Contains test files.
  • dist/: Contains the compiled output.
  • node_modules/: Contains installed dependencies.
  • package.json: Contains project metadata and dependencies.
  • tsconfig.json: TypeScript configuration file.

Using Modules and Namespaces

TypeScript supports both ES Modules and Namespaces for organizing code.

ES Modules

ES Modules are the standard for JavaScript modules.

Example:

src/models/user.ts:

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

				
			

src/services/userService.ts:

				
					import { User } from '../models/user';

export class UserService {
  getUser(id: number): User {
    return { id, name: 'John Doe', email: 'john.doe@example.com' };
  }
}

				
			

Namespaces

Namespaces are a way to group related code.

Example:

src/models/user.ts:

				
					namespace Models {
  export interface User {
    id: number;
    name: string;
    email: string;
  }
}

				
			

src/services/userService.ts:

				
					/// <reference path="../models/user.ts" />

namespace Services {
  import User = Models.User;

  export class UserService {
    getUser(id: number): User {
      return { id, name: 'John Doe', email: 'john.doe@example.com' };
    }
  }
}

				
			

Managing Dependencies

Dependencies are managed using npm.

Installing Dependencies

To install a package, use:

				
					npm install <package-name>

				
			

Development Dependencies

For development dependencies, use:

				
					npm install <package-name> --save-dev

				
			

Example Dependencies

package.json:

				
					{
  "name": "my-typescript-project",
  "version": "1.0.0",
  "main": "dist/index.js",
  "scripts": {
    "build": "tsc",
    "test": "jest"
  },
  "devDependencies": {
    "typescript": "^4.0.0",
    "jest": "^26.0.0",
    "@types/jest": "^26.0.0"
  },
  "dependencies": {
    "express": "^4.17.1"
  }
}

				
			

Building and Compiling TypeScript Code

Compiling TypeScript

To compile TypeScript code, run:

				
					npx tsc

				
			

This uses the tsconfig.json file to compile the TypeScript files in the src directory and outputs them to the dist directory.

Watching for Changes

To automatically recompile TypeScript code when changes are detected, use:

				
					npx tsc --watch

				
			

Testing TypeScript Code

Setting Up Testing Framework

Jest is a popular testing framework that can be used with TypeScript.

Installing Jest:

				
					npm install jest @types/jest ts-jest --save-dev

				
			

Configuring Jest

Create a jest.config.js file:

				
					module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  testMatch: ['**/tests/**/*.test.ts'],
};

				
			

Writing Tests

Example Test:

tests/userService.test.ts:

				
					import { UserService } from '../src/services/userService';

describe('UserService', () => {
  it('should return a user by id', () => {
    const service = new UserService();
    const user = service.getUser(1);
    expect(user).toEqual({ id: 1, name: 'John Doe', email: 'john.doe@example.com' });
  });
});

				
			

Running Tests

To run tests, use:

				
					npm test

				
			

Linting and Formatting

Linting and formatting help maintain code quality and consistency.

Setting Up ESLint

Installing ESLint:

				
					npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev

				
			

Configuring ESLint

Create an .eslintrc.js file:

				
					module.exports = {
  parser: '@typescript-eslint/parser',
  extends: [
    'plugin:@typescript-eslint/recommended'
  ],
  rules: {
    // Add custom rules here
  },
};

				
			

Running ESLint

To lint the code, use:

				
					npx eslint . --ext .ts

				
			

Setting Up Prettier

Installing Prettier:

				
					npm install prettier --save-dev

				
			

Configuring Prettier

Create a .prettierrc file:

				
					{
  "semi": true,
  "singleQuote": true,
  "trailingComma": "all"
}

				
			

Integrating with ESLint

Install Prettier ESLint config:

				
					npm install eslint-config-prettier eslint-plugin-prettier --save-dev

				
			

Update .eslintrc.js:

				
					module.exports = {
  parser: '@typescript-eslint/parser',
  extends: [
    'plugin:@typescript-eslint/recommended',
    'prettier/@typescript-eslint',
    'plugin:prettier/recommended'
  ],
  rules: {
    // Add custom rules here
  },
};

				
			

Version Control and Continuous Integration

Using Git

Initialize a Git repository:

				
					git init

				
			

Create a .gitignore file:

				
					node_modules
dist
.env

				
			

Setting Up Continuous Integration

Using GitHub Actions for CI:

Create a .github/workflows/ci.yml file:

				
					name: CI

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '14'
    - run: npm install
    - run: npm run build
    - run: npm test

				
			

Advanced Configuration and Tips

Path Mapping

Path mapping helps simplify imports.

Example tsconfig.json:

				
					{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@models/*": ["src/models/*"],
      "@services/*": ["src/services/*"]
    }
  }
}

				
			

Aliases in Imports

With path mapping, you can import modules using aliases:

				
					import { User } from '@models/user';
import { UserService } from '@services/userService';

				
			

Using Environment Variables

Environment variables can be managed using the dotenv package.

Installing dotenv:

				
					npm install dotenv

				
			

Using dotenv:

Create a .env file:

				
					API_KEY=your_api_key

				
			

Load environment variables in your code:

				
					import * as dotenv from 'dotenv';
dotenv.config();

const apiKey = process.env.API_KEY;

				
			

Organizing and structuring a TypeScript project involves setting up the correct directory structure, configuring TypeScript, managing dependencies, and ensuring code quality through testing and linting. By following best practices and utilizing tools like ESLint, Prettier, and Jest, you can maintain a clean and efficient codebase. Happy coding !❤️

Table of Contents