Split a Node application into explicit modules
A module exposes a small public interface while keeping helper variables private to its file. For an exam backend, score calculations should be reusable without also starting an HTTP server. Node supports CommonJS and ECMAScript modules. Pick a format deliberately instead of changing import statements at random when an error appears.
CommonJS: .cjs files
// score.cjs
function percentage(correct, total) {
if (!Number.isInteger(total) || total <= 0) {
throw new RangeError('total must be a positive integer');
}
return (correct / total) * 100;
}
module.exports = { percentage };
// main.cjs
const { percentage } = require('./score.cjs');
console.log(percentage(18, 20)); // 90
Save these as two separate files in one folder and run node main.cjs. require() loads the exported value. Assign the public interface to module.exports; assigning a new object to the variable exports alone does not replace module.exports.
ES modules: .mjs files
// score.mjs
export function percentage(correct, total) {
if (!Number.isInteger(total) || total <= 0) {
throw new RangeError('total must be a positive integer');
}
return (correct / total) * 100;
}
// main.mjs
import { percentage } from './score.mjs';
console.log(percentage(18, 20)); // 90
Run node main.mjs. The braces match a named export. A default export instead uses import anyLocalName from './file.mjs'. Include file extensions on relative ESM imports. Package imports such as import express from 'express' are resolved using package metadata.
What package.json changes
For .js files, declare "type": "module" when the package uses ESM, or "type": "commonjs" for CommonJS. The explicit .mjs and .cjs extensions identify the format independently. Avoid relying on runtime inference. require, module.exports and __dirname are not ordinary ESM globals; use import/export and URL-based file locations where appropriate.
Troubleshooting by cause
“Cannot use import statement outside a module” suggests a format mismatch. “Module not found” often means a wrong path, missing extension or missing dependency. A named-export error can mean that the export name and import name differ. Check spelling and case; a path that works on a case-insensitive computer can fail on a Linux server. Avoid circular imports by moving shared constants or helpers into a third independent file.
Practice: isolate the scoring rules
Add a grade(percentage) function to score.mjs. Import it into main.mjs and print both values. Then deliberately request a missing export and explain the resulting error. Finally add validation that correct is an integer between zero and total. Importing a scoring module should not start a server or read unrelated configuration.