Tutorials Logic, IN info@tutorialslogic.com

Express.js Project Setup and Folder Structure: Prevent Backend Chaos Early

Express.js Project Setup and Folder Structure

Many Express projects become messy not because the framework is weak, but because the team delays structure until the codebase is already noisy.

Early folder decisions influence how quickly you can debug routes, add features, and onboard someone new.

Beginners should keep the structure small but intentional. Professionals should keep it predictable and ownership-friendly.

A good setup is one that makes responsibility visible instead of hiding it behind random file placement.

Start Smaller Than You Think

One of the most common beginner mistakes is copying a very large folder structure before there is enough code to justify it. That usually creates confusion because half the folders are empty and the reasons behind them are unclear.

A better start is a small structure with obvious responsibilities: routes, controllers, services, middleware, and config. When the app grows, you can refine it instead of pretending to have a big architecture on day one.

  • Keep the first version readable.
  • Do not multiply folders before real use cases appear.
  • Name folders after responsibility, not fashion.

What Becomes Hard When Structure Is Poor

A weak structure usually shows up as duplicated validation, large route files, random helper modules, and no clear place for business rules. The backend may still run, but every change becomes slower.

Professionals recognize that file structure affects team speed. When nobody knows where new code should live, every feature starts by searching instead of building.

  • Large route files are hard to review.
  • Business logic buried inside handlers is hard to test.
  • Config and environment logic scattered across files creates fragile behavior.

Beginner Walkthrough: Build A Feature-Oriented Express Project

Start with a small application entry point that creates Express, registers shared middleware, mounts feature routers, and adds not-found and error handlers. Keep server startup separate from app creation so integration tests can import the application without opening a real network port.

Organize business features such as users, orders, and authentication into their own folders. Each feature can contain its router, controller, validation schema, service, and repository. This keeps related code close together and prevents one global controllers or services folder from becoming an unsearchable collection as the project grows.

Load configuration once, validate required environment values at startup, and expose a typed configuration object. Create shared infrastructure modules for logging, database access, errors, and observability. Avoid importing process.env throughout the codebase because missing or malformed values then fail unpredictably during requests.

  • Separate application creation from server startup.
  • Group code around business features.
  • Keep shared infrastructure small and intentional.
  • Validate configuration before accepting traffic.
  • Make import direction and ownership easy to explain.

A Better Long-Term Shape

As an Express service becomes serious, folders should reflect the request path and the domain path. Requests arrive through routes and controllers, but business rules and data access should not remain stuck in those outer layers forever.

The goal is not perfect architecture terminology. The goal is to create a codebase where the entry point is clear, the business decision point is clear, and the infrastructure connection point is clear.

  • Use controllers for request-specific concerns.
  • Use services for reusable business logic.
  • Keep config and infrastructure setup easy to find.

Enforce Boundaries in an Express Project

Implement a create-order feature with route, controller, service, repository, and validation modules. Keep HTTP objects out of the service so business behavior can be tested without starting a server.

Work through this as a controlled engineering exercise rather than a copy-and-paste demo. State the expected result before running anything, keep the input small enough to inspect, and record the important intermediate state. That makes the lesson explain not only what to type, but why the result is trustworthy.

Circular imports and controllers that query the database directly erase the intended boundaries. A folder structure has no value when dependencies still point in every direction.

Verification must use evidence that matches the concept. Unit-test the service with a fake repository and integration-test only the HTTP boundary, then inspect imports for forbidden dependencies. Repeat the check after deliberately introducing the failure, then after the fix. The contrast between those runs is the part that turns a definition into practical understanding.

  • Write the expected behavior and the failure condition before starting.
  • Run the smallest representative scenario and preserve its output.
  • Introduce the named failure deliberately instead of waiting for an accidental error.
  • Use the listed evidence to locate the first incorrect state.
  • Rerun the same verification after the fix and document the conclusion.

Experienced Practice: Dependency Direction, Boundaries, And Operational Setup

Use composition at the application edge. Construct repositories and gateways, inject them into services, and inject services into controllers or router factories. Core business modules should not import the Express app, global mutable containers, or database clients directly. This direction improves tests and makes runtime dependencies visible.

Define startup and shutdown behavior. Establish the database pool, register signal handlers, stop accepting traffic, close the HTTP server, drain bounded in-flight requests, stop consumers, and close pools. Startup should fail fast when required dependencies or migrations are unavailable rather than serving partially initialized traffic.

Add linting, formatting, static checks, unit tests, integration tests, migration commands, and a production start script. Keep generated output separate from source. Document the commands, environment variables, health endpoints, and local dependency setup so a new developer and CI execute the same workflow.

  • Compose dependencies at the application edge.
  • Keep core operations independent of Express.
  • Implement graceful startup and shutdown.
  • Use consistent commands locally and in CI.
  • Document environment and operational assumptions.

A simple project shape that scales reasonably well

This is a practical middle ground between a toy app and an over-engineered starter.

A simple project shape that scales reasonably well
src/
  app.js
  routes/
  controllers/
  services/
  middleware/
  config/
  db/
  utils/
  • Every folder has a visible reason to exist.
  • You can grow this structure without immediately rewriting it.
  • It is easier to explain to a new developer than a huge preset architecture.

Enforce Boundaries in an Express Project example

Adapt this focused example to a disposable local environment and inspect every result before expanding it.

Enforce Boundaries in an Express Project example
src/orders/order.routes.js
src/orders/order.controller.js
src/orders/order.service.js
src/orders/order.repository.js
src/orders/order.schema.js
  • Do not run production-changing commands until their scope and rollback are understood.
  • Capture the successful output and one intentionally failing output for comparison.
  • Replace example identifiers and credentials with safe local values.
  • Convert the final verification into a repeatable test, runbook, or review checklist.

Feature-oriented directory structure

Keep one business capability understandable without searching the whole repository.

Feature-oriented directory structure
src/app.js
src/server.js
src/config/index.js
src/platform/database.js
src/platform/logger.js
src/orders/order.routes.js
src/orders/order.controller.js
src/orders/order.service.js
src/orders/order.repository.js
src/orders/order.schema.js
tests/integration/orders.test.js
  • Names should reflect project conventions.
  • Do not create empty layers only for symmetry.
  • Keep database migrations in a clearly owned directory.

Application factory and server startup

Tests import createApp while production owns the listening socket.

Application factory and server startup
export function createApp(dependencies) {
  const app = express();
  app.use(express.json({ limit: \"100kb\" }));
  app.use(\"/orders\", createOrderRouter(dependencies.orders));
  app.use(notFoundHandler);
  app.use(errorHandler);
  return app;
}

const dependencies = await buildDependencies(config);
const server = createApp(dependencies).listen(config.port);
  • Validate config before buildDependencies.
  • Register four-argument error middleware last.
  • Add signal handlers around the server and dependencies.
Key Takeaways
  • I know why a small clear structure is better than a copied giant starter.
  • I can separate request-layer code from business logic.
  • I understand how poor structure slows debugging and change velocity.
  • I can describe a sensible folder layout for a small-to-medium Express app.
Common Mistakes to Avoid
Copying a large enterprise-style structure without understanding it.
Letting route files become the home for every kind of logic.
Hiding configuration and infrastructure details in random helpers.

Practice Tasks

  • Design a folder structure for a small task management API and explain each folder.
  • Take one imaginary overgrown route file and decide which logic belongs in controllers, services, and middleware.
  • Write a short team rule for where new business logic should live.
  • Recreate the Enforce Boundaries in an Express Project exercise and explain why each observed signal proves or disproves the expected behavior.
  • Change one assumption in the example, predict the effect, run the verification again, and document the difference.

Frequently Asked Questions

Not necessarily. Borrow useful separation ideas, but keep the structure practical for your project and team.

When business logic starts being reused, grows beyond simple request handling, or becomes hard to test inside controllers.

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.