Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Node.js Fundamentals: Web Server Without Dependencies (bloomca.me)
192 points by bloomca on Dec 23, 2018 | hide | past | favorite | 41 comments


IMHO articles like this played a major role in Node's initial success. After years of working with ever-growing frameworks with huge learning curves, it was so refreshing to get your first thing running in few seconds. Felt like basic in the 1980s or PHP in the late 1990s.


IIRC before nodejs+express the req/resp/next paradigm and middleware paradigm had never been so accessible. Other frameworks had it of course, but it was often hidden behind layers of abstraction or little quibbly bits. Express gave you just what you needed for both middleware and handlers:

    app.get('/', function(req, res, next) {
      res.send('hello world');
    })
For comparison, flask:

    @app.route("/", method='GET')
    def hello():
        return "Hello World!"
It's really close, but I find the NodeJS approach even simpler and easier to understand. Need to access the request? use `req`. Need to access the response to set headers or something? use `res`. Is your function actually a middleware? use `next`. It doesn't get much simpler, assuming you understand node's semantics well (as in "return"ing a value doesn't mean it gets sent).

Django/Rails required whole project scaffolds and usually a separate routes and handlers files and knowing a whole bunch of stuff. I'm not even going to get into what frameworks like Spring/Struts required.

NodeJS made it dead simple to get a HTTP server up and running in something like <10 lines of code.

People shit on JS and NodeJS every chance they get, but innovations like the amazing standard library (and express), along with npm's ease of use really pushed the field forward IMO. While I'm praising node I might as well say that node got decent bolt-on (gradual) type system support way faster than the other popular dynamic languages as well, but they got there through transpiling which also looked (is?) ridiculous the first time it became well-known. I find Typescript much more approachable than typed python[0]. Is typed ruby even a thing[1]? I don't keep up with ruby so I'm not sure.

[0]: https://docs.python.org/3/library/typing.html

[1]: https://bugs.ruby-lang.org/issues/9999


I agree for the most part, except comparing node/express to Rails: node's equivalent in the Ruby world would the Sinatra, which is about as succinct than node.


You're right -- I didn't mean to compare them in terms of size/functionality but rather just as other options that were out there and why people might have picked node in the presence of other good alternatives.


> innovations like the amazing standard library

Excuse me?


I would agree that the node standard library is quite good compared to many other scripting languages’. It’s nothing on the gold standard (python), but light years better than what is included with stock browser-side JS.


>Need to access the request? use `req`

Well, I don't see what's especially difficult about this?

    from flask import request

    @app.route('/login', methods=['GET', 'POST'])
    def login():
        if request.method == 'POST':
            return do_the_login()
        else:
            return show_the_login_form()
I would actually argue that this is better, since I have to type "request" only once.

Although I have to admitt, the middleware thing looks quite composable.


Other people basically covered it but yeah my problem is how `request` comes out of nowhere. The NodeJS snippet is way more obvious, treating the two handlers as separate (`app.get`, `app.post`) is cleaner, and it's immediately obvious what's happening, no need to know about `flask.request` to undrestand the code. I have come to abhor non-obvious code, and on that metric the flask code loses by a step in my mind.

If we really want to get in the weeds, we could talk about python 2.x's floundering around async IO -- IIRC Twisted/Tornado/Gevent/Eventlet were all close to equally good options (and thus mindshare was split) while node had it built in, with a virtualenv-by-default packaging system. Also there's WSGI -- the interoperability it provides is cool, but also means more moving parts than a simple node+express setup.

I don't want to make this a nodejs vs python thing, because that's a really dumb conversation (argument) to try and have, but personally node+express is what I pick over python+flask any day of the week for spinning up simple "just-good-enough" web services. These days I've been checking out Molten and Falcon (I'm a huge fan of pypy) but when I need to build a quick server I just pick up node+typescript.


I'm not a huge fan of decorators since they obfuscate the state of the runtime. What exactly does the call stack look like when login gets invoked? What variables are in scope? I find it difficult to reason about.

I understand that this function will execute when the HTTP server receives a GET or POST request at `/login`. The semantics of the decorator is clear-- I don't particularly take issue with that.

But the second my assumptions about the decorator break down (e.g., I think I've defined a decorator properly, but I have not), all bets are off on how to make it function properly. It's not as simple as going to the source code for the module and see what API's I'm calling.


Well, You'll be happy to know that a decorator is literally just a function that takes another function as its first argument.

    @app.route(...)
    def login():
        ...


Is literally the same thing as -

    def login():
        ...

    login = app.route(login, ...)

Hopefully that clears up the air a bit?


It clears it up but personally, I'd rather just write `app.route(login, ...)`. Isn't a lot clearer to use more conventional language features? What exactly is this buying me?

I don't know if me saying 'Just, why?' is a good enough reason to throw my arms up and say it's an idea I'm not a fan of. I'm willing to say that it's a rather subjective attitude.

I guess my retort is that, it's far more confusing to have that syntax, then explain what it meant, when I could've just used the conventional syntax.

It's not like it's offering me something substantially simpler. For example, the spread operator that was introduced. It actually allows you to write clearer JavaScript code. This syntax isn't intuitively clear like the rest operator is.


Sinatra and express brought something very important and retaught me something: value simplicity. I can't imagine how a lot of projects would look like without their influence.


People love Rails/Django because of active records ORM.

No idea if JavaScript has similar ORM


Its not as opinionated as rails/django but there's typeORM among others


TypeORM exists, but I hope you really, really like having it own your schema. Its support for migrations, rather than "uh, just let us do our thing!", is sneaky-not-great. There's no documentation, for example, for how anything in a TreeRepository actually is structured in the database, and the recommendation is "let us run the DDLs on a scratch database, you can puzzle it out from there", which should make your blood run cold.

I think a lot of the decisions in TypeORM are good, but I wouldn't trust it to own my schema under any circumstances (because now even if I'm doing things sanely I have to ask if a version bump is going to break compatibility in a way that would be "magically" fixed if I just let it chew on my schema directly).

