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

Build your API with Node.js

Bf962f059393fdaffe5c81fb17d765d8?s=47 M O T
August 03, 2012

Build your API with Node.js

Bf962f059393fdaffe5c81fb17d765d8?s=128

M O T

August 03, 2012
Tweet

Transcript

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

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

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

    express.bodyParser() app.get "/", (req, res) -> res.send "API" Thursday, August 2, 2012
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. app.coffee express = require("express") app = express() app.configure -> app.use

    express.bodyParser() VALID_EMAIL = "api@nodejs.org" 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
  14. app_test.coffee request = require 'request' should = require 'should' ..

    describe "POST /api/sessions.json", -> url = "http://localhost:3000/api/sessions.json" json = {email: "api@nodejs.org", 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 = "nonexisting@email.com" request.post {url: url, json: json}, (e, res) -> json = res.body json.success.should.equal(false) done() Thursday, August 2, 2012
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. app_test.coffee ... describe "POST /api/people/create.json", -> url = "http://localhost:3000/api/people/create.json" json

    = {email: "scott@scottmotte.com", 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
  23. 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