Are the (module) types wrong?

Rate this content
Bookmark
GithubProject website

Have you ever installed a dependency, imported it just like the README instructs, and immediately been met with strange TypeScript errors about default imports, the output module format, or ESM/CommonJS interoperability? When everything works at runtime but TypeScript complains, you might wonder, is this a TypeScript bug? Or… are the types wrong?


The JavaScript module and package publishing landscape is complicated. Nearly one third of the most popular types-included npm packages have some sort of module-related typings issue that can cause confusion for users. But things are starting to improve. In this talk, we’ll:

- explore how we got here—why are things complicated?
- develop a simple and accurate mental model for how TypeScript sees packages
- see how the `arethetypeswrong` tool can help catch issues before publishing
- look at trends in the `arethetypeswrong` data for popular packages over time
- discuss next steps for making the typed package ecosystem better

30 min
21 Sep, 2023

Video Summary and Transcription

This Talk discusses module system complexities and terminology, TypeScript and Node disagreements, and fixing TypeScript errors with the Helmet package. It also explores module resolution and type disagreements, challenges with TypeScript and Node support, and the need for improvements in popular packages.

Available in Español

1. Introduction to Modules and Terminology

Short description:

Hello, everyone. My name is Andrew and I've been on the TypeScript team for about the last four years. Today, we'll be discussing module system complexities and the terminology associated with it, such as ESM and common JS. It's important to be familiar with module resolution options and the differences between Node.16 and Node.Next.

All right. Hello, everyone. And thanks for joining me to talk about modules. My name is Andrew. I've been on the TypeScript team for about the last four years, and we have a lot to talk about today in a short amount of time, so that's enough about me. And I think we all know that module system complexities are super interesting and super fun, so no time to sell you on it, but if you watched Mark Erickson's talk and couldn't get enough, this is definitely the talk for you.

So before we get started, there is a little bit of terminology we should cover. I'm going to use the term ESM or the acronym ESM about a hundred times in this talk. That stands for ECMAScript module. ESM is the module system that was added to the JavaScript language specification itself in 2015. And you can recognize it by the import and export keywords. The other module system we'll discuss is common JS or CJS. That's the community standard that was popularized by Node before ESM existed. If you've ever seen require and module.exports, that's common JS and it's still supported by Node.

I'm also going to have to say a few file extensions during this talk. And since those can overlap with other acronyms, I'll try really hard to remember to pronounce them with a dot at the beginning. It will be somewhat helpful if you're at least vaguely familiar with some module resolution options and typescripts. Node.16 and Node.Next are currently identical, so I might call them by either name. They're the only good choice for modern Node, and they don't just mean emitting ES modules. That's a common misconception. Historically, the one called Node was what everyone used for pretty much everything, but it really hasn't kept up with modern features.

2. Understanding the Typescript Error with Helmet

Short description:

We're going to take a look at a confusing typescript error with the NPM dependency Helmet. When compiling down to common JS, everything runs fine. However, when switching to ES modules, an error occurs where helmet isn't callable anymore. Let's investigate further by running the code.

We're going to take a look at a bit of a confusing typescript error here. I'm looking at the NPM dependency called Helmet. I've just copied and pasted the README example here. This builds with TSC and runs in Node just fine. You can see that I'm currently compiling down to common JS. If I want to move to ES modules here, I can do that by adding the type module field to my own package JSON for the project. But when I do that and come back here, I'm now getting this confusing typescript error that says helmet isn't callable anymore. If I mess around with this enough, I'll see typescript I'm running. This doesn't look right to me, but let's run it and see what happens.

3. Understanding the TypeScript and Node Disagreement

Short description:

This part discusses the disagreement between TypeScript and Node when importing the Helmet package. It explains how TypeScript and Node determine the file format and the relationships between different file types in JavaScript. It also explores what happens when exporting and importing transpiled code from ESM to CommonJS.

Yeah, so this crashes in node. On the other hand, if we go back to the way it was where typescript is complaining, but we run this, this works just fine. So there seems to be a disagreement here between typescript and node, and we can't satisfy both at the same time. Seeing this, you might reasonably wonder, is this a typescript bug, or are the types wrong?

In order to determine that for ourselves, we need to learn a few pieces of background first. The first one is how typescript and node even know what file we're talking about when we say import helmet or require helmet. It's going to be easier to explain this one, this exports field and package JSON when we're back in the code, so let's leave it behind for a moment. The second is module format detection. I think I mentioned that node supports both ESM and common JS, so it needs a way to determine which file is which format, and it does that purely by file extension. A .mjs file is always going to be ESM, a .cjs file is always going to be common JS, and a .js file, we have to look up at the nearest package JSON and if it has this special type module field, it's going to be ESM, otherwise common JS. All right.

The third thing is the relationships between the different kinds of files that JavaScript deals in. When you run TSC on an input.ts file, you get one output.js file and one output.d.ts file, which is called a declaration file. When you publish code for other people to consume, you usually want to ship them the raw, compiled JavaScript. That way their run time doesn't need another compile step before they run it. But then if that user is also using TypeScript themselves, or just an editor with smart TypeScript-powered language features, you want to give them this declaration file, which contains everything that it needs to know about the JavaScript file, including the types that have been erased from the original TypeScript file. Because these two outputs are produced together at the same time, when the user's compiler or editor sees a declaration file, um, it actually doesn't even need to go and look and make sure that the JS file exists. Because of this relationship, it can simply assume it exists, know everything it needs to know about the structure and the types, and also the file extension. When it sees .d.ts, it knows that there must be a .js file there. And the same thing holds for the format-specific file extensions that we were discussing earlier. A .mts file produces a .mjs file, and we get a .d.mts file alongside that. And then we have an analog for the CommonJS versions here. So it might seem like I'm forcing a relatively simple point here, but you'd be surprised how many problems trace the root cause to breaking this relationship somehow.

The next thing we need to learn is, what happens if we were to try to compile this export default down to CommonJS? Default exports in ESM are just a special form of named import with special syntax attached. So all we're going to do is just attach a named property assignment here on module.exports with the value that we're exporting. And then we'll also define this flag here that just says, hey, I've been transpiled down from ESM to CommonJS. Our declaration file, on the other hand, kind of retains this esm syntax. Okay, almost done here. The final thing that we need to look at is what happens when we try to import that same transpiled export default that we just looked at. So here we have our export default transpiled down to CommonJS again. And if we're importing this in another file that is also being compiled down to CommonJS, then it makes sense that what we should get with a default import there is going to be the value that we default exported.

4. Understanding Default Imports and Exports in Node

Short description:

The default import and default export need to match up. When importing a CommonJS module in Node, the whole module.exports object is returned. To diagnose the error between TypeScript and Node, we need to examine the files they are looking at, starting with Helmet's package.json file.

The default import and default export need to match up. So I should just get hello world. And the transformations that are applied as we compile this from a default import to a require statement ensure that that holds. On the other hand, when we do this in a real ES module in node, node doesn't understand that our exporting module here is pretending to be ESM. And in node, when you default import a CommonJS module, what you'll get every time is just the whole module.exports object.

So over here, you can see I've logged the module.exports and I get an object with a property called default. And that's the same thing I would get in node if I were to do a real default import of this fake default export. I'll get the module.exports property with this default. OK, so we should know enough now to be able to diagnose this error for ourselves. If we want to understand the difference between TypeScript and node, we need to know which files each of them are looking at.

So first, let's look at Helmet's package.json file. OK, we have this exports field again that we briefly mentioned earlier. What we're going to do here is take a look at this dot key. And this matches the path. So since I'm importing Helmet and not like Helmet slash utils, the dot path matches and we'll go into here and look at some conditions. So let's pretend we're resolving as node first. We're resolving an import, not a require. And so what node is going to do is look at each of these conditions from top to bottom and go until it finds one that matches. So import matches because we're resolving an import. So we'll go into this object and see some nested conditions. Types doesn't match. It's not one of the conditions that node sets here. So it's going to skip this and check default. Default always matches everything. So note is going to resolve to index dot MJS. Now if we're resolving as TypeScript, we do the exact same process with one exception. TypeScript always matches on the types condition. So usually you don't need to set the types condition unless you're putting all your declaration files in a completely separate directory away from the JavaScript. But it's here and it matches. So TypeScript resolves to index dot D dot TS.

5. Resolving Format Disagreement and Fixing Types

Short description:

By the way, there is a command line flag for TypeScript for TSC called trace resolution. So that will spell out all of this that's going on. So now that we know what files are in play, let's talk about format for a second. We have a format disagreement. If TypeScripts knew that this file was attempting to type a real ES module, then it would see this export as helmet as default and know that when we default import that it's going to work as we expect. But what TypeScript thinks is that this file is representing a common JS file that is doing exports.default equals helmet. If I'm the library author, it seems like everything's working at runtime already and I just need to change something about how the types are set up in order to fix this problem. The library author for Helmet has already fixed this problem and figured it all out long before I started this talk, which is partly why I'm comfortable kind of picking apart a little bit in a conference. Instead of repeating the work that he did and fixing the types to match the existing JavaScript, it might be more fun and instructive for our purposes to try to break the existing JavaScript to match the existing types. So the way I'll do that is to first start by making extensions match. So we know this index DTS file represents a .js file, so I'll go ahead and delete the M here. And our demo isn't going to touch the require side of things, but I'll go ahead and make that match as well. So index.js that we've just put in here doesn't actually exist yet. So I'll go ahead and make that. And as we said before, we know that this file has to be common.js. So we'll start by just copying the index.cjs content into here and assume it's pretty close and then we'll check it. So we saw that we had that export helmet as default, which we see represented right here as exports.default equals helmet. So this looks good. I think the only problem here is that this line is actually overwriting that.

By the way, there is a command line flag for TypeScript for TSC called trace resolution. So that will spell out all of this that's going on. So you don't have to remember this if you just want to see what TypeScript is resolving to.

So now that we know what files are in play, let's talk about format for a second. We know that node on this in JS file is going to interpret this as ESM because of the file extension. The dot D dot TS file, on the other hand, we know this represents a dot JS file because of the triangle that we looked at earlier TypeScript file types. And that JS file it's format would be determined by the package JSON type module field, but there isn't one here in this package JSON anywhere. So we know that that JavaScript file must be common JS. So here we have a format disagreement.

The node file index dot MJS we know has to be ESM and the type declaration file we know is representing a common JS file. And if we take a look at the types themselves, we're concerned with this export helmet as default part. And this kind of finishes explaining what we were seeing. If TypeScripts knew that this file was attempting to type a real ES module, then it would see this export as helmet as default and know that when we default import that it's going to work as we expect. You know, we have a default export. We default import it and we can use it no problem. But what TypeScript thinks is that this file is representing a common JS file that is doing exports.default equals helmet. And if that's the case, when we default import it in Node, we still are going to need to do that extra .default in order to access that exports.default property since we're just getting the view of module.exports as a whole.

So if I'm the library author, it seems like everything's working at runtime already and I just need to change something about how the types are set up in order to fix this problem. But to let you in on a minor spoiler, the library author for Helmet has already fixed this problem and figured it all out long before I started this talk, which is partly why I'm comfortable kind of picking apart a little bit in a conference. So instead of repeating the work that he did and fixing the types to match the existing JavaScript, it might be more fun and instructive for our purposes to try to break the existing JavaScript to match the existing types. So the way I'll do that is to first start by making extensions match. So we know this index DTS file represents a .js file, so I'll go ahead and delete the M here. And our demo isn't going to touch the require side of things, but I'll go ahead and make that match as well. All right, so index.js that we've just put in here doesn't actually exist yet. So I'll go ahead and make that. And as we said before, we know that this file has to be common.js. So we'll start by just copying the index.cjs content into here and assume it's pretty close and then we'll check it. So we saw that we had that export helmet as default, which we see represented right here as exports.default equals helmet. So this looks good. I think the only problem here is that this line is actually overwriting that.

6. Fixing the TypeScript Error with Helmet

Short description:

We encountered a TypeScript error when using the Helmet package. By making changes to the JavaScript code, we were able to match the types and resolve the error. This pattern of a CommonJS library assigning to exports.default is common, and the types successfully warn users who are importing in Node.esm. To simplify the analysis process, I created a tool called rthetypeswrong that checks module resolution and identifies disagreements between TypeScript and runtime. Helmet 7.0 has resolved the issues by properly typing the index.cjs and index.mjs files.

So let's just delete it. And I think we should have agreement now. So coming back to our input file, recall that we didn't touch the types at all. So we are still expecting to see this TypeScript error, but what we should see is that if TypeScript is giving us an error, Node also is going to crash. So let's build and run that. And yeah, this time, helmet is not a function. And if we instead do what TypeScript is telling us we should be doing, helmet dot default, and then build and run this, this now works.

