I was writing the backend for my project. I have noticed that it was getting really messy and was repeating a lot of code over and over again. I decided to re-write everything and use classes. I also took the decision to use ES6 modules for imports.
The first bump I have ran into is that I can't seem to get my routes working.
import express from 'express';
export const router = express.Router();
router.post('/test', async (req, res) => {
'test'
});
Importing it into server.js
import { router } from './routes/user.route'
server.use(router)
The error I get
Cannot find module
ES modules require literal specifiers (there is no magic resolution for extensionless names like in CJS).
In your original import statement, the specifier points to a module that doesn't exist (there is no file named user.route βΒ its name is user.route.js):
import { router } from './routes/user.route'
Instead, you must provide the full path (including the extension):
import { router } from './routes/user.route.js'
See more at Mandatory file extensions - Modules: ECMAScript modules | Node.js Documentation
Related
This question is not whether Node.js supports the ES Module system.
That question has been answered previously, eg:
How can I use an ES6 import in Node.js?
Node.js - SyntaxError: Unexpected token import
And these answers are supported by the official Node.js docs:
ECMAScript modules are the official standard format to package JavaScript code for reuse...Node.js fully supports ECMAScript modules as they are currently specified and provides interoperability between them and its original module format, CommonJS. Node.js has two module systems: CommonJS modules and ECMAScript modules. Authors can tell Node.js to use the ECMAScript modules loader via the .mjs file extension, the package.json "type" field, or the --input-type flag.
Source
So it is clear that Node.js supports the ES Module system.
This question is:
When using the ES Module system in Node.js, can all Node packages be imported using the same import syntax?
The reason I ask is that a lot of packages' documentation does not specify how to import them using ES Module style, eg:
https://expressjs.com/en/starter/hello-world.html
https://github.com/expressjs/session
I want to avoid the situation where I am trying to figure out the correct syntax to use each time I want to import a library.
This answer seems to suggest this syntax:
import * as [ whatever_was_previously_used_as_the_variable_name ] from [ whatever_was_previously_between_the_quotes_in_the_parenthesis ]
So, using the examples above, this:
const express = require('express');
const app = express();
const session = require('express-session');
could safely be converted to:
import * as express from 'express';
const app = express();
import * as session from 'express-session';
Is that the 'universal' syntax that should be used to import all Node packages?
(Update: that session import above doesn't work, at app.use(session(sessionConfig)); it caused the error TypeError: session is not a function - I needed to define the import like this: import session from 'express-session'. I also had to import axios like this: import axios from 'axios', or i got a similar error. However, to use msal-node i had to use import * as msal from '#azure/msal-node').
Just to confuse things, I have come across other syntax examples, such as these in the Node docs (which includes node:):
import * as fs from 'node:fs/promises';
// OR:
import * as fs from 'node:fs';
Source
I am hoping a definitive answer can be provided that covers the different scenarios that may exist, eg: Node packages that live in the node_modules directory as well as one's own custom JavaScript modules that live outside of node_modules.
In this way, if I ever come across a package that does not specify installation instructions using the ES Modules style, I will still know the correct way to import it.
The import statement is indeed universal and can be used to import a module of either system, whether inside node_modules or out, or built-in to Node. When importing a CommonJS module this way, the value it would normally expose to require becomes its default export, so import name from 'url';, or any other form of import statement that retrieves the default export, will get you what you need.
The import * as name from 'url'; form you mentioned is for retrieving all of a module's named exports. This creates a module namespace object, never a function, though the function or other value that a CommonJS module exposes to require is available on the default property. There's generally no need to use import * with a CommonJS module, since everything that Node detects as a named export will be properties on the default export anyways.
You can also create a require from inside an ES module; this will work exactly the same way as require in CommonJS, but that includes the limitation of importing only CommonJS and built-in modules, not ES modules.
I have an express server written in typescript with "module": "es2020" in its tsconfig.
I've also developed another es2020 module for my graphql API, still in typescript, and this module uses mongoose with such named imports:
import { Types } from 'mongoose'
Everything works fine when I compile my graphql module with tsc. But the express server which is ran with
nodemon --watch './**/*.ts' --exec 'node --experimental-specifier-resolution=node --loader ts-node/esm' src/index.ts
is unable to handle the mongoose named import.
import { Types } from 'mongoose';
^^^^^
SyntaxError: Named export 'Types' not found. The requested module 'mongoose' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from 'mongoose';
const { Types } = pkg;
Solution #1
import mongoose from 'mongoose'
And replace Types by mongoose.Types.
But since tsc can handle the mongoose named import, I have hope that it's also possible to make ts-node able to do so.
Solution #2
Switch to commonjs, I can keep the import/export syntax in my graphql module and compile it as a cjs module. But I would have to use a cjs syntax in my express server, and I don't want to.
TL;DR
All you have to do is remove the curly braces from the Types. That should work. Just like this:
import Types from 'mongoose'
But the name does not matter.
Explanation
import Types from 'mongoose' works because we are importing the package's default export (this is why the name we use does not matter).
However, when you do import * as Types from 'mongoose you tell JS that you seriously want everything as it is raw. That means that instead of getting the default export:
{
"function1": {},
"function2": {}
}
You get this:
{
"default": {
"function1": {},
"function2": {}
}
}
So you could have also done Types.default but that's probably not as clean. π
This StackOverflow post suggests that we could make both work, but also suggests it would be a hacky workaround that probably should not work π
I'm using Node 16.3.0 and Express 4.17.1 (though my Node version is flexible)
I have a session.js file that's like this:
// session.js
exports.fetchUserId = async function(token){
...
}
exports.save = async function(userId){
...
}
Then over in my app.js file, I try to do this:
// app.js
import session from './session.js'
I get the following error:
import session from './session.js'
^^^^^^^
SyntaxError: The requested module './session.js' does not provide an export named 'default'
...and then I want to use session in app.js like this:
let userId = await session.fetchUserId(req.body.token)
I have tried the following without any luck:
Adding "type": "module" to package.json
This answer by doing npm i esm
Using nodemon --experimental-modules app.js
My code works fine when used in a NuxtJS project, but I'm trying a vanilla Node/Express app and the named exports don't work.
Any idea what I'm doing wrong?
Problem in your code is that you are mixing the ES modules with CommonJS modules.
By default, node treats each javascript file as a CommonJS module but with the following in the package.json file
"type": "module"
you are telling node to treat each javascript file as a Ecmascript module but in session.js file, you are using the CommonJS module syntax to export the functions.
Solutions
You could solve your problem using one of the following options:
Change the extension of the session.js file to .cjs. (Don't forget to change the extension in the import statement in app.js file as well).
Changing the extension to .cjs will tell node to treat session.cjs file as a CommonJS module.
Just use ES modules and change the exports in session.js file as shown below:
export const fetchUserId = async function(token){ ... }
export const save = async function(userId) { ... }
and change the import statement in app.js file as:
import * as session from "./session.js";
Is there a way to
write my code using ES6 modules in Express app;
without reverting to babel or #std/esm ?
once I am committed to app.js of Express, I can't find a way to get out of it.
This seems like something that should be already on the web, but all I can find is above options (transpiling, esm).
With node.js, you HAVE to tell it that your main file you are loading is an ESM module. There are a couple ways to do that. The simplest is to just give the main file a .mjs file extension.
// app.mjs
import express from 'express';
const app = express();
app.get("/", (req, res) => {
res.send("hello");
});
app.listen(80);
Then, start your program with:
node app.mjs
This works - I just ran it with node v14.4.0:. The others ways to do it are discussed in the link I previously gave you here. Per that documentation, there are three ways to specify you are loading an ESM module as the top-level module file:
Files ending in .mjs.
Files ending in .js when the nearest parent package.json file contains a top-level field "type" with a value of "module".
Strings passed in as an argument to --eval, or piped to node via STDIN, with the flag --input-type=module.
When using es6 import feature in Node.js there is a problem with modules written with commonjs module format. I use babel-register to run the code.
For example I want to use named exports from express module and I write:
import * as express from "express";
let app = express();
I also use TypeScript type definitions with typings and they work this way. But this code fails to run saying that express() is not a function.
When I write as if I use default export like:
import express from "express";
let app = express();
It works, but IntelliSense is not available.
What is the problem with this?