Live data from Hacker News

Modern Node.js Patterns

kashw1n.com

371–380 of 448 posts

Re: Modern Node.js Patterns

#371

  try {
    // Parallel execution of independent operations
    const [config, userData] = await Promise.all([
      readFile('config.json', 'utf8'),
      fetch('/api/user').then(r => r.json())
    ]);
    ...
  } catch (error) {
    // Structured error logging with context
    ...
  }
This might seem fine at a glance, but a big grip I have with node/js async/promise helper functions is that you can't differ which promise returned/threw an exception.

In this example, if you wanted to handle the `config.json` file not existing, you would need to somehow know what kind of error the `readFile` function can throw, and somehow manage to inspect it in the 'error' variable.

This gets even worse when trying to use something like `Promise.race` to handle promises as they are completed, like:

  const result = Promise.race([op1, op2, op3]);
You need to somehow embed the information about what each promise represents inside the promise result, which usually is done through a wrapper that injects the promise value inside its own response... which is really ugly.

Re: Modern Node.js Patterns

#372
post #242

Earlier quoted context omitted.

I very much dislike such features in a runtime or app. The "proper" place to solve this, is in the OS. Where it has been solved, including all the inevitable corner cases, already. Why reinvent this wheel, adding complexity, bug-surface, maintenance burden and whatnot to your project? What problem dies it solve that hasn't been solved by other people?

What's there to dislike? They don't replace the restrictions at OS level, they add to it.

Nope, they don't add. They confuse. From administrator perspective, it sucks when the same conceptual configuration can be performed in many different places using different configuration languages, governed by different upgrade policies, owned by unintended users, logged into unintended places.

Also, I'd bet my monthly salary on that Node.js implementation of this feature doesn't take into account multiple possible corner cases and configurations that are possible on the system level. In particular, I'd be concerned about DNS search path, which I think would be hard to get right in userspace application. Also, what happens with /etc/hosts?

From administrator perspective I don't want applications to add another (broken) level of manipulating of discovery protocol. It usually very time consuming and labor intensive task to figure out why two applications which are meant to connect aren't. If you keep randomly adding more variables to this problem, you are guaranteed to have a bad time.

Re: Modern Node.js Patterns

#373

try { // Parallel execution of independent operations const [config, userData] = await Promise.all([ readFile('config.json', 'utf8'), fetch('/api/user').then(r => r.json()) ]); ... } catch (error) { // Structured error logging with context ... } This might seem fine at a glance, but a big grip I have with node/js async/promise helper functions is that you can't differ which promise returned/threw an exception. In thi…

IMO when you do control-flow in catch blocks, you're fighting against the language. You lose Typescripts type-safety, and the whole "if e instanceof ... else throw e"-dance creates too much boilerplate.

If the config file not existing is a handleable case, then write a "loadConfig" function that returns undefined.

Re: Modern Node.js Patterns

#374
Most importantly, Node has Typescript support even in LTS (starting with v22.18).

I highly recommend the `erasableSyntaxOnly` option in tsconfig because TS is most useful as a linter and smarter Intellisense that doesn't influence runtime code:

https://www.typescriptlang.org/tsconfig/#erasableSyntaxOnly

Re: Modern Node.js Patterns

#375
post #76

The killer upgrade here isn’t ESM. It’s Node baking fetch + AbortController into core. Dropping axios/node-fetch trimmed my Lambda bundle and shaved about 100 ms off cold-start latency. If you’re still npm i axios out of habit, 2025 Node is your cue to drop the training wheels.

Tangential, but thought I'd share since validation and API calls go hand-in-hand: I'm personally a fan of using `ts-rest` for the entire stack since it's the leanest of all the compile + runtime zod/json schema-based validation sets of libraries out there. It lets you plug in whatever HTTP client you want (personally, I use bun, or fastify in a node env). The added overhead is totally worth it (for me, anyway) for sh…

I've been impressed with Hono's zod Validator [1] and the type-safe "RPC" clients [2] you can get from it. Most of my usage of Hono has been in Deno projects, but it seems like it has good support on Node and Bun, too.

[1] https://hono.dev/docs/guides/validation#zod-validator-middle...

[2] https://hono.dev/docs/guides/rpc#client

Re: Modern Node.js Patterns

#376
post #226

Earlier quoted context omitted.

Reading release notes would have solved that issue ;)

