Slide 1

Slide 1 text

Build your API with Node.js Thursday, August 2, 2012

Slide 2

Slide 2 text

About Me I am a Ruby developer getting more and more into Javascript. Find me at http://scottmotte.com I am making http://boxysign.com Find slides at http://speakerdeck.com/u/scottmotte Thursday, August 2, 2012

Slide 3

Slide 3 text

Download & Installation • Visit http://nodejs.org/#download • Follow the install instructions • Open up your command line (Terminal on mac), and type: node -v Thursday, August 2, 2012

Slide 4

Slide 4 text

Setup API Node.js app • Create a folder called node-api • Create node-api/package.json • Create node-api/app.coffee • npm install • coffee app.coffee • open http://localhost:3000 https://gist.github.com/3241615 Thursday, August 2, 2012

Slide 5

Slide 5 text

package.json { "name": "node-api", "version": "0.0.1", "author": "scottmotte", "main": "app.coffee", "dependencies": { "coffee-script" : "latest", "express" : "latest" } } Thursday, August 2, 2012

Slide 6

Slide 6 text

app.coffee express = require("express") app = express() app.configure -> app.use express.bodyParser() app.get "/", (req, res) -> res.send "API" Thursday, August 2, 2012

Slide 7

Slide 7 text

Add your first API route • Add a GET route to app.coffee called: /api/test.json • Restart your server (coffee app.coffee) • Browse to localhost:3000/api/test.json https://gist.github.com/3241676 Thursday, August 2, 2012

Slide 8

Slide 8 text

app.coffee express = require("express") app = express() app.configure -> app.use express.bodyParser() app.get "/", (req, res) -> res.send "API" app.get "/api/test.json", (req, res) -> res.json { success: true } app.listen 3000 Thursday, August 2, 2012

Slide 9

Slide 9 text

Add testing with Mocha • Update package.json with mocha, request, and should • Add test file at node-api/test/app_test.coffee • Restart your server (coffee app.coffee) • Run: mocha --compilers coffee:coffee-script https://gist.github.com/3241806 Thursday, August 2, 2012

Slide 10

Slide 10 text

package.json { "name": "node-api", "version": "0.0.1", "author": "scottmotte", "main": "app.coffee", "dependencies": { "coffee-script" : "latest", "express" : "latest", "mocha" : "latest", "request" : "latest", "should" : "latest" } } Thursday, August 2, 2012

Slide 11

Slide 11 text

app.coffee request = require 'request' should = require 'should' describe "GET /api/test.json", -> url = "http://localhost:3000/api/test.json" it "returns a success response", (done) -> request.get {url: url}, (e, res) -> json = JSON.parse res.body json.success.should.equal(true) done() Thursday, August 2, 2012

Slide 12

Slide 12 text

HTTP Auth: api_token • Add a POST route to app.coffee called: /api/sessions.json • Add some mocha tests to return token on valid email and password combination. • Restart your server (coffee app.coffee) • Run: mocha --compilers coffee:coffee-script https://gist.github.com/3241913 Thursday, August 2, 2012

Slide 13

Slide 13 text

app.coffee express = require("express") app = express() app.configure -> app.use express.bodyParser() VALID_EMAIL = "[email protected]" VALID_PASSWORD = "password" app.get "/", (req, res) -> res.send "API" app.get "/api/test.json", (req, res) -> res.json { success: true } app.post "/api/sessions.json", (req, res) -> if req.body.email == VALID_EMAIL && req.body.password == VALID_PASSWORD res.json { success: true, token: VALID_API_TOKEN } else res.json { success: false, error: { message: "Authentication failed." } } app.listen 3000 Thursday, August 2, 2012

Slide 14

Slide 14 text

app_test.coffee request = require 'request' should = require 'should' .. describe "POST /api/sessions.json", -> url = "http://localhost:3000/api/sessions.json" json = {email: "[email protected]", password: "password"} it "correct email/password combo", (done) -> request.post {url: url, json: json}, (e, res) -> json = res.body json.success.should.equal(true) json.should.have.property('token') done() it "wrong password", (done) -> json.password = "WRONGPASSWORD" request.post {url: url, json: json}, (e, res) -> json = res.body json.success.should.equal(false) done() it "non-existing email", (done) -> json.email = "[email protected]" request.post {url: url, json: json}, (e, res) -> json = res.body json.success.should.equal(false) done() Thursday, August 2, 2012

Slide 15

Slide 15 text

HTTP Auth: parse and authenticate authorization header • Parse the http authorization header • Authenticate person against api token in authorization header • Add a GET route to app.coffee called: /api/test/authentication.json • Restart your server (coffee app.coffee) • Run: mocha --compilers coffee:coffee-script https://gist.github.com/3242082 Thursday, August 2, 2012

Slide 16

Slide 16 text

app.coffee ... class Helper @req_basic_auth: (req) -> header = req.headers['authorization'] return unless header token = header.split(/\s+/).pop() auth = new Buffer(token, 'base64').toString() auth.split(/:/)[0] @requireApiPerson = (req, res, next) -> api_token = Helper.req_basic_auth(req) if api_token == VALID_API_TOKEN next() else res.status(401) return res.json { success: false, error: {message: "Please use a working authorization token." }} ... app.get "/api/test/authentication.json", Helper.requireApiPerson, (req, res) -> res.json { success: true } app.post "/api/sessions.json", (req, res) -> if req.body.email == VALID_EMAIL && req.body.password == VALID_PASSWORD res.json { success: true, token: VALID_API_TOKEN } else res.json { success: false, error: { message: "Authentication failed." } } app.listen 3000 Thursday, August 2, 2012

Slide 17

Slide 17 text

app_test.coffee request = require 'request' should = require 'should' ... describe "GET /api/test/authentication.json", -> valid_api_token = "12345" it "authenticates with valid api token", (done) -> request.get {url: "http://#{valid_api_token}:@localhost:3000/api/test/authentication.json"}, (e, res) -> json = JSON.parse res.body json.success.should.equal(true) done() it "does not authenticate with invalid api token", (done) -> request.get {url: "http://INVALIDTOKEN:@localhost:3000/api/test/authentication.json"}, (err, res) -> json = JSON.parse res.body json.success.should.equal(false) done() Thursday, August 2, 2012

Slide 18

Slide 18 text

Persist to a database • Add a POST route to app.coffee called: /api/people/create.json • Add some mocha tests to create the person with email and password (encrypt the password with bcrypt) • Restart your server (coffee app.coffee) • Run: mocha --compilers coffee:coffee-script https://gist.github.com/3243590 Thursday, August 2, 2012

Slide 19

Slide 19 text

package.json { "name": "node-api", "version": "0.0.1", "author": "scottmotte", "main": "app.coffee", "dependencies": { "bcrypt" : "latest", "coffee-script" : "latest", "express" : "latest", "mocha" : "latest", "mongoose" : "latest", "request" : "latest", "should" : "latest" } } Thursday, August 2, 2012

Slide 20

Slide 20 text

app.coffee express = require("express") app = express() Person = require('./app/models/person') ... app.post "/api/people/create.json", (req, res) -> person = new Person() person.email = req.body.email person.password = req.body.password person.save (e) -> return res.json { success: false, error: {message: e} } if !!e res.json { success: true } app.listen 3000 Thursday, August 2, 2012

Slide 21

Slide 21 text

person.coffee bcrypt = require('bcrypt') mongoose = require('mongoose') mongoose.connect('mongodb://localhost/node_api_development') Schema = mongoose.Schema ObjectId = Schema.ObjectId class Helpers @encryptPassword: (password) -> salt = bcrypt.genSaltSync(10) hash = bcrypt.hashSync(password, salt) @validatePresenceOf = (value) -> value and value.length PersonSchema = new Schema( email: { type: String, index: { unique: true } } crypted_password: { type: String } ) PersonSchema.virtual("password").set (password) -> if !!password @crypted_password = Helpers.encryptPassword(password) PersonSchema.pre "save", (next) -> if !Helpers.validatePresenceOf(@crypted_password) next new Error("Password required") else if !Helpers.validatePresenceOf(@email) next new Error("Email required") else next() Person = mongoose.model("PersonSchema", PersonSchema) module.exports = Person Thursday, August 2, 2012

Slide 22

Slide 22 text

app_test.coffee ... describe "POST /api/people/create.json", -> url = "http://localhost:3000/api/people/create.json" json = {email: "[email protected]", password: "password"} beforeEach (done) -> Person.collection.remove (e) -> done() it "successfully creates", (done) -> request.post {url: url, json: json}, (e, res) -> json = res.body json.success.should.equal(true) done() it "does not create if missing email", (done) -> json.email = "" request.post {url: url, json: json}, (e, res) -> json = res.body json.success.should.equal(false) done() it "does not create if missing password", (done) -> json.password = "" request.post {url: url, json: json}, (e, res) -> json = res.body json.success.should.equal(false) done() Thursday, August 2, 2012

Slide 23

Slide 23 text

Next Steps (for another day) • Update requireApiPerson and /api/sessions to authenticate against people in the mongoose database • Add more routes and models to do things Thursday, August 2, 2012