<![CDATA[Dave is Coding]]>https://daveiscoding.comRSS for NodeSun, 25 Oct 2026 18:21:36 GMT60<![CDATA[Why do you need an init process inside your Docker container (PID 1)]]>https://daveiscoding.com/why-do-you-need-an-init-process-inside-your-docker-container-pid-1https://daveiscoding.com/why-do-you-need-an-init-process-inside-your-docker-container-pid-1Wed, 07 Sep 2022 19:34:48 GMTWhen you run your application inside a Docker container, it will be assigned process identifier (PID) 1. This particular PID is special in the Unix world. PID 1 is assigned to the very first process that the kernel starts, therefore it takes a special role in the system.

Why is PID 1 so special?

  • The default action is ignored when receiving signals, which means your process will not implicitly terminate on SIGINTor SIGTERM. Usually, operating systems terminate your process when processes receive SIGINTor SIGTERM.

  • Any orphaned process is adopted by PID 1.

Let's discuss these more in-depth, especially in the Docker world.

Signals

Do you know what actually happens behind the scenes when you run docker stop?

The main process (PID 1) inside the container will receive SIGTERM, and after a grace period, SIGKILL signal.

By default, Docker waits 10 seconds after SIGTERM before killing it with SIGKILL.

Did you ever have a container that took a long time to stop? Did it take 10 seconds accurately? That means your application doesn't handle signals explicitly!

Here is a simple command that spins up a Node.js container that runs forever:

docker run \
  -d \
  --rm \
  --name node-app \
  node:alpine \
  node -e "setInterval(() => {}, 1000);"

Now stop the container and measure the time:

time docker stop node-app

On my system, it took 10.644 seconds to stop the container.

real    0m10.644s
user    0m0.030s
sys     0m0.062s

It means the container couldn't be stopped gracefully, it had to be killed.

This is all because your Node.js application runs as PID 1, which doesn't run the default actions of signals, which would be process termination in this case.

Now let's handle the SIGTERM signal in the Node.js application:

docker run \
  -d \
  --rm \
  --name node-app \
  node:alpine \
  node -e "
      const interval = setInterval(() => {}, 1000); 
      process.on('SIGTERM', () => clearInterval(interval));
  "

When SIGTERM is received, the interval will be stopped, so the process will exit because there are no things to do.

Stop the container again:

time docker stop node-app

On my system, it took about half a second this time.

real    0m0.613s
user    0m0.000s
sys     0m0.060s

This means the Node.js application has been gracefully stopped instead of being killed after the 10 seconds timeout.

Orphaned processes

When a process dies, all of its children become an orphan and are adopted by PID 1. Now it's more interesting what happens when that child finishes its execution or dies for whatever reason.

Let's run a command that showcases orphaned processes. The following command will launch an Ubuntu container and runs the sh -c "sleep 10 & exec sleep 1000" command. This command creates a shell and executes sleep 10 & exec sleep 1000. The shell creates sleep 10 process in the background and replaces the shell via exec with sleep 1000. Therefore PID 1, in the beginning, is sh, but sleep 1000 takes its place.

docker run -d --rm --name orph ubuntu sh -c "sleep 10 & exec sleep 1000"

Right after running the command, print out the processes in the container:

docker exec orph ps -eaf

If you run it in 10 seconds, you should see a similar output:

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 16:34 ?        00:00:00 sleep 1000
root         7     1  0 16:34 ?        00:00:00 sleep 10

As you can see, sh doesn't exist because sleep 1000 replaced it.

Now wait until sleep 10 finishes, and print out the processes again:

docker exec orph ps -eaf

This time you should see a different output:

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 16:34 ?        00:00:00 sleep 1000
root         7     1  0 16:34 ?        00:00:00 [sleep] <defunct>

As you can see, sleep 10 finished, and it became defunct. Terminated processes are supposed to be cleaned up by their parent processes. This process's parent terminated, and the current PID 1 process doesn't take care of this process because it doesn't know about it - and sleep doesn't handle child processes. These processes are called zombie processes.

In normal circumstances, you should never see zombie processes in your process list. PID 1 should take care of removing zombie processes from the process table.

Using init

When you boot up a Unix-based operating system, the PID 1 will be an init process. This process takes care of reaping the zombie processes throughout your system's uptime.

Since orphaned processes are always adopted by PID 1 - the init process -, it can take care of those zombie processes easily.