Okay, so why did we go through this kind of silly exercise in making a probably undesirable change to the JavaScript in order to match the types? Well, sometimes when people bring a problem like this to me and I explain what's going on in the types, they'll say something to the effect of, you know, it seems like TypeScript is just being overly pedantic here. Clearly it could find the types before, and my CommonJS and ESM JavaScript are nearly identical. So if TypeScript can find the file, just, you know, know what I mean and don't make me jump through so many hoops. But what we've shown here is that the types aren't wrong per se. They're not invalid. They just describe a different library than what we had here. They, they described the one that we just made via our changes. And this pattern of, essentially just a CommonJS library assigning to exports.default is actually pretty common. The library author may not have intended to do this, but I've seen it in the wild several times. And when the types for that are correct, it successfully warns users who are importing in Node.esm that they're going to unintuitively, but legitimately need this extra.default in order to access what they think they need to access. So while all of you are now experts on module resolution and interop, it might be a bit much to ask all users or even all library authors to be experts on this stuff.

So I made a tool that can do some of this analysis for you. rthetypeswrong is a tool that downloads an NPM package and then for each module resolution mode supported by TypeScript, it will go through and do essentially the analysis that we just did by hand. It will check what the TypeScript compiler is resolving to and what the runtime is resolving to, analyze each of those files and check for disagreements. So here we were looking at Helmet 6.1.2 and we see that when we are importing from ESM in Node 16 mode, we resolve to CommonJS types but ESM JavaScript. And that is exactly what we saw. And I mentioned that Helmet has already fixed this. So let's take a look at that. In Helmet 7.0, we see no problems and CommonJS is resolving to CommonJS and ESM to ESM. So this looks great. And since we didn't bother to fix the types ourself, we can see how the library author fixed this. Here we see that both the index.cjs and index.mjs files are now typed with their own declaration file with the proper extension. So TypeScript can be aware of all the files that are there and know the formats for everything.

7. Analyzing Module Resolution and Type Disagreements

Short description:

There's a CLI version of the tool now, making it easy to integrate into publishing processes or check local packages. We tested the changes made to Helmet using the tool and fixed the disagreements between types and JavaScript. However, the ESM version is resolving to the Common JS version, which is not the desired outcome. Analyzing npm packages over time shows improvements for users in all groups, except for node ESM users, who face errors in over a quarter of type dependencies. The problems in the ESM list include CommonJS declarations attempting to represent ES JavaScript and untyped resolutions. The latter is a worse problem as TypeScript is unable to find type declarations shipped with the package. These problems can be relatively easy to fix, unlike others that require adjustments to build systems. The issues stem from the rapid adoption of ESM syntax before implementers and TypeScript caught up, leading to incorrect assumptions about the interoperation of the two formats. Workarounds for TypeScript's lack of support also became problems when TypeScript introduced Node-16 and Node-next modes. It's important to note that when types are considered wrong for a package, it's not a judgment on past publications.

There's also a CLI version of this tool now. So this is easy to integrate into your publishing process or check a local package. So just for fun, let's try that on the changes that we made to Helmet. So I'll run attw and then pack, and we'll run that on node-module-slash-helmet. And you can see here that we have fixed the disagreements, no problems found between types and JavaScript, but unlike the earlier version, but unlike in Helmet 7, we can see that the ESM version of things is actually resolving to the Common JS version of the library. So that's not a problem, but it's not the fix that Helmet probably wanted to make, which was to, you know, make ESM resolve to the ESM that's already there.

So the cool thing about doing all of this analysis on npm packages is that it gives us a natural way to look at a lot of data over time. I ran rthetypes wrong on the top 6,000 or so npm packages as they existed on the first of each month since January of last year, broken down by a module resolution algorithm. So we can see that things are kind of improving for users in all groups here, with the slight exception of the recent uptick in node 10, which just comes from packages dropping support for this. On the other hand, things are not looking great for node ESM users, with over a quarter of types dependencies showing some sort of error. So this kind of puts us in a tough spot, because if the community is going to migrate to ESM, and I think that it should, but regardless, it's already happening. It would be really nice if we could encourage users to make that transition first before libraries, but that's a pretty tough sell as a TypeScript user if making the switch is going to break a quarter of your dependencies.

