Node.js è un ambiente di esecuzione JavaScript lato server. È basato sull'implementazione di runtime di Google, l'interprete V8. V8 e Node sono implementati in C e C++ con un'attenzione speciale sulla performance ed il basso consumo di memoria.

Ma mentre V8 supporta principalmente JavaScript nel browser (ad esempio Google Chrome), Node ha lo scopo di supportare processi di sistema sul server.

A differenza di altri ambienti di esecuzione, un processo Node non si affida al multithreading per l'esecuzione parallela del codice, sfruttando invece un modello I/O asincrono basato sugli eventi. Un processo Node sul server è un demone basato su un singolo thread che incorpora il motore JavaScript per il suo funzionamento. Node è differente dagli altri linguaggi di programmazione che supportano gli eventi tramite librerie: Node supporta il modello ad eventi a livello di linguaggio.

Per gestire infatti le connessioni multiple da parte dei client, solitamente si impiega il multithreading per gestire l'I/O. Questo approccio permette agli sviluppatori di dividere le loro applicazioni in più attività simultanee e coordinate tra loro.

Nel caso dei server web (come Apache), i thread multipli permettono alle applicazioni web di sfruttare meglio i processori disponibili sulla macchina host. L'esecuzione di più thread su un sistema multicore permette di ottenere un semplice parallelismo delle operazioni.

In sistemi basati su un singolo core, il processore esegue un thread e quindi passa al successivo. Ad esempio quando c'è un'operazione di I/O, come un socket TCP, il processore passa dal thread corrente ad un altro thread perché tale operazione può richiedere vari cicli di clock e quindi invece di aspettare che l'operazione finisca, il processore lascia in esecuzione il thread impegnato ed esegue un altro thread in modo da non rimanere inattivo.

Quando l'operazione di I/O termina, il processore torna sul thread precedentemente impegnato in tale operazione e lo riporta allo stato di esecuzione in quanto non è più bloccato dall'operazione di I/O.

Tuttavia, il multithreading presenta dei problemi che possono essere difficili da isolare e correggere in un ambiente di produzione, come ad esempio i deadlock e la protezione delle risorse condivise tra più thread. Inoltre gran parte del controllo sul multithreading spetta al sistema operativo e quindi gli sviluppatori non ne hanno la piena gestione.

La programmazione basata sugli eventi, al contrario, offre una gestione delle applicazioni più efficiente e scalabile, consentendo agli sviluppatori di avere il controllo totale nei passaggi tra un'attività e l'altra di un'applicazione.

In questo modello l'applicazione fa affidamento alle notifiche degli eventi che vengono registrati. Quando un evento ha luogo (ad esempio i dati pronti per la lettura su un socket), il sistema di notifiche avvisa l'applicazione che può così gestire quell'evento.

Il modello di I/O asincrono impedisce quindi che l'applicazione si blocchi nell'attesa di un'operazione di I/O. In questo modo l'applicazione è a conoscenza del fatto che un'operazione può essere bloccante e verrà informata quando tale operazione ha avuto fine, ma nel frattempo non spreca risorse nell'attesa dedicandosi invece all'esecuzione di altre operazioni.

In Node il design della gestione delle operazioni di I/O è interamente asincrono. Se in passato tale design era basato principalmente sulle funzioni di callback (portando quindi al noto problema del callback hell), oggi con l'introduzione delle Promise, del modello async/await e dei generator gli sviluppatori possono sfruttare al meglio il design di Node per scrivere codice più elegante, conciso e soprattutto molto più gestibile e testabile.