Skip to solution
mediumSystem Design

What are some best practices for structuring a Node.js project?

1.1k views
01

Understand the problem

Question presented to candidate: "A new engineer joins the team and needs to add a feature that touches an HTTP route, some business logic, and a database query. How quickly could they find where each of those three things belongs, in your current project structure?"

What a strong answer should cover:

  • A common, effective structure separates code by responsibility layer: routes/controllers (parsing the HTTP request, calling business logic, formatting the response), services (the actual business logic, framework-agnostic), and a data-access layer (database queries, isolated behind an interface — connecting directly to the dedicated Repository-pattern question) — each layer with a clear, narrow responsibility.
  • An alternative, equally valid organizing principle is by feature/domain (a folder per feature containing its own routes, services, and data access together) rather than by technical layer — both are legitimate; the wrong choice is having no consistent principle at all, where files accumulate ad hoc with no predictable location for new code.
  • 📌 A structure that separates business logic from the framework and the database matters concretely, not just aesthetically: it is precisely what makes the dependency-injection pattern (verified with a real, working demonstration in its own dedicated question) practical — a service function receiving its data-access dependency as a parameter, rather than importing a specific database client directly, can be tested with a fake in place of the real one with zero changes to the service itself.
  • Centralized configuration (covered fully in the dedicated environment-configuration question) and centralized error handling (covered fully in the dedicated error-handling question, including the real, verified Express 4-vs-5 async gap) should live in one predictable place each, not scattered.
  • A precise answer names the concrete, practical test for whether a structure is actually working: a new engineer, given a three-layer task (route, logic, data), should be able to find where each piece belongs quickly and confidently — the prompt's own scenario is exactly this test, applied directly.
  • Common structural anti-patterns worth naming explicitly: a single giant file mixing routing, business logic, and raw database queries together; business logic directly importing and calling a specific database client, making it untestable without a real database connection (the exact anti-pattern the dependency-injection question demonstrates fixing); and no consistent convention at all, so every new feature is placed differently from the last.

Clarifying questions expected:

  • "Is the team more comfortable organizing by technical layer, or by feature/domain?" — both are legitimate; consistency matters more than which one is chosen.
  • "Is business logic currently coupled directly to the database client, or already behind an interface?" — the single most consequential structural decision for testability.

Code / implementation expected: No — this is a structural/organizational judgment question; grounding the recommendation in the real, verified dependency-injection demonstration from its own dedicated question is the appropriate level of rigor, not new runnable code.

best practicesproject structurearchitecturemaintainability
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for Node.js system-design interviews — assumes familiarity with the dependency-injection question's real, verified demonstration. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:.

Solution ready — 2 min read

Classified // press E to declassify

04

Read the code

A layered structure that enables the real, verified dependency-injection benefit demonstrated in its own dedicated question
// routes/users.js — HTTP concerns only
app.post("/users", async (req, res) => {
  const user = await userService.register(req.body.email);
  res.json(user);
});

// services/userService.js — business logic, receives its dependency (no direct DB import)
function createUserService(userRepository) {
  return { register: (email) => userRepository.save({ email }) };
}

// data/userRepository.js — the ONLY place that knows about the actual database
const userRepository = {
  save: (user) => db.collection("users").insertOne(user),
};

// Wiring, at the application's entry point:
const userService = createUserService(userRepository);

// In a test — the SAME userService factory, a fake repository, zero changes needed:
const fakeRepo = { save: (u) => Promise.resolve({ ...u, id: "test-1" }) };
const testUserService = createUserService(fakeRepo);
// -- exactly the pattern verified end-to-end in the dependency-injection question
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 47 of 152 decoded in the Node.js track. One more won't hurt.

Back to track