Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Build your API with Node.js

Mot
August 03, 2012

Build your API with Node.js

Mot

August 03, 2012
Tweet

More Decks by Mot

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. 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
  4. package.json { "name": "node-api", "version": "0.0.1", "author": "scottmotte", "main": "app.coffee",

    "dependencies": { "coffee-script" : "latest", "express" : "latest" } } Thursday, August 2, 2012
  5. app.coffee express = require("express") app = express() app.configure -> app.use

    express.bodyParser() app.get "/", (req, res) -> res.send "API" Thursday, August 2, 2012
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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