What belongs in a module and what to export
module cohesion, export discipline, public vs private interface, encapsulation in modules, barrel exports
Module Design: Public Interface vs Private Implementation
A module is a file (or folder) that bundles related code and exposes only what external code needs. The discipline of deciding what to export is just as important as what to write inside.
Every function and class in a module should be either public (exported) or private (not exported). Private means implementation detail โ if it changes, nothing outside breaks.
// user.service.js
// Private โ only used internally
function hashPassword(plain) {
return bcrypt.hash(plain, 10);
}
function generateToken(userId) {
return jwt.sign({ id: userId }, process.env.SECRET);
}
// Public โ intentionally exposed
export async function registerUser(email, password) {
const hashed = await hashPassword(password);
const user = await db.users.create({ email, password: hashed });
return generateToken(user.id);
}The rule: export the minimum necessary. If something doesn't need to be public, don't export it. Every export is a contract you have to maintain โ unexported functions can be freely refactored without touching callers.
Barrel files (index.js) are useful for re-exporting a module's public API from a clean entry point:
// users/index.js โ the public API of the users module
export { registerUser, loginUser, deleteUser } from './user.service.js';
export { UserSchema } from './user.model.js';External code imports from users/ and never reaches into users/user.service.js directly. This lets you restructure internals without breaking consumers.
When reviewing code, flag any export that's only used in one file โ it shouldn't be exported at all.
