From Node to Deno
dev.to
dev.to
My brief skimming of its site indicates that its security model is based around the ability to disable, say, network access for whole Deno programs. However, does it allow starting up with network access, allowing a subset of the program to handle it, and then dropping those rights for the rest of the program, _especially_ subdependencies?
I can't see any mention of a more fine-grained approach: https://deno.land/manual/getting_started/permissions
A modern Node.js web service will have hundreds, if not thousands, of indirect dependencies. Some network access will be required for at least the Express routing, or an equivalent.
For a Deno equivalent, this would amount to enabling network access for all hundreds of those subdependencies, not reproducing the isolation in capability-based security such as WebAssembly nanoprocesses.
Have I missed something here? That doesn't seem like much of an improvement over Node.js except for very small and contained programs. Yes, Deno's dropping of centralised package repositories and package.json might alleviate this problem _somewhat_, but the same fundamental issue seems to remain.
What about keeping the network allowed in the layer handling, say, inbound HTTP connections, but blocking it in the data access layer or purely computational component?
From what I can see, this doesn't work with global boolean flags in the runtime, instead requiring isolated tasks with whitelisted capabilities passed in, some form of "immutable, set-once, dynamically-scoped capability flags", or something like that.
The problem with the global boolean flag approach is that if any part of a service needs it constantly, the entire program gets it, even obscure subdependencies for generating colour pickers.
Don't get me wrong, it's an incremental improvement over's Node.js blase approach. It's also quite niche to see languages support this feature. E was one of them. There was another newer Python-like language with this too, starting with an `M`, but its name escapes me.
I'd recommend Deno's developers look at E a bit more before committing too much to the platform boolean flag approach. Or I've misunderstood their approach and it actually does more than I'm giving it credit for.
What would be really useful is if only sections of code can be delineated as requiring certain permissions. This way, it's much easier to see what parts of the code do what and also to make sure that users only get prompted for such permissions when the code actually runs.
While the grant is a blanket grant to a program and all dependencies, typically, a well behaved program will seek a limited scope of permissions. Like "https://my-program.com", "https://preferred-analytics.com" etc.
This prevents dependencies from using call back locations that are outside the permitted list, preventing much of the nefarious activity they can dream up.
If a dependency needs access to specific resources, it can advertise this fact and the parent module can in turn request this from the user.
Importantly, the user is explicitly aware of these & controls it in an absolute sense, at run time.
If I was just spitballing an ideal scenario, I’d suggest that each module would define what it needs, and then some sort of central file would be built to hold the aggregate of them (urls / modules), for easy scanning.
The reason I’d rather have it in the app is if you are switching platforms, you don’t need to worry about firewall configs being exactly the same, or being fine grained. Also you might be whitelisting up ranges on the network level, then locking it down further on the app level.
I mean, I guess I see value there for the use case of "I want to download a script to run locally on my machine" type of thing, but for the most common use of Node, i.e. I'm running a server process, does this really even matter?
The inbound is a run-time decision and dynamic at that - Firewalls, WAFs etc. are used for control. These are not (and probably should not) be set by the application author, but by the application operator.
The outbound however, is typically something that is designed into the application - it should be specified by the author, be available for auditing - both on first install and all subsequent changes. IMHO, this is where these whitelists shine.
For the server example you mention, whitelists don't prevent a malicious dependency from using your CPU for mining. With deno, by default, there is no way to dial-home the proof-of-work and collect the reward. Eventually, as the operator of the service, you'll notice a performance/cost problem and detect the malicious activity.
I downloaded Deno and tried it out, but at this point I'm just left thinking it doesn't really add anything for me that I need.
SUBCOMMANDS:
bundle Bundle module and dependencies into single file
cache Cache the dependencies
completions Generate shell completions
doc Show documentation for a module
eval Eval script
fmt Format source files
help Prints this message or the help of the given subcommand(s)
info Show info about cache or info related to source file
install Install script as an executable
repl Read Eval Print Loop
run Run a program given a filename or url to the module
test Run tests
types Print runtime TypeScript declarations
upgrade Upgrade deno executable to given version
What would it take to get Node to do all of that? Which documentation tool would you choose, and how would you configure it? Testing? Bundling? Formatting?I also think this is a problem area where the Romejs project is taking a better approach: simplify and fix the toolchain, instead of replacing the entire runtime.
For any given nut in that stack, there is hundreds of different iterations. Even if you can manage that, it changes between projects and people. It is horrible mess if you think about it from company point of view. Everyone who comes to new team, has some own quirks and ideas about that stack, which just makes it even worse.
The whole create-react-app was done precisely because that stack keeps changing like sheep's wool. It tries to do same thing. Having a standard way is better for developers and companies, and Deno having many of these builtin removes friction because of that.
Stop using JS for a year, and come back only to discover that half of the tools you were using back then is at best obsolete or even deprecated and unmaintained, or that their API changed so much in a major version that you can't recognize it (see the Babel6 — AKA Babel Vista— update).
And since you're not only using these tools but also their integration to your favorite editor, you cannot stick on the old version for long, unless you stop upgrading your editor altogether).
For some reason, we continue to replay this fight in each and every language and environment.
When node appeared, it was a 'huge thing' because you could run JS consistently on the server-side, with some kind of packaging scheme.
Most of the things you listed aren't going to be useful to most, even when they are, they are small things that can be managed otherwise. Though admittedly, everyone will runt into at least one of those issues.
For a large, complex deployment, there's no obvious reason at all to shift to Deno.
We'll have to wait and see how it works out for those who want to try it for fun.
1) You need a secure sandbox for running JavaScript (e.g. You run a SaaS and you want your users to be able to customize something). NodeJS has to be sandboxed at VM level like Python, even although JavaScript was designed for this very purpose.
2) You want a TypeScript first nodejs.
3) You want isomorphic JavaScript between the browser and the server, because node does its own thing (for historical reasons) and deno strives for compatibility where possible.
What it lacks is npm compatibility. That is the JavaScript community for better or worse and without being able to use those libraries it doesn't seem compelling to me.
Or rather, they might be 'big' for a few teams in specific areas, but they are not 'big market opportunities' not even close.
The ability to go from Java+Spring OR Perl/PHP - and then have the choice to actually do JS on the server is a big deal - and that's why Node.js was a success.
The sandbox is nice but I don't see it being a big opportunity just yet. 'Running your own SaaS with untrusted code' is a developing area, and I'm not sure of Deno actually is a solution (how does one integrate with it). Also - Node.js does have some options there in the form of VM2. The real security hasn't been validated just yet either.
'TS first' - I think is just a toolchain and packaging optimization. We're all going to be running some kind of process for bundling and packing, it requires no effort to transpile in to JS at that point.
The isomorphism again, is a neat feature.
In other words, Deno has some cool new features in the context of 'the market/users space' for server side JS, whereas Node.js actually founded and created that entire market category.
Under normal circumstances, I wouldn't bet on any movement towards Deno, just because there are too many legacy Node.js already there, but since there seems to be a strong fanboy following, I don't doubt a lot of young devs push for it to be used because it's cool and shiny. All 'shiny new toys' have some benefits here and there, it's just a matter of contextualizing them into the incumbent landscape.
Also makes the system fundamentally unattractive to malware authors whereas installing modules via node.js is like leaving your front door open and taking a chartered holiday while hoping for the best.
Any operating system with "capabilities", "namespaces", "privileges", etc.
That includes Linux, Windows and many others.
If deno let's you design a program that acts like Qmail with all its separation of concerns and permissions but with one binary, that's a plus.
It seems that Deno is probably a nicer, overall version of Node, and if we were 10 years ago, undoubtedly, we would chose Deno. But we're not, we have massive installed bases and operating capabilities, so the choice is less obvious.
For most systems, the raw performance of the JS just is not a bottleneck.
Deno's extremely simple import semantics and built-in tooling could make it a great environment for a learner. They won't have to be exposed to package management, they can just click a URL in their source code and see the exact source code they just imported.
All the stuff that professional developers can do and might want to do for themselves (setting up testing frameworks, pinning transitive dependency versions) is kind of out of scope for educational use. Unless that education is specifically targeted at "how to do modern JS development within this specific ecosystem".
The ease of bringing TypeScript into your Deno programs may make it easier to start "graduating" to static typing as part of the curriculum, without being like "okay, that was Python! Next up: Java!"
[1]: Say what you want about JavaScript, I don't see that modern JS is significantly worse than Python for learning to program (having watched a couple of students go through Python courses). All languages have their warts.
1. Deno lacks the library eco system which is required to build a production level app today. I am not saying it cant be done. Just think of the different third party service integration modern application has to do, their maintainance and testing by individual vendors or open source contributors ! Blogs mentions few DB driver libraries, i highly doubt they are as mature.
2. Deno's security model overly hyped at least i see it this way. In last few version of NodeJS, many security flaws have been addressed, see their changelog if you dont believe me. But most importantly, security flaws with native JS is handled by V8 which is common to both NodeJS and Deno. On top of that, most of the libraries and frameworks in NodeJS during their various releases sorted out many security issues in their code. If someone is still doubtful they can use eslint-plugins for sanity and security checks in their JS files. Adopting Typescript also helps if you cant live without types.
3. Learning curve to adopt new SDK for Socket API, File API, System call API etc. I don't think NodeJS falls short significantly anywhere, in fact it provides more and those APIs have been relatively more battle tested over the years.
4. Irrespective of using NodeJS or Deno, following a BDD/TDD practices to ensure sound test coverage of your business use logic still remain the most promising tool to make, break and refactor your codebase.
https://nodejs.org/dist/latest-v8.x/docs/api/util.html#util_...
It was introduced in NodeJS 8.x release. The latest LTS version is 12.x
But that does not mean NodeJS does not take care of it or at least provide the some API to tackle that issue.
In this case, Stream interface API in NodeJS are the solution to the problem. They help you build layer over native EventEmitter interface or rather strictly speaking they help extend it.
This links might be helpful for you.
Backpressuring in Streams - https://nodejs.org/es/docs/guides/backpressuring-in-streams/
Official Docs on Stream API - https://github.com/nodejs/node/blob/master/doc/api/stream.md
Note that HTTP server in NodeJS already implements it.
It also is something of a moot point anyways, because people pushing something into production already are probably not using something so new. Early adopters don't care about how mature the ecosystem is; part of the appeal of being an early adopter is helping to build that ecosystem...
If someone is eager to try out new possibilities in Deno and their application use cases intersect well with what Deno has to offer, then by all means it's a good decision.
I tried installing Deno, and I tried including a package, and immediately the TypeScript typechecking in my IDE (VSCode) failed, of course. The IDE doesn't know what to do with a URL as a dependency. Is this something Deno will be able to handle?
The next question is that TypeScript packages I have written in the past use `baseUrl` and `paths` in their own configs to allow for absolute import paths. When I look at third-party Deno repositories, I didn't see any that were building out to JS/declaration files, and were instead just meant to be included as the original TypeScript source code. Won't this break things like absolute import paths and other behaviors of the dependency's own tsconfig file, or does this work fine with Deno/TS?
Looks like there's a plug-in. Can't research much now, but I hope there's something like that for emacs tide.
* https://marketplace.visualstudio.com/items?itemName=axetroy....
https://news.ycombinator.com/item?id=23172483
https://news.ycombinator.com/item?id=23093737
https://news.ycombinator.com/item?id=22102656
https://news.ycombinator.com/item?id=20373430
https://news.ycombinator.com/item?id=17183241
Let me know if I've missed any.
But, once you are writing code in TS, then transpiling that to JS, then running that in V8 on a server, you have taken a big step into the Rube Goldberg dimension.
There are really two things I want to put out there:
1) My opinion is that about 90% of your standard, day-to-day queries work just fine in a good ORM. The developer _should_ know enough about the DB schema and SQL to handle the other 10%. (In our 10 y/o enterprise software, the only queries we really drop down into SQL for are complex windowed reporting queries.)
2) Eloquent ORM is... different. It's probably the best I've seen. I wish it existed in other languages. Sequelize, which may be the "best" in the JS ecosystem, doesn't hold a candle to Eloquent, IMO.
In particular, I think relations are great to work with in Eloquent.
That's only true if you need the "relational" part of an ORM - mapping a flat list of values onto a structure of related objects. If you only need to map a flat list of values onto a single object's fields, you don't need a query builder.
I wrote a C# SQLite library along those lines: raw SQL and simple object mapping. https://github.com/zmj/sqlite-fast
Why is that a benefit though? Why would I rather learn some custom query builder-specific DSL when SQL is at least mostly standardized pretty much everywhere. The use of template strings in JS can get rid of all the problems of just plain string concatenation for SQL (e.g. it can prevent SQL injection, enable safe dynamic queries, etc.)
However, Eloquent absolutely is a class above the rest. Honestly one of the best I've ever worked with.
If it's still being debated, isn't that a good indication that there's no perfect solution for every use case?
ORMs are likely more straightforward when you know you're not going to need to do anything advanced, but get in the way when you need fine grained control for example.
I contrast that with technologies that are easy to use when you're small, but then let you layer additional pieces on later when you need to scale, without needing to redo everything.
What most people discover to be the greatest benefit of using an ORM is the "mapper" bit (converting tabulated data into an object graph and visa versa) and, to a lesser degree, change-tracking.
Somewhat ironically, the overwhelming majority of the time criticism of ORMs is directed at neither of the above, instead pointing to query performance.
You can have data mapping, you can have change-tracking, you can even have schema migrations without opting-in to the pain points many ORMs introduce because these are all somewhat orthogonal concerns.
At the end of the day there is very little to be saved between writing:
users->where(u => u.name === "John")
and SELECT * FROM users WHERE [name] = 'John'
Often, as queries become more complex, the SQL is actually a shorter expression than whatever query DSL comes with the ORM.I think that the "query builders" though are just one piece of the ORM that you mention, alongside the change-tracking, data mapping, etc. Having a decent query builder that isn't abstracting away too much of the underlying sql (essentially just mapping 1-to-1) plus data mapping are the sweet spot for me personally.
Interestingly, it is this exact property (composition) that creates the most common problems when using an ORM.
Composition is often at odds with optimization.
Under the hood many[0] ORMs simply construct a query similar to my example above and then convert result set of tabulated strings to the appropriate types (usually using reflection).
This means two things:
First, that the "type-safety" portions of an ORM are really located in the "mapping" code, so not really related to querying.
And second: you don't really have type safety. A database schema could change at any time and break the code even if static analysis seems to think it should work.
[0] Notable exceptions are languages that offer type providers (e.g. F#) but I digress
Ultimately it's useless for both.
Lots of app developers need simple and easy to setup database access so that they can focus on the parts of their app that matters. Not having an ORM means that a decent chunk of them will move on to another language/ecosystem that has the libraries they want.
... As long as there is enough interest still in Node to maintain it. Hopefully, there is still interest in running the same language as browsers do, without additional dependencies.
Personally, I would prefer Clojurescript to TS, but not strongly enough to want to be tied to its fate.