If we break down the problems that we're seeing in that ESM list of problems here, we can see a little more nuance. This green trace is the kind of problem we were just looking at where CommonJS declarations are attempting to represent ES JavaScript. So this does seem to be on the rise in the long term, but on the other hand, it's kind of trading one for one with this orange trace, which is untyped resolutions. This means the package was shipping type declarations, but TypeScript wasn't able to find them at all. And that's a worse problem to have, and the aggregate numbers don't really show that. We don't have time to get into what each of these problems are that are the type strong can detect, but I will point out that the second to most prevalent problems here, unlike some of the others, I think these are relatively easy to fix most of the time. Some of the others require fiddling with build systems, and these are often just a one line fix. They have to do with mixing up export equals and export default in declaration files. So where do all these problems come from in the first place? The gaps in this timeline are the best explanation I can come up with. Writing ESM syntax became incredibly popular really early in 2014 before the ESM final spec was even published in 2015. And this is normal, trying out early language features is part of the TC39 process. But what we saw was a really rapid adoption in writing ESM code in the community with a long lag before implementers were able to catch up. And then another long lag after that before TypeScript could catch up to where Node was. So in all this time, whether they knew it or not, the tools that let you write ESM syntax and output common.js were kind of encoding these implicit assumptions about how those two formats were going to interoperate in real runtimes later on. And not all of those assumptions turned out to be correct. At the same time, between 2019 and 2022, when packages were wanting to ship ESM and type declarations, they kind of had to come up with workarounds for TypeScript's lack of support here. But once TypeScript caught up and shipped the Node-16 and Node-next modes, those workarounds kind of became problems themselves. So it's important to understand when I say the types are wrong for some package, that's not a judgment about what was published in the past.

8. Challenges with TypeScript and Node Support

Short description:

A lot of the problems we see can be traced back to TypeScript's lagging support for Node. I've been focusing on this problem space for about a year, working on getting information out there and improving the Are the Types Wrong tool. Now, I'm looking to make a more direct impact on the existing problems in popular packages.

It's only an observation about what works or doesn't work now. And in fact, a lot of the problems that we see can trace their roots to TypeScript's lagging support for Node. So this problem space has been kind of my main thing for the better part of a year. And up until now, most of what I've been doing has been in service of just getting information out there from Are the types wrong to official TypeScript docs. I think that phase of my work is finally kind of wrapping up and I have some improvements to Are the Types Wrong plan that I'm excited about. But I anticipate kind of shifting into looking for ways to make a more direct impact on some of the existing problems that we see out there in popular packages.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

JSNation Live 2021JSNation Live 2021
31 min
Vite: Rethinking Frontend Tooling
Top Content
Vite is a new build tool that intends to provide a leaner, faster, and more friction-less workflow for building modern web apps. This talk will dive into the project's background, rationale, technical details and design decisions: what problem does it solve, what makes it fast, and how does it fit into the JS tooling landscape.
Remix Conf Europe 2022Remix Conf Europe 2022
23 min
Scaling Up with Remix and Micro Frontends
Top Content
Do you have a large product built by many teams? Are you struggling to release often? Did your frontend turn into a massive unmaintainable monolith? If, like me, you’ve answered yes to any of those questions, this talk is for you! I’ll show you exactly how you can build a micro frontend architecture with Remix to solve those challenges.
React Advanced Conference 2023React Advanced Conference 2023
33 min
React Compiler - Understanding Idiomatic React (React Forget)
Top Content
React provides a contract to developers- uphold certain rules, and React can efficiently and correctly update the UI. In this talk we'll explore these rules in depth, understanding the reasoning behind them and how they unlock new directions such as automatic memoization. 
Remix Conf Europe 2022Remix Conf Europe 2022
37 min
Full Stack Components
Top Content
Remix is a web framework that gives you the simple mental model of a Multi-Page App (MPA) but the power and capabilities of a Single-Page App (SPA). One of the big challenges of SPAs is network management resulting in a great deal of indirection and buggy code. This is especially noticeable in application state which Remix completely eliminates, but it's also an issue in individual components that communicate with a single-purpose backend endpoint (like a combobox search for example).
In this talk, Kent will demonstrate how Remix enables you to build complex UI components that are connected to a backend in the simplest and most powerful way you've ever seen. Leaving you time to chill with your family or whatever else you do for fun.
JSNation Live 2021JSNation Live 2021
29 min
Making JavaScript on WebAssembly Fast
Top Content
JavaScript in the browser runs many times faster than it did two decades ago. And that happened because the browser vendors spent that time working on intensive performance optimizations in their JavaScript engines.Because of this optimization work, JavaScript is now running in many places besides the browser. But there are still some environments where the JS engines can’t apply those optimizations in the right way to make things fast.We’re working to solve this, beginning a whole new wave of JavaScript optimization work. We’re improving JavaScript performance for entirely different environments, where different rules apply. And this is possible because of WebAssembly. In this talk, I'll explain how this all works and what's coming next.

