I/O
TCP Server
How Node.js's net.createServer compares to net.Listen (Go), TcpListener (Rust), Network.framework (Swift), and ServerSocket (Java) for a TCP echo server.
- Minimum versions
- Node.js ≥ 12.20Go ≥ 1.0Rust ≥ 1.65Swift ≥ 5.7Java ≥ 25
- Verified on
- Node.js 24.12.0Go 1.27.1Rust 1.98.1Swift 6.2.4Java 25.0.4.1
Node.js code is an ES module: save it as .mjs or set "type": "module" in package.json.
A TCP server just needs to: listen on a port, accept connections, then read/write data on each one. Node.js handles each socket with events; Go and Rust handle each connection in its own thread/goroutine (Rust has no goroutines — thread::spawn creates a real OS thread, which is heavier; matching a goroutine's lightness needs an async runtime like tokio). Swift uses Network.framework (Apple-only; Foundation has no socket API of its own). Java 25 handles each connection with a virtual thread (Thread.ofVirtual(), finalized in Java 21) — nearly as cheap as a goroutine, no thread pool needed.
Echo server
import net from 'node:net'
function handler(socket) {
socket.write('Received: ')
socket.pipe(socket)
}
const server = net.createServer(handler)
server.listen(3000)package main
import (
"bufio"
"net"
)
func handler(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
for {
message, err := reader.ReadString('\n')
if err != nil {
return
}
conn.Write([]byte("Received: "))
conn.Write([]byte(message))
}
}
func main() {
listener, err := net.Listen("tcp", ":3000")
if err != nil {
panic(err)
}
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
panic(err)
}
go handler(conn)
}
}use std::io::{BufRead, BufReader, Write};
use std::net::TcpListener;
use std::thread;
fn handler(stream: std::net::TcpStream) {
let mut writer = stream.try_clone().unwrap();
let reader = BufReader::new(stream);
for line in reader.lines() {
let Ok(line) = line else { break };
writer.write_all(b"Received: ").unwrap();
writer.write_all(line.as_bytes()).unwrap();
writer.write_all(b"\n").unwrap();
}
}
fn main() {
let listener = TcpListener::bind("0.0.0.0:3000").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
thread::spawn(move || handler(stream)); // a real OS thread per connection
}
}import Network
let listener = try NWListener(using: .tcp, on: 3000)
listener.newConnectionHandler = { connection in
connection.start(queue: .main)
receive(on: connection)
}
func receive(on connection: NWConnection) {
connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { data, _, isComplete, error in
if let data, !data.isEmpty {
connection.send(content: "Received: ".data(using: .utf8)! + data, completion: .contentProcessed { _ in })
}
if isComplete || error != nil {
connection.cancel()
} else {
receive(on: connection) // wait for the next chunk
}
}
}
listener.start(queue: .main)
dispatchMain()void handle(Socket socket) throws Exception {
try (socket;
var reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
var writer = new PrintWriter(socket.getOutputStream(), true)) {
String line;
while ((line = reader.readLine()) != null) {
writer.println("Received: " + line);
}
}
}
void main() throws Exception {
try (var server = new ServerSocket(3000)) {
while (true) {
Socket socket = server.accept();
Thread.ofVirtual().start(() -> { // a virtual thread, not a real OS thread
try {
handle(socket);
} catch (Exception e) {
// connection closed or errored
}
});
}
}
}$ echo 'hello' | nc localhost 3000
Received: helloReference: github.com/miguelmota/golang-for-nodejs-developers#tcp-server