Public topic guide

Project StructureNode.js + Express.js to Java + Spring Boot

Project structure is the organizational convention that determines where code lives, how dependencies flow between layers, and how predictably a new contri

Node.js + Express.js
Java + Spring Boot

Scenario and definition

A Logistics Platform's backend team is splitting a monolith into a shipment-tracking service that must be onboarded quickly by new engineers, tested in isolation, and deployed independently. The team needs a folder structure that separates routing, business logic, data access, and configuration cleanly. In Node.js, this is a convention-driven layout enforced by team discipline and a chosen framework's recommendations (e.g., NestJS's module-based structure). In Spring Boot, this is a convention-over-configuration layout enforced by Maven/Gradle's standard directory structure and Spring's package-by-feature or package-by-layer norms.

Definition: Project structure is the organizational convention that determines where code lives, how dependencies flow between layers, and how predictably a new contributor can navigate the codebase.

Keywords

src/modulescontrollersservicesrepositoriespackage.jsonNestJS modulebarrel exports
src/main/javasrc/main/resources@SpringBootApplicationpackage-by-featurepom.xmlapplication.yml@Component scan

Code comparison

node
src/
  modules/
    shipments/
      shipments.controller.ts
      shipments.service.ts
      shipments.repository.ts
      shipments.module.ts
      dto/
        create-shipment.dto.ts
    common/
      filters/
      guards/
      interceptors/
  main.ts
  app.module.ts
package.json
tsconfig.json
springboot
src/main/java/com/logistics/shipments/
  controller/ShipmentController.java
  service/ShipmentService.java
  repository/ShipmentRepository.java
  dto/CreateShipmentRequest.java
  entity/Shipment.java
  config/
src/main/resources/
  application.yml
  application-prod.yml
pom.xml
ShipmentsApplication.java

Code explanation

node

NestJS's module-based folder structure groups everything related to one feature (shipments.controller, .service, .repository, .module) into a single directory, mirroring how Spring Boot organizes by feature internally even though it's a Java convention rather than a NestJS-specific decorator. shipments.module.ts explicitly declares which providers and controllers belong to this feature and what it exports, giving Node.js the same explicit dependency-boundary declaration Spring achieves implicitly through package scanning. common/ houses cross-cutting concerns (guards, interceptors, filters) shared across modules, avoiding duplication.

springboot

Maven/Gradle enforces src/main/java and src/main/resources as non-negotiable root directories, which is why Spring Boot projects look structurally similar across companies — the build tool itself enforces this convention, unlike Node.js where the src/ layout is purely team-chosen. @SpringBootApplication on the main class triggers component scanning of the entire package tree beneath it by default, meaning ShipmentController, ShipmentService, and ShipmentRepository are auto-discovered and wired without explicit module declarations, the opposite of NestJS's explicit module registration.

Backend integration

node

shipments.module.ts wires the controller, service, and repository together via NestJS's dependency injection container, and this module is then imported into the root app.module.ts, which is what bootstraps the whole application in main.ts via NestFactory.create().

springboot

Spring's classpath component scanning, triggered from ShipmentsApplication's @SpringBootApplication annotation, automatically detects and registers @RestController, @Service, and @Repository-annotated classes anywhere under the base package without any manual module wiring step.

Interview questions

Why does NestJS require explicit module declarations (@Module with controllers/providers/exports) when Spring Boot achieves the same wiring through automatic component scanning?

NestJS modules give explicit, file-level control over what's visible to other modules — a service NOT exported from its module simply cannot be injected elsewhere, which enforces strong encapsulation boundaries between features by design. Spring's component scanning is more implicit and convenience-oriented; anything annotated and on the classpath is discoverable application-wide by default, which is faster to set up but requires more developer discipline to enforce the same encapsulation NestJS gives for free.

Why does Spring Boot enforce src/main/java and src/main/resources as fixed top-level directories while Node.js has no equivalent enforced convention?

Maven and Gradle, the build tools Spring Boot is built on, use a 'standard directory layout' that their build lifecycle phases are hardcoded to expect, so deviating from it requires explicit build-file reconfiguration; Node.js has no comparable build-tool-enforced directory contract — package.json's 'main' field and tooling like tsconfig's 'include' paths are fully configurable, leaving structure entirely up to team convention.

Node.js + Express.js
Java + Spring Boot

Definition

## Quick Bridge | Known Node.js Concept | Spring Boot Equivalent | One-line Difference | Memory Trick | |---|---|---|---| | feature folders with app.js, routers, controllers, services, repositories, and shared middleware | package-by-feature with controller, service, repository, entity, dto, and configuration packages | Express structure is convention by team; Spring Boot structure is discovered and wired by the container. | Express files are imported; Spring beans are scanned. | | import or require modules | constructor-injected beans | Imports load code; injection supplies runtime collaborators. | Import code, inject capability. | | request handler functions | annotated controller methods | Express exposes req/res; Spring binds typed arguments. | Handler becomes method. | | manual app wiring | component scan and auto-configuration | You wire Express; Spring wires beans. | Scan beats setup. | | explicit library choices | framework conventions with override points | Express starts minimal; Spring starts batteries-included. | Minimal vs managed. |
## Quick Bridge | Known Node.js Concept | Spring Boot Equivalent | One-line Difference | Memory Trick | |---|---|---|---| | feature folders with app.js, routers, controllers, services, repositories, and shared middleware | package-by-feature with controller, service, repository, entity, dto, and configuration packages | Express structure is convention by team; Spring Boot structure is discovered and wired by the container. | Express files are imported; Spring beans are scanned. | | process.env driven setup | application.yml plus profiles | Spring layers config by environment and profile. | Env becomes profile property. | | async functions return promises | methods return values, Optional, CompletableFuture, or reactive types | Blocking MVC code is common unless you choose WebFlux. | Do not force async everywhere. | | middleware-owned cross-cutting code | filters, interceptors, AOP, annotations | Spring splits concerns by lifecycle stage. | Pick the right layer. | | plain object results | DTO records and typed responses | Java favors explicit contracts. | Shape data with records. |

How It Works

## Understand - In Express, Project Structure is usually implemented with explicit modules and functions. - You control wiring order, middleware order, error paths, and library boundaries. - Use it when the behavior must stay close to the HTTP request or module boundary. - The biggest mental shift is that Spring often moves the wiring outside your function body. - Keep business rules outside route handlers just as you would in a production Express service. - Prefer typed request/response contracts instead of passing raw request objects downward. - Treat Spring annotations as runtime configuration, not decorative comments. - The Node instinct still applies: keep boundaries small and observable.
## Understand - In Spring Boot, Project Structure is implemented with beans, annotations, configuration, and conventions. - The container discovers components, injects collaborators, and applies framework behavior. - Use it when you want repeatable enterprise structure across teams and services. - The biggest difference from Node.js is inversion of control: Spring calls your code. - Controllers should translate HTTP; services should own business decisions. - Repositories should hide persistence details but not business policy. - Prefer constructor injection, records for DTOs, and narrow transactional methods. - Think in managed lifecycle, not only call stack.

Syntax Keywords

##FlowNode.jsFlowsrc/app.js->routes/index.js->modules/orders/order.routes.js->order.controller.js->order.service.js->order.repository.js
##FlowSpringBootFlowApplication.java->@SpringBootApplicationscan->orders/OrderController->OrderService->OrderRepository->Orderentity

Code Example

## Code Translation

Production-ready Node.js + Express.js code

```js
// src/modules/orders/order.routes.js
import { Router } from "express";
import { requireAuth } from "../../middleware/require-auth.js";
import { OrdersController } from "./order.controller.js";

export function orderRoutes(controller = new OrdersController()) {
  const router = Router();
  router.get("/", requireAuth, controller.list);
  router.post("/", requireAuth, controller.create);
  return router;
}
```

Translation notes

- This is the Node side of orders module boundaries.
- Keep Express handlers thin and push rules into services.
- Use explicit validation, error mapping, and observability around the boundary.
- The Spring version moves framework wiring into annotations and bean configuration.
## Code Translation

Production-ready Java + Spring Boot code

```java
// orders/OrderController.java
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
class OrderController {
  private final OrderService orderService;

  @GetMapping
  List<OrderResponse> list(Authentication auth) {
    return orderService.listFor(auth.getName());
  }

  @PostMapping
  @ResponseStatus(HttpStatus.CREATED)
  OrderResponse create(@Valid @RequestBody CreateOrderRequest request, Authentication auth) {
    return orderService.create(request, auth.getName());
  }
}
```

Translation notes

- This is the Spring Boot equivalent of orders module boundaries.
- Let Spring bind request data, inject collaborators, and manage lifecycle concerns.
- Use Java 21 records/classes where they make contracts clearer.
- Keep the same production boundary you would keep in Express: controller, service, repository.

Code Explanation

## Production Usage Real Project Scenario - Banking System: an Express service uses feature folders with app.js, routers, controllers, services, repositories, and shared middleware while keeping routing, service rules, persistence, and monitoring separated. Enterprise Use Case - E-commerce Platform: teams standardize Project Structure conventions so multiple services behave predictably. Best Practice - Keep route handlers small, validate at the edge, and pass typed command objects into services. Common Mistake - Letting req, res, or ORM-specific objects leak through the business layer. Performance Consideration - Measure the hot path before adding abstractions; watch event-loop blocking, connection pools, and payload size.
## Production Usage Real Project Scenario - Banking System: a Spring Boot service implements package-by-feature with controller, service, repository, entity, dto, and configuration packages with clear controller, service, repository, and configuration boundaries. Enterprise Use Case - E-commerce Platform: platform teams use Spring conventions to make Project Structure consistent across services. Best Practice - Use constructor injection, DTO records, explicit transactions, and Actuator visibility. Common Mistake - Treating annotations as magic and forgetting which layer owns the behavior. Performance Consideration - Watch transaction scope, lazy loading, pool sizing, object mapping, and serialized response size.

Backend Connection

## Interview Ready A. Interview Questions - Easy: How do you implement Project Structure in an Express service? - Medium: Where should Project Structure live so route handlers stay thin? - Hard: What failure modes appear when Project Structure is implemented only in middleware? - Senior Engineer: How would you standardize Project Structure across many Node services? B. Follow-up Questions - How would you test this without starting the full server? - What would you log and what would you avoid logging? - How would you make the behavior safe during a rolling deploy? - How would you detect regressions in production? C. Scenario-Based Questions - In a Inventory Management System, a release increases latency around Project Structure. How do you isolate the cause? - In a Payment Processing System, how do you prevent duplicate side effects when retries happen? - In an Inventory Management System, how do you preserve consistency under concurrent requests? D. Production Tips - Best Practice: Design the module boundary before writing the handler. - Common Mistake: Mixing HTTP, persistence, and business decisions in one function. - Performance Tip: Track pool, queue, and request latency separately. - Code Review Tip: Look for hidden shared mutable state. - Interview Tip: Explain the Node implementation first, then map each responsibility to Spring.
## Interview Ready A. Interview Questions - Easy: What is the Spring Boot equivalent for Project Structure? - Medium: Which Spring layer should own this behavior and why? - Hard: How do proxies, filters, transactions, or validation affect this feature? - Senior Engineer: How would you design this for a multi-team Spring Boot platform? B. Follow-up Questions - What does Spring manage for you that Express does not? - Where can annotation-driven behavior surprise developers? - How would you test this with a slice test versus full integration test? - What production metric proves this is healthy? C. Scenario-Based Questions - In a Inventory Management System, a Spring Boot service has inconsistent behavior across endpoints. How do you audit Project Structure? - In a Logistics Platform, a transaction succeeds but an external notification fails. What changes? - In a Healthcare Platform, how do you keep auditability without leaking sensitive data? D. Production Tips - Best Practice: Keep transactional and security boundaries explicit. - Common Mistake: Putting business rules in controllers because the annotations feel powerful. - Performance Tip: Know when framework defaults affect database and thread usage. - Code Review Tip: Verify DTOs, validation, and exception mapping together. - Interview Tip: Say what Spring owns, what your code owns, and where the boundary sits.