One of the advantages of using Nix is that it allows us to create customized and completely isolated environments for development on specific platforms. The advantage of this is that it saves you from having to install everything you need for development on your system. This can be as extensive as you want.
In this case, I’m going to show you how to create a development environment for learning Nest.js that could even be improved for deploying applications in production.
To do this, we start by creating a flake.nix
file in the project directory with a basic configuration. We can do this manually or use the command nix flake init
. If you use the command, you will get a flake that looks like this:
{
description = "A very basic flake";
outputs = { self, nixpkgs }: {
packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
packages.x86_64-linux.default = self.packages.x86_64-linux.hello;
};
}
Let’s get started!
Flake estructure
description
Sets a description for the flake, serving as documentation to briefly explain its specific use.
outputs
It is a function that defines the output the flake will produce. This output can receive certain parameters. The most common is nixpkgs
, which represents a reference to the available Nix packages.
{
description = "Nest.js dev environment";
outputs = { }: { }
}
Outputs definition
We’ll need to return a specific shell with a set of dependencies that allow us to develop in Node.js and Nest.js. Additionally, we’ll need some extra tools such as nest-cli
or httpie
for making HTTP requests from the console.
Output structure
We will define that shell as follows:
{
description = "Nest.js dev environment";
outputs = { nixpkgs }:
let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
in
{
devShell.x86_64-linux = pkgs.mkShell {
name = "dev-env";
buildInputs = with pkgs; [ ];
shellHook = '''';
};
};
}
The let ... in
expression allows us to define or assign local variables. In this case, we define pkgs
as the available packages, in this case for the Linux x86_64 platform.
Dev shell (devShell.x86_64-linux):
mkShell
is used to create a development shell environment.
name
defines the name of the shell.builtInputs
defines the packages that will be available in the shell.shellHook
is executed every time the shell is run. This property allows us to make previous configurations or show necessary information for the user.
{
description = "Nest.js dev environment";
outputs = { self, nixpkgs }:
let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
yarnWithNode20 = pkgs.yarn.overrideAttrs (oldAttrs: rec {
buildInputs = with pkgs; [
nodejs_20
];
});
in
{
devShell.x86_64-linux = pkgs.mkShell {
name = "dev-env";
buildInputs = with pkgs; [
lolcat
tmux
yarnWithNode20
nodejs_20
httpie
nest-cli
];
shellHook = '' '';
};
};
}
In our case, we have defined the following dependencies for the shell:
- lolcat for highlighting texts in the console.
- tmux as a terminal multiplexer.
- Yarn as a package manager. Notice how a specific variable called
yarnWithNode20
has been created, which preconfigures yarn to be used with this version of Node. - The specific version of Node.js we want to use.
- HTTPie for making http requests from the console.
- and finally, the CLI for NestJS.
Improvements
We’re going to include the use of a basic configuration for tmux and a welcome message for when we start the shell. We will do all this using the shellHook
property:
{
description = "Nest.js dev environment";
outputs = { self, nixpkgs }:
let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
welcomeMessage = pkgs.writeShellScript "welcome_message" ''
#!/usr/bin/env bash
clear
echo "Welcome to the $(node --version) environment!" | lolcat
echo "Nest CLI version: $(nest --version)" | lolcat
echo "****************************************"
echo -e "https://nodejs.org/docs/latest-v20.x/api/"
echo -e "https://nixos.wiki/wiki/Node.js"
echo -e "https://nixos.wiki/wiki/Development_environment_with_nix-shell#direnv"
echo "*********************************************************************"
'';
tmuxSessionName = "dev-env";
yarnWithNode20 = pkgs.yarn.overrideAttrs (oldAttrs: rec {
buildInputs = with pkgs; [
nodejs_20
];
});
in
{
devShell.x86_64-linux = pkgs.mkShell {
name = "dev-env";
buildInputs = with pkgs; [
lolcat
tmux
yarnWithNode20
nodejs_20
httpie
nest-cli
];
shellHook = ''
# Start tmux seesion if not already in one
if [ -z "$TMUX" ]; then
tmux new-session -d -s ${tmuxSessionName}
tmux rename-window "code"
tmux send-keys "bash ${welcomeMessage}" C-m
tmux new-window -n "server"
tmux select-window -t dev-env:code
tmux attach-session -d -t ${tmuxSessionName}
else
bash ${welcomeMessage}
fi
'';
};
};
}
The welcomeMessage
variable defines the message that will be displayed when entering the shell.
In the tmuxSessionName
variable, we define the name that will be used for the tmux session. This prevents us from having to change it in several places at once if we decide to rename it.
In the shellHook
, we define everything that will be executed when starting the shell. In this case, a tmux session is started with two windows, the first named code
for development, and the second server
for starting the project, executing console commands, etc.
Testing the Shell
Run the command nix develop
.
This flake can serve as a guide to create your own development environments without limits. I hope you have found it useful.
Links of Interest