The least-bad NodeJS ORM I have found is Objection.js, but that's a lukewarm recommendation. Its TypeScript support--which, IMO, is critical--is acceptable, but the library itself is a little cumbersome.


I've actually had great success with TypeORM now on various personal and client projects... Maybe I haven't used all the features you have used (like I've never touched TreeRepository...), but I've never had a problem with the migrations because I just write my own, and I write my own models, etc. TypeORM has had the best combination of fullblown ORM and just-build-my-query-intelligently that I've seen to date.

TypeORMs migrations are pretty simple, just write your own migrations? I only use TypeORM to generate the migrations (i.e. `typeorm migration:create -n <name>`, then open the generated file and write `await queryRunner.query(`....`);` and craft the migration myself, and make sure the changes are reflected as need by in the related models.

TypeORM is actually a better ORM than a lot of the options out there. I remember using C#'s EntityFramework (this was during the move from .NET 4.x to core/standard) and I kept wishing I was using TypeORM over it -- the annotations in EF were half-baked (to be fair they were still porting functionality), and it was just shitty all around. Then again, C# is my weakness so maybe I just didn't have enough skill.


Not 100% related to the article but koa with its maybe 300 LoC is hardly a dependency. However in general nodejs projects definitely suffer from dependency bloat. I encourage anybody to try for themselves to go through node_modules in their own projects, it’s quite englightning to see the scale of the unnecessary, duplicated, reimplementated in different ways crap there. Conscious aim at shallow, minimal or no dependency in packages and end apps is definitely something good; less than 5s installs, code audits, more healthy projects in general. I’m not saying it’s always possible or better to trim the code fat, but if you’re aware of dependency tree single root dep introduces you’re usually able to cut it down a lot.


Koa is appreciated to it's true value by the community.

Express was the most popular at the start and after StrongLoop got acquired by IBM it made sense for everyone that express was the foundation of Node.JS.

It's sad knowing how much bloated express is and every single Node.js library uses it from Next to GraphQL server etc...


700 LoC and 40 of its own dependencies.


I still write little http servlets like this sometimes, especially for something like gluing IoT services together.


Great article. Anyone working with Node.js should be familiar with these fundamentals. It also demonstrates the simplicity (and dare I say "elegance", though that's subjective..) of Node.js and its concepts.

These days my favorite library for writing an HTTP server is micro [0] (and a few middlewares). I like how it provides a minimal, functional layer of abstraction.

[0] https://github.com/zeit/micro


There are a lot of gotcha's when rolling your own web server based on the http and https packages and I think the conclusion of the article could have gone further in addressing what they are and in what context they might matter. I recently wrote a little zero-dependency NodeJs http/s server[1] with an equally dumb router for some dev tools which could benefit from a simple, promise based api, for most other use cases I'd just pull in express or something more mature.

[1]https://github.com/8eecf0d2/decade


Love this. There was this project along the same lines, from the olden days of node:

https://github.com/Raynos/http-framework


It's nice to get away from frameworks from time to time. If anyone is interested in something similar written in Java I recently created a couple of examples based on Undertow. They are available at https://github.com/tofflos/undertow-examples.


Performance is also a major advantage. Express is extremely slow compared to a basic "native" API without dependencies. If you check out the results in numbers, you'll be amazed.


Please post some citations to back up your performance claims. I haven’t found strong numbers online.


> Please post some citations to back up your performance claims.

According to TechEmpower benchmark[0],Node.js-MySQL is two time faster than express-mysql.

Others benchmarks of TechEmpower tend to point exactly the same result in terms of performance for express.

[0] https://www.techempower.com/benchmarks/#section=data-r17&hw=...


Express is not actually that much slower - I tested it when I wrote a light-weight Express replacement[1] for my own use (which was mainly about reducing the number of dependency packages from 40+ to 2). IIRC the slowest part was the default 404 router.

I was testing with only a few routes though - maybe it gets slower with more (IIRC Express architecture is that all routes are run in serial, so those defined last have to go through all the previous routes first). That and I didn’t add any latency to my tests to make it like the real world - that always has an interesting effect on results. :) Maybe I should re-run those tests for fun …

[1] https://github.com/borisovg/node-web-framework/blob/master/R...


For performance, I prefer Fastify rather than Express (https://www.fastify.io).


The crimes of these kinds tutorials:

- Downplaying the role of error handling

- Treating the subjects they cover as a black box

- Not warning that the code examples are not suitable for production


you can do the same (except loading certs) with python3 these days thanks to asyncio.


But can it left pad a string?


Please don't do this here.



JavaScript has come a long way in recent years, but the lack of a comprehensive standard library is still absolutely mind boggling.


JavaScript's standard library has a fair amount.

Not Python levels of completeness, but near Java levels.

The parent's example is String.prototype.padStart() [1]

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


This is really pretty sad. A standard library is paramount to a stable and easy to develop ecosystem.


It may be, but this is a weird thread to bring that up since Javascript has padStart, and TFA is about building a web server with only the standard library.


the OP was referring to this incident https://github.com/stevemao/left-pad/issues/4

where a very basic package everyone depended on was yanked from npmjs.

azer's original post: https://news.ycombinator.com/item?id=11340510


What are you missing from the native language + lodash?


While it's a valid point that lodash provides a lot, my point is that with any other language I've ever used, I don't need to add something like lodash.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: