On this page
The core idea
Many Node.js projects start with folders by technical layer: routes, controllers, services, models. As features grow, changing one feature means editing files in four distant folders, and nobody can see a feature's full shape in one place. Organising by feature, such as courses, enrolments and payments, puts each feature's routes, logic, data access and tests together. Shared infrastructure, like database connection or logging, lives in a separate shared area. Layers still exist inside each feature; they are simply co-located.
A practical approach
Step 1
Identify the main features or domains of the application.
Step 2
Create a folder per feature containing its routes, service logic, data access and tests.
Step 3
Keep a shared folder for cross-cutting infrastructure: configuration, database client, logging and error handling.
Step 4
Let features communicate through exported functions from each feature's public entry file, not by importing each other's internal files.
Step 5
Move one feature at a time, running tests after each move, rather than reorganising everything at once.
Worked example
Adding a refund rule to an Express app requires changes in routes/payments.js, controllers/paymentController.js, services/paymentService.js and models/payment.js, plus tests in a separate tree. After moving to src/payments/ with routes, service, repository and tests together, the same change touches one folder, and the pull request is much easier to review.
A mistake to avoid
Do not let feature folders import from each other's internal files freely. Without boundaries, a feature-based structure becomes a different kind of tangle. Expose a small public interface per feature.
Try it yourself
Draw your project's folders and mark which files change for one recent feature. If they are spread across several layer folders, move that one feature into its own folder and see whether the next change is easier.
Your implementation checklist
- Code is grouped by feature
- Each feature holds its routes, logic, data access and tests
- Shared infrastructure lives separately
- Features use each other's public interfaces only
- Restructuring happens incrementally with tests
Share or cite this guide
Innovate with Ajay Editorial (2026-10-07). Structure a Node.js App by Feature, Not by File Type. https://innovatewithajay.com/blogs/structure-a-node-app-by-feature