In a Docker container, an init process should also take care of forwarding the signals to your application as well.

Let's rewrite the previous command a little bit:

 docker run -d --rm --name orph ubuntu sh -c "sh -c 'sleep 10 & exec sleep 1' & exec sleep 1000"

This command will produce the following process list:

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 21:42 ?        00:00:00 sleep 1000
root         7     1  0 21:42 ?        00:00:00 sleep 1
root         8     7  0 21:42 ?        00:00:00 sleep 10

sleep 1000 becomes PID 1, sleep 1 is a child of sleep 1000 and sleep 10 is a child of sleep 1.

sleep 1 immediately finishes, then you will see the following process list:

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 21:42 ?        00:00:00 sleep 1000
root         7     1  0 21:42 ?        00:00:00 [sleep] <defunct>
root         8     1  0 21:42 ?        00:00:00 sleep 10

As you can see, sleep 10 was adopted by sleep 1000. Because sleep 1 terminated, which also became a zombie process.

After 10 seconds, when sleep 10 finishes, it becomes a zombie process as well:

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 18:59 ?        00:00:00 sleep 1000
root         7     1  0 18:59 ?        00:00:00 [sleep] <defunct>
root         8     1  0 18:59 ?        00:00:00 [sleep] <defunct>

This time use the --init flag. This flag will boot up Tini, a lightweight init system in the container as PID 1.

 docker run --init -d --rm --name orph ubuntu sh -c "sh -c 'sleep 10 & exec sleep 1' & exec sleep 1000"

You will immediately see that sleep 1 became a zombie process and sleep 10 is adopted by PID 1:

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 21:45 ?        00:00:00 /sbin/docker-init -- sh -c sh -c 'sleep 10 & exec sleep 1' & exec sleep 1000
root         7     1  0 21:45 ?        00:00:00 sleep 1000
root         8     7  0 21:45 ?        00:00:00 [sleep] <defunct>
root         9     1  0 21:45 ?        00:00:00 sleep 10

As you can see, sleep 1 is a zombie process and stays as-is until sleep 1000 finishes. This is because sleep doesn't take care of cleaning up child processes.

After 10 seconds, sleep 10 is terminated and disappears from the process list.

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 21:45 ?        00:00:00 /sbin/docker-init -- sh -c sh -c 'sleep 10 & exec sleep 1' & exec sleep 1000
root         7     1  0 21:45 ?        00:00:00 sleep 1000
root         8     7  0 21:45 ?        00:00:00 [sleep] <defunct>

This proves that tini takes care of reaping zombie processes.

Now to prove that tini forwards signals, let's stop the docker container:

docker stop orph

The container stopped immediately, instead of waiting 10 seconds. Therefore signal forwarding is proven as well.

Why is this important at all?

Zombie processes reserve the process ID until they are properly cleaned up. Your operating system has a finite amount of process IDs. Therefore they can fill up the process table, and the chaos begins!

This isn't an issue if you are not starting new processes from your application. If you don't have child processes, it's impossible to have zombie processes. Although I still recommend using tini because it's available in Docker by default, and it's easy to switch on for both individual Docker commands (--init) and Docker Compose services (init: true).

Example Node.js project

A repository is available at GitHub/david-szabo97/node-docker-init-or-not-to-init, which showcases zombie processes in a single command.

]]>
<![CDATA[Separate Node.js unit, integration, and E2E tests using Jest projects]]>https://daveiscoding.com/separate-nodejs-unit-integration-and-e2e-tests-using-jest-projectshttps://daveiscoding.com/separate-nodejs-unit-integration-and-e2e-tests-using-jest-projectsMon, 22 Aug 2022 17:31:24 GMTThe classic test pyramid is made up of the unit, integration, and end-to-end tests. Unit tests are supposed to run fast after each change in your IDE and give immediate feedback. In contrast, integration and E2E tests are slow and are run on-demand. Sometimes you want to run all your tests, sometimes you want to run your E2E tests and nothing else. Fortunately, Jest provides a feature called projects that solves this problem relatively easily.

Whatever test pyramid you follow, you will always have faster-slower tests. So let's figure out how to separate them using Jest projects.

Initialize the project

Create an empty directory and initialize your Node.js project:

mkdir jest-projects
cd jest-projects
npm init -y

Of course, you will need Jest to run your tests, so install it as a development dependency:

npm install -D jest

By default, Jest treats any JS file in the __test__ directory as a test file, and Jest will try to run it. First, create the directory and create a test.js file.

mkdir __tests__
touch __tests__/test.js

Add a simple test

__tests__/test.js

it("test works", () => {
  expect(1 + 1).toBe(2);
});

Now ask Jest to run the test:

npx jest

You should see a successful test run.

 PASS  __tests__/test.js
  √ test works (2 ms)

Test Suites: 1 passed, 1 total                                                                                                                                                                                                                                                                                                                                                                                        
Tests:       1 passed, 1 total                                                                                                                                                                                                                                                                                                                                                                                        
Snapshots:   0 total
Time:        1.264 s
Ran all test suites.

Integration and E2E tests

Right now, Jest runs all the tests in the __tests__ folder. So let's create a separate directory for unit, integration, and E2E tests.

mkdir __tests__/unit
mkdir __tests__/integration
mkdir __tests__/e2e

Move the existing test.js file into the unit folder and copy-paste the file to the integration and e2e directory.

At this point, you should have the following directory tree:

├── __tests__
│   ├── e2e
│   │   └── test.js
│   ├── integration
│   │   └── test.js
│   └── unit
│       └── test.js
├── node_modules
├── package-lock.json
└── package.json

If you run Jest, you will see that all tests are run by default.

$ npx jest
 PASS  __tests__/integration/test.js
 PASS  __tests__/e2e/test.js
 PASS  __tests__/unit/test.js

Test Suites: 3 passed, 3 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        1.043 s
Ran all test suites.

Configure Jest projects

You will need four different configuration files. One configuration file will be the container file that references all your Jest projects. Rest three files are for unit, integration, and e2e projects.

jest.config.js

module.exports = {
  projects: [
    "<rootDir>/jest.unit.config.js",
    "<rootDir>/jest.integration.config.js",
    "<rootDir>/jest.e2e.config.js",
  ],
};

jest.unit.config.js

module.exports = {
  displayName: "unit",
  testMatch: ["**/__tests__/**/unit/**/*.[jt]s?(x)"],
};

jest.integration.config.js

module.exports = {
  displayName: "integration",
  testMatch: ["**/__tests__/**/integration/**/*.[jt]s?(x)"],
};

jest.e2e.config.js

module.exports = {
  displayName: "e2e",
  testMatch: ["**/__tests__/**/e2e/**/*.[jt]s?(x)"],
};

As you can see, these configuration files are very similar, except for the main configuration file that references the projects. These configuration files specify a display name and a glob pattern to match the test files.

Now, run your tests

$ npx jest
 PASS   integration  __tests__/integration/test.js
 PASS   e2e  __tests__/e2e/test.js
 PASS   unit  __tests__/unit/test.js

Test Suites: 3 passed, 3 total                                                                                                                                   
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        0.755 s, estimated 1 s
Ran all test suites in 3 projects.

Jest successfully recognized the three separate projects. You can see the display name of the project next to the file path.

What if you want to run specific tests? Jest CLI provides many options, but in this case, you should be interested in --selectProjects and --ignoreProjects.

Let's run e2e tests only

$ npx jest --selectProjects e2e
Running one project: e2e
 PASS   e2e  __tests__/e2e/test.js
  √ test works (1 ms)

Test Suites: 1 passed, 1 total                                                                                                                                   
Tests:       1 passed, 1 total                                                                                                                                   
Snapshots:   0 total
Time:        0.363 s, estimated 1 s
Ran all test suites.

Or run all tests, except e2e tests

$ npx jest --ignoreProjects e2e
Running 2 projects:
- integration
- unit
 PASS   integration  __tests__/integration/test.js
 PASS   unit  __tests__/unit/test.js

Test Suites: 2 passed, 2 total                                                                                                                                   
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.559 s, estimated 1 s
Ran all test suites in 2 projects.

NPM scripts

You will probably get tired of typing the Jest commands all the time. So write a few NPM scripts so your fingers can get some rest.

package.json

{
  "name": "jest-projects",
  "version": "1.0.0",
  "scripts": {
    "test": "jest",
    "test:unit": "jest --selectProjects unit",
    "test:integration": "jest --selectProjects integration",
    "test:e2e": "jest --selectProjects e2e"
  },
  "devDependencies": {
    "jest": "^28.1.3"
  }
}

This will provide you with four scripts:

  • npm test- Runs all tests (unit, integration and E2E)
  • npm run test:unit - Runs unit tests only
  • npm run test:integration - Runs integration tests only
  • npm run test:e2e - Runs E2E tests only

