WS + API: A NodeJS Server
Designing a Node Server with Web Sockets and RESTful API
- Part 1: One Server To Rule Th…Most of Them
- Part 2: WS + API: Getting Started
- Part 3: WS + API: Enter the Index.ts
- Part 4: Interlude: Protocols
- Part 5: WS + API: Web Socket Handler
- Part 6: WS + API: Getting the Message
- Part 7: Interlude: Redis & Redis-OM
- Part 8: WS + API: Actions Speak Louder Than Code
- Part 9: From Node to Deno
I’m not one to jump on a developer’s bandwagon just because there’s a “new hotness” on the scene, but I had been following the development of Deno at a distance and recently decided to get closer by porting my WS+API server project to the new platform.
Deno is another ECMAScript-based engine, this one using TypeScript as it’s default language of choice. Out of the box it’s almost a 1:1 translation between Node code and Deno code (assuming you’re using TypeScript in both places), but there’s some interesting design choices that I’m trying to wrap my head around.
The first — and most thankfully — is that all imports are import
. When building this server for Node, I had always gotten tripped up by the difference between import
and require
. My best understanding is that import
is for things like TS type definitions and interfaces, while require
loaded actual functioning modules. With Deno, I don’t have to worry about that distinction thanks in part to the TS-first approach which doesn’t always require me to import types as separate entities.
The second interesting choice is how those modules are accessed. Modules are third-party (or self-created but external to the current app) libraries which can help prevent side-projects by furnishing functionality we need, like Axios which simplifies REST access, or Express which is a self-contained HTTP server. With Node, modules have to be installed prior to use by using npm install [PACKAGE NAME]
on the command-line. This will download the module files from a remote repository (often NPMJS.org, but other repos exist or can be created), and will register them in the central config file, package.json
. Once a module has been downloaded in this manner, we can refer to it in the application when we need it. Having the module local means we never have to worry about losing the version we develop against, and even when deploying or moving to another machine, NPM’s versioning requirements mean we should always be able to get the same code for the same version, every single time we need to re-download it.
Deno takes a different approach. Modules can be referenced in code via URL, meaning we don’t need to pre-install a module before we use it. When the app is run for the first time (or when certain other CLI instructions are issued), Deno will download, compile, and cache the module. We can define our module access directly at the top of a file using an import
statement and the URL where we can find the module.
import { foo } from 'https://my.repo.com/bar@1.22.1/func/mod.ts
This is a bold choice because whatever you’re thinking right now is also the first thing everyone seems to think when they run into this pattern for the first time. What happens if the URL no longer works? What happens if I use this a lot and the version changes? What if someone updates — or maliciously replaces — the code at the endpoint and it breaks my application? The idea of this “live fire” import seems a bit questionable, but the Deno developers have provided means to minimize the likelihood that such scenarios, should they occur, will have as little an impact as possible. It does make me wonder, then, why to go this route at all if there are caveats, work-arounds, and safety measures put into place that basically result in the npm install
method that Node uses. In fact, between import maps, the specialty deps.ts
, and the option to import a module directly via URL at the top of a file, none of which demand exclusivity, which can lead to different decisions on different days, and references to modules strewn throughout the code. I am trying to convert my Node app to Deno, and have been forced to consider which option is “best” for each and every import I’ve had to make, which I’m sure will confuse the hell out of me further down the road when I’m looking for where its referenced and which version it’s using.
So why bother? I’m sure someone might bust into the post and start talking about performance, or flexibility, or some other thinly-veiled cheerleading that invoking and then complaining about A Thing seems to magically summon on the Internet these days. To be fair, Deno has a learning curve, like all technologies do, and it’s going to be made more difficult than it should be when there’s an opportunity to compare it to something that’s older, more established, and is an alternative that’s more familiar. But I have been having a good time with Deno. Most of my code has ported over without issue, and I’ve been able to improve on some aspects on account of the fact that Deno maintains their own repo, JSR.io, which they claim is necessary to move ECMAScript packages into the future, and to serve more than just Node inherently through NPM (NPM stands for “Node package manager”).
In truth, the thing that got me looking at Deno was the fact that I’ve been pressured to give up my Linux server at work. I had been trying to use this box to host Docker containers for a system that would use my WS+API server, and since Node has a great track record with Docker on Linux, I knew I could get it up and running easily via docker compose
. With the loss of that server, and the shitty support for Docker containers “as a service” (I am fully aware of the reasons) on Windows servers, I thought my project might need to be shelved unless I could somehow swing support and higher level credentials. With Deno, though, applications can be compiled to executables. At that point, the end result would be as native to Windows as anything else, meaning I would have more options to create a workable ecosystem than I could if I had to rely on Docker in a virtual machine on whatever Windows server I could requisition.
So far, I have the basic WebSockets and RESTful API parts working. Clients can connect via either avenue, and my dynamic handler system is working perfectly fine; this was one area where I was able to streamline things significantly, so writing handlers in the future for either approach is going to be a lot easier going forward. I still need to convert my general data access scheme to Deno, but there’s a lot of heavy lifting I need to do before I get to that point. Right now I’m taking a breather, reviewing my progress, reading up on additional Deno documentation, and, of course, writing this post.