Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 265 lines (186 sloc) 8.032 kB
905e1a5 readme update
cloudhead authored
1 journey
2 =======
3
affbe19 README update
cloudhead authored
4 > liberal JSON-only HTTP request routing for node.
5
6 introduction
7 ------------
8
9 Journey's goal is to provide a *fast* and *flexible* *RFC 2616 compliant* request router
10 for *JSON* consuming clients.
11
12 synopsis
13 --------
14
15 var journey = require('journey');
16
17 //
f8c339e @cloudhead (api) default to http-like api
authored
18 // Create a Router
affbe19 README update
cloudhead authored
19 //
f8c339e @cloudhead (api) default to http-like api
authored
20 var router = new(journey.Router);
21
22 // Create the routing table
23 router.map(function () {
24 this.root.bind(function (req, res) { res.send("Welcome") });
25 this.get(/^trolls\/([0-9]+)$/).bind(function (req, res, id) {
affbe19 README update
cloudhead authored
26 database('trolls').get(id, function (doc) {
27 res.send(200, {}, doc);
28 });
50b5507 line 132: missing ')'
hano authored
29 });
f8c339e @cloudhead (api) default to http-like api
authored
30 this.post('/trolls').bind(function (req, res, data) {
affbe19 README update
cloudhead authored
31 sys.puts(data.type); // "Cave-Troll"
32 res.send(200);
33 });
34 });
35
36 require('http').createServer(function (request, response) {
37 var body = "";
38
39 request.addListener('data', function (chunk) { body += chunk });
40 request.addListener('end', function () {
41 //
42 // Dispatch the request to the router
43 //
f8c339e @cloudhead (api) default to http-like api
authored
44 router.handle(request, body, function (result) {
affbe19 README update
cloudhead authored
45 response.writeHead(result.status, result.headers);
46 response.end(result.body);
47 });
48 });
49 }).listen(8080);
50
724b65e (doc) added npm install instructions
cloudhead authored
51 installation
52 ------------
53
54 $ npm install journey
55
affbe19 README update
cloudhead authored
56 API
57 ---
58
59 You create a router with the `journey.Router` constructor:
60
f8c339e @cloudhead (api) default to http-like api
authored
61 var router = new(journey.Router);
62
63 You define some routes, with bound functions:
affbe19 README update
cloudhead authored
64
f8c339e @cloudhead (api) default to http-like api
authored
65 router.get('/hello').bind(function (req, res) { res.send('Hi there!') });
66 router.put('/candles').bind(function (req, res) { ... });
67
68 *Note that you may also use the `map` function to define routes.*
69
70 The `router` object exposes a `handle` method, which takes three arguments:
affbe19 README update
cloudhead authored
71 an `http.ServerRequest` instance, a body, and a callback, as such:
72
73 function route(request, body, callback)
74
75 and asynchronously calls the callback with an object containing the response
f8c339e @cloudhead (api) default to http-like api
authored
76 headers, status and body, on the first matching route:
affbe19 README update
cloudhead authored
77
78 { status: 200,
79 headers: {"Content-Type":"application/json"},
80 body: '{"journey":"Welcome"}'
81 }
82
83 Note that the response body will either be JSON data, or empty.
84
85 ### Routes #
86
87 Here are a couple of example routes:
88
f8c339e @cloudhead (api) default to http-like api
authored
89 // route // matching request
90 router.get('/users') // GET /users
91 router.post('/users') // POST /users
92 router.del(/^users\/(\d+)$/) // DELETE /users/45
93 router.put(/^users\/(\d+)$/) // PUT /users/45
affbe19 README update
cloudhead authored
94
f8c339e @cloudhead (api) default to http-like api
authored
95 router.route('/articles') // * /articles
96 router.route('POST', '/users') // POST /users
97 router.route(['POST', 'PUT'], '/users') // POST or PUT /users
affbe19 README update
cloudhead authored
98
f8c339e @cloudhead (api) default to http-like api
authored
99 router.root // GET /
100 router.any // Matches all request
101 router.post('/', { // Only match POST requests to /
affbe19 README update
cloudhead authored
102 assert: function (req) { // with data in the body.
103 return req.body.length > 0;
104 }
105 });
106
107 Any of these routes can be bound to a function or object which responds
108 to the `apply` method. We use `bind` for that:
109
f8c339e @cloudhead (api) default to http-like api
authored
110 router.get('/hello').bind(function (req, res) {});
affbe19 README update
cloudhead authored
111
112 If there is a match, the bound function is called, and passed the `response` object,
113 as first argument. Calling the `send` method on this object will trigger the callback,
114 passing the response to it:
115
f8c339e @cloudhead (api) default to http-like api
authored
116 router.get('/hello').bind(function (req, res) {
affbe19 README update
cloudhead authored
117 res.send(200, {}, {hello: "world"});
118 });
119
120 The send method is pretty flexible, here are a couple of examples:
121
122 // status, headers, body
123 res.send(404); // 404 {} ''
124 res.send("Welcome"); // 200 {} '{"journey":"Welcome"}'
125 res.send({hello:"world"}); // 200 {} '{"hello":"world"}'
126
127 res.send(200, {"Server":"HAL/1.0"}, ["bob"]);
128
129 As you can see, the body is automatically converted to JSON, and if a string is passed,
130 it acts as a message from `journey`. To send a raw string back, you can use the `sendBody` method:
131
132 res.sendBody(JSON.stringify({hello:"world"}));
133
134 This will bypass JSON conversion.
135
136 ### URL parameters #
137
138 Consider a request such as `GET /users?limit=5`, I can get the url params like this:
139
f8c339e @cloudhead (api) default to http-like api
authored
140 router.get('/users').bind(function (req, res, params) {
affbe19 README update
cloudhead authored
141 params.limit; // 5
142 });
143
144 How about a `POST` request, with form data, or JSON? Same thing, journey will parse the data,
145 and pass it as the last argument to the bound function.
146
147 ### Capture groups #
148
149 Any captured data on a matched route gets passed as arguments to the bound function, so let's
150 say we have a request like `GET /trolls/42`, and the following route:
151
152 get(/^([a-z]+)\/([0-9]+)$/)
153
154 Here's how we can access the captures:
155
f8c339e @cloudhead (api) default to http-like api
authored
156 router.get(/^([a-z]+)\/([0-9]+)$/).bind(function (req, res, resource, id, params) {
affbe19 README update
cloudhead authored
157 res; // response object
158 resource; // "trolls"
159 id; // 42
160 params; // {}
161 });
162
163 ### Summary #
164
165 A bound function has the following template:
166
f8c339e @cloudhead (api) default to http-like api
authored
167 function (request, responder, [capture1, capture2, ...], data/params)
affbe19 README update
cloudhead authored
168
3c7c58d (doc) path()
cloudhead authored
169 ### Paths #
170
171 Sometimes it's useful to have a bunch of routes under a single namespace, that's what the `path` function does.
172 Consider the following path and unbound routes:
173
f8c339e @cloudhead (api) default to http-like api
authored
174 router.path('/domain', function () {
3c7c58d (doc) path()
cloudhead authored
175 this.get(); // match 'GET /domain'
176 this.root; // match 'GET /domain/'
177 this.get('/info'); // match 'GET /domain/info'
178
179 this.path('/users', function () {
180 this.post(); // match 'POST /domain/users'
181 this.get(); // match 'GET /domain/users'
182 });
183 })
f8c339e @cloudhead (api) default to http-like api
authored
184
31bf1a6 @indexzero (doc) Added documentation for filter API
indexzero authored
185 ### Filters #
186
187 Often it's convenient to disallow certain requests based on predefined criteria. A great example of this is Authorization:
188
189 function authorize (request, body, cb) {
f8c339e @cloudhead (api) default to http-like api
authored
190 return request.headers.authorized === true
191 ? cb(null)
31bf1a6 @indexzero (doc) Added documentation for filter API
indexzero authored
192 : cb(new journey.NotAuthorized('Not Authorized'));
193 }
f8c339e @cloudhead (api) default to http-like api
authored
194
31bf1a6 @indexzero (doc) Added documentation for filter API
indexzero authored
195 function authorizeAdmin (request, body, cb) {
f8c339e @cloudhead (api) default to http-like api
authored
196 return request.headers.admin === true
197 ? cb(null)
31bf1a6 @indexzero (doc) Added documentation for filter API
indexzero authored
198 : cb(new journey.NotAuthorized('Not Admin'));
199 }
200
201 Journey exposes this in three separate location through the `filter` API:
202
203 #### Set a global filter
f8c339e @cloudhead (api) default to http-like api
authored
204
205 var router = new(journey.Router)({ filter: authorize });
206
207 *Note: This filter will not actually be enforced until you use the APIs exposed in (2) and (3)*
208
31bf1a6 @indexzero (doc) Added documentation for filter API
indexzero authored
209 #### Set a scoped filter in your route function
f8c339e @cloudhead (api) default to http-like api
authored
210
211 var router = new(journey.Router)({ filter: authorize });
212
213 router.map(function () {
214 this.filter(function () {
31bf1a6 @indexzero (doc) Added documentation for filter API
indexzero authored
215 //
216 // Routes in this scope will use the 'authorize' function
217 //
218 });
f8c339e @cloudhead (api) default to http-like api
authored
219
220 this.filter(authorizeAdmin, function () {
31bf1a6 @indexzero (doc) Added documentation for filter API
indexzero authored
221 //
222 // Routes in this scope will use the 'authorizeAdmin' function
223 //
f8c339e @cloudhead (api) default to http-like api
authored
224 });
225 });
31bf1a6 @indexzero (doc) Added documentation for filter API
indexzero authored
226
227 #### Set a filter on an individual route
f8c339e @cloudhead (api) default to http-like api
authored
228
229 var router = new(journey.Router)({ filter: authorize });
230
231 router.map(function () {
232 this.get('/authorized').filter().bind(function (req, res, params) {
31bf1a6 @indexzero (doc) Added documentation for filter API
indexzero authored
233 //
234 // This route will be filtered using the 'authorize' function
f8c339e @cloudhead (api) default to http-like api
authored
235 //
31bf1a6 @indexzero (doc) Added documentation for filter API
indexzero authored
236 });
f8c339e @cloudhead (api) default to http-like api
authored
237
238 this.get('/admin').filter(authorizeAdmin).bind(function (req, res, params) {
31bf1a6 @indexzero (doc) Added documentation for filter API
indexzero authored
239 //
240 // This route will be filtered using the 'authorizeAdmin' function
f8c339e @cloudhead (api) default to http-like api
authored
241 //
31bf1a6 @indexzero (doc) Added documentation for filter API
indexzero authored
242 });
f8c339e @cloudhead (api) default to http-like api
authored
243 });
3c7c58d (doc) path()
cloudhead authored
244
ff1b290 updated README with this.request example
cloudhead authored
245 ### Accessing the request object #
246
247 From a bound function, you can access the request object with `this.request`, consider
248 a request such as `POST /articles`, and a route:
249
f8c339e @cloudhead (api) default to http-like api
authored
250 router.route('/articles').bind(function (req, res) {
ff1b290 updated README with this.request example
cloudhead authored
251 this.request.method; // "POST"
252 res.send("Thanks for your " + this.request.method + " request.");
253 });
254
affbe19 README update
cloudhead authored
255 license
256 -------
257
258 Released under the Apache License 2.0
259
260 See `LICENSE` file.
261
262 Copyright (c) 2010 Alexis Sellier
263
905e1a5 readme update
cloudhead authored
264
Something went wrong with that request. Please try again.