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

Writing RESTful Web Services using Node.js

Writing RESTful Web Services using Node.js

Jakob Mattsson

June 06, 2013
Tweet

More Decks by Jakob Mattsson

Other Decks in Programming

Transcript

  1. Jakob Mattsson
    @jakobmattsson

    View Slide

  2. Writing RESTful
    web services
    using Node.js

    View Slide

  3. Comparison
    Rocket science
    Product demo
    Silver bullet

    View Slide

  4. Comparison
    Rocket science
    Product demo
    Silver bullet
    NOT

    View Slide

  5. What is it then?

    View Slide

  6. Imagination
    Quantity
    Bottom up
    Principles

    View Slide

  7. Also... CoffeeScript

    View Slide

  8. Node.js

    View Slide

  9. Node.js is a platform built on Chrome's
    JavaScript runtime for easily building fast,
    scalable network applications.
    Node.js uses an event-driven, non-blocking I/O
    model that makes it lightweight and efficient,
    perfect for data-intensive real-time applications
    that run across distributed devices.

    View Slide

  10. Node.js is a platform built on Chrome's
    JavaScript runtime for easily building fast,
    scalable network applications.
    Node.js uses an event-driven, non-blocking I/O
    model that makes it lightweight and efficient,
    perfect for data-intensive real-time applications
    that run across distributed devices.

    View Slide

  11. fs = require('fs')
    fs.readFile 'meaning_of_life.txt', 'utf-8', (err, data) ->
    console.log(data)
    console.log('end')

    View Slide

  12. end
    42

    View Slide

  13. Several protocols,
    including TCP and HTTP,
    are built in to node.

    View Slide

  14. http = require('http')
    onRequest = (req, res) ->
    res.writeHead(200, { 'Content-Type': 'text/plain' })
    res.end('Hello World\n')
    http.createServer(onRequest).listen(1337)

    View Slide

  15. npm

    View Slide

  16. npm is a package manager for node.
    You can use it to install and publish your node programs.
    ”It manages dependencies and does other cool stuff.”

    View Slide

  17. npm install underscore

    View Slide

  18. _ = require('underscore')
    numbers = _.range(1, 10)
    console.log(_.last(numbers))

    View Slide

  19. Connect

    View Slide

  20. Connect is a midleware framework for node.
    It’s shipping with over 18 bundled middleware.
    It has a rich selection of 3rd-party middleware.

    View Slide

  21. npm install connect

    View Slide

  22. connect = require('connect')
    app = connect()
    app.listen(3000)
    // last line equivalent to
    // http.createServer(app).listen(3000);

    View Slide

  23. connect = require('connect')
    app = connect()
    app.use connect.basicAuth (user, pass) ->
    return user == 'jakob' && pass == 'spainjs'
    app.use (req, res) ->
    res.writeHead(200, { 'Content-Type': 'text/plain' })
    res.end('Hello World\n')
    app.listen(3000)

    View Slide

  24. logger
    csrf
    compress
    basicAuth
    bodyParser
    json
    urlencoded
    multipart
    cookieParser
    session
    cookieSession
    methodOverride
    responseTime
    staticCache
    static
    directory
    vhost
    favicon
    limit
    query
    errorHandler
    Request logger with custom format support
    Cross-site request forgery protection
    Gzip compression middleware
    Basic http authentication
    Extensible request body parser
    Application/json parser
    Application/x-www-form-urlencoded parser
    Multipart/form-data parser
    Cookie parser
    Session management support with bundled MemoryStore
    Cookie-based session support
    Faux HTTP method support
    Calculates response-time and exposes via X-Response-Time
    Memory cache layer for the static() middleware
    Streaming static file server supporting Range and more
    Directory listing middleware
    Virtual host sub-domain mapping middleware
    Efficient favicon server (with default icon)
    Limit the bytesize of request bodies
    Automatic querystring parser, populating req.query
    Flexible error handler

    View Slide

  25. Express

    View Slide

  26. High performance
    high class web development
    for Node.js

    View Slide

  27. npm install express

    View Slide

  28. express = require('express')
    app = express.createServer()
    app.get '/', (req, res) ->
    res.send('Hello World')
    app.get '/users/:id', (req, res) ->
    res.send('user ' + req.params.id)
    app.listen(3000)

    View Slide

  29. express = require('express')
    app = express.createServer()
    before1 = (req, res, next) ->
    req.foo = 'bar'
    next()
    before2 = (req, res, next) ->
    res.header('X-Time', new Date().getTime())
    next()
    app.get '/', before1, (req, res) ->
    res.send('Hello World')
    app.get '/users/:id', [before1, before2], (req, res) ->
    console.log(req.foo)
    res.send('user ' + req.params.id)
    app.listen(3000)

    View Slide

  30. Data storage

    View Slide

  31. But which one?

    View Slide

  32. One does not simply
    stick to a single DB

    View Slide

  33. Schemaless is a lie

    View Slide

  34. View Slide

  35. Mongoose

    View Slide

  36. Mongoose is a MongoDB object modeling tool
    designed to work in an asynchronous environment.

    View Slide

  37. npm install mongoose

    View Slide

  38. mongoose = require 'mongoose'
    mongoose.connect 'mongodb://localhost/tamblr'
    model = (name, schema) ->
    mongoose.model name, new mongoose.Schema schema,
    strict: true
    users = model 'users'
    name:
    type: String
    default: ''
    bio:
    type: String
    default: 'IE6-lover'
    age:
    type: Number
    default: null

    View Slide

  39. blogs = model 'blogs'
    name:
    type: String
    default: ''
    description:
    type: String
    default: ''
    users:
    type: ObjectId
    ref: 'users'

    View Slide

  40. posts = model 'posts'
    title:
    type: String
    default: ''
    body:
    type: String
    default: ''
    published:
    type: Date
    blogs:
    type: ObjectId
    ref: 'blogs'

    View Slide

  41. list = (model, callback) ->
    model.find {}, callback
    get = (model, id, callback) ->
    model.findById id, callback
    del = (model, id, callback) ->
    model.remove { _id: id }, callback
    put = (model, id, data, callback) ->
    model.update { _id: id }, data, { multi: false }, callback
    post = (model, data, callback) ->
    new model(data).save callback

    View Slide

  42. app.get '/users/:id', (req, res) ->
    get users, req.params.id, (err, data) ->
    res.json data

    View Slide

  43. copy-paste!

    View Slide

  44. POST /users
    GET /users
    GET /users/42
    DELETE /users/42
    PUT /users/42
    POST /blogs
    GET /blogs
    GET /blogs/42
    DELETE /blogs/42
    PUT /blogs/42
    POST /posts
    GET /posts
    GET /posts/42
    DELETE /posts/42
    PUT /posts/42

    View Slide

  45. or should we?

    View Slide

  46. models = [users, blogs, posts]
    Object.keys(models).forEach (modelName) ->
    app.get "/#{modelName}", (req, res) ->
    list models[modelName], (err, data) ->
    res.json data
    app.get "/#{modelName}/:id", (req, res) ->
    get models[modelName], req.params.id, (err, data) ->
    res.json data
    app.post "/#{modelName}", (req, res) ->
    post models[modelName], req.body, (err, data) ->
    res.json data
    app.del "/#{modelName}/:id", (req, res) ->
    del models[modelName], req.parmas.id, (err, count) ->
    res.json { count: count }
    app.put "/#{modelName}/:id", (req, res) ->
    put models[modelName], req.params.id, req.body, (err, count) ->
    res.json { count: count }

    View Slide

  47. POST /users
    GET /users
    GET /users/42
    DELETE /users/42
    PUT /users/42
    POST /blogs
    GET /blogs
    GET /blogs/42
    DELETE /blogs/42
    PUT /blogs/42
    POST /posts
    GET /posts
    GET /posts/42
    DELETE /posts/42
    PUT /posts/42

    View Slide

  48. But what about the relations/associations?

    View Slide

  49. POST /users/42/blogs
    GET /users/42/blogs
    POST /blogs/42/posts
    GET /blogs/42/posts

    View Slide

  50. paths = models[modelName].schema.paths
    owners = Object.keys(paths).filter (p) ->
    paths[p].options.type == ObjectId &&
    typeof paths[p].options.ref == 'string'
    .map (x) -> paths[x].options.ref
    owners.forEach (owner) ->
    app.get "/#{owner}/:id/#{name}", (req, res) ->
    listSub models[name], owner, req.params.id, (err, data) ->
    res.json data
    app.post "/#{owner}/:id/#{name}", (req, res) ->
    postSub models[name], req.body, owner, req.params.id, (err, data) ->
    res.json data

    View Slide

  51. POST /users/42/blogs
    GET /users/42/blogs
    POST /blogs/42/posts
    GET /blogs/42/posts

    View Slide

  52. Keep on generating!

    View Slide

  53. Authentication

    View Slide

  54. npm install passport
    npm install passport-local

    View Slide

  55. passport = require('passport')
    passportLocal = require('passport-local')
    passport.use new passportLocal.Strategy (user, pass, done) ->
    findUserPlz { username: user, password: pass }, (err, user) ->
    done(err, user)
    app.use(passport.initialize())
    app.use(passport.authenticate('local'))

    View Slide

  56. npm install passport-twitter

    View Slide

  57. passport = require('passport')
    twitter = require('passport-twitter')
    keys = {
    consumerKey: TWITTER_CONSUMER_KEY
    consumerSecret: TWITTER_CONSUMER_SECRET
    callbackURL: "http://127.0.0.1:3000/auth/twitter/callback"
    }
    passport.use new twitter.Strategy keys, (t, ts, profile, done) ->
    findOrCreateUserPlz { twitterId: profile.id }, (err, user) ->
    done(err, user)
    app.use(passport.initialize())
    app.use(passport.authenticate('twitter'))

    View Slide

  58. Part 2
    Convention

    View Slide

  59. ALL CHARACTERS AND
    EVENTS IN THIS SHOW--
    EVENT THOSE BASED ON REAL
    PEOPLE--ARE ENTIRELY FICTIONAL.
    ALL CELEBERTY VOICES ARE
    IMPERSONATED.....POORLY. THE
    FOLLOWING PROGRAM CONTAINS
    COARSE LANGUAGE AND DUE TO
    ITS CONTENT IT SHOULD NOT BE
    VIEWED BE ANYONE

    View Slide

  60. Verbs vs Nouns

    View Slide

  61. /users
    /users/42
    /blogs
    /blogs/73
    /posts
    /posts/314

    View Slide

  62. GET /users
    GET /users/42
    POST /users
    PUT /users/42
    DELETE /users
    DELETE /users/42

    View Slide

  63. Associations

    View Slide

  64. /users
    /blogs
    /posts

    View Slide

  65. /users
    /blogs
    /posts
    /users/42/blogs
    /blogs/314/posts

    View Slide

  66. /users
    /blogs
    /posts
    /users/42/blogs
    /blogs/314/posts
    /users/42/blogs/314/posts

    View Slide

  67. /users
    /blogs
    /posts
    /users/42/blogs
    /blogs/314/posts
    /users/42/blogs/314/posts

    View Slide

  68. /users
    /blogs
    /posts
    /users/42/blogs
    /blogs/314/posts
    /users/42/blogs/314/posts
    Keep URLs short. Don’t overqualify.

    View Slide

  69. GET /blogs/42/posts?tag=javascript

    View Slide

  70. Versions

    View Slide

  71. GET /v1/users

    View Slide

  72. Partial responses

    View Slide

  73. GET /users?fields=email,age
    GET /users?limit=10&offset=0

    View Slide

  74. Verbs

    View Slide

  75. /convert?from=EUR&to=USD&amount=100

    View Slide

  76. Content-types

    View Slide

  77. GET /v1/users/42.xml

    View Slide

  78. Attributes

    View Slide

  79. {
    "userId": 1,
    "firstName": "Jakob",
    "lastName": "Mattsson"
    }

    View Slide

  80. Search

    View Slide

  81. GET /search?q=javascript
    GET /blog/42/posts?q=javascript

    View Slide

  82. Authentication

    View Slide

  83. Part 3
    Conclusion

    View Slide

  84. View Slide

  85. It goes for ideas too!
    Reuse convention.

    View Slide

  86. Reuse code.
    Resuse ideas.
    Build new things.

    View Slide

  87. View Slide

  88. View Slide

  89. ”MOST IMPORTANT STEP
    FOR BUILD PRODUCT
    IS BUILD PRODUCT”
    - @fakegrimlock

    View Slide

  90. @jakobmattsson
    Thank you!

    View Slide