Global setup and teardown modules

Most of the time, integration and E2E tests require some kind of setup and teardown before the test suite. Jest provides globalSetup and globalTeardown options in the configuration file. Let's set it up for the E2E tests.

jest.e2e.config.js

module.exports = {
  displayName: "e2e",
  testMatch: ["**/__tests__/**/e2e/**/*.[jt]s?(x)"],
  testPathIgnorePatterns: [
    "<rootDir>/__tests__/e2e/_setup.js",
    "<rootDir>/__tests__/e2e/_teardown.js",
  ],
  globalSetup: "<rootDir>/__tests__/e2e/_setup.js",
  globalTeardown: "<rootDir>/__tests__/e2e/_teardown.js",
};

This config will tell Jest to run the __tests__/e2e/_setup.js before all E2E test suites. And run <rootDir>/__tests__/e2e/_teardown.js after all E2E test suites.

Now, you might wonder why testPathIgnorePatterns contains the _setup and _teardown files. Take a look at the testMatch's value. It matches any .js, .ts, .jsx and .tsx files in any e2e directory. Therefore this configuration simply matches the setup and teardown files as well. If you don't ignore them, Jest will complain that these files don't contain any tests.

Create the setup and teardown modules

__tests__/e2e/_setup.js

module.exports = () => {
  console.log("E2E setup");
};

__tests__/e2e/_teardown.js

module.exports = () => {
  console.log("E2E teardown");
};

Now, if you run your E2E tests, you will see the messages in your terminal

$ npm run test:e2e

> jest-projects@1.0.0 test:e2e
> jest --selectProjects e2e

Running one project: e2e
Determining test suites to run...E2E setup
 PASS   e2e  __tests__/e2e/test.js
  √ test works (1 ms)

Test Suites: 1 passed, 1 total                                                                                                                                   
Tests:       1 passed, 1 total                                                                                                                                   
Snapshots:   0 total
Time:        0.336 s, estimated 1 s
Ran all test suites.
E2E teardown

Tests by filename suffixes

You might prefer a different setup for your test files. Some projects use different directories, some projects colocate tests, and some projects just use naming conventions to differentiate between tests.

Previously, you separated your tests by using different directories. Now let's assume you want to do the following:

  • my-test.unit.js - should be in the unit project
  • my-test.integration.js - should be in the integration project
  • my-test.e2e.js - should be in the e2e project

Create an empty lib directory, this is where you will store your source code files:

mkdir lib

Write a simple add function:

lib/add.js

module.exports.add = (a, b) => a + b;

Create a unit, integration, and E2E test file for this simple add function.

lib/add.unit.js

const { add } = require("./add");

it("add(1, 1) should return 2", () => {
  expect(add(1, 1)).toBe(2);
});

lib/add.int.js

const { setTimeout } = require("node:timers/promises");
const { add } = require("./add");

it("add(1, 1) should return 2 (1s delay)", async () => {
  await setTimeout(1000);
  expect(add(1, 1)).toBe(2);
});

lib/add.e2e.js

const { setTimeout } = require("node:timers/promises");
const { add } = require("./add");

it("add(1, 1) should return 2 (3s delay)", async () => {
  await setTimeout(3000);
  expect(add(1, 1)).toBe(2);
});

Integration and E2E tests include a delay to simulate the slowness of these tests.

At this point, if you try to run any of your test commands, you notice that none of these are running any of the new tests. Let's modify the configuration files to run colocated test files as well in the lib directory.

jest.unit.config.js

module.exports = {
  displayName: "unit",
  testMatch: [
    "**/__tests__/**/unit/**/*.[jt]s?(x)",
    "<rootDir>/lib/**/*.unit.[jt]s?(x)",
  ],
};

jest.integration.config.js

module.exports = {
  displayName: "integration",
  testMatch: [
    "**/__tests__/**/integration/**/*.[jt]s?(x)",
    "<rootDir>/lib/**/*.int.[jt]s?(x)",
  ],
};

jest.e2e.config.js

module.exports = {
  displayName: "e2e",
  testMatch: [
    "**/__tests__/**/e2e/**/*.[jt]s?(x)",
    "<rootDir>/lib/**/*.e2e.[jt]s?(x)",
  ],
  testPathIgnorePatterns: [
    "<rootDir>/__tests__/e2e/_setup.js",
    "<rootDir>/__tests__/e2e/_teardown.js",
  ],
  globalSetup: "<rootDir>/__tests__/e2e/_setup.js",
  globalTeardown: "<rootDir>/__tests__/e2e/_teardown.js",
};

