Organize TypeScript Project Folders

A well-organized folder structure makes a TypeScript project easier to navigate, build, and scale. Learn practical patterns for src, types, tests, and monorepos.

7 min read

Learning how to organize TypeScript project folders makes a project easier to navigate, faster to build, and simpler for new contributors to understand. The right structure grows with the project without needing constant reorganization.

The foundation is separating source code from everything else. Source code goes in a dedicated directory. Build output goes in another. Configuration, documentation, and tooling stay at the project root.

The Minimal Setup

The smallest practical TypeScript project separates source from output using two directories: src for the TypeScript source files and dist for the compiled JavaScript. The tsconfig.json maps between them using rootDir and outDir.

Here is the directory layout in its simplest form. The src folder holds TypeScript source, and dist receives the compiled output:

plaintextplaintext
project/
  src/
    index.ts
    greeting.ts
  dist/
  tsconfig.json
  package.json

The configuration tells TypeScript where to find source files and where to write compiled output. The rootDir and outDir options create the mapping between these two directories:

jsonjson
{
  "compilerOptions": {
    "rootDir": "./src",
    "outDir": "./dist",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "strict": true
  },
  "include": ["src"]
}

The rootDir tells TypeScript where the source tree begins. The outDir tells TypeScript where to write compiled files.

With this configuration, a file at src/index.ts compiles to dist/index.js, preserving the directory structure inside src.

Feature-Based Organization

As a project grows beyond a handful of files, grouping by feature becomes more practical than grouping by file type.

Each feature folder contains everything related to that feature: the implementation files, any feature-specific types, and a barrel file that exports the public API.

Feature folders are self-contained. The users folder does not import from the posts folder unless there is a genuine dependency.

When a dependency does exist, the import goes through the barrel file, not directly into implementation files. For more on barrel files, see barrel files in TypeScript.

The shared folder contains types and utilities used by multiple features. API response shapes, error classes, and common interfaces belong here. Feature-specific types stay inside their feature folder.

The Types Folder

Shared type definitions deserve a dedicated home. A types folder at the project level holds interfaces, type aliases, and enums that cross feature boundaries.

What goes in the types folder: API request and response shapes used by multiple features, domain model interfaces shared across the application, error types and result types, and configuration types.

What stays in feature folders: types used only within a single feature, component prop types, and internal state types.

A good rule: if a type is imported from more than one feature folder, it belongs in the shared types folder. If it is only used within one feature, keep it there.

Test File Organization

Tests can live next to source files or in a separate tests directory. Both patterns work.

Colocated tests keep test files next to the code they test, making imports shorter. This is the simpler pattern for smaller projects.

A separate tests directory that mirrors the src structure keeps the source tree cleaner and makes it easy to exclude tests from production builds.

Use the exclude field in tsconfig to prevent test files from being compiled during production builds.

Barrel Files at Boundaries

Barrel files give each feature folder a clear public API. An index.ts file in each feature folder re-exports only what external consumers need.

Implementation files inside the feature folder import from each other directly, not through the barrel. External code imports from the barrel.

This creates a clear boundary where the internal file structure can change without affecting consumers. For more on this pattern, see named exports in TypeScript.

Monorepo Patterns

In a monorepo, each package has its own src directory, its own tsconfig.json, and its own build output. A top-level tsconfig.json ties them together with project references.

Each package compiles independently, and the shared package publishes types that server and client consume.

This separation enforces that shared code does not accidentally depend on server-only or client-only code. Dependencies flow in one direction, from shared up to the packages that consume it.

Common Mistakes

Putting source files at the project root. Without a src directory, every .ts file at the root becomes part of the compilation. Configuration files and build outputs can accidentally get picked up by the compiler.

Mixing source and output in the same directory. When outDir is not set, compiled .js files land next to .ts source files. This clutters the source tree. Always set outDir to a separate directory.

Creating too many nested folders early. A project with three files does not need a ten-level directory tree. Start simple and add folders when navigation becomes difficult.

Forgetting to exclude test files from production builds. Test files should be included for development but excluded for production. Use a separate tsconfig.prod.json that extends the base config.

Making every folder a feature. Not every grouping needs a dedicated folder. Small utility functions and shared constants can live in a single file until they grow enough to justify their own directory.

Rune AI

Rune AI

Key Insights

  • Separate source code from build output using src and dist directories.
  • Configure rootDir and outDir in tsconfig to control the input and output directory structure.
  • Group files by feature rather than by file type once a project grows beyond a handful of modules.
  • Keep shared type definitions in a dedicated types folder at the project level.
  • Use barrel files at module boundaries but not inside implementation folders.
RunePowered by Rune AI

Frequently Asked Questions

Should I put all TypeScript files in a src folder?

Yes, it is the standard convention. A src folder separates your source code from configuration files, build outputs, and documentation. It also makes rootDir configuration straightforward.

What goes in a types folder?

Shared type definitions, interfaces, and type aliases that are used across multiple modules. Module-specific types should stay with their module.

Should test files be next to source files or in a separate folder?

Both patterns are common. Colocating tests next to source files makes imports shorter. A separate tests folder keeps the source tree cleaner. Choose one and stay consistent.

Conclusion

A good folder structure grows with your project. Start with a simple src and dist separation. Add feature folders as the project grows. Keep shared types in a dedicated types folder. Use barrel files at module boundaries. Let the compiler options rootDir and outDir handle the mapping between source and output directories.