Table of Contents
Folder Structure
A project’s folder structure is its skeleton—it defines where code, assets, tests, and configs live. The right structure depends on your project’s size (small library vs. enterprise app) and type (frontend, backend, CLI tool). Let’s start with the basics.
Basic Project Structure
For small projects (e.g., a utility library, simple Node.js script, or prototype), a minimal structure suffices. Here’s a typical setup:
my-typescript-project/
├── src/ # Source code (TypeScript)
│ ├── index.ts # Entry point
│ └── utils/ # Helper functions
│ └── format.ts
├── dist/ # Compiled JavaScript (output)
├── tests/ # Test files
│ ├── unit/ # Unit tests
│ └── integration/ # Integration tests
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript compiler config
├── .gitignore # Files to ignore (node_modules, dist, etc.)
└── README.md # Project documentation
Key Folders Explained:
src/: Contains all TypeScript source code. Keep this clean—only write code here that needs compilation.dist/: Output directory for compiled JavaScript (generated bytsc). Add this to.gitignoreto avoid committing build artifacts.tests/: Organize tests by type (unit, integration, E2E). Tools like Jest or Vitest will run these.
Advanced Project Structure
As projects grow (e.g., a React app, enterprise backend, or monorepo), you’ll need a more granular structure to enforce separation of concerns. Here’s an expanded example:
my-advanced-ts-project/
├── src/
│ ├── index.ts # Entry point
│ ├── api/ # API clients (e.g., fetch wrappers)
│ │ └── github-api.ts
│ ├── components/ # Reusable UI components (frontend)
│ │ ├── Button/
│ │ │ ├── Button.tsx
│ │ │ └── Button.test.tsx
│ ├── hooks/ # Custom React hooks (frontend)
│ │ └── useLocalStorage.ts
│ ├── models/ # TypeScript interfaces/types
│ │ └── User.ts
│ ├── services/ # Business logic (e.g., auth, data processing)
│ │ └── user-service.ts
│ └── utils/ # Shared utilities (e.g., validation, formatting)
├── public/ # Static assets (images, CSS, HTML) [frontend]
├── config/ # Shared configuration files
│ ├── eslint.base.js # Base ESLint rules
│ └── jest.base.js # Base Jest config
├── scripts/ # Build/deployment scripts (e.g., deploy.sh)
├── docs/ # Documentation (API docs, guides)
├── .env.example # Example environment variables
└── (other root files: tsconfig.json, package.json, etc.)
New Additions:
src/models/: Centralizes TypeScript interfaces and types (e.g.,User,Product). This avoids duplicating types across files.src/services/: Encapsulates business logic (e.g., API calls, data transformation) to keep components/hooks lean.public/: For static assets (images, fonts) that don’t need compilation (common in frontend frameworks like React/Vue).config/: Stores shared configs (e.g., ESLint, Jest) to reuse across subprojects (useful in monorepos).
Project-Specific Variations
Folder structure varies by project type. Here are examples for common use cases:
Frontend (React/Vue/Svelte):
- Add
src/pages/for route-specific components (e.g.,HomePage.tsx,AboutPage.tsx). src/context/for React Context providers (state management).src/assets/for images, fonts, or SCSS files (if not usingpublic/).
Backend (Node.js/Express/NestJS):
src/controllers/for request handlers (e.g.,UserController.ts).src/routes/for API route definitions (e.g.,user.routes.ts).src/middleware/for Express middleware (e.g., auth, logging).src/db/for database models/migrations (e.g., Prisma schema, Sequelize models).
CLI Tool:
src/commands/for CLI command handlers (e.g.,init.ts,deploy.ts).src/cli.tsas the entry point for parsing CLI arguments (usingcommanderoryargs).
Configuration Files
Configuration files dictate how tools (TypeScript, linters, bundlers) behave. Let’s break down the most critical ones.
tsconfig.json: The Heart of TypeScript
The tsconfig.json file tells the TypeScript compiler (tsc) how to transpile TypeScript to JavaScript. It’s required for any TypeScript project. Here’s a typical setup with key options explained:
// tsconfig.json
{
"compilerOptions": {
// Output settings
"target": "ES2020", // Compile to ES2020 (supports modern JS features)
"module": "ESNext", // Use ES modules (import/export)
"outDir": "./dist", // Output compiled JS to `dist/`
"rootDir": "./src", // Source code lives in `src/`
"declaration": true, // Generate .d.ts type files (for libraries)
"sourceMap": true, // Generate source maps for debugging
// Type-checking strictness
"strict": true, // Enable all strict type-checking (critical!)
"noImplicitAny": true, // Disallow `any` types (unless explicitly used)
"strictNullChecks": true, // Require null/undefined checks (avoids "cannot read property of undefined")
// Module resolution
"moduleResolution": "NodeNext", // Use Node.js module resolution (supports package.json "exports")
"esModuleInterop": true, // Enable interop between CommonJS and ES modules
"skipLibCheck": true, // Skip type-checking for .d.ts files (faster builds)
"forceConsistentCasingInFileNames": true, // Avoid OS-specific casing issues
// Libraries to include (e.g., DOM for frontend, ES2020 for backend)
"lib": ["ES2020", "DOM"] // "DOM" is optional (only for frontend)
},
"include": ["src/**/*"], // Files to compile (all .ts/.tsx in src/)
"exclude": ["node_modules", "dist", "tests/**/*.test.ts"] // Files to ignore
}
Pro Tip: Use extends for Reusability
For large projects or monorepos, split tsconfig.json into base and project-specific configs:
// tsconfig.base.json (shared settings)
{
"compilerOptions": {
"strict": true,
"target": "ES2020",
// ... other shared options
}
}
// tsconfig.app.json (for the app)
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist/app"
},
"include": ["src/app/**/*"]
}
Popular base configs: @tsconfig/node18, @tsconfig/react-app.
Linting & Formatting: ESLint and Prettier
TypeScript enforces type safety, but ESLint and Prettier enforce code quality and style.
ESLint (Linting)
ESLint catches code errors, anti-patterns, and enforces coding standards (e.g., no console.log in production). For TypeScript, use @typescript-eslint:
-
Install dependencies:
npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev -
Create
.eslintrc.js:// .eslintrc.js module.exports = { parser: '@typescript-eslint/parser', // Parse TypeScript parserOptions: { project: './tsconfig.json', // Enable type-aware linting tsconfigRootDir: __dirname, }, extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', // TypeScript rules 'plugin:@typescript-eslint/strict-type-checked', // Strict type checks ], rules: { // Custom rules (e.g., disallow console.log) 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn', '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], }, };
Prettier (Formatting)
Prettier auto-formats code (e.g., line length, indentation) to avoid style debates. Integrate it with ESLint using eslint-config-prettier (disables ESLint rules that conflict with Prettier):
-
Install dependencies:
npm install prettier eslint-config-prettier eslint-plugin-prettier --save-dev -
Create
.prettierrc:// .prettierrc { "semi": true, "singleQuote": true, "tabWidth": 2, "trailingComma": "es5" } -
Update ESLint to use Prettier:
// .eslintrc.js (updated) module.exports = { // ... (previous config) extends: [ // ... (previous extends) 'plugin:prettier/recommended', // Enables Prettier as an ESLint rule ], };
Build Tools: Webpack, Vite, and Rollup
For frontend projects or libraries, you’ll need a bundler to package code. Popular tools:
Vite (Fast Frontend Bundler)
Vite is faster than Webpack and natively supports TypeScript. Configure it with vite.config.ts:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react'; // For React
export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist', // Output directory
},
server: {
port: 3000, // Dev server port
},
});
Rollup (Library Bundler)
For TypeScript libraries, Rollup creates optimized bundles (ESM, CommonJS). Use @rollup/plugin-typescript:
// rollup.config.js
import typescript from '@rollup/plugin-typescript';
export default {
input: 'src/index.ts',
output: [
{ file: 'dist/index.cjs', format: 'cjs' }, // CommonJS
{ file: 'dist/index.mjs', format: 'esm' }, // ESM
],
plugins: [typescript()],
};
Testing: Jest Configuration
Jest is a popular testing framework for TypeScript. Use ts-jest to transpile TypeScript during tests:
-
Install dependencies:
npm install jest ts-jest @types/jest --save-dev -
Create
jest.config.js:// jest.config.js module.exports = { preset: 'ts-jest', // Use ts-jest for TypeScript testEnvironment: 'node', // or 'jsdom' for frontend roots: ['<rootDir>/src'], // Look for tests in src/ testMatch: ['**/*.test.ts'], // Test file pattern moduleFileExtensions: ['ts', 'js'], };
package.json Scripts
package.json scripts automate common tasks (build, test, lint). Here are essential scripts:
// package.json
{
"scripts": {
"build": "tsc", // Compile TypeScript to JS
"dev": "ts-node src/index.ts", // Run with ts-node (no build step)
"watch": "tsc --watch", // Recompile on file changes
"test": "jest", // Run tests
"test:watch": "jest --watch", // Run tests in watch mode
"lint": "eslint 'src/**/*.{ts,tsx}'", // Lint code
"format": "prettier --write 'src/**/*.{ts,tsx,json,md}'", // Format code
"prepare": "husky install" // Set up Husky (optional, for pre-commit hooks)
}
}
Best Practices
- Separate Source and Output: Keep
src/(TypeScript) anddist/(compiled JS) strictly separated. Adddist/to.gitignore. - Centralize Types: Store interfaces/types in
src/models/to avoid duplication. - Keep Configs DRY: Use
extendsintsconfig.json, ESLint, and Jest to reuse configs across projects. - Modularize Code: Split large files into smaller, single-responsibility modules (e.g., one utility per file).
- Document Structure: Add a
README.mdexplaining folder purposes (e.g., “src/services/holds business logic”). - Ignore Unnecessary Files: Use
.gitignoreto excludenode_modules/,dist/,.env, and IDE files (.vscode/).
Conclusion
A well-structured TypeScript project with clear configuration is the foundation of scalability and collaboration. By organizing folders logically, leveraging tools like tsconfig.json and ESLint, and automating tasks with scripts, you’ll reduce errors, speed up development, and make onboarding new team members a breeze.
Remember: There’s no “one-size-fits-all”—adapt the structure and configs to your project’s needs, but start with these principles to avoid common pitfalls.