writing restful web services using node.js

Post on 14-Nov-2014

23.452 Views

Category:

Technology

5 Downloads

Preview:

Click to see full reader

DESCRIPTION

by Jakob Mattsson on Frontend DEV Conf'13 http://bit.ly/Jakob_Mattsson

TRANSCRIPT

@jakobmattsson

Started out doing consulting

3 startups:RecruitingAdvertisingFeedback

2 000 000 000 writes/day!

Back to square one

Writing RESTfulweb servicesusing Node.js

Comparison

Rocket science

Product demo

Silver bullet

Comparison

Rocket science

Product demo

Silver bulletNOT

What is it then?

Imagination

Quantity

Bottom up

Principles

Also... CoffeeScript

Node.js

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.

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.

fs = require('fs')

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

console.log('end')

end42

Several protocols,including TCP and HTTP,

are built in to node.

http = require('http')

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

http.createServer(onRequest).listen(1337)

npm

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.”

npm install underscore

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

Connect

Connect is a midleware framework for node.

It’s shipping with over 18 bundled middleware.

It has a rich selection of 3rd-party middleware.

npm install connect

connect = require('connect')app = connect()app.listen(3000)

// last line equivalent to // http.createServer(app).listen(3000);

connect = require('connect')app = connect()

app.use connect.basicAuth (user, pass) -> return user == 'jakob' && pass == 'fdc13'

app.use (req, res) -> res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end('Hello World\n')

app.listen(3000)

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 supportCross-site request forgery protectionGzip compression middlewareBasic http authenticationExtensible request body parserApplication/json parserApplication/x-www-form-urlencoded parserMultipart/form-data parserCookie parserSession management support with bundled MemoryStoreCookie-based session supportFaux HTTP method supportCalculates response-time and exposes via X-Response-TimeMemory cache layer for the static() middlewareStreaming static file server supporting Range and moreDirectory listing middlewareVirtual host sub-domain mapping middlewareEfficient favicon server (with default icon)Limit the bytesize of request bodiesAutomatic querystring parser, populating req.queryFlexible error handler

Express

High performancehigh class web development

for Node.js

npm install express

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)

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)

Data storage

But which one?

Schemaless is a lie

Mongoose

Mongoose is a MongoDB object modeling tool

designed to work in an asynchronous environment.

npm install mongoose

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

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

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

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

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

copy-paste!

POST /usersGET /usersGET /users/42DELETE /users/42PUT /users/42

POST /blogsGET /blogsGET /blogs/42DELETE /blogs/42PUT /blogs/42

POST /postsGET /postsGET /posts/42DELETE /posts/42PUT /posts/42

or should we?

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 }

POST /usersGET /usersGET /users/42DELETE /users/42PUT /users/42

POST /blogsGET /blogsGET /blogs/42DELETE /blogs/42PUT /blogs/42

POST /postsGET /postsGET /posts/42DELETE /posts/42PUT /posts/42

But what about the relations/associations?

POST /users/42/blogsGET /users/42/blogs

POST /blogs/42/postsGET /blogs/42/posts

paths = models[modelName].schema.pathsowners = 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

POST /users/42/blogsGET /users/42/blogs

POST /blogs/42/postsGET /blogs/42/posts

Keep on generating!

Authentication

npm install passportnpm install passport-local

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'))

npm install passport-twitter

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'))

Part 2Convention

ALL CHARACTERS ANDEVENTS IN THIS SHOW--

EVENT THOSE BASED ON REALPEOPLE--ARE ENTIRELY FICTIONAL.

ALL CELEBERTY VOICES AREIMPERSONATED.....POORLY. THE

FOLLOWING PROGRAM CONTAINSCOARSE LANGUAGE AND DUE TOITS CONTENT IT SHOULD NOT BE

VIEWED BE ANYONE

Verbs vs Nouns

/users/users/42

/blogs/blogs/73

/posts/posts/314

GET /usersGET /users/42

POST /users

PUT /users/42

DELETE /usersDELETE /users/42

Associations

/users/blogs/posts

/users/blogs/posts

/users/42/blogs/blogs/314/posts

/users/blogs/posts

/users/42/blogs/blogs/314/posts

/users/42/blogs/314/posts

/users/blogs/posts

/users/42/blogs/blogs/314/posts

/users/42/blogs/314/posts

/users/blogs/posts

/users/42/blogs/blogs/314/posts

/users/42/blogs/314/posts

Keep URLs short. Don’t overqualify.

GET /blogs/42/posts?tag=javascript

Versions

GET /v1/users

Partial responses

GET /users?fields=email,age

GET /users?limit=10&offset=0

Verbs

/convert?from=EUR&to=BYR&amount=100

Content-types

GET /v1/users/42.xml

Attributes

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

Search

GET /search?q=javascriptGET /blog/42/posts?q=javascript

Authentication

Part 3Conclusion

It goes for ideas too!

Reuse convention.

Reuse code.

Resuse ideas.

Build new things.

”MOST IMPORTANT STEPFOR BUILD PRODUCTIS BUILD PRODUCT”

- @fakegrimlock

@jakobmattsson

Thank you!

top related