Nodejs

Image of Author
April 20, 2023 (last updated September 16, 2024)

npm

npm

Using JSDoc instead of TypeScript on the server

If you are running a lightweight service via Nodejs, I'd recommend trying to use JSDoc instead of TypeScript due to how lightweight it is. This is particularly useful if you are using VSCode as your IDE, since you will get all the same compiler warnings and errors inline same as you would if you were using TS. Coupling this with the #Nodejs test runner will get you a long way towards a low-dependency lightweight servers.

Using express and ws to support WebSockets and normal HTTP requests

I am struggling a bit to determine the ordering of things here, but it appears that the way to do this begin by inferring that the app from const app = express() is actually a function that satisfies the requirements of the node:http http.createServer requestListener parameter. Thus, you can do the following.

const app = express()
const server = http.createServer(app)
server.listen(8080)

This means that you are not using the builtin express functions to initiate server listening, but instead working with the underlying http.Server primitive itself.

Now that you have a nodejs server entity, you can also setup the ws websocket server. (I am adapting this from some ws docs.)

const app = express()
const server = http.createServer(app)
const wss = new WebSocketServer()

server.on('upgrade', (request, socket, head) => {
  wss.handleUpgrade(request, socket, head, (ws) => {
    wss.emit('connection', ws, request)
  })
})

wss.on('connection', (ws, request) => {
  console.log('connection!')

  ws.on('message', (message) => {
    console.log('message received', message)
  })
})

server.listen(8080)

This is establishing the core websocket setup. You can learn more in my note on WebSockets.

The end result is an express app for handling common HTTP requests and ws for handling WebSocket connections, all via the base http.Server.

log full objects

You can use util and the depth: Infinity option. You still have to call console.log.

import util from 'node:util'

const obj = {}

console.log(util.inspect(out, { depth: Infinity }));

directories

process.cwd() is the directory the node process is currently being ran in.

But maybe you want to write a cli lib and want to access static assets/files within your npm library. For that, you can use __direname and __filename, which aren't quite globals, and only work in .cjs (common js) files. Importantly, symlinks are resolved before filename resolution. This means that even though the script is likely executed from node_modules/.bin/<script>, since that is a symlink to the package.bin field, it will actually display that file path. From there, you can construct relative paths to reach your desired static asset.

Nodejs test runner

Nodejs test runner tips and tricks

Nodejs test setup in VSCode

Nodejs test runner setup in VSCode

conditional exports seem great for package authors

To begin with, you should be familiar with the exports key, which seems to be intended to supercede the main key in package.json files: With conditional exports The exports key seems to be intended to supercede the main key in package.json:

When a package has an "exports" field, this will take precedence over the "main" field when importing the package by name.

Now, within the exports key, you can defined conditional exports, which can target different module types.

For example, let's say we are writing a package in TypeScript using esm files. We can use tsup to easily bundle our exports into multiple formats, and then point to each module type bundle within the exports key.

{
  // source code is in esm
  "type": "module",
  // tsup outputs ts types
  "types": "dist/index.d.ts",
  // tsup bundles/builds
  "scripts": {
    "build": "tsup src/index.ts --dts --formats esm,cjs"
  },
  // conditional exports
  "exports": {
    "import": "dist/index.js",
    "require": "dist/index.cjs"
  }
}

Now, packages that depend on your package can require('package') or import _ from 'package'.

Finally, you can also include "types" within "exports". This is considered a community condition. For some reason it must come first. I have not move the types key in the example above because I cannot get it to work with the vscode typescript engine.

package.json spec is in multiple locations