Workshops on related topic

React Advanced Conference 2021React Advanced Conference 2021
174 min
React, TypeScript, and TDD
Top Content
Featured WorkshopFree
ReactJS is wildly popular and thus wildly supported. TypeScript is increasingly popular, and thus increasingly supported.

The two together? Not as much. Given that they both change quickly, it's hard to find accurate learning materials.

React+TypeScript, with JetBrains IDEs? That three-part combination is the topic of this series. We'll show a little about a lot. Meaning, the key steps to getting productive, in the IDE, for React projects using TypeScript. Along the way we'll show test-driven development and emphasize tips-and-tricks in the IDE.
React Advanced Conference 2022React Advanced Conference 2022
148 min
Best Practices and Advanced TypeScript Tips for React Developers
Top Content
Featured Workshop
Are you a React developer trying to get the most benefits from TypeScript? Then this is the workshop for you.In this interactive workshop, we will start at the basics and examine the pros and cons of different ways you can declare React components using TypeScript. After that we will move to more advanced concepts where we will go beyond the strict setting of TypeScript. You will learn when to use types like any, unknown and never. We will explore the use of type predicates, guards and exhaustive checking. You will learn about the built-in mapped types as well as how to create your own new type map utilities. And we will start programming in the TypeScript type system using conditional types and type inferring.
React Day Berlin 2022React Day Berlin 2022
86 min
Using CodeMirror to Build a JavaScript Editor with Linting and AutoComplete
Top Content
WorkshopFree
Using a library might seem easy at first glance, but how do you choose the right library? How do you upgrade an existing one? And how do you wade through the documentation to find what you want?
In this workshop, we’ll discuss all these finer points while going through a general example of building a code editor using CodeMirror in React. All while sharing some of the nuances our team learned about using this library and some problems we encountered.
Node Congress 2024Node Congress 2024
83 min
Deep TypeScript Tips & Tricks
Top Content
Workshop
TypeScript has a powerful type system with all sorts of fancy features for representing wild and wacky JavaScript states. But the syntax to do so isn't always straightforward, and the error messages aren't always precise in telling you what's wrong. Let's dive into how many of TypeScript's more powerful features really work, what kinds of real-world problems they solve, and how to wrestle the type system into submission so you can write truly excellent TypeScript code.
TestJS Summit - January, 2021TestJS Summit - January, 2021
173 min
Testing Web Applications Using Cypress
WorkshopFree
This workshop will teach you the basics of writing useful end-to-end tests using Cypress Test Runner.
We will cover writing tests, covering every application feature, structuring tests, intercepting network requests, and setting up the backend data.
Anyone who knows JavaScript programming language and has NPM installed would be able to follow along.
Node Congress 2023Node Congress 2023
63 min
0 to Auth in an Hour Using NodeJS SDK
WorkshopFree
Passwordless authentication may seem complex, but it is simple to add it to any app using the right tool.
We will enhance a full-stack JS application (Node.JS backend + React frontend) to authenticate users with OAuth (social login) and One Time Passwords (email), including:- User authentication - Managing user interactions, returning session / refresh JWTs- Session management and validation - Storing the session for subsequent client requests, validating / refreshing sessions
At the end of the workshop, we will also touch on another approach to code authentication using frontend Descope Flows (drag-and-drop workflows), while keeping only session validation in the backend. With this, we will also show how easy it is to enable biometrics and other passwordless authentication methods.
Table of contents- A quick intro to core authentication concepts- Coding- Why passwordless matters
Prerequisites- IDE for your choice- Node 18 or higher