0 to App faster with NodeJS and Ruby
-
Upload
rebecca-mills -
Category
Technology
-
view
745 -
download
1
Transcript of 0 to App faster with NodeJS and Ruby
![Page 1: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/1.jpg)
0 to App faster with NodeJS and RubyRebecca Mills @RebccaMills
Patrick McFadin @PatrickMcFadin
![Page 2: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/2.jpg)
The situation
• REST interface to Cassandra data • Support CRUD operations • It’s Friday and…
2
Umm yeah, and I’m going to need that by Monday morning.
![Page 3: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/3.jpg)
Choose your language
• Support for all Cassandra features • Easy to work with • Performant
3
![Page 4: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/4.jpg)
Fast prototype
4
![Page 5: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/5.jpg)
5
![Page 6: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/6.jpg)
Need to get this done now
6
![Page 7: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/7.jpg)
DataStax NodeJS driver
• Works with current OSS Cassandra • Apache Licensed
7
https://github.com/datastax/nodejs-driver
Based on node-cassandra-cql by Jorge Bay
![Page 8: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/8.jpg)
Get it!
8
> npm install cassandra-driver
![Page 9: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/9.jpg)
DataStax Ruby driver
9
https://github.com/datastax/ruby-driver
• Works with current OSS Cassandra • Apache Licensed
Based on cql-rb by Theo Hultberg (@iconara)
![Page 10: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/10.jpg)
Get it!
10
> gem install cassandra-driver
![Page 11: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/11.jpg)
DataStax Cassandra Drivers
• Load Balancing Policies • Retry Policies • Asynchronous • Prepared statements • Connection and cluster management
11
A Cassandra Driver should have…
![Page 12: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/12.jpg)
On to the code!
• RESTful web server • Need a few helpers
12
+ +
![Page 13: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/13.jpg)
REST methods
• Operates on the “USERS” table in Cassandra
13
POST Insert a user
GET Select a user
PUT Update a user
DELETE Delete a user
CREATE TABLE users ( firstname text, lastname text, age int, email text, city text, PRIMARY KEY (lastname));
![Page 14: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/14.jpg)
14
![Page 15: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/15.jpg)
Connection for NodeJS• Express as the web server • body-parser to get POST data
15
var client = new cassandra.Client({ contactPoints: ['127.0.0.1'], keyspace: 'demo', policies: { retry: new cassandra.policies.retry.RetryPolicy(), loadBalancing: new cassandra.policies.loadBalancing.DCAwareRoundRobinPolicy('datacenter1') } });
var express = require('express') var bodyParser = require('body-parser'); var cassandra = require('cassandra-driver');
![Page 16: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/16.jpg)
var client = new cassandra.Client({ contactPoints: ['127.0.0.1'], keyspace: 'demo', policies: { retry: new cassandra.policies.retry.RetryPolicy(), loadBalancing: new cassandra.policies.loadBalancing.DCAwareRoundRobinPolicy('datacenter1') } });
Connection for NodeJS• Express as the web server • body-parser to get POST data
16
var express = require('express') var bodyParser = require('body-parser'); var cassandra = require('cassandra-driver');
![Page 17: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/17.jpg)
var client = new cassandra.Client({ contactPoints: ['127.0.0.1'], keyspace: 'demo', policies: { retry: new cassandra.policies.retry.RetryPolicy(), loadBalancing: new cassandra.policies.loadBalancing.DCAwareRoundRobinPolicy('datacenter1') } });
Connection for NodeJS• Express as the web server • body-parser to get POST data
17
var express = require('express') var bodyParser = require('body-parser'); var cassandra = require('cassandra-driver');
![Page 18: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/18.jpg)
var client = new cassandra.Client({ contactPoints: ['127.0.0.1'], keyspace: 'demo', policies: { retry: new cassandra.policies.retry.RetryPolicy(), loadBalancing: new cassandra.policies.loadBalancing.DCAwareRoundRobinPolicy('datacenter1') } });
Connection for NodeJS• Express as the web server • body-parser to get POST data
18
var express = require('express') var bodyParser = require('body-parser'); var cassandra = require('cassandra-driver');
![Page 19: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/19.jpg)
Load balancing
19
Client 10.0.0.1 00-25
10.0.0.4 76-100
10.0.0.2 26-50
10.0.0.3 51-75
SELECT firstNameFROM usersWHERE lastname = ‘mills’;
datacenter1
datacenter1
Node Primary Replica Replica
10.0.0.1 00-25 76-100 51-75
10.0.0.2 26-50 00-25 76-100
10.0.0.3 51-75 26-50 00-25
10.0.0.4 76-100 51-75 26-50
loadBalancing: new DCAwareRoundRobinPolicy('datacenter1')
![Page 20: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/20.jpg)
Insert a user with a POST
20
app.post('/users', function (req, res) { var lastname = req.body.lastname; var age = req.body.age; var city = req.body.city; var email = req.body.email; var firstname = req.body.firstname; var query = "INSERT INTO users (lastname, age, city, email, firstname) VALUES ( ?,?,?,?,?)"; var params = [lastname, age, city, email, firstname]; client.execute(query, params, {prepare: true}, function (err, result) { if (!err) { res.send("Inserted"); } else { res.sendStatus(404) } })})
![Page 21: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/21.jpg)
Insert a user with a POST
21
app.post('/users', function (req, res) { var lastname = req.body.lastname; var age = req.body.age; var city = req.body.city; var email = req.body.email; var firstname = req.body.firstname; var query = "INSERT INTO users (lastname, age, city, email, firstname) VALUES ( ?,?,?,?,?)"; var params = [lastname, age, city, email, firstname]; client.execute(query, params, {prepare: true}, function (err, result) { if (!err) { res.send("Inserted"); } else { res.sendStatus(404) } })})
![Page 22: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/22.jpg)
Insert a user with a POST
22
app.post('/users', function (req, res) { var lastname = req.body.lastname; var age = req.body.age; var city = req.body.city; var email = req.body.email; var firstname = req.body.firstname; var query = "INSERT INTO users (lastname, age, city, email, firstname) VALUES ( ?,?,?,?,?)"; var params = [lastname, age, city, email, firstname]; client.execute(query, params, {prepare: true}, function (err, result) { if (!err) { res.send("Inserted"); } else { res.sendStatus(404) } })})
![Page 23: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/23.jpg)
Insert a user with a POST
23
app.post('/users', function (req, res) { var lastname = req.body.lastname; var age = req.body.age; var city = req.body.city; var email = req.body.email; var firstname = req.body.firstname; var query = "INSERT INTO users (lastname, age, city, email, firstname) VALUES ( ?,?,?,?,?)"; var params = [lastname, age, city, email, firstname]; client.execute(query, params, {prepare: true}, function (err, result) { if (!err) { res.send("Inserted"); } else { res.sendStatus(404) } })})
![Page 24: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/24.jpg)
Select user with GET
24
app.get('/users/:lastname',function (req, res) { var query = "SELECT lastname, age, city, email, firstname FROM users WHERE lastname= ?"; var params = [req.params.lastname]; client.execute(query, params, {prepare: true}, function (err, result) { if (!err){ if ( result.rows.length > 0 ) { var user = result.rows[0]; console.log("name = %s, age = %d", user.firstname, user.age); res.send(user) } else { res.sendStatus(404); } } }); })
![Page 25: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/25.jpg)
Select user with GET
25
app.get('/users/:lastname',function (req, res) { var query = "SELECT lastname, age, city, email, firstname FROM users WHERE lastname= ?"; var params = [req.params.lastname]; client.execute(query, params, {prepare: true}, function (err, result) { if (!err){ if ( result.rows.length > 0 ) { var user = result.rows[0]; console.log("name = %s, age = %d", user.firstname, user.age); res.send(user) } else { res.sendStatus(404); } } }); })
![Page 26: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/26.jpg)
Select user with GET
26
app.get('/users/:lastname',function (req, res) { var query = "SELECT lastname, age, city, email, firstname FROM users WHERE lastname= ?"; var params = [req.params.lastname]; client.execute(query, params, {prepare: true}, function (err, result) { if (!err){ if ( result.rows.length > 0 ) { var user = result.rows[0]; console.log("name = %s, age = %d", user.firstname, user.age); res.send(user) } else { res.sendStatus(404); } } }); })
![Page 27: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/27.jpg)
Select user with GET
27
app.get('/users/:lastname',function (req, res) { var query = "SELECT lastname, age, city, email, firstname FROM users WHERE lastname= ?"; var params = [req.params.lastname]; client.execute(query, params, {prepare: true}, function (err, result) { if (!err){ if ( result.rows.length > 0 ) { var user = result.rows[0]; console.log("name = %s, age = %d", user.firstname, user.age); res.send(user) } else { res.sendStatus(404); } } }); })
![Page 28: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/28.jpg)
Select user with GET
28
app.get('/users/:lastname',function (req, res) { var query = "SELECT lastname, age, city, email, firstname FROM users WHERE lastname= ?"; var params = [req.params.lastname]; client.execute(query, params, {prepare: true}, function (err, result) { if (!err){ if ( result.rows.length > 0 ) { var user = result.rows[0]; console.log("name = %s, age = %d", user.firstname, user.age); res.send(user) } else { res.sendStatus(404); } } }); })
![Page 29: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/29.jpg)
Update a user with PUT
29
app.put('/users/:lastname', function (req, res) { var age = req.body.age; console.log("lastname = " + req.params.lastname + ", age= " + age); var query = "UPDATE users SET age = ? WHERE lastname = ?"; var params = [age, req.params.lastname]; client.execute(query, params, {prepare: true}, function (err, result) { if (!err) { res.send("Updated"); } else { res.sendStatus(404) } }); })
![Page 30: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/30.jpg)
Remove a user with DELETE
30
app.delete('/users/:lastname', function (req, res) { var query = "DELETE FROM users WHERE lastname = ?"; var params = [req.params.lastname]; client.execute(query, params, {prepare: true}, function (err, result) { if (!err) { res.send("Deleted"); } else { res.sendStatus(404) } }); })
![Page 31: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/31.jpg)
31
![Page 32: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/32.jpg)
Connection with Ruby
• Sinatra as the web server • JSON for returning formatted results
32
cluster = Cassandra.cluster( :hosts => ['127.0.01'], :load_balancing_policy => Cassandra::LoadBalancing::Policies::RoundRobin.new, :retry_policy => Cassandra::Retry::Policies::Default.new, logger: log) keyspace = 'demo'session = cluster.connect(keyspace)
require 'sinatra'require 'JSON'require 'cassandra'require 'logger'
![Page 33: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/33.jpg)
Connection with Ruby
• Sinatra as the web server • JSON for returning formatted results
33
cluster = Cassandra.cluster( :hosts => ['127.0.01'], :load_balancing_policy => Cassandra::LoadBalancing::Policies::RoundRobin.new, :retry_policy => Cassandra::Retry::Policies::Default.new, logger: log) keyspace = 'demo'session = cluster.connect(keyspace)
require 'sinatra'require 'JSON'require 'cassandra'require 'logger'
![Page 34: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/34.jpg)
Connection with Ruby
• Sinatra as the web server • JSON for returning formatted results
34
cluster = Cassandra.cluster( :hosts => ['127.0.01'], :load_balancing_policy => Cassandra::LoadBalancing::Policies::RoundRobin.new, :retry_policy => Cassandra::Retry::Policies::Default.new, logger: log) keyspace = 'demo'session = cluster.connect(keyspace)
require 'sinatra'require 'JSON'require 'cassandra'require 'logger'
![Page 35: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/35.jpg)
Retry Policies
• Retry requests on server errors • Write Timeout • Read Timeout • Unavailable
35
![Page 36: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/36.jpg)
Connection with Ruby
• Sinatra as the web server • JSON for returning formatted results
36
cluster = Cassandra.cluster( :hosts => ['127.0.01'], :load_balancing_policy => Cassandra::LoadBalancing::Policies::RoundRobin.new, :retry_policy => Cassandra::Retry::Policies::Default.new, logger: log) keyspace = 'demo'session = cluster.connect(keyspace)
require 'sinatra'require 'JSON'require 'cassandra'require 'logger'
![Page 37: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/37.jpg)
Insert a user with a POST
37
post '/users' do begin session.execute(userInsertStatement, :arguments => [params[:firstname], params[:lastname], params[:age].to_i, params[:city], params[:email]]) "Inserted" rescue Exception => e log.error 'Error in insert a user' log.error(e) halt(404) endend
userInsertStatement = session.prepare("INSERT INTO users (firstname, lastname, age, city, email) VALUES (?,?,?,?,?)")
![Page 38: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/38.jpg)
Insert a user with a POST
38
post '/users' do begin session.execute(userInsertStatement, :arguments => [params[:firstname], params[:lastname], params[:age].to_i, params[:city], params[:email]]) "Inserted" rescue Exception => e log.error 'Error in insert a user' log.error(e) halt(404) endend
userInsertStatement = session.prepare("INSERT INTO users (firstname, lastname, age, city, email) VALUES (?,?,?,?,?)")
…
![Page 39: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/39.jpg)
Insert a user with a POST
39
post '/users' do begin session.execute(userInsertStatement, :arguments => [params[:firstname], params[:lastname], params[:age].to_i, params[:city], params[:email]]) "Inserted" rescue Exception => e log.error 'Error in insert a user' log.error(e) halt(404) endend
userInsertStatement = session.prepare("INSERT INTO users (firstname, lastname, age, city, email) VALUES (?,?,?,?,?)")
…
![Page 40: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/40.jpg)
Select user with GET
40
get '/users/:lastname' do begin result = session.execute(userSelectStatement, :arguments => [params[:lastname]]) if result.size < 1 halt(404) end result.first.to_json rescue Exception => e log.error 'Error in select a user' log.error(e) halt(404) endend
userSelectStatement = session.prepare("SELECT firstname,lastname, age, email, city FROM users where lastname = ?")
![Page 41: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/41.jpg)
Select user with GET
41
get '/users/:lastname' do begin result = session.execute(userSelectStatement, :arguments => [params[:lastname]]) if result.size < 1 halt(404) end result.first.to_json rescue Exception => e log.error 'Error in select a user' log.error(e) halt(404) endend
userSelectStatement = session.prepare("SELECT firstname,lastname, age, email, city FROM users where lastname = ?") …
![Page 42: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/42.jpg)
Select user with GET
42
get '/users/:lastname' do begin result = session.execute(userSelectStatement, :arguments => [params[:lastname]]) if result.size < 1 halt(404) end result.first.to_json rescue Exception => e log.error 'Error in select a user' log.error(e) halt(404) endend
userSelectStatement = session.prepare("SELECT firstname,lastname, age, email, city FROM users where lastname = ?")
…
![Page 43: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/43.jpg)
Select user with GET
43
get '/users/:lastname' do begin result = session.execute(userSelectStatement, :arguments => [params[:lastname]]) if result.size < 1 halt(404) end result.first.to_json rescue Exception => e log.error 'Error in select a user' log.error(e) halt(404) endend
userSelectStatement = session.prepare("SELECT firstname,lastname, age, email, city FROM users where lastname = ?")
…
![Page 44: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/44.jpg)
Quick note on Async
• Generates a Future • Non-blocking until get
44
future = session.execute_async(statement)# register success listenerfuture.on_success do |rows| rows.each do |row| puts "#{row["artist"]}: #{row["title"]} / #{row["album"]}" endend
![Page 45: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/45.jpg)
Quick note on Async
• Generates a Future • Non-blocking until get
45
future = session.execute_async(statement)# register success listenerfuture.on_success do |rows| rows.each do |row| puts "#{row["artist"]}: #{row["title"]} / #{row["album"]}" endend
![Page 46: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/46.jpg)
Quick note on Async
• Generates a Future • Non-blocking until get
46
future = session.execute_async(statement)# register success listenerfuture.on_success do |rows| rows.each do |row| puts "#{row["artist"]}: #{row["title"]} / #{row["album"]}" endend
![Page 47: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/47.jpg)
Update a user with PUT
47
put '/users' do begin session.execute(userUpdateStatement, :arguments => [params[:age].to_i, params[:lastname]]) "Updated" rescue Exception => e log.error 'Error in update a user' log.error(e) halt(404) endend
userUpdateStatement = session.prepare("UPDATE users SET age = ? WHERE lastname = ?")
![Page 48: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/48.jpg)
Remove a user with DELETE
48
delete '/users/:lastname' do begin session.execute(userDeleteStatement, :arguments => [params[:lastname]]) "Deleted" rescue Exception => e log.error 'Error in delete a user' log.error(e) halt(404) endend
userDeleteStatement = session.prepare("DELETE FROM users WHERE lastname = ?")
![Page 49: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/49.jpg)
Made it!
49
Nice code. Have a beer.
![Page 50: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/50.jpg)
Get the code! Try yourself!
50
https://github.com/beccam/rest_server_ruby
https://github.com/beccam/rest_server_nodejs
NodeJS Code
Ruby Code
![Page 51: 0 to App faster with NodeJS and Ruby](https://reader030.fdocuments.in/reader030/viewer/2022032620/55c62f2dbb61ebe15e8b4820/html5/thumbnails/51.jpg)
Questions?Don’t forget to follow us on Twitter for more:
@RebccaMills @PatrickMcFadin