Different Node.js Versions and Package Managers Per Project – A Solved Problem
I explain my setup to make it easy dealing with different Node.js version and package managers per project.
You work on different projects, maybe in different teams, or just on older and newer personal projects. Chances are you are using different Node.js versions and different package managers or package manager versions.
You should be able to switch between projects without fiddling around with Node.js and your package manager. This post shows you my setup that has solved those issues for me. I am using MacOS, so that is what I will be writing about but the solutions should be applicable for other operating systems as well.
Dealing With Different Node Versions
The most popular solution for using different Node.js versions on your system is probably nvm (Node Version Manager). It lets you easily install new Node.js versions (e.g.
nvm install 20,
nvm install --lts etc.), list all available versions (
nvm ls-remote), list your installed versions (
nvm ls), switch to specific versions (
nvm use 20), set a default version (
nvm alias default 20) or switch to the Node.js versions specified by the
.nvmrc file in the current project (
I am using a (mostly) NVM-compatible alternative to NVM though: fnm (Fast Node Manager). Why? First of all, it is faster (of course it is written in Rust 😝). But it also seems to be simpler (just compare the length of the readmes). Also, one of the main functionalities that lets you forget about dealing with Node.js versions in your day-to-day work: auto-switching NodeJS versions based on
.node-version is easier to set up. You just have to add
eval "$(fnm env --use-on-cd)" to your
.zshrc (other shells are also supported). With that, you automatically switch Node.js versions if you switch to a different project that has the desired version specified. You should always have a
.node-version file in your Node.js projects.
There are some other differences that are less relevant to me, like
fnm supporting fish shell which
brew install fnm.
I have set up an alias for
nvm. With that, I can execute all of the mentioned
nvm commands via
fnm. That is also what I do in practice. As mentioned,
fnm tries to be mostly compatible to
nvm and for all of the basic commands
fnm is just a (better) drop-in replacement for
What About a System-Level Node?
I have still installed
node via Homebrew because some other formulas or casks may depend on this. If I try to uninstall it via
brew uninstall node I get the error
Error: Refusing to uninstall /opt/homebrew/Cellar/node/21.5.0 because it is required by [...] . That is fine, I just leave it there but otherwise don't use it.
The system-level Node.js version is also listed in
nvm ls as
system, but that is not my default version, so it never gets for commands I run in the terminal.
Dealing With Different Package Managers
I have projects that are using
pnpm and with different versions of each of those. How to deal with that? We don't want to install them globally via
npm install -g pnpm. Because that would mean that the Node.js (or rather
npm in this case) version that was activated while running the global installation now has a single version of
pnpm installed – all projects using the same Node.js version now also have the same
pnpm version. That is not what we want.
But what is the solution? Corepack! Corepack is a tool that comes with Node.js since version 16.9.0 (released on Sep 07, 2021). It has also been backported to version 14.19.0 (released on Feb 01, 2022).
Corepack is an experimental tool to help with managing versions of your package managers. It exposes binary proxies for each supported package manager that, when called, will identify whatever package manager is configured for the current project, transparently install it if needed, and finally run it without requiring explicit user interactions.
— Node.js docs
With corepack you don't have to explicitly install package managers at all. Specify the package manager and its version using the
packageManager property in
package.json like this:
"packageManager": "email@example.com" . Now you can run
pnpm commands without installing it explicitly – corepack will do that for you. This also prevents you and others from using the wrong package manager. If you try to run
yarn in a project that has set
pnpm you will see an error:
Usage Error: This project is configured to use pnpm. Changing the version in
packageManager leads to installing the new version when executing the package manager the next time (without you noticing, it just takes a little longer the first time).
You can also explicitly switch the package with
corepack prepare firstname.lastname@example.org --activate. This is only useful if you can't (or don't want to?) specify the
packageManager will take precedence over explicitly activating a different version.
You have to enable corepack via
corepack enable. This has to be done once for each Node.JS version that you are using. With fnm you can add the
--corepack-enabled flag (or setting the
FNM_COREPACK_ENABLED environment variable – I have set this in
.zshrc) installing a new Node.JS version to activate corepack right after the Node.js installation.
Dealing with different Node.js versions and different package managers and its versions is a solved problem. These measurements make it a breeze:
fnmand enable its auto-switching capabilities
always specify projects' Node.JS versions either via
FNM_COREPACK_ENABLED=1globally to automatically activate corepack for each new Node.js version
With that in place, you never have to explicitly install Node.js or package manager versions. They are configurations in your projects and get installed automatically.