Generate multiple public bitcoin receiving addresses (HD wallet) from seed phrase in nodejs
If you want to do Bitcoin stuff in nodejs, I recommend using bcoin:

repo: https://github.com/bcoin-org/bcoin

docs, guides: https://bcoin.io

It has a fully-validating full node implementation, SPV implementation, and BIP44 wallet that is used by Purse.io and several other Bitcoin-accepting merchants for this exact use case: Each customer gets a dedicated account and the wallet can track balances per-user, generating new addresses each time the current receive is used (a la BIP44)

You can set up your server with a watch-only wallet to report incoming deposits, and keep the private keys either offline or on more secure, local machine.

Example:

On secure or offline machine:

$ bcoin --daemon
$ bwallet-cli mkwallet --witness=true --id=HOT
$ bwallet-cli --if=HOT account get default
{
  "name": "default",
  "initialized": true,
  "witness": false,
  "watchOnly": false,
  ..
  "accountKey": "tpubDD3BFBuLYg2cspeDSWZgo9qv4qYi7KQF5oov8theqbFRUFqXPmsowPqAY1PQqnEdUe5JrFqf8ZGWLwEQEKB5WjJjxgMwcXmFuu1XDrG4RSr",
  ..
}


On "hot" production server:

$ bcoin --daemon
$ bwallet-cli mkwallet --id=WATCH --watch-only=true --account-key=tpubDD3BFBuLYg2cspeDSWZgo9qv4qYi7KQF5oov8theqbFRUFqXPmsowPqAY1PQqnEdUe5JrFqf8ZGWLwEQEKB5WjJjxgMwcXmFuu1XDrG4RSr
$ bwallet-cli --id=WATCH address default
{
  "name": "default",
  "account": 0,
  "branch": 0,
  "index": 1,
  "witness": false,
  "nested": false,
  "publicKey": "026f1afcab2942fd932760557943b287957bb8aa6ebe0f8dc64fa25862e23d52a2",
  "script": null,
  "program": null,
  "type": "pubkeyhash",
  "address": "mjdcszSLXHd6awrnM4bgJiFEbTFZdyw7de"
}


...etc

From here you can use API calls to create transaction templates and sign them on the offline machine.

If you are queasy about running an alternative implantation of Bitcoin, you can still run bitcoind on the hot server and run bcoin behind it (even in SPV mode) with bitcoind as it's --only= peer, like a Bitcoin Core Firewall



(edit)

Generate static addresses

'use strict';

const bcoin = require('bcoin');
const m = bcoin.Mnemonic.fromPhrase(
  'abandon abandon abandon abandon abandon abandon'
  + ' abandon abandon abandon abandon abandon about');
const master = bcoin.HDPrivateKey.fromMnemonic(m);
const key0 = master.derivePath("m'/44'/0'/0'/0/0");
const ring0 = new bcoin.KeyRing({privateKey: key0});
console.log(ring0)


Will output:

{
  witness: false,
  nested: false,
  publicKey: '03aaeb52dd7494c361049de67cc680e83ebcbbbdbeb13637d92cd845f70308af5e',
  script: null,
  program: null,
  type: 'pubkeyhash',
  address: '1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA'
}


You can verify this is address 0 from that mnemonic phrase using https://iancoleman.io/bip39/ and of course because this phrase is so abysmally insecure and used by developers testing on mainnet (sigh) there are several transactions to this address: https://blockstream.info/address/1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA