Node.JS rocks when it comes to building I/O-intensive services. It is fast and easy to use. You can do the same in Dart! Out of the box it comes with a set of libraries to access the file system, create http servers, etc. To compare Node and Dart I built two versions of the simplest application imaginable.

Screenshot

The first version of the application is built using Node.JS, Socket.IO, and Angular.JS. The second version is built using the built in "dart:io" library and Angular.Dart. The versions of the application will be shown side by side. Hopefully, if you are familiar with Node and Angular, you will see that writing applications in Dart is not any harder.

Templates

Let's start with comparing templates, since they look almost identical.


<!doctype html>
<html>
  <head>
    <title>Chat</title>
    <link rel="stylesheet" href="/css/style.css"></link>
    <script src="/bower_components/angular/angular.js"></script>
    <script src="/bower_components/socket.io-client/dist/socket.io.js"></script>
    <script src="/js/chat.js"></script>
  </head>

  <body ng-app="chat" ng-controller="ChatCtrl as ctrl">
  <h2>Chat</h2>
  <div id="messages">
    <ul>
      <li ng-repeat="m in ctrl.chat.messages">
        {{m.text}}
      </li>
    </ul>
  </div>



  <form ng-submit="ctrl.sendMessage()">
    <input ng-model="ctrl.message">
     <button type="submit">Send</button>
  </form>


  </body>
</html>

<!doctype html>
<html>
  <head>
    <title>Chat</title>
    <link rel="stylesheet" href="/css/style.css"></link>
    <script type="application/dart" src="/chat.dart"></script>
  </head>



  <body chat-ctrl>
  <h2>Chat</h2>
  <div id="messages">
    <ul>
      <li ng-repeat="m in ctrl.chat.messages">
        {{m["text"]}}
      </li>
    </ul>
  </div>



  <form ng-submit="ctrl.sendMessage()">
    <input ng-model="ctrl.message">
    <button type="submit">Send</button>
  </form>


  </body>
</html>

Client Side

I've tried to keep the code simple, so you can directly compare the Dart and JS versions. There are ways to make both the versions more compact, but it will make the comparison more difficult. At the same time, I wanted it to look idiomatic. For instance, it is possible to omit all the type annotations in the Dart version, making it look more like JavaScript, but it goes against the conventions of the Dart community.

Though there are attempts to port Socket.IO to Dart, none of them seems stable enough. So instead I wrote a very thin wrapper around WebSocket - Socket. If you want to know more about how it works, check out the github repo.









function Chat(){
  var messages = this.messages = [];

  this.newUser = function(message){
    addMessage(message.name + " just joined the chat!");
  };

  this.newMessage = function(message){
    addMessage(message.name + ": " + message.text);
  };

  function addMessage(message){
    messages.push({timestamp: new Date(), text: message});
  }
}

angular.module("chat", [])







  .controller("ChatCtrl", function(socket, $scope){
    this.message = "";
    var chat = this.chat = new Chat();

    socket.on("newMessage", newMessage);
    socket.on("newUser", newUser);

    function newUser(m){
      $scope.$apply(function(){
        chat.newUser(m);
      });
    }

    function newMessage(m){
      $scope.$apply(function(){
        chat.newMessage(m);
      });
    }

    this.sendMessage = function(){
      chat.newMessage({name: "You", text: this.message});
      socket.emit("message", {type: "message", text: this.message});
      this.message = "";
    };
  })

  .value("socket", io.connect());

library chat;

import 'dart:html';
import 'dart:convert';
import 'dart:async';
import 'package:angular/angular.dart';

class Chat {
  List messages = [];

  void newUser(Map message) =>
  addMessage("${message["name"]} just joined the chat!");

  void newMessage(Map message) =>
  addMessage("${message["name"]}: ${message["text"]}");

  addMessage(message) =>
      messages.add({"timestamp": new DateTime.now(), "text": message});
}




main(){
  final module = new Module()
      ..type(ChatController)
      ..value(Socket, new Socket("ws://127.0.0.1:3001/ws"));

  ngBootstrap(module: module);
}

@NgController(selector: '[chat-ctrl]', publishAs: 'ctrl')
class ChatController {
  @NgTwoWay("message") String message;

  Chat chat = new Chat();
  Socket socket;

  ChatController(this.socket){
    socket.onMessage.listen(handleMessage);
  }

  void handleMessage(Map message){
    final handlers = {"newUser": chat.newUser, "newMessage": chat.newMessage};
    handlers[message["type"]](message);
  }




  void sendMessage(){
    chat.newMessage({"name": "You", "text": message});
    socket.sendMessage({"type": "message", "text": message});
    message = "";
  }
}


Server Side

Since there is no Socket.IO for Dart, it is a little bit more work to wire up all the components.

Please note the differences in the APIs. All the Dart APIs return futures and streams, whereas Node APIs are callback-based.




var http = require("http");
var fs = require("fs");
var path = require("path");
var mime = require("mime");
var socketio = require("socket.io");



var server = http.createServer(serveStatic);
server.listen(3000);
chatBackend(server);

function serveStatic(request, response){
  var absPath = request.url == "/" ? "public/index.html" : "public" + request.url;

  fs.exists(absPath, function(exists){
    if(exists){
      sendFile(response, absPath);
    } else {
      send404(response);
    }
  });

  function sendFile(response, filePath){
    fs.readFile(absPath, function(err, data){
      if(err){
        send404(response);
      } else {
        response.writeHead(200, {"Content-Type" : mime.lookup(path.basename(filePath))});
        response.end(data);
      }
    });
  }

  function send404(response){
    response.writeHead(404, {"Content-Type" : "text/plain"});
    response.write("Error 404: resource not found.");
    response.end();
  }
}

function chatBackend(server){
  var io = socketio.listen(server);
  var users = {};





  io.sockets.on('connection', function(socket){
    registerUser(socket);
    setUpListener(socket);
  });

  function registerUser(socket){
    var name = "Guest " + (Object.keys(users).length + 1);
    users[socket.id] = {name: name};
    socket.join("chat");
    socket.broadcast.to("chat").emit("newUser", {name: name});
  }

  function setUpListener(socket){
    socket.on("message", function(message){
      socket.broadcast.to("chat").emit("newMessage", {
        name: users[socket.id].name,
        text: message.text
      });
    });
  }
}





library dartchat;

import 'dart:io';
import 'dart:async';
import 'dart:convert';
import 'package:http_server/http_server.dart' show VirtualDirectory;

final HOST = "127.0.0.1";
final PORT = 3001;

main() {
  HttpServer.bind(HOST, PORT).then((server){
    final sockets = new WebSockets();
    final chatBackend = new ChatBackend(sockets);
    var root = Platform.script.resolve('./public').toFilePath();
    final vDir = new VirtualDirectory(root)
        ..followLinks = true
        ..allowDirectoryListing = true
        ..jailRoot = false;

    server.listen((request) {
      if(request.uri.path == '/ws') {
        sockets.handleRequest(request);
      } else {
        vDir.serveRequest(request);
      }

    });
  });
}














class ChatBackend {
  final WebSockets sockets;
  final Map users = {};

  ChatBackend(this.sockets){
    sockets.onNewConnection.listen(onNewConnection);;
  }

  void onNewConnection(conn){
    registerGuest(conn);
    setUpListener(conn);
  }

  registerGuest(conn){
    var name = "Guest ${users.length + 1}";
    users[conn] = {"name": name};
    sockets.broadcast(conn, {"type" : "newUser", "name" : name});
  }


  setUpListener(conn){
    sockets.onMessage(conn).listen((m){
      if(m["type"] == "message"){
        sockets.broadcast(conn, {
            "type" : "newMessage",
            "name" : users[conn]["name"],
            "text" : m["text"]
        });
      }
    });
  }
}

Wrapping Up

I showed:

  • How to build a simple server using Node and Dart.
  • How to build a client in Angular.JS and Angular.Dart.
  • How to use WebSocket to communicate between the server and the client.
  • How to wire everything up.

If you want to know more, check out the following repositories.