
Using require in Node.js is a fundamental part of working with modules. It allows you to load and use external libraries or your own modules. The most simpler scenario for using require is when you are working in a CommonJS environment, which is the default module system in Node.js.
When you want to include a module, you can simply call require with the path to the module. For instance, if you have a local module named myModule.js, you would include it like this:
const myModule = require('./myModule');
This method is synchronous and will block the execution of your code until the module is loaded. That is typically not an issue for most applications, since module loading is often done at the start of the application. However, if you’re dealing with a large number of modules or have performance considerations, you might want to be mindful of how require is used.
Another common use case for require is when you’re working with third-party libraries installed via npm. For example, to use the popular express framework, you’d do the following:
const express = require('express');
const app = express();
In this case, require allows you to easily incorporate external functionality into your application. It is important to note that require can also be used to load JSON files, which can be particularly useful for configuration settings or static data:
const config = require('./config.json');
When using require, be aware of the caching behavior in Node.js. Once a module is loaded, it is cached, meaning subsequent calls to require with the same module will return the cached version. This can lead to unexpected behavior if you modify the module’s exports after it has been loaded. To force a module to reload, you would need to delete it from the cache:
delete require.cache[require.resolve('./myModule')];
const myModule = require('./myModule');
In general, you should use require when you need to work within a CommonJS module. That is especially true for Node.js environments that do not support ES modules or when you’re dealing with legacy codebases. Understanding the trade-offs between synchronous loading and the simplicity of require can help you make informed decisions about your coding practices.
Apple iPad 11-inch: A16 chip, 11-inch Model, Liquid Retina Display, 128GB, Wi-Fi 6, 12MP Front/12MP Back Camera, Touch ID, All-Day Battery Life — Blue
$284.99 (as of June 2, 2026 22:39 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)Understanding import and ES modules in Node.js
ES modules, on the other hand, introduce a standardized syntax for importing and exporting code across different JavaScript environments, including browsers and Node.js. The syntax revolves around the import and export keywords, which offer more flexibility and static analysis capabilities compared to CommonJS.
To use an ES module in Node.js, your file must be treated as an ES module. This can be achieved in one of two ways: by setting "type": "module" in your package.json, or by using the .mjs file extension. Once the environment recognizes the module type, you can use import like so:
import express from 'express'; const app = express();
Note that ES modules support both named and default exports. For example, assuming a module utils.js has a named export and a default export:
// utils.js
export function calculateSum(a, b) {
return a + b;
}
export default function greeting(name) {
return Hello, ${name};
}
You can import these exports selectively:
import greeting, { calculateSum } from './utils.js';
console.log(greeting('Scott')); // Hello, Scott
console.log(calculateSum(4, 5)); // 9
Unlike require, import is asynchronous in nature at runtime but statically analyzed at compile time. This difference allows tools like bundlers and linters to optimize and verify your code more effectively. However, ES modules must be loaded using URLs or explicit paths, so importing without a file extension or without specifying the extension can cause errors:
// Correct import myModule from './myModule.js'; // Incorrect - missing extension will fail in ES modules import myModule from './myModule';
Dynamic imports are supported too, allowing modules to be loaded on demand instead of upfront, which can improve performance and code organization.
async function loadModule() {
const { default: myModule } = await import('./myModule.js');
myModule.doSomething();
}
One notable difference between ES modules and CommonJS is how exports are handled. In CommonJS, exports is an object you mutate, whereas in ES modules, exports are immutable bindings, making circular dependencies and live bindings more predictable.
Also, top-level await is supported in ES modules, enabling asynchronous initialization without wrapping code in functions:
const data = await fetchDataFromAPI(); console.log(data);
This capability is unavailable in CommonJS, where any asynchronous setup must be wrapped inside async functions explicitly.
When importing JSON files as ES modules, Node.js requires explicit import assertions:
import config from './config.json' assert { type: 'json' };
console.log(config);
This syntax is necessary because JSON imports are not natively supported as modules without the assertion, helping distinguish data from code. Keep in mind this feature requires recent Node.js versions to function correctly.
Best practices for mixing require and import in your codebase
When mixing require and import in your codebase, it is crucial to maintain clarity and consistency. The interoperability between CommonJS and ES modules can lead to confusion if not handled properly. One best practice is to clearly separate the files that use require from those that use import. This can help prevent circular dependencies and unexpected behaviors that arise from the differences in how these systems operate.
For example, if you have a project structure that includes both types of modules, consider organizing them into directories:
/src
/commonjs
- moduleA.js
/esm
- moduleB.js
In this structure, moduleA.js can use require, while moduleB.js can use import. This separation makes it easier for developers to know what to expect when navigating the codebase.
Another practice is to consistently use one module system for the majority of your application. If you’re starting a new project, lean towards using ES modules, as they are the future of JavaScript and offer enhanced features. If you are maintaining a legacy codebase that predominantly uses require, consider gradually converting modules to ES modules when feasible, instead of mixing them indiscriminately.
When you do need to mix require and import, the approach is to import the CommonJS modules using import syntax. For example:
import myCommonJSModule from './commonjs/moduleA.js';
Keep in mind that when importing a CommonJS module, the entire module is treated as the default export. This means you won’t have access to named exports directly, as you would with ES modules. To access specific functionalities, ensure that your CommonJS module is exporting what you need:
// moduleA.js
module.exports = {
myFunction: () => console.log('Function from CommonJS'),
};
Then, in your ES module:
import myCommonJSModule from './commonjs/moduleA.js'; myCommonJSModule.myFunction(); // Function from CommonJS
Additionally, if you find yourself needing to convert a CommonJS module to an ES module, you can wrap the require statement in a dynamic import. This allows you to keep using the module while transitioning your codebase:
async function loadModule() {
const myCommonJSModule = await import('./commonjs/moduleA.js');
myCommonJSModule.myFunction();
}
When dealing with mixed module types, be cautious about the implications of caching. Since CommonJS modules are cached after the first load, changes made to the exported values may not reflect in subsequent imports. In contrast, ES modules provide live bindings, which can lead to different behaviors if you are not aware of the distinctions.
Lastly, always ensure that your build tools and transpilers are configured correctly to handle both module types, especially if you’re using Babel or Webpack. Proper configuration will prevent issues related to module resolution and ensure that your application runs smoothly across different environments.