Each configuration has a new element in the testMatch array. Each matches its respective desired file naming format. Note that in this case <rootDir>/lib is used. So the lib directory must reside in the root directory of your project to be matched. Whereas **/__tests__ matches nested ___tests__ directories as well.

Now, let's test what you have worked on

$ npm test

> jest-projects@1.0.0 test
> jest

Determining test suites to run...E2E setup
 PASS   unit  __tests__/unit/test.js
 PASS   integration  __tests__/integration/test.js
 PASS   e2e  __tests__/e2e/test.js                                                                                                                               
 PASS   unit  lib/add.unit.js                                                                                                                                    
 PASS   integration  lib/add.int.js
 PASS   e2e  lib/add.e2e.js

Test Suites: 6 passed, 6 total
Tests:       6 passed, 6 total
Snapshots:   0 total
Time:        4.086 s
Ran all test suites in 3 projects.
E2E teardown

Jest now runs the tests in the lib directory without any issues! The test took 4 seconds due to delays in the new integration and E2E tests.

Now try to run unit tests only

$ npm run test:unit

> jest-projects@1.0.0 test:unit
> jest --selectProjects unit

Running one project: unit
 PASS   unit  lib/add.unit.js
 PASS   unit  __tests__/unit/test.js

Test Suites: 2 passed, 2 total                                                                                                                                   
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.542 s, estimated 1 s
Ran all test suites.

It took half a second, and no integration and E2E tests were run. Looks good!

You are done! Great job!

The project's source code is available on GitHub.

Check out the official documentation to learn more about Jest projects:

]]>
<![CDATA[Node.js TypeScript monorepo via NPM workspaces]]>https://daveiscoding.com/nodejs-typescript-monorepo-via-npm-workspaceshttps://daveiscoding.com/nodejs-typescript-monorepo-via-npm-workspacesSun, 14 Aug 2022 11:58:19 GMTMonorepos are all the rage right now. Modern projects are all using NX to set up a monorepo. But why would you introduce such a complex tool into your tech stack when something simple is often enough?

Both Yarn and NPM include workspace management in their feature-set. Thus you can manage multiple projects in one workspace. In addition, one of the tools is always available in your project, so why not use it?

The fantastic project

You are working on a fantastic project that you happened to name fantastic. How creative, isn't it?

fantastic is a command line application that will showcase how to set up a TypeScript monorepo using NPM workspaces. The fantastic project was a massive success as a CLI application, and many users wanted to have a graphical user interface to use it. So you decided to create a web interface. Your code currently lives in a single module containing the core logic and the CLI entry-point.

Therefore you decided to separate the project into three separate packages:

  • core - this package contains the core logic of your fantastic project
  • web - provides a web interface that interacts with the core package
  • cli - provides a command line interface that interacts with the core package

Initialize the project

Let's create an empty directory and initialize an NPM package:

mkdir fantastic
cd fantastic
npm init -y

Now create the packages:

npm init -y --scope @fantastic -w packages/core
npm init -y --scope @fantastic -w packages/web
npm init -y --scope @fantastic -w packages/cli

Define the dependencies between the packages:

npm install @fantastic/core -w @fantastic/web
npm install @fantastic/core -w @fantastic/cli

Test it!

Now that we have the foundation in place, let's add some code to test it:

packages/core/index.js

console.log("Hello from Core!");

packages/web/index.js

require("@fantastic/core");
console.log("Hello from Web!");

packages/cli/index.js

require("@fantastic/core");
console.log("Hello from CLI!");

Running the CLI outputs the following:

node packages/cli/index.js
Hello from Core!
Hello from CLI!

This confirms that the setup is working fine.

Here comes TypeScript

Time to turn this project from JavaScript to TypeScript!

First of all, install typescript as a dev dependency in the workspace project:

npm install -D typescript

Every package requires its own tsconfig.json file. Since the fantastic project uses the same configuration for all the three packages, create a common tsconfig.base.json file in the root directory.

tsconfig.base.json

{
  "compilerOptions": {
    "incremental": true,
    "target": "es2020",
    "module": "commonjs",
    "declaration": true,
    "sourceMap": true,
    "strict": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,

    "composite": true
  }
}

