In small Express.js apps, everything often lives inside a single file:
server.js
But in real-world applications — especially with TypeScript — a good folder structure is essential because it gives you:
- Clean separation of responsibilities
- A place for each layer (routes, controllers, services, db, validation)
- Easier debugging
- Easier testing
- Easier scalability
- Predictability for future developers
Express is extremely flexible, which is good — but it also means beginners get confused.
This guide shows the most common and practical structure used in the industry.
Table of Contents
⭐ 1. The Real Industrial Standard (Used by ~90% of Express + TS teams)
The following structure is simple, clear, and scalable. It works for small apps and grows with your project: This structure is called Feature-based Architecture (a.k.a. vertical slices)
project/
│
├── src/
│ ├── app.ts
│ ├── server.ts
│ │
│ ├── config/
│ │ └── env.ts
│ │
│ ├── db/
│ │ └── prisma.ts (or db.ts for SQLite/Knex/Sequelize)
│ │
│ ├── common/
│ │ ├── middleware/
│ │ │ ├── errorHandler.ts
│ │ │ └── validate.ts
│ │ └── utils/
│ │ └── asyncHandler.ts
│ │
│ ├── modules/
│ │ └── todos/
│ │ ├── todo.routes.ts
│ │ ├── todo.controller.ts
│ │ ├── todo.service.ts
│ │ ├── todo.repository.ts
│ │ ├── todo.schema.ts
│ │ └── todo.types.ts
│ │
│ └── types/
│ └── global.d.ts
│
├── prisma/
│ └── schema.prisma
│
├── .env
├── package.json
├── tsconfig.json
└── README.md
The above project structure is a very good representation of modern industrial best practices for building scalable applications with Express and TypeScript.
It uses a module-based (or feature-based) architecture, where all the code related to a specific feature (like todos) is grouped together. This includes routes, controllers, services, and data access layers.
This approach is highly favored in the industry for larger applications because it:
- Is Scalable: It’s easy to add or remove features without affecting other parts of the application.
- Is Maintainable: Code is easier to find and reason about since related files are co-located.
- Promotes High Cohesion: Keeps related logic tightly packed.
While some projects use a “layer-based” structure (e.g., a single controllers folder with all controllers), the modular approach are widely considered a more robust and scalable standard for professional development. The above structure is an example of a Feature-based Architecture, often referred to as vertical slices.
The core idea is to group all related components (routes, controllers, services, repositories, schemas, types) for a specific feature (like todos) into its own module.
Regarding “100% self-contained”: While the goal of this architecture is to make features as independent as possible, “100% self-contained” is a very strong claim and rarely achievable in practice. Features will still rely on shared infrastructure like:
- Common utilities (src/common/utils)
- Middleware (src/common/middleware)
- Configuration (src/config)
- Database connections (src/db)
- Global types (src/types)
So, while each feature is largely self-contained and encapsulates its specific logic, it still operates within the broader application context and utilizes shared foundational elements
⭐ 2. Why This Structure Is the “Most Realistic” Industrial Standard
🔹 Reason 1 — Feature-based modules
Most companies group code by feature:
modules/
todos/
users/
orders/
This is cleaner than grouping by technical layers (routes/, controllers/ etc.).
🔹 Reason 2 — Separate app.ts and server.ts
app.tsconfigures Expressserver.tsactually starts the server
This separation allows:
- Testing
appwithout starting server - Cleaner bootstrapping
- Easier deployment
🔹 Reason 3 — Dedicated config folder
Environment handling is critical in real projects:
config/
env.ts
You validate environment variables with Zod.
🔹 Reason 4 — A thin, simple “common” folder
Contains things widely reused:
common/middleware
common/utils
common/errors (optional)
🔹 Reason 5 — Modular, layered design inside each feature
Each feature (like todos) has:
router → defines endpoints
controller → handles request/response
service → business logic
repository → database queries
schema → Zod validation
types → TS interfaces
This mirrors clean architecture principles without over-engineering.
⭐ 3. How This Structure Compares to FastAPI
We can use horizontal or layered architecutre which i used with FastAPI. Every layer contains all features:
- All routes inside
web/ - All schemas inside
schemas/ - All services inside
services/ - All repositories inside
repository/
✔ This is a real industrial pattern: It is used widely in FastAPI, Django, Laravel, and simple Express projects.
- routers
- schemas (Pydantic)
- services
- repository
- db
- app/main
Express.js + TypeScript equivalent:
| FastAPI | Express.js + TS |
|---|---|
| main.py | server.ts |
| app initialization | app.ts |
| routers | routes.ts |
| Pydantic schemas | Zod schemas |
| services | service.ts |
| repository | repository.ts |
| models | Prisma/ORM models |
| DB session | prisma.ts/db.ts |
The patterns are extremely similar. This structure feels natural to FastAPI developers.
⭐ Which One Is Industrial?
Both are. But they are used for different project sizes.
| Architecture | Industry Usage |
| Layered Architecture (a.k.a. horizontal architecture) | Small → medium projects, FastAPI apps, monoliths |
| Feature-based (modules/) | Medium → large projects, microservices, TypeScript Express apps |
Neither is wrong. Both are clean architecture styles.
⭐ Why Express.js Often Uses Feature-Based (modules)
TypeScript projects tend to grow quickly. Industries prefer:
modules/
auth/
todos/
orders/
Because:
- everything for 1 feature is in one place
- easier to delete or move modules
- easier for big teams
- easier to migrate to microservices
But again: This does NOT mean your FastAPI structure is wrong.
⭐ 4. How Your FastAPI Structure Would Look in Express.js
If we translate fastapi structure EXACTLY into Express, it would be:
src/
│── web/ (routes)
│── schemas/ (Zod schemas)
│── services/ (todo.service.ts, user.service.ts)
│── repository/ (todo.repo.ts, user.repo.ts)
│── db/ (db.ts / prisma.ts)
app.ts
server.ts
Yes — this is completely valid for Express.js. Many companies use this “layered” structure.
⭐ 5. How This Differs from DeepSeek’s Enterprise Structure suggestion
DeepSeek’s version added:
- CI/CD
- workflows folder
- docs folder
- rate limiter
- helmet/cors/compression
- versioned API structure
- full logging system
- global error classes
- pagination utilities
- seeders + migrations
- graceful shutdown logic
- full testing structure
project/
│
├── 📁 .github/ # GitHub workflows
│ └── 📁 workflows/
│ ├── ci.yml
│ └── cd.yml
│
├── 📁 docs/ # API documentation
│ ├── api.md
│ └── setup.md
│
├── 📁 src/
│ ├── main.ts # Server entry point
│ ├── app.ts # Express app configuration
│ │
│ ├── 📁 common/ # Shared utilities
│ │ ├── errors/ # Custom error classes
│ │ │ ├── AppError.ts
│ │ │ ├── HttpStatus.ts
│ │ │ └── index.ts
│ │ ├── middleware/ # Global middleware
│ │ │ ├── errorHandler.ts
│ │ │ ├── validation.ts
│ │ │ ├── rateLimiter.ts
│ │ │ └── logger.ts
│ │ └── utils/
│ │ ├── apiResponse.ts
│ │ └── helpers.ts
│ │
│ ├── 📁 config/ # Configuration management
│ │ ├── environment.ts
│ │ ├── database.ts
│ │ └── constants.ts
│ │
│ ├── 📁 modules/ # Feature-based modules (RECOMMENDED)
│ │ └── 📁 todos/ # Todo feature module
│ │ ├── todo.routes.ts
│ │ ├── todo.controller.ts
│ │ ├── todo.service.ts
│ │ ├── todo.repository.ts
│ │ ├── todo.schema.ts # Zod validation
│ │ ├── todo.types.ts # TypeScript interfaces
│ │ └── todo.test.ts
│ │
│ ├── 📁 db/ # Database layer
│ │ ├── connection.ts
│ │ ├── migrations/ # If using raw SQL
│ │ └── seeders/ # Test data
│ │
│ └── 📁 types/ # Global TypeScript types
│ ├── express.d.ts # Express type extensions
│ └── global.d.ts
│
├── 📁 tests/ # Test suites
│ ├── unit/
│ ├── integration/
│ └── fixtures/
│
├── 📁 public/ # Static files
│ └── 📁 uploads/ # File uploads
│
├── .env.example
├── .env
├── package.json
├── tsconfig.json
├── docker-compose.yml # For local development
├── Dockerfile
├── jest.config.ts # Testing framework
└── README.md
✔ All of these are valid: But they are enterprise-level and unnecessary for learning.
❌ Too heavy for small projects: Your goal right now is to learn Express, not build a microservice architecture.
✔ Real Industrial Standard recommended is:
- Industrial
- Used by 90% of teams
- Much simpler
- Easier to re-use
- Easier to modify
- Perfect for small/medium apps
⭐ 6. Folder-by-Folder Explanation (Easy to Understand) For Featured based Architecture
🔷 src/app.ts
Creates and configures the Express app:
- middleware
- routes
- JSON parser
- CORS
- error handler
🔷 src/server.ts
Starts the server listening on a port.
Useful in tests because you can test app without running the server.
🔷 src/config/
Environment and configuration logic.
Example:
env.ts → validates and exports env vars using Zod
🔷 src/db/
Responsible for database connection:
prisma.ts → new PrismaClient()
or
sqlite.ts → sqlite3 db connection
🔷 src/common/
Shared functions that don’t belong to any feature.
middleware/→ error handling, validationutils/→ reusable helpers
🔷 src/modules/
This is where the real application lives.
Each feature is isolated.
Example:
todos/
todo.routes.ts
todo.controller.ts
todo.service.ts
todo.repository.ts
todo.schema.ts
todo.types.ts
This follows the same separation as FastAPI.
🔷 src/types/
Global TypeScript definitions.
⭐ 7. When to Expand This Structure
When your project grows, you can add:
For medium projects:
tests/
docs/
public/
For enterprise scale:
.github/workflows/
migrations/
seeders/
global error classes
request logging system
rate limiters
role-based auth
modular versioned APIs (v1, v2)
⭐ 8. Summary — The Best Structure for Learning + Real Jobs
✔ Use the simplified industrial structure
Not heavy weight version.
✔ This structure matches FastAPI logic
So it will feel familiar.
✔ This structure is used by the majority of companies
Not too small, not too enterprise.
✔ Perfect for:
- learning
- building real apps
- preparing for interviews
- future scalability
Related Articles