Which release notes. Id need to read hundreds!

Maybe there's an idea in here, a website that shows you all release notes since the last time you've used something, removing those that have been superseeded by later ones, ranked by importance.

Re: Modern Node.js Patterns

#377

Earlier quoted context omitted.

I have a blog post[1] and accompanying repo[2] that shows how to use SEA to build a binary (and compares it to bun and deno) and strip it down to 67mb (for me, depends on the size of your local node binary). [1]: https://notes.billmill.org/programming/javascript/Making_a_s... [2]: https://github.com/llimllib/node-esbuild-executable#making-a...

> 67 MB binary I hope you can appreciate how utterly insane this sounds to anyone outside of the JS world. Good on you for reducing the size, but my god…

It's not insane at all. Any binary that gets packed with the entire runtime will be in MBs. But that's the point, the end user downloads a standalone fragment and doesn't need to give a flying fuck about what kind of garbage has to be preinstalled for the damn binary to work. You think people care if a binary is 5MB or 50MB in 2025? It's more insane that you think it's insane than it is actually insane. Reminds me of all the Membros and Perfbros crying about Electron apps and meanwhile these things going brrrrrrr with 100MB+ binaries and 1GB+ eaten memory on untold millions of average computers

Re: Modern Node.js Patterns

#378
post #348

Earlier quoted context omitted.

ts-rest doesn't see a lot of support these days. It's lack of adoption of modern tanstack query integration patterns finally drove us look for alternatives. Luckily, oRPC had progressed enough to be viable now. I cannot recommend it over ts-rest enough. It's essentially tRPC but with support for ts-rest style contracts that enable standard OpenAPI REST endpoints. - https://orpc.unnoq.com/ - https://github.com/unnoq/o…

First time hearing about oRPC, never heard of or used ts-rest and I'm a big fan of tRPC. Is the switch worth the time and energy?

If you're happy with tRPC and don't need proper REST functionality it might not be worth it.

However, if you want to lean that direction where it is a helpful addition they recently added some tRPC integrations that actually let you add oRPC alongside an existing tRPC setup so you can do so or support a longer term migration.

- https://orpc.unnoq.com/docs/openapi/integrations/trpc

Re: Modern Node.js Patterns

#379

try { // Parallel execution of independent operations const [config, userData] = await Promise.all([ readFile('config.json', 'utf8'), fetch('/api/user').then(r => r.json()) ]); ... } catch (error) { // Structured error logging with context ... } This might seem fine at a glance, but a big grip I have with node/js async/promise helper functions is that you can't differ which promise returned/threw an exception. In thi…

You are probably looking for `Promise.allSettled`[1]. Which, to be fair, becomes quite convulated with destructuring (note that the try-catch is not necessary anymore, since allSettled doesn't "throw"):

  // Parallel execution of independent operations
  const [
    { value: config, reason: configError },
    { value: userData, reason: userDataError },
  ] = await Promise.allSettled([
    readFile('config.json', 'utf8'),
    fetch('/api/user').then(r => r.json())
  ]);

  if (configError) {
    // Error with config
  }

  if (userDataError) {
    // Error with userData
  }
When dealing with multiple parallel tasks that I care about their errors individually, I prefer to start the promises first and then await for their results after all of them are started, that way I can use try catch or be more explicit about resources:

  // Parallel execution of independent operations
  const configPromise = readFile('config.json', 'utf8')
  const userDataPromise = fetch('/api/user').then(r => r.json())

  let config;
  try {
    config = await configPromise
  } catch (err) {
    // Error with config
  }

  let userData;
  try {
    userData = await userDataPromise
  } catch (err) {
    // Error with userData
  }
Edit: added examples for dealing with errors with allSettled

[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

Re: Modern Node.js Patterns

#380

Earlier quoted context omitted.

What's there to dislike? They don't replace the restrictions at OS level, they add to it.

Nope, they don't add. They confuse. From administrator perspective, it sucks when the same conceptual configuration can be performed in many different places using different configuration languages, governed by different upgrade policies, owned by unintended users, logged into unintended places. Also, I'd bet my monthly salary on that Node.js implementation of this feature doesn't take into account multiple possible…

If you're confused over such things, you're a crap admin.
Post reply on HN