This is just a typical tsconfig.json file, except for one compiler option: composite. This option makes it possible for TypeScript to determine if a project has been built yet quickly.

Now you have a common TS config file, but you aren't using it yet. Create a tsconfig.json file in each package's root directory:

packages/core/tsconfig.json

{
  "extends": "../../tsconfig.base.json"
}

The cli and web package is a bit different. You need to list out all your dependencies in the references property:

packages/cli/tsconfig.json and packages/web/tsconfig.json

{
  "extends": "../../tsconfig.base.json",
  "references": [{ "path": "../core" }]
}

Now that independent packages are setup, create the tsconfig.json in the root directory:

tsconfig.json

{
  "files": [],
  "references": [
    { "path": "packages/core" },
    { "path": "packages/cli" },
    { "path": "packages/web" }
  ]
}

Empty files array tells TypeScript to ignore all files except those in the references.

Rename all your .js files to .ts and replace require with import:

require("@fantastic/core");

to

import "@fantastic/core";

You are ready to compile:

npx tsc --build

--build flag is required because the project contains multiple projects.

Now that you are done with all these changes, test your app again:

$ node packages/cli/index.js
Hello from Core!
Hello from CLI!

$ node packages/web/index.js
Hello from Core!
Hello from Web!

Separate source code and build output

First of all, remove all the build outputs from the previous step. The following command will delete all .js, .js.map, .d.ts and .tsbuildinfo files in the packages directory.

rm packages/**/{*.js,*.js.map,*.d.ts,*.tsbuildinfo}

Having your source code and build output in different directories is a good practice. Therefore, move each package's source code into an src directory, and change the build output directory to dist.

