What would be a good project structure for a Node.js application?
Small applications don't matter that much, but for larger apps with more complex logic, it's worth thinking about the project structure. A person diving fresh into the project should get quickly on their feet. Or, it can be you 6 months from now trying to wrap your head around what's going on in the codebase. Separating concerns is a great way to enforce structure, and it also provides useful qualities when thinking about testing.
One way to organize a project is to use the three-layer architecture.
The project is structured into three layers: API, Service and Integration layers.
Each layer has a specific set of responsibilities that are clearly defined and easy to grasp. Each layer accesses the layer below it, never above it. Serving a request touches each layer starting from the top, traveling all the way down and then resurfacing back to the topmost layer.
Responsibilities are receiving the HTTP request and parsing the payload from it. This layer would then forward the payload removed of any HTTP-specific items to the following Service layer. Input validation is performed at this level as well as translating result from the Service layer to HTTP status code and JSON response.
Express.js lives only on this level. You'd have your main app.js that sets up the server and individual route files that define validations and call service layer leaving all req and res objects behind.
Responsibility is to perform business logic. This layer is agnostic of any HTTP specific constructs or JSON data. Input arguments are vanilla objects. Services in this layer are not tied to HTTP in any way. The driver for the services could also be a command-line application or a test suite.
Services perform business logic i.e., make things happen. They can call other services in the Service layer and clients in the Integration layer to talk to outside systems.
This layer is responsible for communicating outside the process boundaries. It talks to databases and makes HTTP requests to 3rd party web APIs.
Most of the contents in this layer would be higher abstraction clients. For example, a client handling the communication to a web API would accept vanilla objects as arguments and would hide the complexity of formulating an HTTP request payload and making the call.
This division achieves separation of concerns. Each layer has a specific responsibility, and it can be translated well into project directory structure.
You could translate this architecture into project structure by having a separate directory for each layer.