That's a tricky question to answer. If you wrote a really lightweight node server to just serve static files, it would most likely perform better than nginx, but it's not that simple. (Here's a "benchmark" comparing a nodejs file server and lighttpd - which is similar in performance to ngingx when serving static files).
Performance in regard to serving static files often comes down to more than just the web-server doing the work. If you want the highest performance possible, you'll be using a CDN to serve your files to reduce latency for end-users, and benefit from edge-caching.
If you're not worried about that, node can serve static files just fine in most situation. Node lends itself to asynchronous code, which it also relies on since it's single-threaded and any blocking i/o can block the whole process, and degrade your applications performance. More than likely you're writing your code in a non-blocking fashion, but if you are doing anything synchronously, you may cause blocking, which would degrade how fast other clients can get their static files served. The easy solution is to not write blocking code, but sometimes that's not a possibility, or you can't always enforce it.
I'll have to disagree with the answers here. While Node will do fine, nginx will most definitely be faster when configured correctly. nginx is implemented efficiently in C following a similar pattern (returning to a connection only when needed) with a tiny memory footprint. Moreover, it supports the sendfile syscall to serve those files which is as fast as you can possibly get at serving files, since it's the OS kernel itself that's doing the job.
By now nginx has become the de facto standard as the frontend server. You can use it for its performance in serving static files, gzip, SSL, and even load-balancing later on.
P.S.: This assumes that files are really "static" as in at rest on disk at the time of the request.
I did a quick ab -n 10000 -c 100 for serving a static 1406 byte favicon.ico, comparing nginx, Express.js (static middleware) and clustered Express.js. Hope this helps:
Unfortunately I can't test 1000 or even 10000 concurrent requests as nginx, on my machine, will start throwing errors.
EDIT: as suggested by artvolk, here are the results of cluster + static middleware (slower):
Either way, I'd setup Nginx to cache the static files...you'll see a HUGE difference there. Then, whether you serve them from node or not, you're basically getting the same performance and the same load-relief on your node app.
I personally don't like the idea of my Nginx frontend serving static assets in most cases, in that
1) The project has to now be on the same machine - or has to be split into assets (on nginx machine) & web app (on multiple machines for scaling)
2) Nginx config now has to maintain path locations for static assets / reload when they change.
I am certain that purely node.js can outperform nginx in a lot of aspect.
All said I have to stay NginX has an in-built cache, whereas node.js doesn't come with it factory installed (YOU HAVE TO BUILD YOUR OWN FILE CACHE).
The custom file cache does outperform nginx and any other server in the market as it is super simple.
Also Nginx runs on multiple cores. To use the full potential of Node you have to cluster node servers. If you are interested to know how then please pm.
You need to deep digger to achieve performance nirvana with node, that is the only problem. Once done hell yeah... it beats Nginx.
I have a different interpretation of @gremo's charts. It looks to me like both node and nginx scale at the same number of requests (between 9-10k). Sure the latency in the response for nginx is lower by a constant 20ms, but I don't think users will necessarily perceive that difference (if your app is built well).
Given a fixed number of machines, it would take quite a significant amount of load before I would convert a node machine to nginx considering that node is where most of the load will occur in the first place.
The one counterpoint to this is if you are already dedicating a machine to nginx for load balancing. If that is the case then you might as well have it serve your static content as well.
FWIW, I did a test on a rather large file download (~60 MB) on an AWS EC2 t2.medium instance, to compare the two approaches.
Download time was roughly the same (~15s), memory usage was negligible in both cases (<= 0.2%), but I got a huge difference in CPU load during the download:
Using Node + express.static(): 3.0 ~ 5.0% (single node process)