Extend your packages/*/tsconfig.json files with the following snippet:

  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist"
  },

As an example, this is how the packages/web/tsconfig.json looks now:

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist"
  },
  "references": [{ "path": "../core" }]
}

This tells TypeScript that your source code files are in the src directory, and the build output should go into the dist directory. These are relative to your tsconfig.json file.

Move your index.ts files into the respective src directory. At this point, you should have the following directory tree:

├── package-lock.json
├── package.json
├── packages
│   ├── cli
│   │   ├── package.json
│   │   ├── src
│   │   │   └── index.ts
│   │   └── tsconfig.json
│   ├── core
│   │   ├── package.json
│   │   ├── src
│   │   │   └── index.ts
│   │   └── tsconfig.json
│   └── web
│       ├── package.json
│       ├── src
│       │   └── index.ts
│       └── tsconfig.json
├── tsconfig.base.json
└── tsconfig.json

Before building your project, adjust the main property in the package.json of each package. Change index.js to dist/index.js since that's where it lives now.

Now build your project and run your CLI app:

npx tsc --build
node packages/cli/dist/index.js

You should see the usual output:

Hello from Core!
Hello from CLI!

You've done it! Good Job!

Now that you have laid the foundation for your project, go on and create something extraordinary!

The project's source code is available on GitHub. The repository contains a few little changes. Feel free to explore!

If you would like to learn more about NPM workspaces and TypeScript, check out these links:

PS. NX is an awesome tool! But sometimes, it's better to use the tools you already have at your disposal. Please take your time to decide whether to use NX or NPM / Yarn workspaces.

]]>
<![CDATA[My feelings about Go after spending a weekend together]]>https://daveiscoding.com/my-feelings-about-go-after-spending-a-weekend-togetherhttps://daveiscoding.com/my-feelings-about-go-after-spending-a-weekend-togetherWed, 17 Feb 2021 17:36:16 GMTI had a great weekend following the Learn Go with Tests online book. I spent about 15 hours going through the book and firing up a few practice projects.

I know it’s not a lot to conclude, but I had enough experience with other languages to find the pros and cons of the language. As we all know, Go’s most significant and most exciting features are the goroutines and channels. It’s built for concurrency, and it’s so stupid simple to work with it.

Go comes with its own minor problems. You need to pass Context around down to the deepest of your call stack. At the time of this post, Go doesn’t have generics. It’s on the way though. Check out the last blog post about it on Go’s blog for yourself. Don’t get excited about the syntax…

// Print prints the elements of any slice.
// Print has a type parameter T and has a single (non-type)
// parameter s which is a slice of that type parameter.
func Print[T interface{}](s []T) {
    // same as above
}

What’s the fuss about the syntax?

I’m a JavaScript developer who switched from C, Java, C#, PHP languages in this particular order. If you ever worked with those languages – and I bet you did -, then you know the drill:

type variableName = value;
function functionName(MyType $val) {}
public static void main(String[] args) {}

You know, going left to right. Type of the variable then the name of it. Then Go comes to the show and…

func SayHelloToThePeople(people []Person) (hellos []string) {}

Left to right, then right to the left. Like, what in the name of Gopher is happening here?

I guess you get it now.

Maybe the problem lies in me, I should’ve gone down the route of exotic languages rather than the mainstream ones.

After all, as a full-time JS developer, I’m not sure if these two languages can live together in one muscle. As you get used to writing out type variableName then suddenly switching to variableName []type really twitches my muscle memories. Practice makes perfect though.

Simplicity of Go

Did you know Go only has 25 keywords? We got 32 in C, 54 in Java, 54 in PHP, 59 in JavaScript, and 102 in C#. That’s according to a mid-2017 Reddit post. God knows how many keywords now C# got… Anyway, I was lazy to look it up on my own, so probably those numbers are off – according to my one-minute Google research C# only got 79 keywords. You still get the point, right? Go has few keywords, and they managed to stuff the magic of Go – concurrency – into those few keywords.

Let’s talk about the included batteries. In my opinion, that’s still something that belongs to the simplicity section. Go comes with all the fun stuff without any external dependencies. Let’s see what we got here:

  • Code formatting (gofmt)
  • Code linting (golint)
  • More code static checks (go vet)
  • Tests (go test)
  • Test coverage (go test -cover, go test -coverprofile=coverprofile.out, go tool cover)
  • An awesome standard library

If you are just starting with Go then you probably don’t need to do anything other than just install Go. That’s a massive plus for me. Whenever I fire up a new JS project, I need to set up a linter, a formatter, a test runner, and many dependencies (depends on the project though).

I’m sure you are familiar with this.

Our modern world of programming is very bloated, I must say. I miss the good old days of just putting together a few PHP files and uploading them via FTP.

What’s next?

Well, Go 2 is coming, and I’m thrilled but scared at the same time. Hopefully, Go 2 will follow what Go 1 started.

I’m hoping for the best regarding the future of Go. I love the language’s current state, and I’ll probably continue learning it and putting together a few projects with it.

Let’s hope this won’t happen. Source: Reddit

]]>
<![CDATA[Setting up Prettier in a Create React App project]]>https://daveiscoding.com/setting-up-prettier-in-a-create-react-app-projecthttps://daveiscoding.com/setting-up-prettier-in-a-create-react-app-projectSun, 20 Dec 2020 03:00:38 GMTInstall dependencies
# Yarn
yarn add -D prettier eslint-config-prettier eslint-plugin-prettier

# NPM
npm install -D prettier eslint-config-prettier eslint-plugin-prettier

By default your eslintConfig looks like this:

"eslintConfig": {
    "extends": [
        "react-app",
        "react-app/jest"
    ]
}

Let’s add Prettier and Prettier React to the extends array:

"eslintConfig": {
    "extends": [
        "react-app",
        "react-app/jest",
        "plugin:prettier/recommended",
        "prettier/react"
    ]
}

Your editor should pick up the eslint config change after saving package.json. Let’s add two scripts to our package.json, one for formatting and one for lining.

"format:js": "prettier --write src/**/*.{js,jsx,ts,tsx}",
"lint:js": "eslint src/**/*.{js,jsx,ts,tsx}"

(NOTE: I used js,jsx,ts,tsx in the patterns above. If you don’t have any files that match any of those patterns, then you will get an error. Feel free to remove extensions that you aren’t using)

If you are using TypeScript then you might want to add type checking to the lint script as well:

"lint:js": "tsc --noEmit && eslint src/**/*.{js,jsx,ts,tsx}"

Automagically lint before git commit

You might want to automatically run the lint script before git commits. This can be achieved easily by using husky and lint-staged. Let’s start by installing them as dev dependencies:

# Yarn
yarn add -D husky lint-staged

# NPM
npm install -D husky lint-staged
```bash

Next you just need to add a few lines to your package.json:

```json
"husky": {
    "hooks": {
        "pre-commit": "lint-staged"
    }
},
"lint-staged": {
    "src/**/*.{js,jsx,ts,tsx}": [
        "yarn lint:js"
    ]
}
]]>