Node.js vs Play Framework
VS
Node.js: server-side JavaScript runtime environment; open source; single threaded; non-blocking I/O.
express.js: the most popular web framework for Node.js.
Play Framework: Java/Scala web framework; open source; multithreaded; non-blocking I/O.
Former Play Tech Lead at LinkedIn. Long time Node.js user.
Yevgeniy Brikman
The framework scorecard
Learn
Develop
Test
Secure
Build
Deploy
Debug
Scale
Maintain
Share
1
For each feature we discuss...
Much worse than most frameworks
About the same as most frameworks
Much better than most frameworks
510
The framework scorecard
Learn
Develop
Test
Secure
Build
Deploy
Debug
Scale
Maintain
Share
Node.js: 1-click installers for every OS
The “Hello World” Node app: 1 file, 6 lines of code.
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
server.js
The “Hello World” Express app: 1 file, 8 lines of code.
var express = require('express');
var app = express();
app.get('/', function(req, res){
res.send('Hello World');
});
var server = app.listen(1337, function() {
console.log('Listening on port %d', server.address().port);
});
server.js
Run using node <filename>. Starts instantly!
Hit http://localhost:1337 to test
Node Beginner Book, Mastering Node.js, Node: Up and Running, Node.js in Action
Express Web Application Development Express.js Guide
And much, much more Tons of resources; very gradual learning curve.
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10
Play: download from playframework.com, extract, add activator to your PATH
Generate a new app using activator new
The “Hello World” Play app: ~35 files and folders
Run the app using activator run
(Downloading all dependencies can take a while the first time around)
Hit http://localhost:9000 to test
Play Framework Documentation
Activator Templates
Play for Scala Learning Play Framework 2
Ultimate Guide to Getting Started with Play. Not as many resources; steep learning curve.
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
Routing
GET clients/:id Clients.show(id: Long)
def show(id: Long) = Action { request =>
getClient(id).map { client =>
Ok(views.html.clients.show(client))
}
}
app.get('clients/:id', function(req, res) {
getClient(req.params.id, function(client) {
res.render('show', client);
});
});
RESTful routing. Extracts query & path params.
RESTful routing. Extracts query & path params. Type safe. Actions are composable. Reverse routing.
Templates
@(name: String, headline: String)
<div class="client">
<h1>@name</h1>
<div class="headline">
Headline: @headline
</div>
</div>
<div class="client">
<h1>{{name}}</h1>
<div class="headline">
Headline: {{headline}}
</div>
</div>
Many template options: handlebars, mustache, dust, jade, etc. Most support client-side rendering!
Twirl templates are compiled into Scala functions: type safe and composable! Other template types via plugins.
i18n
Translations: i18next-node, i18n-node. Formatting: moment.js, numeral.js.
Translations: Play i18n API. Formatting: Java formatting libraries.
<div class="client">
<h1>{{name}}</h1>
<div class="headline">
{{t "headline.label" headline=headline}}
</div>
</div>
@(name: String, headline: String)
<div class="client">
<h1>@name</h1>
<div class="headline">
Messages("headline.label", headline)
</div>
</div>
Form binding and validation
Forms, node-formidable, validator.js. Play form binding and validation API.
var regForm = forms.create({
name: fields.string({required: true}),
age: fields.number({min: 18})
});
regForm.handle(req, {
success: function(form) { ... },
error: function(form) { ... }
});
val regForm = Form(mapping(
"name" -> nonEmptyText,
"age" -> number(min = 18)
)(UserData.apply)(UserData.unapply))
regForm.bindFromRequest.fold(
err => BadRequest("Validation error"),
data => Ok(s"Hi $data.name!")
)
JSON, XML, File Upload
bodyParser, xml2js, node-formidable. Play JSON, XML, File Upload APIs.
// Automatically parse application/json body
app.use(bodyParser.json());
app.post('/clients', function (req, res, next) {
var name = req.body.name;
var age = req.body.age;
res.send(name + " is " + age + " years old.");
});
case class Person(name: String, age: Int)
implicit val prsnFmt = Json.format[Person]
def create = Action(parse.json) { request =>
val person = request.body.as[Person]
Ok(s"$person.name is $person.age years old")
}
POST /clients Clients.create
Data
Slick, Anorm, Ebean, JPAMySQL, MariaDB, PostgreSLQ, SQLite, Oracle, SQL Server,
DB2, Derby, H2SQLSequelize, Bookshelf.js, node-orm2
MySQL, MariaDB, PostgreSQL, SQLite
NoSQLmongojs/mongoose, cassandra-client, cradle/nano, node_redis, node-neo4j
MongoDB, Cassandra, CouchDB, Redis, Neo4j
ReactiveMongo, DataStax, sprouch, play-plugins-redis, Neo4j-play, JPA
MongoDB, Cassandra, CouchDB, Redis, Neo4j
Cachingnode-memcached, connect-cachememcached, in-memory (not recommended)
play2-memcached, memcontinuationed, Play Cache, ehcache, Guava
memcached, in-memory
Schemasnode-db-migrate, node-migrate Play database evolutions
Real-time web
socket.io: server & client APIs; WebSockets, Flash Sockets, polling, etc.
Play WebSockets, Comet, and EventSource APIs. Server-side only.
// server code
io.on('connection', function (socket) {
socket.emit('msg', 'Server says hi!');
socket.on('msg', function (msg) { … });
});
def chat = WebSocket.acceptWithActor {
request => out => Props(new Chat(out))
}
class Chat(out: ActorRef) extends Actor {
def receive = {
case m: String => out ! s"Got msg: $m"
}
}
// client code
socket.emit('msg', 'Client says hi!');
socket.on('msg', function (msg) { … });
● Play is a full stack framework● Express.js is a minimal framework
● You need plugins for most tasks
● Finding good plugins takes time
● Gluing plugins together takes time
● There are defaults for most tasks
● Defaults are mostly high quality
● All defaults can be replaced
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
Unit testing: Jasmine, Mocha, QUnit, nodeunit, Expresso or Vows
Functional testing: use supertest or call server.listen directly.
var request = require('supertest')
, app = require('express')();
app.get('/user', function(req, res){
res.send(200, { name: 'tobi' });
});
request(app)
.get('/user')
.expect('Content-Type', /json/)
.expect(200);
UI testing: phantom.js or zombie.js
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10
Unit testing: junit, ScalaTest, specs2, or testng
Functional testing: use Play’s built-in functional test helpers.
"respond to the index Action" in new App(FakeApplication()) {
val Some(result) = route(FakeRequest(GET, "/Bob"))
status(result) mustEqual OK
contentType(result) mustEqual Some("text/html")
charset(result) mustEqual Some("utf-8")
contentAsString(result) must include ("Hello Bob")
}
UI testing: use Play’s built-in integration test helpers and built-in Selenium support.
class ExampleSpec extends PlaySpec with OneServerPerSuite with OneBrowserPerSuite {
"The OneBrowserPerTest trait" must {
"provide a web driver" in {
go to (s"http://localhost:$port/testing")
pageTitle mustBe "Test Page"
click on find(name("b")).value
eventually { pageTitle mustBe "scalatest" }
}
}
}
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
Security
CSRFFilter(not enabled by default!)
CSRFConnect CSRF Middleware(not enabled by default!)
XSSDepends on template engine Twirl escapes correctly
InjectionVulnerabilities: eval, setTimeout, setInterval, new Function
Few vulnerabilities of this sort
HeadersHelmet middleware SecurityHeadersFilter
Authpassport.js, everyauth SecureSocial, deadbolt, play-authenticate
AdvisoriesNode Security Project Play Security Vulnerabilities
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
Node.js uses NPM to manage dependencies and basic build commands
{
"name": "Hello World App",
"version": "0.0.3",
"dependencies": {
"express": "4.8.0",
"underscore": "1.6.0"
},
"scripts": {
"test": "node tests/run.js",
"lint": "jshint src"
}
}
Many options available for more complicated builds: grunt.js, gulp.js, or broccoli
Thousands of plugins for all common build tasks
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10
Play uses SBT as the build system. SBT is an interactive build system.
In SBT, build definitions are written in Scala! … But the learning curve is very steep.
object AppBuild extends Build {
lazy val root = Project(id = "root", base = file(".")).settings(
name := "test-play-app",
version := version,
libraryDependencies += Seq(
"org.scala-tools" % "scala-stm_2.11.1" % "0.3",
"org.apache.derby" % "derby" % "10.4.1.3"
)
)
def version = Option(System.getProperty("version")).getOrElse("0.0.3")
}
Dependencies are managed using Ivy: familiar, but slow.
Play uses sbt-web to build static content: few plugins; depends on Rhino (slow) or node.js (!).
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
Many node-friendly hosting options: Heroku, Joyent, Azure, OpenShift, Nodejitsu
Monitoring: New Relic, Nodetime, AppDynamics, node-monitor
Use cluster to run one node instance per CPU
if (cluster.isMaster) {
for (var i = 0; i < numCPUs; i++) {
cluster.fork(); // Fork workers
}
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
} else {
http.createServer(function(req, res) {
// ...
}).listen(8000);
}
Use forever, monit, or Domain to handle crashes.
Configuration: node-config or nconf
var config = require('config');
var host = config.get('dbConfig.host');
{
"dbConfig": {
"host": "localhost",
"port": 5984,
"dbName": "customers"
}
}
server.js
config/default.json
Use nginx, apache, or ATS to load balance, serve static content, terminate SSL
Client
Data Center
Reverse proxy(e.g. nginx) DB
Static server(e.g. nginx)
Node instanceNode
instanceNode instanceNode
instanceNode instance
Node instanceNode
instanceNode instanceNode
instance
Node instanceNode
instanceNode instanceNode
instance
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8
A few Play-friendly hosting options: Heroku, playframework-cloud, CloudBees
Monitoring: New Relic, metrics-play
Use the SBT Native Packager to package the app as tgz, deb, RPM, etc.
Configuration: Play comes with Typesafe Config
val host = Play.current.configuration.getString("dbConfig.host")
dbConfig = {
host: "localhost",
port: 5984,
dbName: "customers"
}
app/controllers/Application.scala
conf/application.conf
Use nginx, apache, or ATS to load balance, serve static content, terminate SSL
Client
Data Center
Reverse proxy(e.g. nginx)
Play app
DB
Static server(e.g. nginx)
Play app
Play app
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
Node.js: use IntelliJ or node-inspector to debug
Use DTrace, TraceGL, node-stackviz, and node-profiler to debug perf issues
Use winston, log4js, or bunyan for logging
var winston = require("winston");
winston.info("CHILL WINSTON! ... I put it in the logs.");
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
Play runs on the JVM, so you can use your favorite IDE to debug: IntelliJ, Eclipse, NetBeans
In dev, Play shows errors right in the browser
Use YourKit, VisualVM, BTrace, and jmap to debug perf issues
Use logback, slf4j, or log4j for logging
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10 10
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10 10
Part 1: scaling for lots of traffic
TechEmpower benchmarks. Warning: microbenchmarks are not a substitute for real world perf testing!
JSON serialization
Single query (Note: JDBC is blocking!)
Multiple queries(Note: JDBC is blocking!)
LinkedIn experience #1: Play and Node.js are very fast in a service oriented architecture with NIO.
Internet Load Balancer
Frontend Server
Frontend Server
Frontend Server
Backend Server
Backend Server
Backend Server
Backend Server
Backend Server
Data Store
Data Store
Data Store
Data Store
LinkedIn experience #2: Play is ok with blocking I/O & CPU/memory bound use cases. Node.js is not.
// BAD: write files synchronously
fs.writeFileSync('message.txt', 'Hello Node');
console.log("It's saved, but you just blocked ALL requests!");
// Good: write files asynchronously
fs.writeFile('message.txt', 'Hello Node', function (err) {
console.log("It's saved and the server remains responsive!");
});
Part 2: scaling for large teams and projects
Node.js: best for small projects, short projects, small teams.
Play: best for longer projects. Slower ramp up, but scales well with team and project size.
For comparison: Spring MVC
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
10
10
10
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
10
10
10
Maintenance: the good parts
Functional programming: first class functions, closures, underscore.js
JavaScript is ubiquitous...
...Which means you can share developers, practices, and even code: rendr, derby, meteor
Node core is (mostly) stable and mature. Bugs, regressions, and backwards incompatibility are rare.
Maintenance: the bad parts
'' == '0' // false
0 == '' // true
0 == '0' // true
false == 'false' // false
false == '0' // true
false == undefined // false
false == null // false
null == undefined // true
' \t\r\n ' == 0 // true
Bad Parts
// Default scope is global
var foo = "I'm a global variable!"
// Setting undeclared variables puts them in global scope too
bar = "I'm also a global variable!";
if (foo) {
// Look ma, no block scope!
var baz = "Believe it or not, I'll also be a global variable!"
}
Awful Parts
this keyword
doSomethingAsync(req1, function(err1, res1) {
doSomethingAsync(req2, function(err2, res2) {
doSomethingAsync(req3, function(err3, res3) {
doSomethingAsync(req4, function(err4, res4) {
// ...
});
});
});
});
Callback hell: control flow, error handling, and composition are all difficult
Many NPM packages are NOT stable or mature.Incompatibility + bugs + dynamic typing = pain.
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
10
10
10
3
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
10
10
10
3
Maintenance: the good parts
Functional programming
def sort(a: List[Int]): List[Int] = {
if (a.length < 2) a
else {
val pivot = a(a.length / 2)
sort(a.filter(_ < pivot)) :::
a.filter(_ == pivot) :::
sort(a.filter(_ > pivot))
}
}
Powerful type system
Very expressive: case classes, pattern matching, lazy, option, implicits
val NameTagPattern = "Hello, my name is (.+) (.+)".r
val ListPattern = "Last: (.+). First: (.+).".r
// Case classes automatically generate immutable fields, equals, hashCode, constructor
case class Name(first: String, last: String)
// Use Option to avoid returning null if there is no name found
def extractName(str: String): Option[Name] = {
Option(str).collectFirst {
// Pattern matching on regular expressions
case NameTagPattern(fname, lname) => Name(fname, lname)
case ListPattern(lname, fname) => Name(fname, lname)
}
}
Runs on the JVM; interop with Java.
Concurrency & streaming tools: Futures, Akka, STM, threads, Scala.rx, Async/Await, Iteratees
No callback hell!
def index = Action {
// Make 3 sequential, async calls
for {
foo <- WS.url(url1).get()
bar <- WS.url(url2).get()
baz <- WS.url(url3).get()
} yield {
// Build a result using foo, bar, and baz
}
}
Good IDE support
Maintenance: the bad parts
Slow compiler
Fortunately, Play/SBT support incremental compilation and hot reload!
Complexity
More complexity
Play is stable, but not mature: backwards incompatible API changes every release.
Even worse: Scala is not binary compatible between releases!
Backwards incompatibility = pain.… Static typing makes it a little more manageable.
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
10
10
10
3 8
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
10
10
10
3 8
351Contributors544
Watchers2,376 576
Stars31,332 5,077
Github activity as of 08/10/14
Forks6,970 1,872
PR’s3,066 2,201
10,698StackOverflow Questions53,555
Google Group Members14,199 11,577
Google Group Posts/Month ~400 ~1,100
StackOverflow, mailing list activity as of 08/12/14
18langpop.com4
TIOBE10 39
CodeEval5 12
Language Popularity as of 08/12/14
IEEE Spectrum7 17
RedMonk1 13
Lang-Index12 26
88,000 packages in NPM ~80 Play Modules
Joyent offers commercial support for Node.js
Typesafe offers commercial support for Play
Play in production
49LinkedIn1,172
Indeed3,605 179
CareerBuilder214 16
Open jobs as of 08/13/14
9,037LinkedIn82,698
Indeed2,267 206
CareerBuilder447 30
Candidates as of 08/13/14
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
10
10
10
3 8
10 7
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
10
10
10
3 8
10 7
Final score
85
84
Final score
85
84
Both frameworks are great. Decide based on strengths/weakness, not my arbitrary score!
1. You’re building small apps with small teams
2. You already have a bunch of JavaScript ninjas
3. Your app is mostly client-side JavaScript
4. Your app is mostly real-time
5. Your app is purely I/O bound
Use node.js if:
1. You don’t write lots of automated tests
2. Your code base or team is going to get huge
3. You do lots of CPU or memory intensive tasks
Don’t use node.js if:
1. You’re already using the JVM
2. You like type safety and functional programming
3. Your code base or team is going to get big
4. You want a full stack framework
5. You need flexibility: non-blocking I/O, blocking I/O, CPU intensive tasks, memory intensive tasks
Use Play if:
1. You don’t have time to master Play, Scala, and SBT
2. You hate functional programming or static typing
Don’t use Play if:
Questions?
Top Related