Tao of Node
2026-02-09
Edited: 2026-02-09
Introduction
Taken from here. I find this to be a great resource and agree with many of its recommendations.
Architecture
Services, Routes, Handlers
Break logic up for the backend. This is mainly for routes. Group the logic into the domain they operate one. So you would have for example a users/ directory and an organizations/ directory containing user logic and organization logic. In each folder you would have at the minimum
handler.js: these handle passing the payload to serviceservice.js: business logic, can be used in other modules, like users-service can be used in organization-serviceroutes.js: these take in routes and are generally controlled by something like express
This is different from the usual separation of all routes in a routes/ directory and all services in a services/ directory.
Here are some examples of a possible setup
// user-service.js
// snip..
export async function registerUser(userid, name, password) {
// Do database queries or modifications ...
}
// snip..
// user-hander.js
// snip...
export async function registerUser(req, res) {
const { error, value } = schema.validatereq.body;
service.registerUservalue.name, value.password;
}
// snip..
// user-routes.js
// snip...
router.post'/user', hander.registerUser;
// snip...Pretty clean.
Data
You can use a repository to fetch data and then use a DAO to map repository data into your data. I find this part optional, since if its data you control or the data exactly matches your requirements, having a DAO is just an unnecessary layer of abstraction. However, having something like a repository is useful, for both fetching and modifying the data, though it can be fulfilled by something like a Mongoose Schema Model.
Utility
You can put utility functions in the same directory as well. But separate it from global utilities which should be somewhere near the root of the project.
Validation
Validation is great and replaces a lot of the if statement checks, but most importantly, it serves as schema and documentation for the endpoints. When combine with something that auto-generates docs like OpenAPI it is awesome.
You can validate in an middleware or directly in handlers. I like it in middleware so you have centralized logic.
Logging
I like using a middleware which logs requests body, id, params, before a request gets executed. So using something like pino, the flow would be
- log request body params
- route handler handles routes (log within service functions)
- error handler (log and handle error)
Error Handling
Use a centralized error handler. This can be done in a middleware which runs after the routes middleware. In this module
- if there is no error then send a 404 since that mean routes were not able to find the resource
- if there is a error, log it and handle it accordingly, also send response notifying user of error
And in the handlers, use next(...) to forward the error down instead of handling it on a handler by handler basis.
Other Stuff
- prefix your api, for example like
v1/users/. - use dependency injection
- send logs to a centralized location elsewhere
- stick with node.js (for now)
- use fastify (express is fine though) since it offers more features and is faster