Compare commits

...

343 Commits

Author SHA1 Message Date
Tj Holowaychuk
d4f4b6682d Release 2.0.0beta2 2011-03-07 09:40:39 -08:00
Tj Holowaychuk
7f4a12f4cd Added regular css file to stylus example 2011-03-07 09:34:41 -08:00
Tj Holowaychuk
07d06adf86 Added res.render() .locals support back to aid in migration process 2011-03-07 09:15:36 -08:00
Tj Holowaychuk
47dc188d57 Updated jade submodule 2011-03-04 18:33:29 -08:00
Tj Holowaychuk
85d22541d2 Fixed flash example 2011-03-04 11:18:19 -08:00
Tj Holowaychuk
90ec1031f9 typo in flash example 2011-03-04 11:03:47 -08:00
Tj Holowaychuk
9bc9ad13a4 < connect 2.0.0 2011-03-04 08:52:15 -08:00
Tj Holowaychuk
1782c7d778 regenerated docs 2011-03-04 08:46:55 -08:00
Tj Holowaychuk
e85752c527 Add haml back in docs 2011-03-04 08:44:35 -08:00
Tj Holowaychuk
dfdbbb514f haml working fine 2011-03-04 08:43:52 -08:00
Tj Holowaychuk
976f5ca93e Updated haml submodule 2011-03-04 08:43:07 -08:00
Tj Holowaychuk
2086f8d34c beta in express --version 2011-03-03 17:16:25 -08:00
Tj Holowaychuk
6873a68216 changelog typo 2011-03-03 16:34:05 -08:00
Tj Holowaychuk
9eb2c61760 docs 2011-03-03 16:15:17 -08:00
Tj Holowaychuk
543e791206 docs 2011-03-03 16:13:20 -08:00
Tj Holowaychuk
b6e8b3fa84 added express-namespace link 2011-03-03 16:10:38 -08:00
Tj Holowaychuk
6f8e406b7b added express-messages link 2011-03-03 16:08:52 -08:00
Tj Holowaychuk
93bc54b65c added express-configure link 2011-03-03 16:07:22 -08:00
Tj Holowaychuk
3bf0ac4a36 added express-resource link 2011-03-03 16:05:49 -08:00
Tj Holowaychuk
bc01bfff21 connect docs 2011-03-03 16:02:27 -08:00
Tj Holowaychuk
0d4eaf03fc Added make site 2011-03-03 15:59:47 -08:00
Tj Holowaychuk
2bb0412473 docs 2011-03-03 15:49:34 -08:00
Tj Holowaychuk
07ce5d3158 docs for res.render() and res.partial() 2011-03-03 15:49:30 -08:00
Tj Holowaychuk
131970b3e5 docs 2011-03-03 15:39:44 -08:00
Tj Holowaychuk
00e1dbfbd9 docs for res.cookie() 2011-03-03 15:30:43 -08:00
Tj Holowaychuk
4c274c524d Added res.cookie() maxAge support 2011-03-03 15:27:03 -08:00
Tj Holowaychuk
5581ca2751 docs 2011-03-03 15:07:35 -08:00
Tj Holowaychuk
5cd9a9540f docs 2011-03-03 14:40:47 -08:00
Tj Holowaychuk
1db2efa75a docs 2011-03-03 14:39:10 -08:00
Tj Holowaychuk
c1a82bf3ea docs 2011-03-03 14:18:42 -08:00
Tj Holowaychuk
7705042cac docs 2011-03-03 13:31:09 -08:00
Tj Holowaychuk
b9e311e893 version docs 2011-03-03 13:26:31 -08:00
Tj Holowaychuk
460998497d docs 2011-03-03 13:25:31 -08:00
Tj Holowaychuk
022c5e1411 note on locals 2011-03-03 13:10:50 -08:00
Tj Holowaychuk
60daf0d6c7 Ignore .DS_Store 2011-03-03 12:53:34 -08:00
Tj Holowaychuk
0b6a941624 more docs 2011-03-03 12:47:01 -08:00
Tj Holowaychuk
1fc520b45a Fixed partial() issue where the parent locals clobber those given directly 2011-03-03 12:44:37 -08:00
Tj Holowaychuk
cbbee38be8 Added more view examples 2011-03-03 11:54:16 -08:00
Tj Holowaychuk
2dae6d8285 Fixed mounting of non-express servers 2011-03-03 11:33:53 -08:00
Tj Holowaychuk
65c8a956f1 do not try ../index when rendering a layout 2011-03-03 11:23:16 -08:00
Tj Holowaychuk
2b907e9a8c fixing tests 2011-03-03 11:21:57 -08:00
Tj Holowaychuk
25e520c243 misc 2011-03-03 11:11:08 -08:00
Tj Holowaychuk
2124c8465a removed scope tests 2011-03-03 11:05:16 -08:00
Tj Holowaychuk
a49e1f4f3d Changed default view scope to a plain object
easier to debug, and kinda useless to use "this" anyway
2011-03-03 10:59:00 -08:00
Tj Holowaychuk
32be4a7812 Added View#upIndexPath 2011-03-03 10:54:54 -08:00
Tj Holowaychuk
92967d3457 Added failing test for partial("foo") loading the index 2011-03-03 10:28:51 -08:00
Tj Holowaychuk
d70fee7432 Added partial() index test 2011-03-03 10:24:50 -08:00
Tj Holowaychuk
97b8e75932 Added index view test 2011-03-03 10:18:19 -08:00
Tj Holowaychuk
8a62f8cef0 view lookup docs 2011-03-03 10:10:18 -08:00
Tj Holowaychuk
10f4f523fa populate changelog for 2.0.0beta 2011-03-03 10:00:56 -08:00
Tj Holowaychuk
1e392d2628 populate changelog from 1.x 2011-03-03 09:36:38 -08:00
Tj Holowaychuk
eb6ff62113 Fixed req.param() bug returning Array.prototype methods. Closes #552 2011-03-03 09:20:28 -08:00
Tj Holowaychuk
64da2621da Merge branch 'docs' 2011-03-02 18:37:43 -08:00
Tj Holowaychuk
61aec6e961 Added markdown template engine example 2011-03-02 18:37:36 -08:00
Tj Holowaychuk
42f3ad436d docs 2011-03-02 18:37:29 -08:00
Tj Holowaychuk
2f7b78c03a ejs example using app.register() 2011-03-02 18:27:12 -08:00
Tj Holowaychuk
402d37d613 docs 2011-03-02 18:25:21 -08:00
Tj Holowaychuk
96327c979c docs 2011-03-02 18:10:51 -08:00
Tj Holowaychuk
9d1b3f59d5 docs 2011-03-02 17:20:39 -08:00
Tj Holowaychuk
14bd50efe4 docs 2011-03-02 16:59:48 -08:00
Tj Holowaychuk
631c1f95e7 node ">= 0.4.1 < 0.5.0" 2011-03-02 16:36:05 -08:00
Tj Holowaychuk
50006f7e43 docs 2011-03-02 16:22:53 -08:00
Tj Holowaychuk
4c9503158b connect >= 1.0.1 2011-03-02 16:16:03 -08:00
Tj Holowaychuk
6c8e461db5 beta 2011-03-02 16:15:37 -08:00
Tj Holowaychuk
aaa8415169 Removed partial() / render() status and header options 2011-03-02 16:11:02 -08:00
Tj Holowaychuk
51e964498c docs 2011-03-02 16:08:39 -08:00
Tj Holowaychuk
0cddc70ff5 node 0.4.x only 2011-03-02 15:57:33 -08:00
Tj Holowaychuk
99dcf7508f Added res.redirect() mount test 2011-03-02 15:52:41 -08:00
Tj Holowaychuk
7822d1f148 docs 2011-03-02 15:45:24 -08:00
Tj Holowaychuk
a572a7126c Merge branch 'mounting' 2011-03-02 15:42:34 -08:00
Tj Holowaychuk
a1609faba0 stylesheet respecting base 2011-03-02 15:42:29 -08:00
Tj Holowaychuk
9bf823d893 Added mount support for res.redirect(), now respects the mount-point 2011-03-02 15:37:12 -08:00
Tj Holowaychuk
ca184e7725 Updated connect submodule 2011-03-02 15:26:21 -08:00
Tj Holowaychuk
5054507487 added "base" local to blog example for mounting 2011-03-02 15:17:19 -08:00
Tj Holowaychuk
ab93be0178 refactored blog example 2011-03-02 15:06:23 -08:00
Tj Holowaychuk
8b0993bb10 misc 2011-03-02 15:02:57 -08:00
Tj Holowaychuk
0a034bb3ea started mounting example 2011-03-02 15:01:46 -08:00
Tj Holowaychuk
4ce815a3ed Removed annotated docs 2011-03-02 14:49:39 -08:00
Tj Holowaychuk
4da860eae1 removed api.html 2011-03-02 14:49:26 -08:00
Tj Holowaychuk
27e4bdb814 more res.partial() examples 2011-03-02 14:39:24 -08:00
Tj Holowaychuk
dd9406cd52 Added res.partial(), using the same interface as partial() within a view. Closes #539 2011-03-02 14:27:07 -08:00
Tj Holowaychuk
509312773d partials example utilizing index 2011-03-02 13:56:47 -08:00
Tj Holowaychuk
42d2758c36 Added union() util, taking place of merge(clone()) combo 2011-03-02 13:41:36 -08:00
Tj Holowaychuk
ef1c858ffc more test refactoring 2011-03-02 13:31:11 -08:00
Tj Holowaychuk
7fd47c0ac6 more test refactoring 2011-03-02 13:21:54 -08:00
Tj Holowaychuk
a63b232ef1 more test refactoring 2011-03-02 13:20:27 -08:00
Tj Holowaychuk
acb502e5e3 refactoring tsts 2011-03-02 13:18:39 -08:00
Tj Holowaychuk
3661922bb0 refactored res.send() 2011-03-02 12:39:39 -08:00
Tj Holowaychuk
0704f149be misc 2011-03-02 12:28:24 -08:00
Tj Holowaychuk
334604de5d utilizing app.set() 2011-03-02 12:03:05 -08:00
Tj Holowaychuk
530328ac15 misc refactoring 2011-03-02 11:43:40 -08:00
Tj Holowaychuk
9914a1eb3f copyright 2011-03-02 11:37:19 -08:00
Tj Holowaychuk
5db5710d3f Merge branch 'bugs/local-precedence' 2011-03-02 11:31:21 -08:00
Tj Holowaychuk
9e9f55a8e3 fixed local precedence 2011-03-02 11:31:14 -08:00
Tj Holowaychuk
0800141a9d semi-colons 2011-03-02 11:13:39 -08:00
Tj Holowaychuk
dc6d469f0c added more local tests 2011-03-02 11:11:18 -08:00
Tj Holowaychuk
89299d77e4 bodyParser not bodyDecoder 2011-03-02 10:52:37 -08:00
Tj Holowaychuk
27d8bf6311 refactored express(1) stdin usage 2011-03-02 10:50:13 -08:00
Tj Holowaychuk
8c229a8560 fixed express(1) confirmation for 0.4.x 2011-03-02 10:47:14 -08:00
Tj Holowaychuk
29eed65a67 removed constants from express(1) 2011-03-02 10:43:55 -08:00
Tj Holowaychuk
f0e0073a09 more partial examples 2011-03-02 10:39:32 -08:00
Tj Holowaychuk
b70bde58be fixing more examples 2011-03-02 10:30:33 -08:00
Tj Holowaychuk
635fac776d renamed example to params 2011-03-02 10:26:43 -08:00
Tj Holowaychuk
7753d4e91d refactored partials example 2011-03-02 10:24:57 -08:00
Tj Holowaychuk
66a6214216 fixing more examples 2011-03-02 10:15:10 -08:00
Tj Holowaychuk
6cae1f09f1 docs 2011-03-02 10:09:07 -08:00
Tj Holowaychuk
c4432ee827 download example 2011-03-02 10:06:14 -08:00
Tj Holowaychuk
80a5bf2063 fixing examples 2011-03-02 10:02:42 -08:00
Tj Holowaychuk
5b3d5fc431 removed cache example 2011-03-02 09:57:11 -08:00
Tj Holowaychuk
cd38fb4177 refactored auth example 2011-03-02 09:56:09 -08:00
Tj Holowaychuk
9b1cc75b36 res.download() and res.sendfile() using connect now 2011-03-02 09:41:21 -08:00
Tj Holowaychuk
db8b2883e2 Updated connect submodule 2011-03-01 15:53:56 -08:00
Tj Holowaychuk
59a74ecce4 refactoring to use connects static server 2011-03-01 15:42:42 -08:00
Tj Holowaychuk
9099c61df9 Merge branch 'progressive-headers' 2011-03-01 14:41:01 -08:00
Tj Holowaychuk
50521281ce utilizing nodes progressive response header api 2011-03-01 14:40:46 -08:00
Tj Holowaychuk
0cc6abf741 refactored req.is() 2011-03-01 14:34:36 -08:00
Tj Holowaychuk
fe266ae4fe misc refactoring 2011-03-01 14:32:59 -08:00
Tj Holowaychuk
7c15cdeb69 misc refactoring 2011-03-01 14:30:48 -08:00
Tj Holowaychuk
848086947c refactor errorHandler registration 2011-03-01 14:20:25 -08:00
Tj Holowaychuk
5ec4ea754c misc refactoring 2011-03-01 14:18:14 -08:00
Tj Holowaychuk
41467d54df docs 2011-03-01 14:16:16 -08:00
Tj Holowaychuk
8261437764 Merge branch 'features/https' 2011-03-01 14:08:40 -08:00
Tj Holowaychuk
54eac368d0 Finished https support 2011-03-01 14:06:53 -08:00
Tj Holowaychuk
72511ea1e9 Started https.js 2011-03-01 13:53:22 -08:00
Tj Holowaychuk
da7921f2c6 Moved server.js into http.js 2011-03-01 13:34:50 -08:00
Tj Holowaychuk
cd519e97e7 tests running again 2011-03-01 11:00:50 -08:00
Tj Holowaychuk
2a7a5aeefe Renaming usage of old connect middleware 2011-03-01 10:59:09 -08:00
Tj Holowaychuk
bdbacb41da prepping for connect 1.0 2011-03-01 10:54:04 -08:00
Tj Holowaychuk
2dd736a201 Updated expresso submodule 2011-03-01 10:45:07 -08:00
Tj Holowaychuk
ec3c797105 Updated connect submodule 2011-03-01 10:39:50 -08:00
Tj Holowaychuk
770f05c060 styling 2011-02-23 15:41:59 -08:00
Tj Holowaychuk
6993a27d8e refactored res.redirect() with res.req.header() for referrer 2011-02-23 15:39:06 -08:00
Tj Holowaychuk
68fd658536 more tests 2011-02-23 15:37:27 -08:00
Tj Holowaychuk
9fc349965d Changed; charsets are no longer implied 2011-02-23 15:24:56 -08:00
Tj Holowaychuk
0a667ff741 Added mime support submod 2011-02-23 15:23:48 -08:00
Tj Holowaychuk
d37ad85f72 replaced connect.utils.mime() with mime module 2011-02-23 15:23:08 -08:00
Tj Holowaychuk
2b63568611 node >= 0.4.0 2011-02-21 13:46:40 -08:00
Tj Holowaychuk
fcf9f93825 2.0.0-pre 2011-02-21 13:46:26 -08:00
Tj Holowaychuk
07efc0b73b clean up generated app 2011-02-21 13:43:00 -08:00
Tj Holowaychuk
6813b48c7e replaced link to express-contrib with new solo repos 2011-02-21 11:37:19 -08:00
Tj Holowaychuk
d58444c6d2 Updated ejs submodule 2011-02-17 23:41:28 -08:00
Tj Holowaychuk
dd8a0bd30f removed .config (unused prop) 2011-02-15 14:09:01 -08:00
Tj Holowaychuk
b65b0636aa Added req.header() Referrer / Referer special-case 2011-02-14 19:38:01 -08:00
Tj Holowaychuk
9ec6321261 qs >= 0.0.6 2011-02-14 15:26:00 -08:00
Tj Holowaychuk
07eb66c205 Fixed res.download() callback, passing stream. Closes #528
should be able to figure something out with this.
at very least use redis or something with INCR/DECR to
keep a count on active downloads
2011-02-10 08:57:53 -08:00
Tj Holowaychuk
0e790e6fb5 using Stream#pipe() instead of sys.pump() 2011-02-10 08:50:11 -08:00
Tj Holowaychuk
08186924a4 qs >= 0.0.3 2011-02-08 16:57:09 -08:00
Tj Holowaychuk
e9faf5cf94 Updated qs submodule 2011-02-08 16:56:33 -08:00
Tj Holowaychuk
8c0efe09e5 comment typo 2011-02-08 12:34:00 -08:00
Tj Holowaychuk
32d2c96d22 docs 2011-02-08 11:07:06 -08:00
Tj Holowaychuk
e958988989 styling for stylus example 2011-02-08 11:01:53 -08:00
Tj Holowaychuk
131f10bd80 more work on stylus example 2011-02-08 10:55:22 -08:00
Tj Holowaychuk
a61615a196 Setting up Stylus example 2011-02-08 10:42:39 -08:00
Tj Holowaychuk
4fdb6910a5 Allow req.query to be pre-defined (via middleware or other parent app) 2011-02-07 16:30:38 -08:00
Tj Holowaychuk
67c4c90351 Updated qs submodule 2011-02-07 15:35:46 -08:00
Tj Holowaychuk
16f7288b19 Updated connect submodule 2011-02-07 15:35:43 -08:00
Tj Holowaychuk
36a7e87ae4 querystring -> qs 2011-02-07 14:36:28 -08:00
Tj Holowaychuk
16badda118 Added test for periods in dirname when using "view engine" 2011-02-07 13:59:14 -08:00
Tj Holowaychuk
99820e7edc Refactored req/res proto assignments
since the names may change in the near future (is suspect at least)
due to inconsistancies
2011-02-03 21:05:04 -08:00
Tj Holowaychuk
6a03a92f5f Refactored res.header() 2011-02-03 21:03:27 -08:00
Tj Holowaychuk
80c3b98a84 Added multiple Set-Cookie header tests 2011-02-03 21:01:28 -08:00
Tj Holowaychuk
afd2b4b387 Removed alternate installation methods (use git, tarball, or npm) 2011-02-03 21:00:33 -08:00
Tj Holowaychuk
df49769f3d Fixed two tests 2011-02-03 20:49:53 -08:00
Tj Holowaychuk
05c2946322 Added qs submod 2011-02-03 20:47:54 -08:00
Tj Holowaychuk
2ed392edbf Updated connect submodule 2011-02-03 20:44:19 -08:00
Tj Holowaychuk
3d597a554d moved files from lib/express/* to lib/ 2011-02-03 20:20:42 -08:00
Tj Holowaychuk
42af4e40ae Updated connect submodule 2011-02-03 20:19:32 -08:00
Tj Holowaychuk
242efc7219 Updated jade submodule 2011-02-02 09:42:09 -08:00
Tj Holowaychuk
a6bacb857d Added redis session example 2011-02-01 10:02:15 -08:00
Tj Holowaychuk
eab54724a1 connect >= 0.5.7 2011-02-01 08:32:50 -08:00
Tj Holowaychuk
49885cfeff Updated connect submodule 2011-02-01 08:31:14 -08:00
Tj Holowaychuk
4330f49011 Added stylus support to express(1) generated app 2011-01-31 14:40:19 -08:00
Tj Holowaychuk
7687299275 Added secret to session middleware used in examples and generated app 2011-01-29 12:36:48 -08:00
Tj Holowaychuk
79ba787b03 Re-added jade submodule to hopefully fix #514 2011-01-28 12:04:12 -08:00
Tj Holowaychuk
5a184a205f Removed jade submodule 2011-01-28 12:03:39 -08:00
Tj Holowaychuk
4b5a6b597a Added formidable submod for multipart example 2011-01-25 10:04:06 -08:00
Tj Holowaychuk
9e9c3eeb79 Added vhost example 2011-01-25 09:38:02 -08:00
Tj Holowaychuk
cd996f9dbf Updated connect submodule 2011-01-25 09:12:24 -08:00
Tj Holowaychuk
ab4c91158a Added another partial locals test 2011-01-25 07:56:20 -08:00
Tj Holowaychuk
1130a23e5b Added test for partial locals on subsequent calls 2011-01-25 07:39:14 -08:00
Tj Holowaychuk
9ac6943220 Updated jade submodule 2011-01-24 11:45:58 -08:00
Tj Holowaychuk
0f55284b77 Updated changelog 2011-01-20 08:39:29 -08:00
Tj Holowaychuk
c75046de3f docs 2011-01-19 11:01:59 -08:00
Tj Holowaychuk
e7df82f110 Added 416 support to res.sendfile() 2011-01-19 09:28:48 -08:00
Tj Holowaychuk
ee4c7d3007 Removed "stream threshold" setting 2011-01-19 09:17:27 -08:00
Tj Holowaychuk
2895303421 Added Content-Length to res.sendfile(). Closes #507 2011-01-19 09:07:05 -08:00
Tj Holowaychuk
3f16f05525 Added HEAD support to res.sendfile() 2011-01-19 08:47:42 -08:00
Tj Holowaychuk
fb33671923 Ignore testing.js 2011-01-19 08:43:51 -08:00
Tj Holowaychuk
482fb5b6e2 Strip unsafe chars from jsonp callbacks 2011-01-13 14:13:16 -08:00
Tj Holowaychuk
b1004b61f9 fixed session example/tests due to connect changes 2011-01-13 12:11:18 -08:00
Tj Holowaychuk
27af9b2605 Updated connect submodule 2011-01-13 12:09:49 -08:00
Tj Holowaychuk
e28ac866f8 fixed width for github example css 2011-01-12 10:17:31 -08:00
Tj Holowaychuk
5e0a80f227 Updated connect submodule 2011-01-09 23:42:42 -08:00
Tj Holowaychuk
ec0982ca1f Added flash messages to blog example 2011-01-05 19:09:56 -08:00
Tj Holowaychuk
e7c442d21c Updated connect submodule 2011-01-03 13:34:36 -08:00
Tj Holowaychuk
a9db47fdd3 Updated connect submodule 2011-01-03 13:30:54 -08:00
Tj Holowaychuk
b856a72886 more examples 2010-12-31 16:13:19 -08:00
Tj Holowaychuk
03da0d31f9 Styling 2010-12-31 15:46:50 -08:00
Tj Holowaychuk
1c40e4374b More example 2010-12-31 15:37:27 -08:00
Tj Holowaychuk
d7df1426f2 Updated connect submodule 2010-12-31 15:07:00 -08:00
Tj Holowaychuk
5035e86f59 more example 2010-12-31 15:03:00 -08:00
Tj Holowaychuk
0af91265ab Added default param support to req.param(name, default) 2010-12-31 09:21:51 -08:00
Tj Holowaychuk
8e63d04e66 Re-built docs 2010-12-31 09:04:44 -08:00
Tj Holowaychuk
5ac0c6deb0 Added res.local(name[, val]) docs 2010-12-31 09:04:30 -08:00
Tj Holowaychuk
6b0d82fb0c Added res.local() for progressive view locals
for middleware etc.
res.local("foo", "bar") => "bar"
res.local("foo") => "bar"
2010-12-31 08:59:35 -08:00
Tj Holowaychuk
2bfdfe366c Fixed for middleware stacked via createServer() 2010-12-29 11:21:24 -08:00
Tj Holowaychuk
898fb0f320 Added failing middleware precedence tests 2010-12-29 11:15:40 -08:00
Tj Holowaychuk
f53686a2f3 Fixed index view resolution bug introduced by 1c2e9a476c 2010-12-29 08:57:56 -08:00
mgutz
eae1670f54 madestr local 2010-12-29 08:45:22 -08:00
Tj Holowaychuk
544d68833c Fixed express(1) support for < 0.3.x 2010-12-28 12:00:04 -08:00
Tj Holowaychuk
c24a6b2359 Fixed express(1) mode 2010-12-28 11:56:44 -08:00
Tj Holowaychuk
b720b57d1d Updated jade submodule 2010-12-28 04:59:28 -08:00
Tj Holowaychuk
365b7fe90a Merge branch 'OPTIONS' 2010-12-28 04:05:44 -08:00
Tj Holowaychuk
c7ab833e66 Added OPTIONS test 2010-12-28 04:05:39 -08:00
Tj Holowaychuk
9536f288e9 Updated connect submodule 2010-12-28 04:03:43 -08:00
Tj Holowaychuk
e64d4039d0 Generating methods based on connect.router.methods 2010-12-28 03:12:11 -08:00
Tj Holowaychuk
5f077e56f0 Updated connect submodule 2010-12-28 03:09:07 -08:00
Tj Holowaychuk
a9253a24e0 Added docs for app.{disabled,enabled}() 2010-12-23 09:29:43 -08:00
Tj Holowaychuk
749136b526 Added app.disabled() and app.enabled() 2010-12-23 09:28:15 -08:00
Tj Holowaychuk
36a8b7b7f3 Docs 2010-12-22 15:49:37 -08:00
Tj Holowaychuk
a29908c865 Few jade changes to partials example 2010-12-22 15:48:41 -08:00
Hunter Loftis
57d87c27cd Created examples/partials to demonstrate view name, file, object resolution 2010-12-22 15:44:26 -08:00
Hunter Loftis
1c2e9a476c Fixed _ prefix object resolution on partials (resolveObjectName), added _ prefix fallback to view file resolution (prefixPath) 2010-12-22 15:44:26 -08:00
shuwatto
5d87133df0 Constants don't exist in process. The same problem is reported following link https://github.com/senchalabs/connect/issues/closed#issue/119 2010-12-21 09:04:23 -08:00
Tj Holowaychuk
fd2f67b73f Route precondition docs 2010-12-20 05:53:50 -08:00
Tj Holowaychuk
d3abcaf67b Regenerated docs 2010-12-20 05:41:05 -08:00
Tj Holowaychuk
14dce5ac2b Docs 2010-12-20 05:40:51 -08:00
Tj Holowaychuk
abd002071a Test for named capture groups 2010-12-20 05:29:01 -08:00
Tj Holowaychuk
77add42eeb Updated connect submodule 2010-12-20 05:27:21 -08:00
Tj Holowaychuk
bba049b4bf Docs 2010-12-20 05:27:07 -08:00
Tj Holowaychuk
4e41c3520c Contrib guide link 2010-12-20 05:18:32 -08:00
Tj Holowaychuk
4144af3351 Added screencasts page 2010-12-20 05:17:25 -08:00
Tj Holowaychuk
30f749a2a2 Docs 2010-12-20 05:05:47 -08:00
Tj Holowaychuk
6e9f282af7 added .npmignore 2010-12-18 13:42:43 -08:00
Tj Holowaychuk
4491be01dd Using named capture group for downloads example 2010-12-16 09:21:31 -08:00
Tj Holowaychuk
8c163fd980 Updated connect-form submodule 2010-12-15 09:45:45 -08:00
Tj Holowaychuk
9f6a158fc2 Fixed SyntaxError in express(1) app tests 2010-12-14 08:20:00 -08:00
Tj Holowaychuk
19965fa3c8 Aliased app.helpers() as app.locals(). Closes #481 2010-12-12 16:10:59 -08:00
Tj Holowaychuk
6bb42ffafc should 2010-12-12 16:10:01 -08:00
Tj Holowaychuk
4a1edc0720 indentation fix 2010-12-12 16:08:27 -08:00
Tj Holowaychuk
a5a016bf33 Updated connect submodule 2010-12-10 12:42:55 -08:00
Tj Holowaychuk
4be4f597a3 Updated connect submodule 2010-12-10 12:42:36 -08:00
Tj Holowaychuk
dfbbd4f784 Merge branch 'partials' 2010-12-10 10:18:44 -08:00
Tj Holowaychuk
2662dae560 Removed "_" prefix from partials 2010-12-10 10:18:38 -08:00
Tj Holowaychuk
6f6f9f08f8 Added app.register() support for omitting leading "." 2010-12-10 08:15:27 -08:00
Tj Holowaychuk
a76fba57c4 Added extname with no leading "." support to res.contentType)
For example now you may:

    res.contentType("png");

or

    res.contentType(".png");

both are fine now
2010-12-10 08:14:15 -08:00
Tj Holowaychuk
680766f29b Updated jade submodule 2010-12-08 10:29:07 -08:00
Tj Holowaychuk
adcfd4ca4d Added more screencast links 2010-12-06 19:25:17 -08:00
Tj Holowaychuk
e276a49a91 Updated connect submodule 2010-12-06 18:13:07 -08:00
Tj Holowaychuk
5995f763b1 connect >= 0.5.0 2010-12-06 18:11:34 -08:00
Tj Holowaychuk
a123639b5c Fixed placeholder example 2010-12-06 17:45:01 -08:00
Tj Holowaychuk
8a118d53c8 Fixed some namespaced connect requires 2010-12-06 17:41:56 -08:00
Tj Holowaychuk
d572391532 Updated connect submodule 2010-12-06 17:39:27 -08:00
Tj Holowaychuk
58ae7a0402 Added param placeholder precondition example 2010-12-06 17:38:51 -08:00
Tj Holowaychuk
f843093a8c Added Array support to Server#param() 2010-12-06 17:28:17 -08:00
Tj Holowaychuk
3e06586f81 Added Server#param() 2010-12-06 17:18:44 -08:00
Tj Holowaychuk
60f148326a Updated expresso submodule 2010-12-06 17:05:30 -08:00
Tj Holowaychuk
02e1413ca5 Updated connect submodule 2010-12-06 17:04:50 -08:00
Tj Holowaychuk
7b4b85ee54 Updated connect submodule 2010-12-06 14:53:14 -08:00
Tj Holowaychuk
6c0a416f16 Added view partials screencast link 2010-12-05 11:06:26 -08:00
Tj Holowaychuk
23cc88bf92 Added introduction screencast link 2010-12-05 10:44:13 -08:00
Tj Holowaychuk
5528f0ddf2 Misc refactoring 2010-12-05 10:13:06 -08:00
Tj Holowaychuk
8d3296fb1b Fixed mvc example due to view lookup changes 2010-12-05 10:02:38 -08:00
Tj Holowaychuk
136f9209a3 route-separation example using new view resolution 2010-12-05 10:00:07 -08:00
Tj Holowaychuk
a290134fd2 Merge branch 'view-index' 2010-12-05 09:56:20 -08:00
Tj Holowaychuk
a54e59666a Added view index resolution support 2010-12-05 09:55:11 -08:00
Tj Holowaychuk
42ea6af1eb Added view index template support 2010-12-05 09:49:11 -08:00
Tj Holowaychuk
f9b741766b Started view index support 2010-12-05 09:46:59 -08:00
Tj Holowaychuk
09359329a0 ejs example using new partial lookup 2010-12-05 09:37:50 -08:00
Tj Holowaychuk
647bb41ddf Merge branch 'partial-lookup' 2010-12-05 09:33:26 -08:00
Tj Holowaychuk
f25c14d48d Fixed partial parent view ref 2010-12-05 09:33:20 -08:00
Tj Holowaychuk
81a3d559f5 Removed a require 2010-12-05 09:22:19 -08:00
Tj Holowaychuk
4e0ee7a5da Added more partial view path tests 2010-12-05 09:20:58 -08:00
Tj Holowaychuk
1c80a258d2 Passing dir 2010-12-05 09:17:20 -08:00
Tj Holowaychuk
129ed6a07b Added Partial#resolvePartialPath() 2010-12-05 09:16:12 -08:00
Tj Holowaychuk
df09a0c73b Partial tests using real fixtures 2010-12-05 09:13:41 -08:00
Tj Holowaychuk
ff5c958354 express(1) no longer passing assert in tests 2010-12-02 15:51:52 -08:00
Tj Holowaychuk
4f90c89db6 refactored req.accepts() 2010-12-02 10:07:28 -08:00
Tj Holowaychuk
524b515fdb docs 2010-12-02 10:06:40 -08:00
Tj Holowaychuk
163256b001 Added req.accepts() support for extensions 2010-12-02 10:05:29 -08:00
Tj Holowaychuk
05bbc65dcc Added failing req.accepts() test 2010-12-02 10:03:59 -08:00
Tj Holowaychuk
6624f6b7f9 Added more req.accepts() tests 2010-12-02 10:03:25 -08:00
Tj Holowaychuk
f32279dec5 more partial docs 2010-11-29 17:20:13 -08:00
Tj Holowaychuk
a90fd85907 Added strategic cache example (with redis) 2010-11-29 17:01:28 -08:00
Tj Holowaychuk
9815b27e94 Updated connect submodule 2010-11-29 09:57:53 -08:00
Tj Holowaychuk
ebbe481fe7 Added default link color for express(1) generated apps 2010-11-28 13:19:23 -08:00
Tj Holowaychuk
9e337faa33 Removed ./views/partials generation in express(1) 2010-11-28 10:45:48 -08:00
Tj Holowaychuk
37826e0261 Fixed locals in express(1) generated app 2010-11-28 10:29:10 -08:00
Tj Holowaychuk
20e4342796 Fixed uncached templates in development 2010-11-25 15:46:57 -08:00
Tj Holowaychuk
51be16950d Added isLayout back 2010-11-25 14:54:42 -08:00
Tj Holowaychuk
be866a78ff fixed session example 2010-11-25 14:14:31 -08:00
Tj Holowaychuk
f9d2432c69 fixed route-separation example 2010-11-25 14:14:13 -08:00
Tj Holowaychuk
6543a3d75a added format support to resource example 2010-11-25 14:11:52 -08:00
Tj Holowaychuk
a77936981e Fixed resource example 2010-11-25 14:08:27 -08:00
Tj Holowaychuk
6f2120bb59 Fixed mvc example 2010-11-25 14:05:28 -08:00
Tj Holowaychuk
98e18239fd refactored multipart example 2010-11-25 13:59:10 -08:00
Tj Holowaychuk
3d159015be misc 2010-11-25 13:56:12 -08:00
Tj Holowaychuk
f5af66a1ab fixed github example 2010-11-25 13:53:51 -08:00
Tj Holowaychuk
540192ff96 refactored format example 2010-11-25 13:50:32 -08:00
Tj Holowaychuk
c1cfd4a8c6 refactored form example 2010-11-25 13:47:49 -08:00
Tj Holowaychuk
322300c329 fixed flash example 2010-11-25 13:46:40 -08:00
Tj Holowaychuk
1ac18f7903 refactored error-pages example 2010-11-25 13:44:52 -08:00
Tj Holowaychuk
4f4987e9ea misc 2010-11-25 13:41:50 -08:00
Tj Holowaychuk
65bf562ff4 refactored downloads example 2010-11-25 13:41:15 -08:00
Tj Holowaychuk
f47032e7e9 refactored cookies example 2010-11-25 13:40:50 -08:00
Tj Holowaychuk
379f3ec8dd removed blog example 2010-11-25 13:40:02 -08:00
Tj Holowaychuk
8d7e35b723 refactored auth example 2010-11-25 13:39:07 -08:00
Tj Holowaychuk
1349eea0d0 misc refactoring 2010-11-24 14:44:00 -08:00
Tj Holowaychuk
bed5157f32 Make note in docs that res.send() is a convenience method, and should only be called once (ATM) 2010-11-24 10:07:25 -08:00
Tj Holowaychuk
12c7f51a1e Updated jade submodule 2010-11-24 08:14:36 -08:00
Tj Holowaychuk
7e32c8de4d unroll partial rendering for performance 2010-11-23 10:27:26 -08:00
Tj Holowaychuk
3a42350c21 cache resolveObjectName() 2010-11-23 10:14:30 -08:00
Tj Holowaychuk
1a54597202 todo 2010-11-23 10:07:24 -08:00
Tj Holowaychuk
39f4eb9940 Added "cache views" setting, defaulting to enabled in "production" env 2010-11-23 09:49:43 -08:00
Tj Holowaychuk
4b996651af fixed template caching 2010-11-23 09:45:50 -08:00
Tj Holowaychuk
884d40f321 caching compiled view functions 2010-11-23 09:42:19 -08:00
Tj Holowaychuk
3d2b41da08 todo 2010-11-23 09:07:36 -08:00
Tj Holowaychuk
1bf2dbb7df Added memoized path utils 2010-11-23 08:58:36 -08:00
Tj Holowaychuk
e0dfb77dd1 misc refactoring 2010-11-22 21:08:24 -08:00
Tj Holowaychuk
578fddc1b7 cache view contents again 2010-11-22 18:28:19 -08:00
Tj Holowaychuk
089355d706 Merge branch 'views' 2010-11-22 17:55:17 -08:00
Tj Holowaychuk
6b2c3caf03 fixed register 2010-11-22 17:55:14 -08:00
Tj Holowaychuk
065c37cb28 moved View and Partial into their own files 2010-11-22 17:52:45 -08:00
Tj Holowaychuk
3a40025fd3 Merge branch 'views' 2010-11-22 17:36:59 -08:00
Tj Holowaychuk
8dc341d868 removed old code 2010-11-22 17:36:50 -08:00
Tj Holowaychuk
0ed121ddbd Merge branch 'views' 2010-11-22 17:24:50 -08:00
Tj Holowaychuk
969f4229dc added Partial.resolveObjectName() 2010-11-22 17:24:44 -08:00
Tj Holowaychuk
27a940184e removed old cacheViewSync() 2010-11-22 17:02:50 -08:00
Tj Holowaychuk
77fa09a506 relative view option 2010-11-22 16:58:39 -08:00
Tj Holowaychuk
8836104ab2 absolute layouts 2010-11-22 16:56:55 -08:00
Tj Holowaychuk
396837517d fixed ejs example for new locals 2010-11-22 16:43:47 -08:00
Tj Holowaychuk
38b3f1bde2 fixed "view options" support 2010-11-22 16:41:56 -08:00
Tj Holowaychuk
4780168ba8 implementing new locals 2010-11-22 16:39:55 -08:00
Tj Holowaychuk
d1a790d103 fixed jade example for new locals 2010-11-22 16:32:51 -08:00
Tj Holowaychuk
3ad6ab8c6a removed locals: from view tests 2010-11-22 16:24:05 -08:00
Tj Holowaychuk
1e189da85e refactored dynamicHelper support 2010-11-22 16:19:53 -08:00
Tj Holowaychuk
a094c50873 indentation 2010-11-22 16:16:29 -08:00
Tj Holowaychuk
ad324410c6 Utilizing the new View / Partial constructors 2010-11-22 16:12:30 -08:00
Tj Holowaychuk
9f4b7368a0 Started View implementation 2010-11-22 11:33:20 -08:00
Tj Holowaychuk
c8ddedb8d0 jade example fixes 2010-11-19 17:12:34 -08:00
Tj Holowaychuk
7deab2826b Updated jade submodule 2010-11-19 17:08:35 -08:00
Tj Holowaychuk
643879f779 using should / refactored tests 2010-11-19 16:00:21 -08:00
Tj Holowaychuk
ce2a18f483 master is 2.0.0-pre 2010-11-18 07:48:31 -08:00
158 changed files with 6732 additions and 5982 deletions

3
.gitignore vendored
View File

@@ -6,4 +6,5 @@ lib-cov
*.dat
*.out
*.pid
benchmarks/graphs
benchmarks/graphs
testing.js

12
.gitmodules vendored
View File

@@ -13,6 +13,18 @@
[submodule "support/connect"]
path = support/connect
url = git://github.com/senchalabs/connect.git
[submodule "support/should"]
path = support/should
url = git://github.com/visionmedia/should.js.git
[submodule "support/formidable"]
path = support/formidable
url = git://github.com/felixge/node-formidable.git
[submodule "support/jade"]
path = support/jade
url = git://github.com/visionmedia/jade.git
[submodule "support/qs"]
path = support/qs
url = git://github.com/visionmedia/node-querystring.git
[submodule "support/mime"]
path = support/mime
url = https://github.com/bentomas/node-mime.git

7
.npmignore Normal file
View File

@@ -0,0 +1,7 @@
.git*
docs/
examples/
support/
test/
testing.js
.DS_Store

View File

@@ -1,4 +1,90 @@
2.0.0beta2 / 2011-03-07
==================
* Added res.render() `.locals` support back to aid in migration process
* Fixed flash example
2.0.0beta / 2011-03-03
==================
* Added HTTPS support
* Added `res.cookie()` maxAge support
* Added `req.header()` _Referrer_ / _Referer_ special-case, either works
* Added mount support for `res.redirect()`, now respects the mount-point
* Added `union()` util, taking place of `merge(clone())` combo
* Added stylus support to express(1) generated app
* Added secret to session middleware used in examples and generated app
* Added `res.local(name, val)` for progressive view locals
* Added default param support to `req.param(name, default)`
* Added `app.disabled()` and `app.enabled()`
* Added `app.register()` support for omitting leading ".", either works
* Added `res.partial()`, using the same interface as `partial()` within a view. Closes #539
* Added `app.param()` to map route params to async/sync logic
* Added; aliased `app.helpers()` as `app.locals()`. Closes #481
* Added extname with no leading "." support to `res.contentType()`
* Added `cache views` setting, defaulting to enabled in "production" env
* Added index file partial resolution, eg: partial('user') may try _views/user/index.jade_.
* Added `req.accepts()` support for extensions
* Changed; `res.download()` and `res.sendfile()` now utilize Connect's
static file server `connect.static.send()`.
* Changed; replaced `connect.utils.mime()` with npm _mime_ module
* Changed; allow `req.query` to be pre-defined (via middleware or other parent
* Changed view partial resolution, now relative to parent view
* Changed view engine signature. no longer `engine.render(str, options, callback)`, now `engine.compile(str, options) -> Function`, the returned function accepts `fn(locals)`.
* Fixed `req.param()` bug returning Array.prototype methods. Closes #552
* Fixed; using `Stream#pipe()` instead of `sys.pump()` in `res.sendfile()`
* Fixed; using _qs_ module instead of _querystring_
* Fixed; strip unsafe chars from jsonp callbacks
* Removed "stream threshold" setting
1.0.8 / 2011-03-01
==================
* Allow `req.query` to be pre-defined (via middleware or other parent app)
* "connect": ">= 0.5.0 < 1.0.0". Closes #547
* Removed the long deprecated __EXPRESS_ENV__ support
1.0.7 / 2011-02-07
==================
* Fixed `render()` setting inheritance.
Mounted apps would not inherit "view engine"
1.0.6 / 2011-02-07
==================
* Fixed `view engine` setting bug when period is in dirname
1.0.5 / 2011-02-05
==================
* Added secret to generated app `session()` call
1.0.4 / 2011-02-05
==================
* Added `qs` dependency to _package.json_
* Fixed namespaced `require()`s for latest connect support
1.0.3 / 2011-01-13
==================
* Remove unsafe characters from JSONP callback names [Ryan Grove]
1.0.2 / 2011-01-10
==================
* Removed nested require, using `connect.router`
1.0.1 / 2010-12-29
==================
* Fixed for middleware stacked via `createServer()`
previously the `foo` middleware passed to `createServer(foo)`
would not have access to Express methods such as `res.send()`
or props like `req.query` etc.
1.0.0 / 2010-11-16
==================

View File

@@ -1,6 +1,6 @@
(The MIT License)
Copyright (c) 2009-2010 TJ Holowaychuk <tj@vision-media.ca>
Copyright (c) 2009-2011 TJ Holowaychuk <tj@vision-media.ca>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@@ -1,8 +1,6 @@
PREFIX ?= /usr/local
LIB_PREFIX = ~/.node_libraries
DOCS = docs/index.md \
docs/screencasts.md \
docs/executable.md \
docs/contrib.md \
docs/guide.md \
@@ -12,32 +10,10 @@ DOCS = docs/index.md \
MANPAGES =$(DOCS:.md=.1)
HTMLDOCS =$(DOCS:.md=.html)
install: install-docs
@mkdir -p $(PREFIX)/bin
@mkdir -p $(LIB_PREFIX)
cp -f bin/express $(PREFIX)/bin/express
cp -fr lib/express $(LIB_PREFIX)/express
uninstall: uninstall-docs
rm -f $(PREFIX)/bin/express
rm -fr $(LIB_PREFIX)/express
install-support:
cd support/connect && $(MAKE) install
uninstall-support:
cd support/connect && $(MAKE) uninstall
install-docs:
@mkdir -p $(PREFIX)/share/man/man1
cp -f docs/executable.1 $(PREFIX)/share/man/man1/express.1
uninstall-docs:
rm -f $(PREFIX)/share/man/man1/express.1
test:
@NODE_ENV=test ./support/expresso/bin/expresso \
-I lib \
-I support \
-I support/connect/lib \
-I support/haml/lib \
-I support/jade/lib \
@@ -48,17 +24,10 @@ test:
test-cov:
@TESTFLAGS=--cov $(MAKE) test
docs: docs/api.html $(MANPAGES) $(HTMLDOCS)
docs: $(MANPAGES) $(HTMLDOCS)
@ echo "... generating TOC"
@./support/toc.js docs/guide.html
docs/api.html: lib/express/*.js
dox \
--private \
--title Express \
--desc "High performance web framework for [node](http://nodejs.org)." \
$(shell find lib/express/* -type f) > $@
%.1: %.md
@echo "... $< -> $@"
@ronn -r --pipe $< > $@
@@ -70,7 +39,14 @@ docs/api.html: lib/express/*.js
| sed 's/NAME/Express/g' \
> $@
site:
rm -fr /tmp/docs \
&& cp -fr docs /tmp/docs \
&& git checkout gh-pages \
&& cp -fr /tmp/docs/* . \
&& echo "done"
docclean:
rm -f docs/*.{1,html}
.PHONY: install uninstall install-docs install-support uninstall-support install-docs uninstall-docs test test-cov docs docclean
.PHONY: site test test-cov docs docclean

View File

@@ -5,23 +5,17 @@
built on [node](http://nodejs.org) and [Connect](http://github.com/senchalabs/connect).
var app = express.createServer();
app.get('/', function(req, res){
res.send('Hello World');
res.send('Hello World');
});
app.listen(3000);
app.listen(3000);
## Installation
npm:
$ npm install express
curl:
$ curl -# http://expressjs.com/install.sh | sh
## Features
* Robust routing
@@ -60,28 +54,28 @@ The following are the major contributors of Express (in no specific order).
## More Information
* Express [Contrib](http://github.com/visionmedia/express-contrib) repo for additional functionality
* [express-configure](http://github.com/visionmedia/express-configure) async configuration support
* [express-messages](http://github.com/visionmedia/express-messages) flash notification rendering helper
* [express-namespace](http://github.com/visionmedia/express-namespace) namespaced route support
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates
* [Google Group](http://groups.google.com/group/express-js) for discussion
* Visit the [Wiki](http://github.com/visionmedia/express/wiki)
* Screencast - [Introduction](http://bit.ly/eRYu0O)
* Screencast - [View Partials](http://bit.ly/dU13Fx)
* Screencast - [Route Specific Middleware](http://bit.ly/hX4IaH)
* Screencast - [Route Path Placeholder Preconditions](http://bit.ly/eNqmVs)
## Node Compatibility
The latest release of Express is compatible with node --version:
v0.2.4
Express 1.x is compatible with node 0.2.x and connect < 1.0.
and connect --version:
0.3.0
Express 1.x is maintained in the _1.x_ branch.
Express 2.x is compatible with node 0.4.x and connect 1.x
## License
(The MIT License)
Copyright (c) 2009-2010 TJ Holowaychuk &lt;tj@vision-media.ca&gt;
Copyright (c) 2009-2011 TJ Holowaychuk &lt;tj@vision-media.ca&gt;
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@@ -5,20 +5,13 @@
*/
var fs = require('fs')
, sys = require('sys')
, exec = require('child_process').exec;
/**
* Framework version.
*/
var version = '1.0.0';
/**
* stdin stream.
*/
var stdin;
var version = '2.0.0beta2';
/**
* Add session support.
@@ -48,7 +41,7 @@ var usage = ''
+ '\x1b[1mOptions\x1b[0m:\n'
+ ' -s, --sessions Add session support\n'
+ ' -t, --template ENGINE Add template ENGINE support (jade|ejs). Defaults to jade\n'
+ ' -c, --css ENGINE Add stylesheet ENGINE support (less|sass). Defaults to plain css\n'
+ ' -c, --css ENGINE Add stylesheet ENGINE support (less|sass|stylus). Defaults to plain css\n'
+ ' -v, --version Output framework version\n'
+ ' -h, --help Output help information\n'
;
@@ -110,6 +103,10 @@ var css = [
, ' padding: 50px;'
, ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;'
, '}'
, ''
, 'a {'
, ' color: #00B7FF;'
, '}'
].join('\n');
/**
@@ -121,6 +118,10 @@ var less = [
, ' padding: 50px;'
, ' font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;'
, '}'
, ''
, 'a {'
, ' color: #00B7FF;'
, '}'
].join('\n');
/**
@@ -131,6 +132,20 @@ var sass = [
'body'
, ' :padding 50px'
, ' :font 14px "Lucida Grande", Helvetica, Arial, sans-serif'
, 'a'
, ' :color #00B7FF'
].join('\n');
/**
* Default stylus template.
*/
var stylus = [
'body'
, ' padding 50px'
, ' font 14px "Lucida Grande", Helvetica, Arial, sans-serif'
, 'a'
, ' color #00B7FF'
].join('\n');
/**
@@ -145,10 +160,11 @@ var appTest = [
, " * Module dependencies."
, " */"
, ""
, "var app = require('../app');"
, "var app = require('../app')"
, " , assert = require('assert');"
, "",
, "module.exports = {"
, " 'GET /': function(assert){"
, " 'GET /': function(){"
, " assert.response(app,"
, " { url: '/' },"
, " { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' }},"
@@ -178,10 +194,10 @@ var app = [
, 'app.configure(function(){'
, ' app.set(\'views\', __dirname + \'/views\');'
, ' app.set(\'view engine\', \':TEMPLATE\');'
, ' app.use(express.bodyDecoder());'
, ' app.use(express.methodOverride());:SESS:CSS'
, ' app.use(express.bodyParser());'
, ' app.use(express.methodOverride());{sess}{css}'
, ' app.use(app.router);'
, ' app.use(express.staticProvider(__dirname + \'/public\'));'
, ' app.use(express.static(__dirname + \'/public\'));'
, '});'
, ''
, 'app.configure(\'development\', function(){'
@@ -196,9 +212,7 @@ var app = [
, ''
, 'app.get(\'/\', function(req, res){'
, ' res.render(\'index\', {'
, ' locals: {'
, ' title: \'Express\''
, ' }'
, ' title: \'Express\''
, ' });'
, '});'
, ''
@@ -258,7 +272,7 @@ while (args.length) {
} else {
confirm('destination is not empty, continue? ', function(ok){
if (ok) {
stdin.destroy();
process.stdin.destroy();
createApplicationAt(path);
} else {
abort('aborting');
@@ -282,6 +296,9 @@ function createApplicationAt(path) {
mkdir(path + '/public/images');
mkdir(path + '/public/stylesheets', function(){
switch (cssEngine) {
case 'stylus':
write(path + '/public/stylesheets/style.styl', stylus);
break;
case 'less':
write(path + '/public/stylesheets/style.less', less);
break;
@@ -292,7 +309,7 @@ function createApplicationAt(path) {
write(path + '/public/stylesheets/style.css', css);
}
});
mkdir(path + '/views/partials', function(){
mkdir(path + '/views', function(){
switch (templateEngine) {
case 'ejs':
write(path + '/views/layout.ejs', ejsLayout);
@@ -312,15 +329,18 @@ function createApplicationAt(path) {
switch (cssEngine) {
case 'sass':
case 'less':
app = app.replace(':CSS', '\n app.use(express.compiler({ src: __dirname + \'/public\', enable: [\'' + cssEngine + '\'] }));');
app = app.replace('{css}', '\n app.use(express.compiler({ src: __dirname + \'/public\', enable: [\'' + cssEngine + '\'] }));');
break;
case 'stylus':
app = app.replace('{css}', '\n app.use(require(\'stylus\').middleware({ src: __dirname + \'/public\' }));');
break;
default:
app = app.replace(':CSS', '');
app = app.replace('{css}', '');
}
// Session support
app = app.replace(':SESS', sessions
? '\n app.use(express.cookieDecoder());\n app.use(express.session());'
app = app.replace('{sess}', sessions
? '\n app.use(express.cookieParser());\n app.use(express.session({ secret: \'your secret here\' }));'
: '');
// Template support
@@ -351,7 +371,7 @@ function createApplicationAt(path) {
function emptyDirectory(path, fn) {
fs.readdir(path, function(err, files){
if (err && err.errno !== process.ENOENT) throw err;
if (err && 'ENOENT' != err.code) throw err;
fn(!files || !files.length);
});
}
@@ -365,7 +385,7 @@ function emptyDirectory(path, fn) {
function write(path, str) {
fs.writeFile(path, str);
console.log(' \x1b[33mcreate\x1b[0m : ' + path);
console.log(' \x1b[36mcreate\x1b[0m : ' + path);
}
/**
@@ -389,13 +409,18 @@ function confirm(msg, fn) {
*/
function prompt(msg, fn) {
stdin = stdin || process.openStdin();
sys[msg[msg.length - 1] == ' ' ? 'print' : 'puts'](msg);
stdin.setEncoding('ascii');
stdin.addListener('data', function(data){
// prompt
if (' ' == msg[msg.length - 1]) {
process.stdout.write(msg);
} else {
console.log(msg);
}
// stdin
process.stdin.setEncoding('ascii');
process.stdin.once('data', function(data){
fn(data);
stdin.removeListener('data', arguments.callee);
});
}).resume();
}
/**
@@ -408,7 +433,7 @@ function prompt(msg, fn) {
function mkdir(path, fn) {
exec('mkdir -p ' + path, function(err){
if (err) throw err;
console.log(' \x1b[33mcreate\x1b[0m : ' + path);
console.log(' \x1b[36mcreate\x1b[0m : ' + path);
fn && fn();
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "APPLICATIONS" "" "October 2010" "" ""
.TH "APPLICATIONS" "" "March 2011" "" ""
.
.SH "NAME"
\fBapplications\fR

View File

@@ -133,7 +133,7 @@
font-size: 11px;
}
#menu {
margin-left: 65px;
margin-left: 75px;
padding: 0;
padding-bottom: 30px; }
#menu li {
@@ -187,7 +187,7 @@
<ul id="menu">
<li><a href="index.html">Home</a></li>
<li><a href="guide.html">Guide</a></li>
<li><a href="contrib.html">Contributing</a></li>
<li><a href="screencasts.html">Screencasts</a></li>
<li><a href="applications.html">Applications</a></li>
</ul>
<div class='mp'>

View File

@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "CONTRIB" "" "October 2010" "" ""
.TH "CONTRIB" "" "March 2011" "" ""
.
.SH "NAME"
\fBcontrib\fR

View File

@@ -133,7 +133,7 @@
font-size: 11px;
}
#menu {
margin-left: 65px;
margin-left: 75px;
padding: 0;
padding-bottom: 30px; }
#menu li {
@@ -187,7 +187,7 @@
<ul id="menu">
<li><a href="index.html">Home</a></li>
<li><a href="guide.html">Guide</a></li>
<li><a href="contrib.html">Contributing</a></li>
<li><a href="screencasts.html">Screencasts</a></li>
<li><a href="applications.html">Applications</a></li>
</ul>
<div class='mp'>

View File

@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "EXECUTABLE" "" "October 2010" "" ""
.TH "EXECUTABLE" "" "March 2011" "" ""
.
.SH "NAME"
\fBexecutable\fR
@@ -10,21 +10,22 @@
.
.nf
express [\-h|\-\-help] [\-v|\-\-version] [\-c|\-css ENGINE] [PATH]
express [options] [PATH]
.
.fi
.
.SH "Description"
The \fBexpress\fR executable generates apps at the given \fBPATH\fR or the current working directory\. Although Express is not bound to a specific application structure, this executable creates a maintainable base app\.
The \fIexpress\fR executable generates apps at the given \fBPATH\fR or the current working directory\. Although Express is not bound to a specific application structure, this executable creates a maintainable base app\.
.
.SH "Options"
.
.nf
\-s, \-\-sessions Add session support
\-c, \-\-css ENGINE Add css ENGINE support (less|sass)\. Defaults to plain css
\-v, \-\-version Output framework version
\-h, \-\-help Display help information
\-s, \-\-sessions Add session support
\-t, \-\-template ENGINE Add template ENGINE support (jade|ejs)\. Defaults to jade
\-c, \-\-css ENGINE Add stylesheet ENGINE support (less|sass|stylus)\. Defaults to plain css
\-v, \-\-version Output framework version
\-h, \-\-help Output help information
.
.fi

View File

@@ -133,7 +133,7 @@
font-size: 11px;
}
#menu {
margin-left: 65px;
margin-left: 75px;
padding: 0;
padding-bottom: 30px; }
#menu li {
@@ -187,7 +187,7 @@
<ul id="menu">
<li><a href="index.html">Home</a></li>
<li><a href="guide.html">Guide</a></li>
<li><a href="contrib.html">Contributing</a></li>
<li><a href="screencasts.html">Screencasts</a></li>
<li><a href="applications.html">Applications</a></li>
</ul>
<div class='mp'>
@@ -197,21 +197,22 @@
</p>
<h2 id="Synopsis">Synopsis</h2>
<pre><code>express [-h|--help] [-v|--version] [-c|-css ENGINE] [PATH]
<pre><code>express [options] [PATH]
</code></pre>
<h2 id="Description">Description</h2>
<p>The <code>express</code> executable generates apps at the given <strong>PATH</strong> or the
<p>The <em>express</em> executable generates apps at the given <strong>PATH</strong> or the
current working directory. Although Express is not bound to a specific
application structure, this executable creates a maintainable base app.</p>
<h2 id="Options">Options</h2>
<pre><code>-s, --sessions Add session support
-c, --css ENGINE Add css ENGINE support (less|sass). Defaults to plain css
-v, --version Output framework version
-h, --help Display help information
<pre><code> -s, --sessions Add session support
-t, --template ENGINE Add template ENGINE support (jade|ejs). Defaults to jade
-c, --css ENGINE Add stylesheet ENGINE support (less|sass|stylus). Defaults to plain css
-v, --version Output framework version
-h, --help Output help information
</code></pre>
</div>

View File

@@ -1,18 +1,21 @@
## Synopsis
express [-h|--help] [-v|--version] [-c|-css ENGINE] [PATH]
express [options] [PATH]
## Description
The `express` executable generates apps at the given **PATH** or the
The _express_ executable generates apps at the given **PATH** or the
current working directory. Although Express is not bound to a specific
application structure, this executable creates a maintainable base app.
## Options
-s, --sessions Add session support
-c, --css ENGINE Add css ENGINE support (less|sass). Defaults to plain css
-v, --version Output framework version
-h, --help Display help information
-s, --sessions Add session support
-t, --template ENGINE Add template ENGINE support (jade|ejs). Defaults to jade
-c, --css ENGINE Add stylesheet ENGINE support (less|sass|stylus). Defaults to plain css
-v, --version Output framework version
-h, --help Output help information

File diff suppressed because it is too large Load Diff

View File

@@ -133,7 +133,7 @@
font-size: 11px;
}
#menu {
margin-left: 65px;
margin-left: 75px;
padding: 0;
padding-bottom: 30px; }
#menu li {
@@ -180,7 +180,8 @@
<div id="wrapper">
<div id="container"><ul id="toc">
<li><a href="#Installation">Installation</a></li>
<li><a href="#Creating-An-Application">Creating An Application</a></li>
<li><a href="#Creating-A-Server">Creating A Server</a></li>
<li><a href="#Creating-An-HTTPS-Server">Creating An HTTPS Server</a></li>
<li><a href="#Configuration">Configuration</a></li>
<li><a href="#Settings">Settings</a></li>
<li><a href="#Routing">Routing</a></li>
@@ -189,8 +190,10 @@
<li><a href="#Route-Middleware">Route Middleware</a></li>
<li><a href="#HTTP-Methods">HTTP Methods</a></li>
<li><a href="#Error-Handling">Error Handling</a></li>
<li><a href="#Route-Param-Pre-conditions">Route Param Pre-conditions</a></li>
<li><a href="#View-Rendering">View Rendering</a></li>
<li><a href="#View-Partials">View Partials</a></li>
<li><a href="#View-Lookup">View Lookup</a></li>
<li><a href="#Template-Engines">Template Engines</a></li>
<li><a href="#Session-Support">Session Support</a></li>
<li><a href="#Migration-Guide">Migration Guide</a></li>
@@ -198,7 +201,7 @@
<li><a href="#req-header-key-defaultValue-">header()</a></li>
<li><a href="#req-accepts-type-">accepts()</a></li>
<li><a href="#req-is-type-">is()</a></li>
<li><a href="#req-param-name-">param()</a></li>
<li><a href="#req-param-name-default-">param()</a></li>
<li><a href="#req-flash-type-msg-">flash()</a></li>
<li><a href="#req-isXMLHttpRequest">isXMLHttpRequest</a></li>
</ul></li>
@@ -206,19 +209,22 @@
<li><a href="#res-header-key-val-">header()</a></li>
<li><a href="#res-contentType-type-">contentType()</a></li>
<li><a href="#res-attachment-filename-">attachment()</a></li>
<li><a href="#res-sendfile-path-">sendfile()</a></li>
<li><a href="#res-download-file-filename-">download()</a></li>
<li><a href="#res-sendfile-path-options-callback-">sendfile()</a></li>
<li><a href="#res-download-file-filename-callback-">download()</a></li>
<li><a href="#res-send-body-status-headers-status-status-">send()</a></li>
<li><a href="#res-redirect-url-status-">redirect()</a></li>
<li><a href="#res-cookie-name-val-options-">cookie()</a></li>
<li><a href="#res-clearCookie-name-">clearCookie()</a></li>
<li><a href="#res-render-view-options-fn-">render()</a></li>
<li><a href="#res-partial-view-options-">partial()</a></li>
<li><a href="#res-local-name-val-">local()</a></li>
</ul></li>
<li><a href="#" class="toggle">+</a> <a class="section-title" href="#">Server</a><ul class="section" id="section-Server">
<li><a href="#app-set-name-val-">set()</a></li>
<li><a href="#app-enable-name-">enable()</a></li>
<li><a href="#app-enabled-name-">enabled()</a></li>
<li><a href="#app-disable-name-">disable()</a></li>
<li><a href="#app-disabled-name-">disabled()</a></li>
<li><a href="#app-configure-env-function-function-">configure()</a></li>
<li><a href="#app-redirect-name-val-">redirect()</a></li>
<li><a href="#app-error-function-">error()</a></li>
@@ -237,7 +243,7 @@
<ul id="menu">
<li><a href="index.html">Home</a></li>
<li><a href="guide.html">Guide</a></li>
<li><a href="contrib.html">Contributing</a></li>
<li><a href="screencasts.html">Screencasts</a></li>
<li><a href="applications.html">Applications</a></li>
</ul>
<div class='mp'>
@@ -247,38 +253,30 @@
</p>
<h3 id="Installation">Installation</h3>
<p>curl:</p>
<pre><code>$ curl -# http://expressjs.com/install.sh | sh
</code></pre>
<p>npm:</p>
<pre><code>$ npm install express
</code></pre>
<p>git clone, first update the submodules:</p>
<h3 id="Creating-A-Server">Creating A Server</h3>
<pre><code>$ git submodule update --init
$ make install
$ make install-support
</code></pre>
<h3 id="Creating-An-Application">Creating An Application</h3>
<p>The <em>express.Server</em> now inherits from <em>http.Server</em>, however
follows the same idiom by providing <em>express.createServer()</em> as shown below. This means
that you can utilize Express server's transparently with other libraries.</p>
<p> To create an instance of the <em>express.HTTPServer</em>, simply invoke the <em>createServer()</em> method. With our instance <em>app</em> we can then define routes based on the HTTP verbs, in this example <em>app.get()</em>.</p>
<pre><code>var app = require('express').createServer();
app.get('/', function(req, res){
res.send('hello world');
res.send('hello world');
});
app.listen(3000);
</code></pre>
<h3 id="Creating-An-HTTPS-Server">Creating An HTTPS Server</h3>
<p> To initialize a <em>express.HTTPSServer</em> we do the same as above, however we
pass an options object, accepting <em>key</em>, <em>cert</em> and the others mentioned in node's <a href="http://nodejs.org/docs/v0.3.7/api/https.html#https.createServer">https documentation</a>.</p>
<pre><code> var app = require('express').createServer({ key: ... });
</code></pre>
<h3 id="Configuration">Configuration</h3>
<p>Express supports arbitrary environments, such as <em>production</em> and <em>development</em>. Developers
@@ -287,39 +285,44 @@ can use the <em>configure()</em> method to setup needs required by the current e
prior to the environment specific callback.</p>
<p>In the example below we only <em>dumpExceptions</em>, and respond with exception stack traces
in <em>development</em> mode, however for both environments we utilize <em>methodOverride</em> and <em>bodyDecoder</em>.
in <em>development</em> mode, however for both environments we utilize <em>methodOverride</em> and <em>bodyParser</em>.
Note the use of <em>app.router</em>, which can (optionally) be used to mount the application routes,
otherwise the first call to <em>app.{get,put,del,post}()</em> will mount the routes.</p>
otherwise the first call to <em>app.get()</em>, <em>app.post()</em>, etc will mount the routes.</p>
<pre><code>app.configure(function(){
app.use(express.methodOverride());
app.use(express.bodyDecoder());
app.use(express.bodyParser());
app.use(app.router);
app.use(express.staticProvider(__dirname + '/public'));
});
app.configure('development', function(){
app.use(express.static(__dirname + '/public'));
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function(){
app.use(express.errorHandler());
var oneYear = 31557600000;
app.use(express.static({ root: __dirname + '/public', maxAge: oneYear }));
app.use(express.errorHandler());
});
</code></pre>
<p>For internal and arbitrary settings Express provides the <em>set(key[, val])</em>, <em>enable(key)</em>, <em>disable(key)</em> methods:</p>
<pre><code>app.configure(function(){
<pre><code> app.configure(function(){
app.set('views', __dirname + '/views');
app.set('views');
// =&gt; "... views directory ..."
// =&gt; "/absolute/path/to/views"
app.enable('some feature');
// same as app.set('some feature', true);
app.disable('some feature');
// same as app.set('some feature', false);
});
app.enabled('some feature')
// =&gt; false
});
</code></pre>
<p>To alter the environment we can set the <em>NODE_ENV</em> environment variable, for example:</p>
@@ -334,13 +337,10 @@ app.configure('production', function(){
<p>Express supports the following settings out of the box:</p>
<ul>
<li><em>env</em> Application environment set internally, use <em>app.set('env')</em> on <em>Server#listen()</em></li>
<li><em>home</em> Application base path used for <em>res.redirect()</em> and transparently handling mounted apps.</li>
<li><em>views</em> Root views directory defaulting to <strong>CWD/views</strong></li>
<li><em>view engine</em> Default view engine name for views rendered without extensions</li>
<li><em>view options</em> An object specifying global view options</li>
<li><em>partials</em> Root view partials directory defaulting to <em>views</em>/partials.</li>
<li><em>stream threshold</em> Bytesize indicating when a file should be streamed for <em>res.sendfile()</em> using <em>fs.ReadStream()</em> and <em>sys.pump()</em>.</li>
</ul>
@@ -363,7 +363,7 @@ when <em>/user/:id</em> is compiled, a simplified version of the regexp may look
</code></pre>
<p>Regular expression literals may also be passed for complex uses. Since capture
groups with literal <em>RegExp</em>'s are anonymous we can access them directly <code>req.params</code>.</p>
groups with literal <em>RegExp</em>'s are anonymous we can access them directly <code>req.params</code>. So our first capture group would be <em>req.params[0]</em> and the second would follow as <em>req.params[1]</em>.</p>
<pre><code>app.get(/^\/users?(?:\/(\d+)(?:\.\.(\d+))?)?/, function(req, res){
res.send(req.params);
@@ -412,14 +412,18 @@ may consume:</p>
/products.json
/products.xml
/products
"/user/:id.:format?"
/user/12
/user/12.json
</code></pre>
<p>For example we can <strong>POST</strong> some json, and echo the json back using the <em>bodyDecoder</em> middleware which will parse json request bodies (as well as others), and place the result in <em>req.body</em>:</p>
<p>For example we can <strong>POST</strong> some json, and echo the json back using the <em>bodyParser</em> middleware which will parse json request bodies (as well as others), and place the result in <em>req.body</em>:</p>
<pre><code>var express = require('express')
, app = express.createServer();
app.use(express.bodyDecoder());
app.use(express.bodyParser());
app.post('/', function(req, res){
res.send(req.body);
@@ -428,11 +432,13 @@ app.post('/', function(req, res){
app.listen(3000);
</code></pre>
<p>Typically we may use a "dumb" placeholder such as "/user/:id" which has no restrictions, however say for example we are limiting a user id to digits, we may use <em>'/user/:id(\d+)'</em> which will <em>not</em> match unless the placeholder value contains only digits.</p>
<h3 id="Passing-Route-Control">Passing Route Control</h3>
<p>We may pass control to the next <em>matching</em> route, by calling the <em>third</em> argument,
the <em>next()</em> function. When a match cannot be made, control is passed back to Connect,
and middleware continue to be invoked. The same is true for several routes which have the same path defined, they will simply be executed in order until one does <em>not</em> call <em>next()</em>.</p>
and middleware continue to be invoked in the order that they are added via <em>use()</em>. The same is true for several routes which have the same path defined, they will simply be executed in order until one does <em>not</em> call <em>next()</em> and decides to respond.</p>
<pre><code>app.get('/users/:id?', function(req, res, next){
var id = req.params.id;
@@ -448,7 +454,7 @@ app.get('/users', function(req, res){
});
</code></pre>
<p>Express 1.0 also introduces the <em>all()</em> method, which provides a route callback matching any HTTP method. This is useful in many ways, one example being the loading of resources before executing subsequent routes as shown below:</p>
<p>The <em>app.all()</em> method is useful for applying the same logic for all HTTP verbs in a single call. Below we use this to load a user from our fake database, and assign it to <em>req.user</em>.</p>
<pre><code>var express = require('express')
, app = express.createServer();
@@ -491,12 +497,12 @@ passed to <em>express.createServer()</em> as you would with a regular Connect se
<pre><code> var express = require('express');
var app = express.createServer(
express.logger(),
express.bodyDecoder()
express.logger()
, express.bodyParser()
);
</code></pre>
<p>Alternatively we can <em>use()</em> them which is useful when adding middleware within <em>configure()</em> blocks:</p>
<p>Alternatively we can <em>use()</em> them which is useful when adding middleware within <em>configure()</em> blocks, in a progressive manor.</p>
<pre><code>app.use(express.logger({ format: ':method :uri' }));
</code></pre>
@@ -505,11 +511,13 @@ var app = express.createServer(
<pre><code>var connect = require('connect');
app.use(connect.logger());
app.use(connect.bodyParser());
</code></pre>
<p>This is somewhat annoying, so express re-exports these middleware properties, however they are <em>identical</em>:</p>
<pre><code>app.use(express.logger());
app.use(express.bodyParser());
</code></pre>
<h3 id="Route-Middleware">Route Middleware</h3>
@@ -519,56 +527,56 @@ app.use(connect.logger());
<p>Typically async data retrieval might look similar to below, where we take the <em>:id</em> parameter, and attempt loading a user.</p>
<pre><code>app.get('/user/:id', function(req, res, next){
loadUser(req.params.id, function(err, user){
if (err) return next(err);
res.send('Viewing user ' + user.name);
});
loadUser(req.params.id, function(err, user){
if (err) return next(err);
res.send('Viewing user ' + user.name);
});
});
</code></pre>
<p>To keep things DRY and to increase readability we can apply this logic within a middleware. As you can see below, abstracting this logic into middleware allows us to reuse it, and clean up our route at the same time.</p>
<pre><code>function loadUser(req, res, next) {
// You would fetch your user from the db
var user = users[req.params.id];
if (user) {
req.user = user;
next();
} else {
next(new Error('Failed to load user ' + req.params.id));
}
// You would fetch your user from the db
var user = users[req.params.id];
if (user) {
req.user = user;
next();
} else {
next(new Error('Failed to load user ' + req.params.id));
}
}
app.get('/user/:id', loadUser, function(req, res){
res.send('Viewing user ' + req.user.name);
res.send('Viewing user ' + req.user.name);
});
</code></pre>
<p>Multiple route middleware can be applied, and will be executed sequentially to apply further logic such as restricting access to a user account. In the example below only the authenticated user may edit his/her account.</p>
<pre><code>function andRestrictToSelf(req, res, next) {
req.authenticatedUser.id == req.user.id
? next()
: next(new Error('Unauthorized'));
req.authenticatedUser.id == req.user.id
? next()
: next(new Error('Unauthorized'));
}
app.get('/user/:id/edit', loadUser, andRestrictToSelf, function(req, res){
res.send('Editing user ' + req.user.name);
res.send('Editing user ' + req.user.name);
});
</code></pre>
<p>Keeping in mind that middleware are simply functions, we can define function that <em>returns</em> the middleware in order to create a more expressive and flexible solution as shown below.</p>
<pre><code>function andRestrictTo(role) {
return function(req, res, next) {
req.authenticatedUser.role == role
? next()
: next(new Error('Unauthorized'));
}
return function(req, res, next) {
req.authenticatedUser.role == role
? next()
: next(new Error('Unauthorized'));
}
}
app.del('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){
res.send('Deleted user ' + req.user.name);
res.send('Deleted user ' + req.user.name);
});
</code></pre>
@@ -601,22 +609,22 @@ app.get('/', all, function(){});
&lt;/form&gt;
</code></pre>
<p>By default Express does not know what to do with this request body, so we should add the <em>bodyDecoder</em> middleware, which will parse <em>application/x-www-form-urlencoded</em> request bodies and place the variables in <em>req.body</em>. We can do this by "using" the middleware as shown below:</p>
<p>By default Express does not know what to do with this request body, so we should add the <em>bodyParser</em> middleware, which will parse <em>application/x-www-form-urlencoded</em> and <em>application/json</em> request bodies and place the variables in <em>req.body</em>. We can do this by "using" the middleware as shown below:</p>
<pre><code>app.use(express.bodyDecoder());
<pre><code>app.use(express.bodyParser());
</code></pre>
<p>Our route below will now have access to the <em>req.body.user</em> object which will contain the <em>name</em> and <em>email</em> properties when defined.</p>
<pre><code>app.post('/', function(req, res){
console.log(req.body.user);
res.redirect('back');
console.log(req.body.user);
res.redirect('back');
});
</code></pre>
<p>When using methods such as <em>PUT</em> with a form, we can utilize a hidden input named <em>_method</em>, which can be used to alter the HTTP method. To do so we first need the <em>methodOverride</em> middleware, which should be placed below <em>bodyDecoder</em> so that it can utilize it's <em>req.body</em> containing the form values.</p>
<p>When using methods such as <em>PUT</em> with a form, we can utilize a hidden input named <em>_method</em>, which can be used to alter the HTTP method. To do so we first need the <em>methodOverride</em> middleware, which should be placed below <em>bodyParser</em> so that it can utilize it's <em>req.body</em> containing the form values.</p>
<pre><code>app.use(express.bodyDecoder());
<pre><code>app.use(express.bodyParser());
app.use(express.methodOverride());
</code></pre>
@@ -642,19 +650,19 @@ or passed to <em>next(err)</em>. Below is an example which serves different page
ad-hoc <em>NotFound</em> exception:</p>
<pre><code>function NotFound(msg){
this.name = 'NotFound';
Error.call(this, msg);
Error.captureStackTrace(this, arguments.callee);
this.name = 'NotFound';
Error.call(this, msg);
Error.captureStackTrace(this, arguments.callee);
}
sys.inherits(NotFound, Error);
NotFound.protoype.__proto__ = Error.prototype;
app.get('/404', function(req, res){
throw new NotFound;
throw new NotFound;
});
app.get('/500', function(req, res){
throw new Error('keyboard cat!');
throw new Error('keyboard cat!');
});
</code></pre>
@@ -677,14 +685,12 @@ handle exceptions in different ways based on the environment.</p>
</code></pre>
<p>Here we assume all errors as 500 for the simplicity of
this demo, however you can choose whatever you like</p>
this demo, however you can choose whatever you like. For example when node performs filesystem syscalls, you may receive an error object with the <em>error.code</em> of <em>ENOENT</em>, meaning "no such file or directory", we can utilize this in our error handling and display a page specific to this if desired.</p>
<pre><code>app.error(function(err, req, res){
res.render('500.jade', {
locals: {
error: err
}
});
res.render('500.jade', {
error: err
});
});
</code></pre>
@@ -704,26 +710,65 @@ that are passed or thrown, so we can set <em>showStack</em> to true:</p>
<p>The <em>errorHandler</em> middleware also responds with <em>json</em> if <em>Accept: application/json</em>
is present, which is useful for developing apps that rely heavily on client-side JavaScript.</p>
<h3 id="Route-Param-Pre-conditions">Route Param Pre-conditions</h3>
<p>Route param pre-conditions can drastically improve the readability of your application, through implicit loading of data, and validation of request urls. For example if you are constantly fetching common data for several routes, such as loading a user for <em>/user/:id</em>, we might typically do something like below:</p>
<pre><code>app.get('/user/:userId', function(req, res, next){
User.get(req.params.userId, function(err, user){
if (err) return next(err);
res.send('user ' + user.name);
});
});
</code></pre>
<p>With preconditions our params can be mapped to callbacks which may perform validation, coercion, or even loading data from a database. Below we invoke <em>app.param()</em> with the parameter name we wish to map to some middleware, as you can see we receive the <em>id</em> argument which contains the placeholder value. Using this we load the user and perform error handling as usual, and simple call <em>next()</em> to pass control to the next precondition or route handler.</p>
<pre><code>app.param('userId', function(req, res, next, id){
User.get(id, function(err, user){
if (err) return next(err);
if (!user) return next(new Error('failed to find user'));
req.user = user;
next();
});
});
</code></pre>
<p>Doing so, as mentioned drastically improves our route readability, and allows us to easily share this logic throughout our application:</p>
<pre><code>app.get('/user/:userId', function(req, res){
res.send('user ' + req.user.name);
});
</code></pre>
<p>For simple cases such as route placeholder validation and coercion we can simple pass a callback which has an arity of 1 (accepts one argument). Any errors thrown will be passed to <em>next(err)</em>.</p>
<pre><code>app.param('number', function(n){ return parseInt(n, 10); });
</code></pre>
<p>We may also apply the same callback to several placeholders, for example a route GET <em>/commits/:from-:to</em> are both numbers, so we may define them as an array:</p>
<pre><code>app.param(['from', 'to'], function(n){ return parseInt(n, 10); });
</code></pre>
<h3 id="View-Rendering">View Rendering</h3>
<p>View filenames take the form <em>Express</em>.<em>ENGINE</em>, where <em>ENGINE</em> is the name
<p>View filenames take the form "&lt;name&gt;.&lt;engine&gt;", where &lt;engine&gt; is the name
of the module that will be required. For example the view <em>layout.ejs</em> will
tell the view system to <em>require('ejs')</em>, the module being loaded must export the method <em>exports.render(str, options)</em> to comply with Express, however
<em>app.register()</em> can be used to map engines to file extensions, so that for example "foo.html" can be rendered by jade.</p>
tell the view system to <em>require('ejs')</em>, the module being loaded must export the method <em>exports.compile(str, options)</em>, and return a <em>Function</em> to comply with Express. To alter this behaviour
<em>app.register()</em> can be used to map engines to file extensions, so that for example "foo.html" can be rendered by ejs.</p>
<p>Below is an example using <a href="http://github.com/visionmedia/haml.js">Haml.js</a> to render <em>index.html</em>,
and since we do not use <em>layout: false</em> the rendered contents of <em>index.html</em> will be passed as
the <em>body</em> local variable in <em>layout.haml</em>.</p>
<p>Below is an example using <a href="http://github.com/visionmedia/jade">Jade</a> to render <em>index.html</em>,
and since we do not use <em>layout: false</em> the rendered contents of <em>index.jade</em> will be passed as
the <em>body</em> local variable in <em>layout.jade</em>.</p>
<pre><code>app.get('/', function(req, res){
res.render('index.haml', {
locals: { title: 'My Site' }
});
res.render('index.jade', { title: 'My Site' });
});
</code></pre>
<p>The new <em>view engine</em> setting allows us to specify our default template engine,
so for example when using <a href="http://github.com/visionmedia/jade">Jade</a> we could set:</p>
so for example when using jade we could set:</p>
<pre><code>app.set('view engine', 'jade');
</code></pre>
@@ -747,11 +792,11 @@ mix and match template engines:</p>
<p>Express also provides the <em>view options</em> setting, which is applied each time a view is rendered, so for example if you rarely use layouts you may set:</p>
<pre><code>app.set('view options', {
layout: false
layout: false
});
</code></pre>
<p>Which can then be overridden within the <code>res.render()</code> call if need be:</p>
<p>Which can then be overridden within the <em>res.render()</em> call if need be:</p>
<pre><code>res.render('myview.ejs', { layout: true });
</code></pre>
@@ -781,21 +826,13 @@ mix and match template engines:</p>
<h3 id="View-Partials">View Partials</h3>
<p>The Express view system has built-in support for partials and collections, which are
sort of "mini" views representing a document fragment. For example rather than iterating
<p>The Express view system has built-in support for partials and collections, which are "mini" views representing a document fragment. For example rather than iterating
in a view to display comments, we would use a partial with collection support:</p>
<pre><code>partial('comment.haml', { collection: comments });
</code></pre>
<p>To make things even less verbose we can assume the extension as <em>.haml</em> when omitted,
however if we wished we could use an ejs partial, within a haml view for example.</p>
<pre><code>partial('comment', { collection: comments });
</code></pre>
<p>And once again even further, when rendering a collection we can simply pass
an array, if no other options are desired:</p>
<p>If no other options are desired, we can omit the object and simply pass our array, which is equivalent to above:</p>
<pre><code>partial('comment', comments);
</code></pre>
@@ -804,22 +841,33 @@ an array, if no other options are desired:</p>
for free:</p>
<ul>
<li><em>firstInCollection</em> True if this is the first object</li>
<li><em>indexInCollection</em> Index of the object in the collection</li>
<li><em>lastInCollection</em> True if this is the last object</li>
<li><em>collectionLength</em> Length of the collection</li>
<li><em>firstInCollection</em> true if this is the first object</li>
<li><em>indexInCollection</em> index of the object in the collection</li>
<li><em>lastInCollection</em> true if this is the last object</li>
<li><em>collectionLength</em> length of the collection</li>
</ul>
<p>Local variables passed (or generated) take precedence, however locals passed to the parent view are available in the child view as well. So for example if we were to render a blog post with <em>partial('blog/post', post)</em> it would generate the <em>post</em> local, but the view calling this function had the local <em>user</em>, it would be available to the <em>blog/post</em> view as well.</p>
<p>For documentation on altering the object name view <a href="http://expressjs.com/guide.html#res-partial-view-options-">res.partial()</a>.</p>
<p><strong>NOTE:</strong> be careful about when you use partial collections, as rendering an array with a length of 100 means we have to render 100 views. For simple collections you may inline the iteration instead of using partial collection support to decrease overhead.</p>
<h3 id="View-Lookup">View Lookup</h3>
<p>View lookup is performed relative to the parent view, for example if we had a page view named <em>views/user/list.jade</em>, and within that view we did <em>partial('edit')</em> it would attempt to load <em>views/user/edit.jade</em>, whereas <em>partial('../messages')</em> would load <em>views/messages.jade</em>.</p>
<p>The view system also allows for index templates, allowing you to have a directory of the same name. For example within a route we may have <em>res.render('users')</em> either <em>views/users.jade</em>, or <em>views/users/index.jade</em>.</p>
<p>When utilizing index views as shown above, we may reference <em>views/users/index.jade</em> from a view in the same directory by <em>partial('users')</em>, and the view system will try <em>../users/index</em>, preventing us from needing to call <em>partial('index')</em>.</p>
<h3 id="Template-Engines">Template Engines</h3>
<p>Below are a few template engines commonly used with Express:</p>
<ul>
<li><a href="http://jade-lang.com">Jade</a> haml.js successor</li>
<li><a href="http://github.com/visionmedia/haml.js">Haml</a> pythonic indented templates</li>
<li><a href="http://github.com/visionmedia/ejs">EJS</a> Embedded JavaScript</li>
<li><a href="http://github.com/mauricemach/coffeekup">CoffeeKup</a> CoffeeScript based templating</li>
<li><a href="https://github.com/kof/node-jqtpl">jQuery Templates</a> for node</li>
@@ -828,29 +876,29 @@ for free:</p>
<h3 id="Session-Support">Session Support</h3>
<p>Sessions support can be added by using Connect's <em>session</em> middleware. To do so we also need the <em>cookieDecoder</em> middleware place above it, which will parse and populate cookie data to <em>req.cookies</em>.</p>
<p>Sessions support can be added by using Connect's <em>session</em> middleware. To do so we also need the <em>cookieParser</em> middleware place above it, which will parse and populate cookie data to <em>req.cookies</em>.</p>
<pre><code>app.use(express.cookieDecoder());
app.use(express.session());
<pre><code>app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat" }));
</code></pre>
<p>By default the <em>session</em> middleware uses the memory store bundled with Connect, however many implementations exist. For example <a href="http://github.com/visionmedia/connect-redis">connect-redis</a> supplies a <a href="http://code.google.com/p/redis/">Redis</a> session store and can be used as shown below:</p>
<pre><code>var RedisStore = require('connect-redis');
app.use(express.cookieDecoder());
app.use(express.session({ store: new RedisStore }));
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
</code></pre>
<p>Now the <em>req.session</em> and <em>req.sessionStore</em> properties will be accessible to all routes and subsequent middleware. Properties on <em>req.session</em> are automatically saved on a response, so for example if we wish to shopping cart data:</p>
<pre><code>var RedisStore = require('connect-redis');
app.use(express.bodyDecoder());
app.use(express.cookieDecoder());
app.use(express.session({ store: new RedisStore }));
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
app.post('/add-to-cart', function(req, res){
// Perhaps we posted several items with a form
// (use the bodyDecoder() middleware for this)
// (use the bodyParser() middleware for this)
var items = req.body.items;
req.session.items = items;
res.redirect('back');
@@ -867,11 +915,11 @@ app.get('/add-to-cart', function(req, res){
});
</code></pre>
<p>The <em>req.session</em> object also has methods such as <em>Session#touch()</em>, <em>Session#destroy()</em>, <em>Session#regenerate()</em> among others to maintain and manipulate sessions. For more information view the <a href="http://senchalabs.github.com/connect/session.html">Connect Session</a> documentation.</p>
<p>The <em>req.session</em> object also has methods such as <em>Session#touch()</em>, <em>Session#destroy()</em>, <em>Session#regenerate()</em> among others to maintain and manipulate sessions. For more information view the <a href="http://senchalabs.github.com/connect/middleware-session.html">Connect Session</a> documentation.</p>
<h3 id="Migration-Guide">Migration Guide</h3>
<p> Pre-beta Express developers may reference the <a href="migrate.html">Migration Guide</a> to get up to speed on how to upgrade your application.</p>
<p> Express 1.x developers may reference the <a href="migrate.html">Migration Guide</a> to get up to speed on how to upgrade your application to work with Express 2.x, Connect 1.x, and Node 0.4.x.</p>
<h3 id="req-header-key-defaultValue-">req.header(key[, defaultValue])</h3>
@@ -882,6 +930,17 @@ req.header('host');
req.header('Accept', '*/*');
</code></pre>
<p>The <em>Referrer</em> and <em>Referer</em> header fields are special-cased, either will work:</p>
<pre><code>// sent Referrer: http://google.com
req.header('Referer');
// =&gt; "http://google.com"
req.header('Referrer');
// =&gt; "http://google.com"
</code></pre>
<h3 id="req-accepts-type-">req.accepts(type)</h3>
<p>Check if the <em>Accept</em> header is present, and includes the given <em>type</em>.</p>
@@ -961,12 +1020,12 @@ can perform any request assertion you wish.</p>
<pre><code>req.is('*/json');
</code></pre>
<h3 id="req-param-name-">req.param(name)</h3>
<h3 id="req-param-name-default-">req.param(name[, default])</h3>
<p>Return the value of param <em>name</em> when present.</p>
<p>Return the value of param <em>name</em> when present or <em>default</em>.</p>
<ul>
<li>Checks route placeholders (<em>req.params</em>), ex: /user/:id</li>
<li>Checks route params (<em>req.params</em>), ex: /user/:id</li>
<li>Checks query string params (<em>req.query</em>), ex: ?id=12</li>
<li>Checks urlencoded body params (<em>req.body</em>), ex: id=12</li>
</ul>
@@ -974,7 +1033,7 @@ can perform any request assertion you wish.</p>
<p>To utilize urlencoded request bodies, <em>req.body</em>
should be an object. This can be done by using
the <em>express.bodyDecoder</em> middleware.</p>
the _express.bodyParser middleware.</p>
<h3 id="req-flash-type-msg-">req.flash(type[, msg])</h3>
@@ -1029,7 +1088,7 @@ res.header('Content-Length');
<pre><code> var filename = 'path/to/image.png';
res.contentType(filename);
// res.headers['Content-Type'] is now "image/png"
// Content-Type is now "image/png"
</code></pre>
<h3 id="res-attachment-filename-">res.attachment([filename])</h3>
@@ -1039,27 +1098,33 @@ res.header('Content-Length');
<pre><code> res.attachment('path/to/my/image.png');
</code></pre>
<h3 id="res-sendfile-path-">res.sendfile(path)</h3>
<h3 id="res-sendfile-path-options-callback-">res.sendfile(path[, options[, callback]])</h3>
<p>Used by <code>res.download()</code> to transfer an arbitrary file.</p>
<pre><code>res.sendfile('path/to/my.file');
</code></pre>
<p>This method accepts a callback which when given will be called on an exception, as well as when the transfer has completed. When a callback is not given, and the file has <strong>not</strong> been streamed, <em>next(err)</em> will be called on an exception.</p>
<p>This method accepts an optional callback which is called when
an error occurs, or when the transfer is complete. By default failures call <code>next(err)</code>, however when a callback is supplied you must do this explicitly, or act on the error.</p>
<pre><code>res.sendfile(path, function(err, path){
<pre><code>res.sendfile(path, function(err){
if (err) {
// handle the error
next(err);
} else {
console.log('transferred %s', path);
}
});
</code></pre>
<p>When the filesize exceeds the <em>stream threshold</em> (defaulting to 32k), the file will be streamed using <em>fs.ReadStream</em> and <em>sys.pump()</em>.</p>
<p>Options may also be passed to the internal <em>fs.createReadStream()</em> call, for example altering the <em>bufferSize</em>:</p>
<h3 id="res-download-file-filename-">res.download(file[, filename])</h3>
<pre><code>res.sendfile(path, { bufferSize: 1024 }, function(err){
// handle
});
</code></pre>
<h3 id="res-download-file-filename-callback-">res.download(file[, filename[, callback]])</h3>
<p>Transfer the given <em>file</em> as an attachment with optional alternative <em>filename</em>.</p>
@@ -1073,11 +1138,17 @@ res.download('path/to/image.png', 'foo.png');
res.sendfile(file);
</code></pre>
<p>An optional callback may be supplied as either the second or third argument, which is passed to <em>res.sendfile()</em>:</p>
<pre><code>res.download(path, 'expenses.doc', function(err){
// handle
});
</code></pre>
<h3 id="res-send-body-status-headers-status-status-">res.send(body|status[, headers|status[, status]])</h3>
<p>The <code>res.send()</code> method is a high level response utility allowing you to pass
objects to respond with json, strings for html, arbitrary _Buffer_s or numbers for status
code based responses. The following are all valid uses:</p>
<p>The <em>res.send()</em> method is a high level response utility allowing you to pass
objects to respond with json, strings for html, Buffer instances, or numbers representing the status code. The following are all valid uses:</p>
<pre><code> res.send(); // 204
res.send(new Buffer('wahoo'));
@@ -1092,6 +1163,8 @@ code based responses. The following are all valid uses:</p>
assigned through <code>res.send()</code> or previously with <code>res.header()</code> or <code>res.contentType()</code>
it will not be set again.</p>
<p>Note that this method <em>end()</em>s the response, so you will want to use node's <em>res.write()</em> for multiple writes or streaming.</p>
<h3 id="res-redirect-url-status-">res.redirect(url[, status])</h3>
<p>Redirect to the given <em>url</em> with a default response <em>status</em> of 302.</p>
@@ -1109,18 +1182,23 @@ the "home" setting and defaults to "/".</p>
<h3 id="res-cookie-name-val-options-">res.cookie(name, val[, options])</h3>
<p>Sets the given cookie <em>name</em> to <em>val</em>, with <em>options</em> such as "httpOnly: true", "expires", "secure" etc.</p>
<p>Sets the given cookie <em>name</em> to <em>val</em>, with options <em>httpOnly</em>, <em>secure</em>, <em>expires</em> etc.</p>
<pre><code>// "Remember me" for 15 minutes
res.cookie('rememberme', 'yes', { expires: new Date(Date.now() + 900000), httpOnly: true });
</code></pre>
<p>The <em>maxAge</em> property may be used to set <em>expires</em> relative to <em>Date.now()</em> in milliseconds, so our example above can now become:</p>
<pre><code>res.cookie('rememberme', 'yes', { maxAge: 900000 });
</code></pre>
<p>To parse incoming <em>Cookie</em> headers, use the <em>cookieDecoder</em> middleware, which provides the <em>req.cookies</em> object:</p>
<pre><code>app.use(express.cookieDecoder());
<pre><code>app.use(express.cookieParser());
app.get('/', function(req, res){
// use req.cookies.rememberme
// use req.cookies.rememberme
});
</code></pre>
@@ -1137,17 +1215,11 @@ app.get('/', function(req, res){
When a callback function is given a response will <em>not</em> be made
automatically, however otherwise a response of <em>200</em> and <em>text/html</em> is given.</p>
<p> Most engines accept one or more of the following options,
both <a href="http://github.com/visionmedia/haml.js">haml</a> and <a href="http://github.com/visionmedia/jade">jade</a> accept all:</p>
<ul>
<li><em>scope</em> Template evaluation context (value of <em>this</em>)</li>
<li><em>locals</em> Object containing local variables</li>
<li><em>debug</em> Output debugging information</li>
<li><em>status</em> Response status code, defaults to 200</li>
<li><em>headers</em> Response headers object</li>
</ul>
<p>The <em>options</em> passed are the local variables as well, for example if we want to expose "user" to the view, and prevent a local we do so within the same object:</p>
<pre><code>var user = { name: 'tj' };
res.render('index', { layout: false, user: user });
</code></pre>
<h3 id="res-partial-view-options-">res.partial(view[, options])</h3>
@@ -1221,6 +1293,40 @@ partial('movie', { object: movie });
// =&gt; In view: movie.director
</code></pre>
<p>This exact API can be utilized from within a route, to respond with a fragment via Ajax or WebSockets, for example we can render a collection of users directly from a route:</p>
<pre><code>app.get('/users', function(req, res){
if (req.xhr) {
// respond with the each user in the collection
// passed to the "user" view
res.partial('user', users);
} else {
// respond with layout, and users page
// which internally does partial('user', users)
// along with other UI
res.render('users', { users: users });
}
});
</code></pre>
<h3 id="res-local-name-val-">res.local(name[, val])</h3>
<p>Get or set the given local variable <em>name</em>. The locals built up for a response are applied to those given to the view rendering methods such as <code>res.render()</code>.</p>
<pre><code> app.all('/movie/:id', function(req, res, next){
Movie.get(req.params.id, function(err, movie){
// Assigns res.locals.movie = movie
res.local('movie', movie);
});
});
app.get('/movie/:id', function(req, res){
// movie is already a local, however we
// can pass more if we wish
res.render('movie', { displayReviews: true });
});
</code></pre>
<h3 id="app-set-name-val-">app.set(name[, val])</h3>
<p>Apply an application level setting <em>name</em> to <em>val</em>, or
@@ -1244,6 +1350,21 @@ app.set('views');
<pre><code>app.enable('some arbitrary setting');
app.set('some arbitrary setting');
// =&gt; true
app.enabled('some arbitrary setting');
// =&gt; true
</code></pre>
<h3 id="app-enabled-name-">app.enabled(name)</h3>
<p>Check if setting <em>name</em> is enabled:</p>
<pre><code>app.enabled('view cache');
// =&gt; false
app.enable('view cache');
app.enabled('view cache');
// =&gt; true
</code></pre>
<h3 id="app-disable-name-">app.disable(name)</h3>
@@ -1253,6 +1374,23 @@ app.set('some arbitrary setting');
<pre><code>app.disable('some setting');
app.set('some setting');
// =&gt; false
app.disabled('some setting');
// =&gt; false
</code></pre>
<h3 id="app-disabled-name-">app.disabled(name)</h3>
<p>Check if setting <em>name</em> is disabled:</p>
<pre><code>app.enable('view cache');
app.disabled('view cache');
// =&gt; false
app.disable('view cache');
app.disabled('view cache');
// =&gt; true
</code></pre>
<h3 id="app-configure-env-function-function-">app.configure(env|function[, function])</h3>
@@ -1270,7 +1408,7 @@ app.configure('development', function(){
<h3 id="app-redirect-name-val-">app.redirect(name, val)</h3>
<p>For use with <code>res.redirect()</code> we can map redirects at the application level as shown below:</p>
<p>For use with <em>res.redirect()</em> we can map redirects at the application level as shown below:</p>
<pre><code>app.redirect('google', 'http://google.com');
</code></pre>
@@ -1282,7 +1420,7 @@ app.configure('development', function(){
<p>We may also map dynamic redirects:</p>
<pre><code>app.redirect('comments', function(req, res){
return '/post/' + req.params.id + '/comments';
return '/post/' + req.params.id + '/comments';
});
</code></pre>
@@ -1291,10 +1429,15 @@ the context of the request. If we called this route with <em>GET /post/12</em> o
redirect <em>Location</em> would be <em>/post/12/comments</em>.</p>
<pre><code>app.get('/post/:id', function(req, res){
res.redirect('comments');
res.redirect('comments');
});
</code></pre>
<p>When mounted, <em>res.redirect()</em> will respect the mount-point. For example if a blog app is mounted at <em>/blog</em>, the following will redirect to <em>/blog/posts</em>:</p>
<pre><code>res.redirect('/posts');
</code></pre>
<h3 id="app-error-function-">app.error(function)</h3>
<p>Adds an error handler <em>function</em> which will receive the exception as the first parameter as shown below.
@@ -1302,7 +1445,7 @@ Note that we may set several error handlers by making several calls to this meth
should call <em>next(err)</em> if it does not wish to deal with the exception:</p>
<pre><code>app.error(function(err, req, res, next){
res.send(err.message, 500);
res.send(err.message, 500);
});
</code></pre>
@@ -1310,13 +1453,15 @@ should call <em>next(err)</em> if it does not wish to deal with the exception:</
<p>Registers static view helpers.</p>
<pre><code>app.helpers({
name: function(first, last){ return first + ', ' + last },
firstName: 'tj',
lastName: 'holowaychuk'
});
<p> app.helpers({</p>
<pre><code> name: function(first, last){ return first + ', ' + last }
, firstName: 'tj'
, lastName: 'holowaychuk'
</code></pre>
<p> });</p>
<p>Our view could now utilize the <em>firstName</em> and <em>lastName</em> variables,
as well as the <em>name()</em> function exposed.</p>
@@ -1331,9 +1476,9 @@ evaluated against the <em>Server</em> instance before a view is rendered. The <e
becomes the local variable it is associated with.</p>
<pre><code>app.dynamicHelpers({
session: function(req, res){
return req.session;
}
session: function(req, res){
return req.session;
}
});
</code></pre>
@@ -1350,8 +1495,8 @@ becomes the local variable it is associated with.</p>
blog = express.createServer();
blog.mounted(function(parent){
// parent is app
// "this" is blog
// parent is app
// "this" is blog
});
app.use(blog);
@@ -1375,14 +1520,21 @@ of layout.hamljs, we can register the engine as ".haml":</p>
</code></pre>
<p>For engines that do not comply with the Express
specification, we can also wrap their api this way.</p>
specification, we can also wrap their api this way. Below
we map <em>.md</em> to render markdown files, rendering the html once
since it will not change on subsequent calls, and support local substitution
in the form of "{name}".</p>
<pre><code> app.register('.foo', {
render: function(str, options) {
// perhaps their api is
// return foo.toHTML(str, options);
}
});
<pre><code> app.register('.md', {
compile: function(str, options){
var html = md.toHTML(str);
return function(locals){
return html.replace(/\{([^}]+)\}/g, function(_, name){
return locals[name];
});
};
}
});
</code></pre>
<h3 id="app-listen-port-host-">app.listen([port[, host]])</h3>

View File

@@ -1,27 +1,26 @@
### Installation
curl:
$ curl -# http://expressjs.com/install.sh | sh
npm:
$ npm install express
### Creating An Application
### Creating A Server
The _express.Server_ now inherits from _http.Server_, however
follows the same idiom by providing _express.createServer()_ as shown below. This means
that you can utilize Express server's transparently with other libraries.
To create an instance of the _express.HTTPServer_, simply invoke the _createServer()_ method. With our instance _app_ we can then define routes based on the HTTP verbs, in this example _app.get()_.
var app = require('express').createServer();
app.get('/', function(req, res){
res.send('hello world');
});
app.listen(3000);
app.get('/', function(req, res){
res.send('hello world');
});
app.listen(3000);
### Creating An HTTPS Server
To initialize a _express.HTTPSServer_ we do the same as above, however we
pass an options object, accepting _key_, _cert_ and the others mentioned in node's [https documentation](http://nodejs.org/docs/v0.3.7/api/https.html#https.createServer).
var app = require('express').createServer({ key: ... });
### Configuration
@@ -31,38 +30,43 @@ _configure()_ is called without an environment name it will be run in _every_ en
prior to the environment specific callback.
In the example below we only _dumpExceptions_, and respond with exception stack traces
in _development_ mode, however for both environments we utilize _methodOverride_ and _bodyDecoder_.
in _development_ mode, however for both environments we utilize _methodOverride_ and _bodyParser_.
Note the use of _app.router_, which can (optionally) be used to mount the application routes,
otherwise the first call to _app.{get,put,del,post}()_ will mount the routes.
otherwise the first call to _app.get()_, _app.post()_, etc will mount the routes.
app.configure(function(){
app.use(express.methodOverride());
app.use(express.bodyDecoder());
app.use(express.bodyParser());
app.use(app.router);
app.use(express.staticProvider(__dirname + '/public'));
});
app.configure('development', function(){
app.use(express.static(__dirname + '/public'));
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function(){
app.use(express.errorHandler());
});
app.configure('production', function(){
var oneYear = 31557600000;
app.use(express.static({ root: __dirname + '/public', maxAge: oneYear }));
app.use(express.errorHandler());
});
For internal and arbitrary settings Express provides the _set(key[, val])_, _enable(key)_, _disable(key)_ methods:
app.configure(function(){
app.set('views', __dirname + '/views');
app.set('views');
// => "... views directory ..."
app.enable('some feature');
// same as app.set('some feature', true);
app.disable('some feature');
// same as app.set('some feature', false);
});
app.configure(function(){
app.set('views', __dirname + '/views');
app.set('views');
// => "/absolute/path/to/views"
app.enable('some feature');
// same as app.set('some feature', true);
app.disable('some feature');
// same as app.set('some feature', false);
app.enabled('some feature')
// => false
});
To alter the environment we can set the _NODE_ENV_ environment variable, for example:
@@ -74,13 +78,10 @@ This is _very_ important, as many caching mechanisms are _only enabled_ when in
Express supports the following settings out of the box:
* _env_ Application environment set internally, use _app.set('env')_ on _Server#listen()_
* _home_ Application base path used for _res.redirect()_ and transparently handling mounted apps.
* _views_ Root views directory defaulting to **CWD/views**
* _view engine_ Default view engine name for views rendered without extensions
* _view options_ An object specifying global view options
* _partials_ Root view partials directory defaulting to _views_/partials.
* _stream threshold_ Bytesize indicating when a file should be streamed for _res.sendfile()_ using _fs.ReadStream()_ and _sys.pump()_.
### Routing
@@ -99,7 +100,7 @@ when _/user/:id_ is compiled, a simplified version of the regexp may look simila
\/user\/([^\/]+)\/?
Regular expression literals may also be passed for complex uses. Since capture
groups with literal _RegExp_'s are anonymous we can access them directly `req.params`.
groups with literal _RegExp_'s are anonymous we can access them directly `req.params`. So our first capture group would be _req.params[0]_ and the second would follow as _req.params[1]_.
app.get(/^\/users?(?:\/(\d+)(?:\.\.(\d+))?)?/, function(req, res){
res.send(req.params);
@@ -146,13 +147,17 @@ may consume:
/products.json
/products.xml
/products
"/user/:id.:format?"
/user/12
/user/12.json
For example we can __POST__ some json, and echo the json back using the _bodyDecoder_ middleware which will parse json request bodies (as well as others), and place the result in _req.body_:
For example we can __POST__ some json, and echo the json back using the _bodyParser_ middleware which will parse json request bodies (as well as others), and place the result in _req.body_:
var express = require('express')
, app = express.createServer();
app.use(express.bodyDecoder());
app.use(express.bodyParser());
app.post('/', function(req, res){
res.send(req.body);
@@ -160,11 +165,13 @@ For example we can __POST__ some json, and echo the json back using the _bodyDec
app.listen(3000);
Typically we may use a "dumb" placeholder such as "/user/:id" which has no restrictions, however say for example we are limiting a user id to digits, we may use _'/user/:id(\\d+)'_ which will _not_ match unless the placeholder value contains only digits.
### Passing Route Control
We may pass control to the next _matching_ route, by calling the _third_ argument,
the _next()_ function. When a match cannot be made, control is passed back to Connect,
and middleware continue to be invoked. The same is true for several routes which have the same path defined, they will simply be executed in order until one does _not_ call _next()_.
and middleware continue to be invoked in the order that they are added via _use()_. The same is true for several routes which have the same path defined, they will simply be executed in order until one does _not_ call _next()_ and decides to respond.
app.get('/users/:id?', function(req, res, next){
var id = req.params.id;
@@ -179,7 +186,7 @@ and middleware continue to be invoked. The same is true for several routes which
// do something else
});
Express 1.0 also introduces the _all()_ method, which provides a route callback matching any HTTP method. This is useful in many ways, one example being the loading of resources before executing subsequent routes as shown below:
The _app.all()_ method is useful for applying the same logic for all HTTP verbs in a single call. Below we use this to load a user from our fake database, and assign it to _req.user_.
var express = require('express')
, app = express.createServer();
@@ -221,11 +228,11 @@ passed to _express.createServer()_ as you would with a regular Connect server. F
var express = require('express');
var app = express.createServer(
express.logger(),
express.bodyDecoder()
express.logger()
, express.bodyParser()
);
Alternatively we can _use()_ them which is useful when adding middleware within _configure()_ blocks:
Alternatively we can _use()_ them which is useful when adding middleware within _configure()_ blocks, in a progressive manor.
app.use(express.logger({ format: ':method :uri' }));
@@ -233,10 +240,12 @@ Typically with connect middleware you would _require('connect')_ like so:
var connect = require('connect');
app.use(connect.logger());
app.use(connect.bodyParser());
This is somewhat annoying, so express re-exports these middleware properties, however they are _identical_:
app.use(express.logger());
app.use(express.bodyParser());
### Route Middleware
@@ -245,53 +254,53 @@ Routes may utilize route-specific middleware by passing one or more additional c
Typically async data retrieval might look similar to below, where we take the _:id_ parameter, and attempt loading a user.
app.get('/user/:id', function(req, res, next){
loadUser(req.params.id, function(err, user){
if (err) return next(err);
res.send('Viewing user ' + user.name);
});
loadUser(req.params.id, function(err, user){
if (err) return next(err);
res.send('Viewing user ' + user.name);
});
});
To keep things DRY and to increase readability we can apply this logic within a middleware. As you can see below, abstracting this logic into middleware allows us to reuse it, and clean up our route at the same time.
function loadUser(req, res, next) {
// You would fetch your user from the db
var user = users[req.params.id];
if (user) {
req.user = user;
next();
} else {
next(new Error('Failed to load user ' + req.params.id));
}
// You would fetch your user from the db
var user = users[req.params.id];
if (user) {
req.user = user;
next();
} else {
next(new Error('Failed to load user ' + req.params.id));
}
}
app.get('/user/:id', loadUser, function(req, res){
res.send('Viewing user ' + req.user.name);
res.send('Viewing user ' + req.user.name);
});
Multiple route middleware can be applied, and will be executed sequentially to apply further logic such as restricting access to a user account. In the example below only the authenticated user may edit his/her account.
function andRestrictToSelf(req, res, next) {
req.authenticatedUser.id == req.user.id
? next()
: next(new Error('Unauthorized'));
req.authenticatedUser.id == req.user.id
? next()
: next(new Error('Unauthorized'));
}
app.get('/user/:id/edit', loadUser, andRestrictToSelf, function(req, res){
res.send('Editing user ' + req.user.name);
res.send('Editing user ' + req.user.name);
});
Keeping in mind that middleware are simply functions, we can define function that _returns_ the middleware in order to create a more expressive and flexible solution as shown below.
function andRestrictTo(role) {
return function(req, res, next) {
req.authenticatedUser.role == role
? next()
: next(new Error('Unauthorized'));
}
return function(req, res, next) {
req.authenticatedUser.role == role
? next()
: next(new Error('Unauthorized'));
}
}
app.del('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){
res.send('Deleted user ' + req.user.name);
res.send('Deleted user ' + req.user.name);
});
Commonly used "stacks" of middleware can be passed as an array (_applied recursively_), which can be mixed and matched to any degree.
@@ -321,20 +330,20 @@ We have seen _app.get()_ a few times, however Express also exposes other familia
<input type="submit" value="Submit" />
</form>
By default Express does not know what to do with this request body, so we should add the _bodyDecoder_ middleware, which will parse _application/x-www-form-urlencoded_ request bodies and place the variables in _req.body_. We can do this by "using" the middleware as shown below:
By default Express does not know what to do with this request body, so we should add the _bodyParser_ middleware, which will parse _application/x-www-form-urlencoded_ and _application/json_ request bodies and place the variables in _req.body_. We can do this by "using" the middleware as shown below:
app.use(express.bodyDecoder());
app.use(express.bodyParser());
Our route below will now have access to the _req.body.user_ object which will contain the _name_ and _email_ properties when defined.
app.post('/', function(req, res){
console.log(req.body.user);
res.redirect('back');
console.log(req.body.user);
res.redirect('back');
});
When using methods such as _PUT_ with a form, we can utilize a hidden input named _\_method_, which can be used to alter the HTTP method. To do so we first need the _methodOverride_ middleware, which should be placed below _bodyDecoder_ so that it can utilize it's _req.body_ containing the form values.
When using methods such as _PUT_ with a form, we can utilize a hidden input named _\_method_, which can be used to alter the HTTP method. To do so we first need the _methodOverride_ middleware, which should be placed below _bodyParser_ so that it can utilize it's _req.body_ containing the form values.
app.use(express.bodyDecoder());
app.use(express.bodyParser());
app.use(express.methodOverride());
The reason that these are not always defaults, is simply because these are not required for Express to be fully functional. Depending on the needs of your application, you may not need these at all, your methods such as _PUT_ and _DELETE_ can still be accessed by clients which can use them directly, although _methodOverride_ provides a great solution for forms. Below shows what the usage of _PUT_ might look like:
@@ -357,21 +366,21 @@ Express provides the _app.error()_ method which receives exceptions thrown withi
or passed to _next(err)_. Below is an example which serves different pages based on our
ad-hoc _NotFound_ exception:
function NotFound(msg){
this.name = 'NotFound';
Error.call(this, msg);
Error.captureStackTrace(this, arguments.callee);
}
sys.inherits(NotFound, Error);
app.get('/404', function(req, res){
throw new NotFound;
});
app.get('/500', function(req, res){
throw new Error('keyboard cat!');
});
function NotFound(msg){
this.name = 'NotFound';
Error.call(this, msg);
Error.captureStackTrace(this, arguments.callee);
}
NotFound.protoype.__proto__ = Error.prototype;
app.get('/404', function(req, res){
throw new NotFound;
});
app.get('/500', function(req, res){
throw new Error('keyboard cat!');
});
We can call _app.error()_ several times as shown below.
Here we check for an instanceof _NotFound_ and show the
@@ -391,15 +400,13 @@ handle exceptions in different ways based on the environment.
});
Here we assume all errors as 500 for the simplicity of
this demo, however you can choose whatever you like
this demo, however you can choose whatever you like. For example when node performs filesystem syscalls, you may receive an error object with the _error.code_ of _ENOENT_, meaning "no such file or directory", we can utilize this in our error handling and display a page specific to this if desired.
app.error(function(err, req, res){
res.render('500.jade', {
locals: {
error: err
}
});
});
app.error(function(err, req, res){
res.render('500.jade', {
error: err
});
});
Our apps could also utilize the Connect _errorHandler_ middleware
to report on exceptions. For example if we wish to output exceptions
@@ -415,25 +422,59 @@ that are passed or thrown, so we can set _showStack_ to true:
The _errorHandler_ middleware also responds with _json_ if _Accept: application/json_
is present, which is useful for developing apps that rely heavily on client-side JavaScript.
### Route Param Pre-conditions
Route param pre-conditions can drastically improve the readability of your application, through implicit loading of data, and validation of request urls. For example if you are constantly fetching common data for several routes, such as loading a user for _/user/:id_, we might typically do something like below:
app.get('/user/:userId', function(req, res, next){
User.get(req.params.userId, function(err, user){
if (err) return next(err);
res.send('user ' + user.name);
});
});
With preconditions our params can be mapped to callbacks which may perform validation, coercion, or even loading data from a database. Below we invoke _app.param()_ with the parameter name we wish to map to some middleware, as you can see we receive the _id_ argument which contains the placeholder value. Using this we load the user and perform error handling as usual, and simple call _next()_ to pass control to the next precondition or route handler.
app.param('userId', function(req, res, next, id){
User.get(id, function(err, user){
if (err) return next(err);
if (!user) return next(new Error('failed to find user'));
req.user = user;
next();
});
});
Doing so, as mentioned drastically improves our route readability, and allows us to easily share this logic throughout our application:
app.get('/user/:userId', function(req, res){
res.send('user ' + req.user.name);
});
For simple cases such as route placeholder validation and coercion we can simple pass a callback which has an arity of 1 (accepts one argument). Any errors thrown will be passed to _next(err)_.
app.param('number', function(n){ return parseInt(n, 10); });
We may also apply the same callback to several placeholders, for example a route GET _/commits/:from-:to_ are both numbers, so we may define them as an array:
app.param(['from', 'to'], function(n){ return parseInt(n, 10); });
### View Rendering
View filenames take the form _NAME_._ENGINE_, where _ENGINE_ is the name
View filenames take the form "&lt;name&gt;.&lt;engine&gt;", where &lt;engine&gt; is the name
of the module that will be required. For example the view _layout.ejs_ will
tell the view system to _require('ejs')_, the module being loaded must export the method _exports.render(str, options)_ to comply with Express, however
_app.register()_ can be used to map engines to file extensions, so that for example "foo.html" can be rendered by jade.
tell the view system to _require('ejs')_, the module being loaded must export the method _exports.compile(str, options)_, and return a _Function_ to comply with Express. To alter this behaviour
_app.register()_ can be used to map engines to file extensions, so that for example "foo.html" can be rendered by ejs.
Below is an example using [Haml.js](http://github.com/visionmedia/haml.js) to render _index.html_,
and since we do not use _layout: false_ the rendered contents of _index.html_ will be passed as
the _body_ local variable in _layout.haml_.
Below is an example using [Jade](http://github.com/visionmedia/jade) to render _index.html_,
and since we do not use _layout: false_ the rendered contents of _index.jade_ will be passed as
the _body_ local variable in _layout.jade_.
app.get('/', function(req, res){
res.render('index.haml', {
locals: { title: 'My Site' }
});
res.render('index.jade', { title: 'My Site' });
});
The new _view engine_ setting allows us to specify our default template engine,
so for example when using [Jade](http://github.com/visionmedia/jade) we could set:
so for example when using jade we could set:
app.set('view engine', 'jade');
@@ -453,10 +494,10 @@ mix and match template engines:
Express also provides the _view options_ setting, which is applied each time a view is rendered, so for example if you rarely use layouts you may set:
app.set('view options', {
layout: false
layout: false
});
Which can then be overridden within the `res.render()` call if need be:
Which can then be overridden within the _res.render()_ call if need be:
res.render('myview.ejs', { layout: true });
@@ -481,65 +522,70 @@ A good example of this is specifying custom _ejs_ opening and closing tags:
### View Partials
The Express view system has built-in support for partials and collections, which are
sort of "mini" views representing a document fragment. For example rather than iterating
The Express view system has built-in support for partials and collections, which are "mini" views representing a document fragment. For example rather than iterating
in a view to display comments, we would use a partial with collection support:
partial('comment.haml', { collection: comments });
To make things even less verbose we can assume the extension as _.haml_ when omitted,
however if we wished we could use an ejs partial, within a haml view for example.
partial('comment', { collection: comments });
And once again even further, when rendering a collection we can simply pass
an array, if no other options are desired:
If no other options are desired, we can omit the object and simply pass our array, which is equivalent to above:
partial('comment', comments);
When using the partial collection support a few "magic" variables are provided
for free:
* _firstInCollection_ True if this is the first object
* _indexInCollection_ Index of the object in the collection
* _lastInCollection_ True if this is the last object
* _collectionLength_ Length of the collection
* _firstInCollection_ true if this is the first object
* _indexInCollection_ index of the object in the collection
* _lastInCollection_ true if this is the last object
* _collectionLength_ length of the collection
Local variables passed (or generated) take precedence, however locals passed to the parent view are available in the child view as well. So for example if we were to render a blog post with _partial('blog/post', post)_ it would generate the _post_ local, but the view calling this function had the local _user_, it would be available to the _blog/post_ view as well.
For documentation on altering the object name view [res.partial()](http://expressjs.com/guide.html#res-partial-view-options-).
__NOTE:__ be careful about when you use partial collections, as rendering an array with a length of 100 means we have to render 100 views. For simple collections you may inline the iteration instead of using partial collection support to decrease overhead.
### View Lookup
View lookup is performed relative to the parent view, for example if we had a page view named _views/user/list.jade_, and within that view we did _partial('edit')_ it would attempt to load _views/user/edit.jade_, whereas _partial('../messages')_ would load _views/messages.jade_.
The view system also allows for index templates, allowing you to have a directory of the same name. For example within a route we may have _res.render('users')_ either _views/users.jade_, or _views/users/index.jade_.
When utilizing index views as shown above, we may reference _views/users/index.jade_ from a view in the same directory by _partial('users')_, and the view system will try _../users/index_, preventing us from needing to call _partial('index')_.
### Template Engines
Below are a few template engines commonly used with Express:
* [Haml](http://github.com/visionmedia/haml.js) haml implementation
* [Jade](http://jade-lang.com) haml.js successor
* [Haml](http://github.com/visionmedia/haml.js) pythonic indented templates
* [EJS](http://github.com/visionmedia/ejs) Embedded JavaScript
* [CoffeeKup](http://github.com/mauricemach/coffeekup) CoffeeScript based templating
* [jQuery Templates](https://github.com/kof/node-jqtpl) for node
### Session Support
Sessions support can be added by using Connect's _session_ middleware. To do so we also need the _cookieDecoder_ middleware place above it, which will parse and populate cookie data to _req.cookies_.
Sessions support can be added by using Connect's _session_ middleware. To do so we also need the _cookieParser_ middleware place above it, which will parse and populate cookie data to _req.cookies_.
app.use(express.cookieDecoder());
app.use(express.session());
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat" }));
By default the _session_ middleware uses the memory store bundled with Connect, however many implementations exist. For example [connect-redis](http://github.com/visionmedia/connect-redis) supplies a [Redis](http://code.google.com/p/redis/) session store and can be used as shown below:
var RedisStore = require('connect-redis');
app.use(express.cookieDecoder());
app.use(express.session({ store: new RedisStore }));
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
Now the _req.session_ and _req.sessionStore_ properties will be accessible to all routes and subsequent middleware. Properties on _req.session_ are automatically saved on a response, so for example if we wish to shopping cart data:
var RedisStore = require('connect-redis');
app.use(express.bodyDecoder());
app.use(express.cookieDecoder());
app.use(express.session({ store: new RedisStore }));
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({ secret: "keyboard cat", store: new RedisStore }));
app.post('/add-to-cart', function(req, res){
// Perhaps we posted several items with a form
// (use the bodyDecoder() middleware for this)
// (use the bodyParser() middleware for this)
var items = req.body.items;
req.session.items = items;
res.redirect('back');
@@ -555,11 +601,11 @@ Now the _req.session_ and _req.sessionStore_ properties will be accessible to al
res.render('shopping-cart');
});
The _req.session_ object also has methods such as _Session#touch()_, _Session#destroy()_, _Session#regenerate()_ among others to maintain and manipulate sessions. For more information view the [Connect Session](http://senchalabs.github.com/connect/session.html) documentation.
The _req.session_ object also has methods such as _Session#touch()_, _Session#destroy()_, _Session#regenerate()_ among others to maintain and manipulate sessions. For more information view the [Connect Session](http://senchalabs.github.com/connect/middleware-session.html) documentation.
### Migration Guide
Pre-beta Express developers may reference the [Migration Guide](migrate.html) to get up to speed on how to upgrade your application.
Express 1.x developers may reference the [Migration Guide](migrate.html) to get up to speed on how to upgrade your application to work with Express 2.x, Connect 1.x, and Node 0.4.x.
### req.header(key[, defaultValue])
@@ -569,6 +615,16 @@ Get the case-insensitive request header _key_, with optional _defaultValue_:
req.header('host');
req.header('Accept', '*/*');
The _Referrer_ and _Referer_ header fields are special-cased, either will work:
// sent Referrer: http://google.com
req.header('Referer');
// => "http://google.com"
req.header('Referrer');
// => "http://google.com"
### req.accepts(type)
Check if the _Accept_ header is present, and includes the given _type_.
@@ -642,17 +698,17 @@ We may also assert the _type_ as shown below, which would return true for _"appl
req.is('*/json');
### req.param(name)
### req.param(name[, default])
Return the value of param _name_ when present.
Return the value of param _name_ when present or _default_.
- Checks route placeholders (_req.params_), ex: /user/:id
- Checks route params (_req.params_), ex: /user/:id
- Checks query string params (_req.query_), ex: ?id=12
- Checks urlencoded body params (_req.body_), ex: id=12
To utilize urlencoded request bodies, _req.body_
should be an object. This can be done by using
the _express.bodyDecoder_ middleware.
the _express.bodyParser middleware.
### req.flash(type[, msg])
@@ -691,9 +747,9 @@ Get or set the response header _key_.
res.header('Content-Length');
// => undefined
res.header('Content-Length', 123);
res.header('Content-Length', 123);
// => 123
res.header('Content-Length');
// => 123
@@ -703,7 +759,7 @@ Sets the _Content-Type_ response header to the given _type_.
var filename = 'path/to/image.png';
res.contentType(filename);
// res.headers['Content-Type'] is now "image/png"
// Content-Type is now "image/png"
### res.attachment([filename])
@@ -711,25 +767,30 @@ Sets the _Content-Disposition_ response header to "attachment", with optional _f
res.attachment('path/to/my/image.png');
### res.sendfile(path)
### res.sendfile(path[, options[, callback]])
Used by `res.download()` to transfer an arbitrary file.
res.sendfile('path/to/my.file');
This method accepts a callback which when given will be called on an exception, as well as when the transfer has completed. When a callback is not given, and the file has __not__ been streamed, _next(err)_ will be called on an exception.
This method accepts an optional callback which is called when
an error occurs, or when the transfer is complete. By default failures call `next(err)`, however when a callback is supplied you must do this explicitly, or act on the error.
res.sendfile(path, function(err, path){
res.sendfile(path, function(err){
if (err) {
// handle the error
next(err);
} else {
console.log('transferred %s', path);
}
});
When the filesize exceeds the _stream threshold_ (defaulting to 32k), the file will be streamed using _fs.ReadStream_ and _sys.pump()_.
Options may also be passed to the internal _fs.createReadStream()_ call, for example altering the _bufferSize_:
### res.download(file[, filename])
res.sendfile(path, { bufferSize: 1024 }, function(err){
// handle
});
### res.download(file[, filename[, callback]])
Transfer the given _file_ as an attachment with optional alternative _filename_.
@@ -741,11 +802,16 @@ This is equivalent to:
res.attachment(file);
res.sendfile(file);
An optional callback may be supplied as either the second or third argument, which is passed to _res.sendfile()_:
res.download(path, 'expenses.doc', function(err){
// handle
});
### res.send(body|status[, headers|status[, status]])
The `res.send()` method is a high level response utility allowing you to pass
objects to respond with json, strings for html, arbitrary _Buffer_s or numbers for status
code based responses. The following are all valid uses:
The _res.send()_ method is a high level response utility allowing you to pass
objects to respond with json, strings for html, Buffer instances, or numbers representing the status code. The following are all valid uses:
res.send(); // 204
res.send(new Buffer('wahoo'));
@@ -759,6 +825,8 @@ By default the _Content-Type_ response header is set, however if explicitly
assigned through `res.send()` or previously with `res.header()` or `res.contentType()`
it will not be set again.
Note that this method _end()_s the response, so you will want to use node's _res.write()_ for multiple writes or streaming.
### res.redirect(url[, status])
Redirect to the given _url_ with a default response _status_ of 302.
@@ -775,17 +843,21 @@ the "home" setting and defaults to "/".
### res.cookie(name, val[, options])
Sets the given cookie _name_ to _val_, with _options_ such as "httpOnly: true", "expires", "secure" etc.
Sets the given cookie _name_ to _val_, with options _httpOnly_, _secure_, _expires_ etc.
// "Remember me" for 15 minutes
res.cookie('rememberme', 'yes', { expires: new Date(Date.now() + 900000), httpOnly: true });
The _maxAge_ property may be used to set _expires_ relative to _Date.now()_ in milliseconds, so our example above can now become:
res.cookie('rememberme', 'yes', { maxAge: 900000 });
To parse incoming _Cookie_ headers, use the _cookieDecoder_ middleware, which provides the _req.cookies_ object:
app.use(express.cookieDecoder());
app.use(express.cookieParser());
app.get('/', function(req, res){
// use req.cookies.rememberme
// use req.cookies.rememberme
});
### res.clearCookie(name)
@@ -800,14 +872,10 @@ Render _view_ with the given _options_ and optional callback _fn_.
When a callback function is given a response will _not_ be made
automatically, however otherwise a response of _200_ and _text/html_ is given.
Most engines accept one or more of the following options,
both [haml](http://github.com/visionmedia/haml.js) and [jade](http://github.com/visionmedia/jade) accept all:
The _options_ passed are the local variables as well, for example if we want to expose "user" to the view, and prevent a local we do so within the same object:
- _scope_ Template evaluation context (value of _this_)
- _locals_ Object containing local variables
- _debug_ Output debugging information
- _status_ Response status code, defaults to 200
- _headers_ Response headers object
var user = { name: 'tj' };
res.render('index', { layout: false, user: user });
### res.partial(view[, options])
@@ -869,6 +937,38 @@ When a non-collection (does _not_ have _.length_) is passed as the second argume
partial('movie', movie);
// => In view: movie.director
This exact API can be utilized from within a route, to respond with a fragment via Ajax or WebSockets, for example we can render a collection of users directly from a route:
app.get('/users', function(req, res){
if (req.xhr) {
// respond with the each user in the collection
// passed to the "user" view
res.partial('user', users);
} else {
// respond with layout, and users page
// which internally does partial('user', users)
// along with other UI
res.render('users', { users: users });
}
});
### res.local(name[, val])
Get or set the given local variable _name_. The locals built up for a response are applied to those given to the view rendering methods such as `res.render()`.
app.all('/movie/:id', function(req, res, next){
Movie.get(req.params.id, function(err, movie){
// Assigns res.locals.movie = movie
res.local('movie', movie);
});
});
app.get('/movie/:id', function(req, res){
// movie is already a local, however we
// can pass more if we wish
res.render('movie', { displayReviews: true });
});
### app.set(name[, val])
Apply an application level setting _name_ to _val_, or
@@ -891,6 +991,20 @@ Enable the given setting _name_:
app.set('some arbitrary setting');
// => true
app.enabled('some arbitrary setting');
// => true
### app.enabled(name)
Check if setting _name_ is enabled:
app.enabled('view cache');
// => false
app.enable('view cache');
app.enabled('view cache');
// => true
### app.disable(name)
Disable the given setting _name_:
@@ -898,6 +1012,22 @@ Disable the given setting _name_:
app.disable('some setting');
app.set('some setting');
// => false
app.disabled('some setting');
// => false
### app.disabled(name)
Check if setting _name_ is disabled:
app.enable('view cache');
app.disabled('view cache');
// => false
app.disable('view cache');
app.disabled('view cache');
// => true
### app.configure(env|function[, function])
@@ -913,7 +1043,7 @@ Define a callback function for the given _env_ (or all environments) with callba
### app.redirect(name, val)
For use with `res.redirect()` we can map redirects at the application level as shown below:
For use with _res.redirect()_ we can map redirects at the application level as shown below:
app.redirect('google', 'http://google.com');
@@ -924,7 +1054,7 @@ Now in a route we may call:
We may also map dynamic redirects:
app.redirect('comments', function(req, res){
return '/post/' + req.params.id + '/comments';
return '/post/' + req.params.id + '/comments';
});
So now we may do the following, and the redirect will dynamically adjust to
@@ -932,9 +1062,13 @@ the context of the request. If we called this route with _GET /post/12_ our
redirect _Location_ would be _/post/12/comments_.
app.get('/post/:id', function(req, res){
res.redirect('comments');
res.redirect('comments');
});
When mounted, _res.redirect()_ will respect the mount-point. For example if a blog app is mounted at _/blog_, the following will redirect to _/blog/posts_:
res.redirect('/posts');
### app.error(function)
Adds an error handler _function_ which will receive the exception as the first parameter as shown below.
@@ -942,18 +1076,18 @@ Note that we may set several error handlers by making several calls to this meth
should call _next(err)_ if it does not wish to deal with the exception:
app.error(function(err, req, res, next){
res.send(err.message, 500);
});
res.send(err.message, 500);
});
### app.helpers(obj)
Registers static view helpers.
app.helpers({
name: function(first, last){ return first + ', ' + last },
firstName: 'tj',
lastName: 'holowaychuk'
});
app.helpers({
name: function(first, last){ return first + ', ' + last }
, firstName: 'tj'
, lastName: 'holowaychuk'
});
Our view could now utilize the _firstName_ and _lastName_ variables,
as well as the _name()_ function exposed.
@@ -968,9 +1102,9 @@ evaluated against the _Server_ instance before a view is rendered. The _return v
becomes the local variable it is associated with.
app.dynamicHelpers({
session: function(req, res){
return req.session;
}
session: function(req, res){
return req.session;
}
});
All views would now have _session_ available so that session data can be accessed via _session.name_ etc:
@@ -985,8 +1119,8 @@ Assign a callback _fn_ which is called when this _Server_ is passed to _Server#u
blog = express.createServer();
blog.mounted(function(parent){
// parent is app
// "this" is blog
// parent is app
// "this" is blog
});
app.use(blog);
@@ -1007,14 +1141,21 @@ of layout.hamljs, we can register the engine as ".haml":
app.register('.haml', require('haml-js'));
For engines that do not comply with the Express
specification, we can also wrap their api this way.
specification, we can also wrap their api this way. Below
we map _.md_ to render markdown files, rendering the html once
since it will not change on subsequent calls, and support local substitution
in the form of "{name}".
app.register('.foo', {
render: function(str, options) {
// perhaps their api is
// return foo.toHTML(str, options);
}
});
app.register('.md', {
compile: function(str, options){
var html = md.toHTML(str);
return function(locals){
return html.replace(/\{([^}]+)\}/g, function(_, name){
return locals[name];
});
};
}
});
### app.listen([port[, host]])

View File

@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "INDEX" "" "October 2010" "" ""
.TH "INDEX" "" "March 2011" "" ""
.
.SH "NAME"
\fBindex\fR
@@ -40,6 +40,9 @@ Application level view options
Content negotiation
.
.IP "\(bu" 4
Application mounting
.
.IP "\(bu" 4
Focus on high performance
.
.IP "\(bu" 4
@@ -79,6 +82,23 @@ Guillermo Rauch (guille \fIhttp://github\.com/guille\fR)
.
.IP "" 0
.
.SH "Third\-Party Modules"
The following modules compliment or extend Express directly:
.
.IP "\(bu" 4
express\-resource \fIhttp://github\.com/visionmedia/express\-resource\fR provides resourceful routing
.
.IP "\(bu" 4
express\-messages \fIhttp://github\.com/visionmedia/express\-messages\fR flash message notification rendering
.
.IP "\(bu" 4
express\-configure \fIhttp://github\.com/visionmedia/express\-configure\fR async configuration support (load settings from redis etc)
.
.IP "\(bu" 4
express\-namespace \fIhttp://github\.com/visionmedia/express\-namespace\fR namespaced routing support
.
.IP "" 0
.
.SH "More Information"
.
.IP "\(bu" 4
@@ -88,10 +108,7 @@ Google Group \fIhttp://groups\.google\.com/group/express\-js\fR for discussion
Follow tjholowaychuk \fIhttp://twitter\.com/tjholowaychuk\fR on twitter for updates
.
.IP "\(bu" 4
Annotated source documentation \fIapi\.html\fR
.
.IP "\(bu" 4
View the Connect \fIhttp://github\.com/senchalabs/connect\fR repo for middleware usage
View the Connect \fIhttp://senchalabs\.github\.com/connect\fR documentation
.
.IP "\(bu" 4
View the Connect Wiki \fIhttp://wiki\.github\.com/senchalabs/connect/\fR for contrib middleware
@@ -102,5 +119,8 @@ View the examples \fIhttp://github\.com/visionmedia/express/tree/master/examples
.IP "\(bu" 4
View the source \fIhttp://github\.com/visionmedia/express\fR
.
.IP "\(bu" 4
View the contrib guide \fIcontrib\.html\fR
.
.IP "" 0

View File

@@ -133,7 +133,7 @@
font-size: 11px;
}
#menu {
margin-left: 65px;
margin-left: 75px;
padding: 0;
padding-bottom: 30px; }
#menu li {
@@ -187,7 +187,7 @@
<ul id="menu">
<li><a href="index.html">Home</a></li>
<li><a href="guide.html">Guide</a></li>
<li><a href="contrib.html">Contributing</a></li>
<li><a href="screencasts.html">Screencasts</a></li>
<li><a href="applications.html">Applications</a></li>
</ul>
<div class='mp'>
@@ -212,6 +212,7 @@ app.listen(3000);
<li>Dynamic view helpers</li>
<li>Application level view options</li>
<li>Content negotiation</li>
<li>Application mounting</li>
<li>Focus on high performance</li>
<li>View rendering and partials support</li>
<li>Environment based configuration</li>
@@ -234,16 +235,28 @@ app.listen(3000);
</ul>
<h2 id="Third-Party-Modules">Third-Party Modules</h2>
<p>The following modules compliment or extend Express directly:</p>
<ul>
<li><a href="http://github.com/visionmedia/express-resource">express-resource</a> provides resourceful routing</li>
<li><a href="http://github.com/visionmedia/express-messages">express-messages</a> flash message notification rendering</li>
<li><a href="http://github.com/visionmedia/express-configure">express-configure</a> async configuration support (load settings from redis etc)</li>
<li><a href="http://github.com/visionmedia/express-namespace">express-namespace</a> namespaced routing support</li>
</ul>
<h2 id="More-Information">More Information</h2>
<ul>
<li><a href="http://groups.google.com/group/express-js">Google Group</a> for discussion</li>
<li>Follow <a href="http://twitter.com/tjholowaychuk">tjholowaychuk</a> on twitter for updates</li>
<li>Annotated source <a href="api.html">documentation</a></li>
<li>View the <a href="http://github.com/senchalabs/connect">Connect</a> repo for middleware usage</li>
<li>View the <a href="http://senchalabs.github.com/connect">Connect</a> documentation</li>
<li>View the <a href="http://wiki.github.com/senchalabs/connect/">Connect Wiki</a> for contrib middleware</li>
<li>View the <a href="http://github.com/visionmedia/express/tree/master/examples/">examples</a></li>
<li>View the <a href="http://github.com/visionmedia/express">source</a></li>
<li>View the <a href="contrib.html">contrib guide</a></li>
</ul>

View File

@@ -14,6 +14,7 @@
* Dynamic view helpers
* Application level view options
* Content negotiation
* Application mounting
* Focus on high performance
* View rendering and partials support
* Environment based configuration
@@ -31,12 +32,21 @@ The following are the major contributors of Express (in no specific order).
* Aaron Heckmann ([aheckmann](http://github.com/aheckmann))
* Guillermo Rauch ([guille](http://github.com/guille))
## Third-Party Modules
The following modules compliment or extend Express directly:
* [express-resource](http://github.com/visionmedia/express-resource) provides resourceful routing
* [express-messages](http://github.com/visionmedia/express-messages) flash message notification rendering
* [express-configure](http://github.com/visionmedia/express-configure) async configuration support (load settings from redis etc)
* [express-namespace](http://github.com/visionmedia/express-namespace) namespaced routing support
## More Information
* [Google Group](http://groups.google.com/group/express-js) for discussion
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates
* Annotated source [documentation](api.html)
* View the [Connect](http://github.com/senchalabs/connect) repo for middleware usage
* View the [Connect](http://senchalabs.github.com/connect) documentation
* View the [Connect Wiki](http://wiki.github.com/senchalabs/connect/) for contrib middleware
* View the [examples](http://github.com/visionmedia/express/tree/master/examples/)
* View the [source](http://github.com/visionmedia/express)
* View the [contrib guide](contrib.html)

View File

@@ -133,7 +133,7 @@
font-size: 11px;
}
#menu {
margin-left: 65px;
margin-left: 75px;
padding: 0;
padding-bottom: 30px; }
#menu li {
@@ -187,6 +187,6 @@
<ul id="menu">
<li><a href="index.html">Home</a></li>
<li><a href="guide.html">Guide</a></li>
<li><a href="contrib.html">Contributing</a></li>
<li><a href="screencasts.html">Screencasts</a></li>
<li><a href="applications.html">Applications</a></li>
</ul>

View File

@@ -1,345 +1,270 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "MIGRATE" "" "October 2010" "" ""
.TH "MIGRATE" "" "March 2011" "" ""
.
.SH "NAME"
\fBmigrate\fR
.
.SS "Built On Connect"
Express 1\.x is written to run on\-top of the Connect \fIhttp://extjs\.github\.com/Connect\fR middlware framework, thus the \fIPlugin\fR has been replaced by Connect\'s middleware\. By abstracting our middleware to Connect we allow additional community frameworks to develop robust, high\-level frameworks using the same technologies as Express\.
.SS "Express 1\.x to 2\.x Migration"
.
.SS "Creating Applications"
Previously due to legacy code implemented in the early days of node, Express unfortunately had some globals\. The DSL would previously be accessed as shown below:
.SS "HTTPS"
Creating an HTTPS server is simply, simply pass the TLS options to \fIexpress\.createServer()\fR:
.
.IP "" 4
.
.nf
require(\'express\');
var app = express\.createServer({
key: \.\.\.
, cert: \.\.\.
});
configure(function(){
// app configuration
});
app\.listen(443);
.
.fi
.
.IP "" 0
.
.SS "req\.header() Referrer"
Previously if anyone was doing something similar to:
.
.IP "" 4
.
.nf
get(\'/\', function(){
return \'hello world\';
});
req\.headers\.referrer || req\.headers\.referer
req\.header(\'Referrer\') || req\.header(\'Referer\')
.
.fi
.
.IP "" 0
.
.P
Now we utilize the CommonJS module system appropriately, and introduce \fIexpress\.createServer()\fR which accepts the same arguments as \fIhttp\.createServer()\fR:
With the new special\-case we may now simply use \fIReferrer\fR which will return either if defined:
.
.IP "" 4
.
.nf
var express = require(\'express\'),
app = express\.createServer();
req\.header(\'Referrer\')
.
.fi
.
.IP "" 0
.
.SS "res\.local(name, val)"
Previously all local variables had to be passed to \fIres\.render()\fR, or either \fIapp\.helpers()\fR or \fIapp\.dynamicHelpers()\fR, now we may do this at the request\-level progressively\. The \fIres\.local()\fR method accepts a \fIname\fR and \fIval\fR, however the locals passed to \fIres\.render()\fR will take precedence\.
.
.P
For example we may utilize this feature to create locals in middleware:
.
.IP "" 4
.
.nf
app\.configure(function(){
// app configuration
});
function loadUser(req, res, next) {
User\.get(req\.params\.id, function(err, user){
res\.local(\'user\', user);
next();
});
}
app\.get(\'/\', function(req, res){
res\.send(\'hello world\');
});
app\.get(\'/user/:id\', loadUser, function(req, res){
res\.render(\'user\');
});
.
.fi
.
.IP "" 0
.
.SS "req\.param(name[, defaultValue])"
Previously only \fIname\fR was accepted, so some of you may have been doing the following:
.
.IP "" 4
.
.nf
var id = req\.param(\'id\') || req\.user\.id;
.
.fi
.
.IP "" 0
.
.P
Express 1\.x does \fInot\fR currently allow returning of a string\.
.
.SS "Plugins vs Middleware"
Previously Express was bundled with plugins, which were essentially what are now Connect middleware\. Previously plugins would be utilized in a manor similar to below:
The new \fIdefaultValue\fR argument can handle this nicely:
.
.IP "" 4
.
.nf
use(Logger);
use(MethodOverride);
use(Cookie);
var id = req\.param(\'id\', req\.user\.id);
.
.fi
.
.IP "" 0
.
.SS "app\.helpers() / app\.locals()"
\fIapp\.locals()\fR is now an alias of \fIapp\.helpers()\fR, as helpers makes more sense for functions\.
.
.SS "req\.accepts(type)"
\fIreq\.accepts()\fR now accepts extensions:
.
.IP "" 4
.
.nf
// Accept: text/html
req\.accepts(\'html\');
req\.accepts(\'\.html\');
// => true
// Accept: text/*; application/json
req\.accepts(\'html\');
req\.accepts(\'text/*\');
req\.accepts(\'text/plain\');
req\.accepts(\'application/json\');
// => true
req\.accepts(\'image/png\');
req\.accepts(\'png\');
// => false
.
.fi
.
.IP "" 0
.
.SS "res\.cookie()"
Previously only directly values could be passed, so for example:
.
.IP "" 4
.
.nf
res\.cookie(\'rememberme\', \'yes\', { expires: new Date(Date\.now() + 900000) });
.
.fi
.
.IP "" 0
.
.P
Which we can now \fIuse()\fR within our app, or pass to the \fIexpress\.createServer()\fR method:
However now we have the alternative \fImaxAge\fR property which may be used to set \fIexpires\fR relative to \fIDate\.now()\fR in milliseconds, so our example above can now become:
.
.IP "" 4
.
.nf
var app = express\.createServer(
express\.logger(),
express\.methodOverride(),
express\.cookieDecoder()
);
res\.cookie(\'rememberme\', \'yes\', { maxAge: 900000 });
.
.fi
.
.IP "" 0
.
.SS "res\.download() / res\.sendfile()"
Both of these methods now utilize Connect\'s static file server behind the scenes (actually the previous Express code was ported to Connect 1\.0)\. With this change comes a change to the callback as well\. Previously the \fIpath\fR and \fIstream\fR were passed, however now only an \fIerror\fR is passed, when no error has occurred the callback will be invoked indicating that the file transfer is complete\. The callback remains optional:
.
.IP "" 4
.
.nf
res\.download(\'/path/to/file\');
res\.download(\'/path/to/file\', function(err){
if (err) {
console\.error(err);
} else {
console\.log(\'transferred\');
}
});
.
.fi
.
.IP "" 0
.
.P
or:
The \fIstream threshold\fR setting was removed\.
.
.SS "res\.render()"
Previously locals were passed as a separate key:
.
.IP "" 4
.
.nf
var app = express\.createServer();
app\.use(express\.logger());
app\.use(express\.methodOverride());
app\.use(express\.cookieDecoder());
res\.render(\'user\', { layout: false, locals: { user: user }});
.
.fi
.
.IP "" 0
.
.P
For documentation on creating Connect middleware visit Middleware Authoring \fIhttp://extjs\.github\.com/Connect/#Middleware\-Authoring\fR\.
.
.SS "Running Applications"
Previously a global function \fIrun()\fR, was available:
In Express 2\.0 both the locals and the options are one in the same, meaning you cannot have a local variable named \fIlayout\fR as it is reserved for express, however this cleans up the API:
.
.IP "" 4
.
.nf
run();
res\.render(\'user\', { layout: false, user: user });
.
.fi
.
.IP "" 0
.
.SS "res\.partial()"
Express 2\.0 adds the \fIres\.partial()\fR method, helpful for rendering partial fragments over WebSockets or Ajax requests etc\. The API is identical to the \fIpartial()\fR calls within views\.
.
.IP "" 4
.
.nf
// render a collection of comments
res\.partial(\'comment\', [comment1, comment2]);
// render a single comment
res\.partial(\'comment\', comment);
.
.fi
.
.IP "" 0
.
.SS "Template Engine Compliance"
To comply with Express previously engines needed the following signature:
.
.IP "" 4
.
.nf
engine\.render(str, options, function(err){});
.
.fi
.
.IP "" 0
.
.P
The new \fIexpress\.Server\fR has the same API as \fIhttp\.Server\fR, so we can do things like:
Now they must export a \fIcompile()\fR function, returning a function which when called with local variables will render the template\. This allows Express to cache the compiled function in memory during production\.
.
.IP "" 4
.
.nf
app\.listen();
app\.listen(3000);
var fn = engine\.compile(str, options);
fn(locals);
.
.fi
.
.IP "" 0
.
.SS "Route Parameters"
Previously we could use \fIthis\.param()\fR to attempt fetching a route, query string, or request body parameter:
.SS "View Partial Lookup"
Previously partials were loaded relative to the now removed \fIview partials\fR directory setting, or by default \fIviews/partials\fR, now they are relative to the view calling them, read more on view lookup \fIguide\.html#View\-Lookup\fR\.
.
.SS "Mime Types"
Express and Connect now utilize the \fImime\fR module in npm, so to add more use:
.
.IP "" 4
.
.nf
get(\'/user/:id\', function(){
this\.param(\'id\');
});
.
.fi
.
.IP "" 0
.
.P
Polymorphic parameter access can be done using \fBreq\.param()\fR:
.
.IP "" 4
.
.nf
app\.get(\'/user/:id\', function(req, res){
req\.param(\'id\');
});
.
.fi
.
.IP "" 0
.
.P
Route parameters are available via \fBreq\.params\fR:
.
.IP "" 4
.
.nf
app\.get(\'/user/:id\', function(req, res){
req\.params\.id;
});
.
.fi
.
.IP "" 0
.
.SS "Passing Route Control"
Old express had a weak notion of route passing, which did not support async, and was never properly implemented for practical use:
.
.IP "" 4
.
.nf
get(\'/\', function(){
this\.pass(\'/foobar\');
});
.
.fi
.
.IP "" 0
.
.P
Now Express has access to Connect\'s \fInext()\fR function, which is passed as the third and final argument\. Calling \fInext()\fR will pass control to the next \fImatching route\fR, or continue down the stack of Connect middleware\.
.
.IP "" 4
.
.nf
app\.get(\'/user/:id?\', function(req, res, next){
next();
});
app\.get(\'/user\', function(){
// \.\.\. respond
});
.
.fi
.
.IP "" 0
.
.SS "View Rendering"
View filenames no longer take the form \fINAME\fR\.\fITYPE\fR\.\fIENGINE\fR, the \fIContent\-Type\fR can be set via \fIres\.contentType()\fR or \fIres\.header()\fR\. For example what was previously \fIlayout\.html\.haml\fR, should now be \fIlayout\.haml\fR\.
.
.P
Previously a view render looked something like this:
.
.IP "" 4
.
.nf
get(\'/\', function(){
this\.render(\'index\.html\.haml\', {
locals: { title: \'My Site\' }
});
});
.
.fi
.
.IP "" 0
.
.P
We now have \fIres\.render()\fR, however the options passed to haml \fIhttp://github\.com/visionmedia/haml\.js\fR, jade \fIhttp://github\.com/visionmedia/jade\fR, and others remain the same\.
.
.IP "" 4
.
.nf
app\.get(\'/\', function(req, res){
res\.render(\'index\.haml\', {
locals: { title: \'My Site\' }
});
});
.
.fi
.
.IP "" 0
.
.P
Previously rendering of a collection via \fIpartial()\fR would look something like this:
.
.IP "" 4
.
.nf
this\.partial(\'comment\.html\.haml\', { collection: comments });
.
.fi
.
.IP "" 0
.
.P
Although this worked just fine, it was generally to verbose, the similar but new API looks like this, as \fIpartial()\fR is \fIalways\fR passed as a local variable:
.
.IP "" 4
.
.nf
partial(\'comment\.haml\', { collection: comments });
.
.fi
.
.IP "" 0
.
.P
To make things even less verbose we can assume the extension when omitted:
.
.IP "" 4
.
.nf
partial(\'comment\', { collection: comments });
.
.fi
.
.IP "" 0
.
.P
And once again even further, when rendering a collection we can simply pass an array, if no other options are desired:
.
.IP "" 4
.
.nf
partial(\'comments\', comments);
.
.fi
.
.IP "" 0
.
.SS "Redirecting"
Previously you would
.
.IP "" 4
.
.nf
this\.redirect(\'/somewhere\');
.
.fi
.
.IP "" 0
.
.P
However you would now:
.
.IP "" 4
.
.nf
res\.redirect(\'/somewhere\');
res\.redirect(\'/somewhere\', 301);
.
.fi
.
.IP "" 0
.
.SS "HTTP Client"
Previously Express provided a high level http client, this library is no more as it does not belong in Express, however it may be resurrected as a separate module\.
.
.SS "Core Extensions"
Express is no longer dependent on the JavaScript Extensions \fIhttp://github\.com/visionmedia/ext\.js\fR library, so those of you using the methods provided by it such as \fBObject\.merge(a, b)\fR will need to roll your own, or install the module via:
.
.IP "" 4
.
.nf
$ npm install ext
require(\'mime\')\.define({ \'foo/bar\': [\'foo\', \'bar\'] });
.
.fi
.

View File

@@ -133,7 +133,7 @@
font-size: 11px;
}
#menu {
margin-left: 65px;
margin-left: 75px;
padding: 0;
padding-bottom: 30px; }
#menu li {
@@ -187,7 +187,7 @@
<ul id="menu">
<li><a href="index.html">Home</a></li>
<li><a href="guide.html">Guide</a></li>
<li><a href="contrib.html">Contributing</a></li>
<li><a href="screencasts.html">Screencasts</a></li>
<li><a href="applications.html">Applications</a></li>
</ul>
<div class='mp'>
@@ -195,214 +195,162 @@
<p class="man-name">
<code>migrate</code>
</p>
<h3 id="Built-On-Connect">Built On Connect</h3>
<h3 id="Express-1-x-to-2-x-Migration">Express 1.x to 2.x Migration</h3>
<p>Express 1.x is written to run on-top of the <a href="http://extjs.github.com/Connect">Connect</a> middlware
framework, thus the <em>Plugin</em> has been replaced by Connect's middleware. By abstracting our middleware
to Connect we allow additional community frameworks to develop robust, high-level frameworks using
the same technologies as Express.</p>
<h3 id="HTTPS">HTTPS</h3>
<h3 id="Creating-Applications">Creating Applications</h3>
<p> Creating an HTTPS server is simply, simply pass the TLS options to <em>express.createServer()</em>:</p>
<p>Previously due to legacy code implemented in the early days of node,
Express unfortunately had some globals. The DSL would previously be
accessed as shown below:</p>
<pre><code> var app = express.createServer({
key: ...
, cert: ...
});
<pre><code>require('express');
configure(function(){
// app configuration
});
get('/', function(){
return 'hello world';
});
app.listen(443);
</code></pre>
<p>Now we utilize the CommonJS module system appropriately, and
introduce <em>express.createServer()</em> which accepts the same arguments
as <em>http.createServer()</em>:</p>
<h3 id="req-header-Referrer">req.header() Referrer</h3>
<pre><code>var express = require('express'),
app = express.createServer();
<p> Previously if anyone was doing something similar to:</p>
app.configure(function(){
// app configuration
});
app.get('/', function(req, res){
res.send('hello world');
});
<pre><code> req.headers.referrer || req.headers.referer
req.header('Referrer') || req.header('Referer')
</code></pre>
<p>Express 1.x does <em>not</em> currently allow returning of a string.</p>
<p> With the new special-case we may now simply use <em>Referrer</em> which will return either if defined:</p>
<h3 id="Plugins-vs-Middleware">Plugins vs Middleware</h3>
<p>Previously Express was bundled with plugins, which were essentially what
are now Connect middleware. Previously plugins would be utilized in a manor
similar to below:</p>
<pre><code>use(Logger);
use(MethodOverride);
use(Cookie);
<pre><code> req.header('Referrer')
</code></pre>
<p>Which we can now <em>use()</em> within our app, or pass to the <em>express.createServer()</em> method:</p>
<h3 id="res-local-name-val-">res.local(name, val)</h3>
<pre><code>var app = express.createServer(
express.logger(),
express.methodOverride(),
express.cookieDecoder()
);
<p> Previously all local variables had to be passed to <em>res.render()</em>, or either <em>app.helpers()</em> or <em>app.dynamicHelpers()</em>, now we may do this at the request-level progressively. The <em>res.local()</em> method accepts a <em>name</em> and <em>val</em>, however the locals passed to <em>res.render()</em> will take precedence.</p>
<p> For example we may utilize this feature to create locals in middleware:</p>
<pre><code> function loadUser(req, res, next) {
User.get(req.params.id, function(err, user){
res.local('user', user);
next();
});
}
app.get('/user/:id', loadUser, function(req, res){
res.render('user');
});
</code></pre>
<p>or:</p>
<h3 id="req-param-name-defaultValue-">req.param(name[, defaultValue])</h3>
<pre><code>var app = express.createServer();
<p> Previously only <em>name</em> was accepted, so some of you may have been doing the following:</p>
app.use(express.logger());
app.use(express.methodOverride());
app.use(express.cookieDecoder());
<pre><code> var id = req.param('id') || req.user.id;
</code></pre>
<p>For documentation on creating Connect middleware visit <a href="http://extjs.github.com/Connect/#Middleware-Authoring">Middleware Authoring</a>.</p>
<p> The new <em>defaultValue</em> argument can handle this nicely:</p>
<h3 id="Running-Applications">Running Applications</h3>
<p>Previously a global function <em>run()</em>, was available:</p>
<pre><code>run();
<pre><code> var id = req.param('id', req.user.id);
</code></pre>
<p>The new <em>express.Server</em> has the same API as <em>http.Server</em>,
so we can do things like:</p>
<h3 id="app-helpers-app-locals-">app.helpers() / app.locals()</h3>
<pre><code>app.listen();
app.listen(3000);
<p> <em>app.locals()</em> is now an alias of <em>app.helpers()</em>, as helpers makes more sense for functions.</p>
<h3 id="req-accepts-type-">req.accepts(type)</h3>
<p> <em>req.accepts()</em> now accepts extensions:</p>
<pre><code> // Accept: text/html
req.accepts('html');
req.accepts('.html');
// =&gt; true
// Accept: text/*; application/json
req.accepts('html');
req.accepts('text/*');
req.accepts('text/plain');
req.accepts('application/json');
// =&gt; true
req.accepts('image/png');
req.accepts('png');
// =&gt; false
</code></pre>
<h3 id="Route-Parameters">Route Parameters</h3>
<h3 id="res-cookie-">res.cookie()</h3>
<p>Previously we could use <em>this.param()</em> to attempt
fetching a route, query string, or request body parameter:</p>
<p> Previously only directly values could be passed, so for example:</p>
<pre><code>get('/user/:id', function(){
this.param('id');
});
<pre><code>res.cookie('rememberme', 'yes', { expires: new Date(Date.now() + 900000) });
</code></pre>
<p>Polymorphic parameter access can be done using <code>req.param()</code>:</p>
<p>However now we have the alternative <em>maxAge</em> property which may be used to set <em>expires</em> relative to <em>Date.now()</em> in milliseconds, so our example above can now become:</p>
<pre><code>app.get('/user/:id', function(req, res){
req.param('id');
});
<pre><code>res.cookie('rememberme', 'yes', { maxAge: 900000 });
</code></pre>
<p>Route parameters are available via <code>req.params</code>:</p>
<h3 id="res-download-res-sendfile-">res.download() / res.sendfile()</h3>
<pre><code>app.get('/user/:id', function(req, res){
req.params.id;
});
<p> Both of these methods now utilize Connect's static file server behind the scenes (actually the previous Express code was ported to Connect 1.0). With this change comes a change to the callback as well. Previously the <em>path</em> and <em>stream</em> were passed, however now only an <em>error</em> is passed, when no error has occurred the callback will be invoked indicating that the file transfer is complete. The callback remains optional:</p>
<pre><code> res.download('/path/to/file');
res.download('/path/to/file', function(err){
if (err) {
console.error(err);
} else {
console.log('transferred');
}
});
</code></pre>
<h3 id="Passing-Route-Control">Passing Route Control</h3>
<p> The <em>stream threshold</em> setting was removed.</p>
<p>Old express had a weak notion of route passing,
which did not support async, and was never properly
implemented for practical use:</p>
<h3 id="res-render-">res.render()</h3>
<pre><code>get('/', function(){
this.pass('/foobar');
});
<p> Previously locals were passed as a separate key:</p>
<pre><code> res.render('user', { layout: false, locals: { user: user }});
</code></pre>
<p>Now Express has access to Connect's <em>next()</em> function,
which is passed as the third and final argument. Calling <em>next()</em> will
pass control to the next <em>matching route</em>, or continue down the stack
of Connect middleware.</p>
<p> In Express 2.0 both the locals and the options are one in the same, meaning you cannot have a local variable named <em>layout</em> as it is reserved for express, however this cleans up the API:</p>
<pre><code>app.get('/user/:id?', function(req, res, next){
next();
});
app.get('/user', function(){
// ... respond
});
<pre><code> res.render('user', { layout: false, user: user });
</code></pre>
<h3 id="View-Rendering">View Rendering</h3>
<h3 id="res-partial-">res.partial()</h3>
<p>View filenames no longer take the form <em>Express</em>.<em>TYPE</em>.<em>ENGINE</em>,
the <em>Content-Type</em> can be set via <em>res.contentType()</em> or
<em>res.header()</em>. For example what was previously <em>layout.html.haml</em>,
should now be <em>layout.haml</em>.</p>
<p> Express 2.0 adds the <em>res.partial()</em> method, helpful for rendering partial fragments over WebSockets or Ajax requests etc. The API is identical to the <em>partial()</em> calls within views.</p>
<p>Previously a view render looked something like this:</p>
<pre><code> // render a collection of comments
res.partial('comment', [comment1, comment2]);
<pre><code>get('/', function(){
this.render('index.html.haml', {
locals: { title: 'My Site' }
});
});
// render a single comment
res.partial('comment', comment);
</code></pre>
<p>We now have <em>res.render()</em>, however the options passed to <a href="http://github.com/visionmedia/haml.js">haml</a>, <a href="http://github.com/visionmedia/jade">jade</a>, and others
remain the same.</p>
<h3 id="Template-Engine-Compliance">Template Engine Compliance</h3>
<pre><code>app.get('/', function(req, res){
res.render('index.haml', {
locals: { title: 'My Site' }
});
});
<p> To comply with Express previously engines needed the following signature:</p>
<pre><code> engine.render(str, options, function(err){});
</code></pre>
<p>Previously rendering of a collection via <em>partial()</em> would look something like this:</p>
<p> Now they must export a <em>compile()</em> function, returning a function which when called with local variables will render the template. This allows Express to cache the compiled function in memory during production.</p>
<pre><code>this.partial('comment.html.haml', { collection: comments });
<pre><code> var fn = engine.compile(str, options);
fn(locals);
</code></pre>
<p>Although this worked just fine, it was generally to verbose, the similar but new API
looks like this, as <em>partial()</em> is <em>always</em> passed as a local variable:</p>
<h3 id="View-Partial-Lookup">View Partial Lookup</h3>
<pre><code>partial('comment.haml', { collection: comments });
</code></pre>
<p> Previously partials were loaded relative to the now removed <em>view partials</em> directory setting, or by default <em>views/partials</em>, now they are relative to the view calling them, read more on <a href="guide.html#View-Lookup">view lookup</a>.</p>
<p>To make things even less verbose we can assume the extension when omitted:</p>
<h3 id="Mime-Types">Mime Types</h3>
<pre><code>partial('comment', { collection: comments });
</code></pre>
<p> Express and Connect now utilize the <em>mime</em> module in npm, so to add more use:</p>
<p>And once again even further, when rendering a collection we can simply pass
an array, if no other options are desired:</p>
<pre><code>partial('comments', comments);
</code></pre>
<h3 id="Redirecting">Redirecting</h3>
<p>Previously you would</p>
<pre><code>this.redirect('/somewhere');
</code></pre>
<p>However you would now:</p>
<pre><code>res.redirect('/somewhere');
res.redirect('/somewhere', 301);
</code></pre>
<h3 id="HTTP-Client">HTTP Client</h3>
<p>Previously Express provided a high level http client, this library is no more
as it does not belong in Express, however it may be resurrected as a separate module.</p>
<h3 id="Core-Extensions">Core Extensions</h3>
<p>Express is no longer dependent on the <a href="http://github.com/visionmedia/ext.js">JavaScript Extensions</a> library, so those of you using the methods provided by it such as <code>Object.merge(a, b)</code> will need to
roll your own, or install the module via:</p>
<pre><code>$ npm install ext
<pre><code> require('mime').define({ 'foo/bar': ['foo', 'bar'] });
</code></pre>
</div>

View File

@@ -1,189 +1,143 @@
### Built On Connect
### Express 1.x to 2.x Migration
Express 1.x is written to run on-top of the [Connect](http://extjs.github.com/Connect) middlware
framework, thus the _Plugin_ has been replaced by Connect's middleware. By abstracting our middleware
to Connect we allow additional community frameworks to develop robust, high-level frameworks using
the same technologies as Express.
### HTTPS
### Creating Applications
Creating an HTTPS server is simply, simply pass the TLS options to _express.createServer()_:
var app = express.createServer({
key: ...
, cert: ...
});
Previously due to legacy code implemented in the early days of node,
Express unfortunately had some globals. The DSL would previously be
accessed as shown below:
app.listen(443);
require('express');
### req.header() Referrer
configure(function(){
// app configuration
});
Previously if anyone was doing something similar to:
req.headers.referrer || req.headers.referer
req.header('Referrer') || req.header('Referer')
get('/', function(){
return 'hello world';
});
With the new special-case we may now simply use _Referrer_ which will return either if defined:
req.header('Referrer')
Now we utilize the CommonJS module system appropriately, and
introduce _express.createServer()_ which accepts the same arguments
as _http.createServer()_:
### res.local(name, val)
var express = require('express'),
app = express.createServer();
app.configure(function(){
// app configuration
});
app.get('/', function(req, res){
res.send('hello world');
});
Previously all local variables had to be passed to _res.render()_, or either _app.helpers()_ or _app.dynamicHelpers()_, now we may do this at the request-level progressively. The _res.local()_ method accepts a _name_ and _val_, however the locals passed to _res.render()_ will take precedence.
Express 1.x does _not_ currently allow returning of a string.
For example we may utilize this feature to create locals in middleware:
### Plugins vs Middleware
function loadUser(req, res, next) {
User.get(req.params.id, function(err, user){
res.local('user', user);
next();
});
}
Previously Express was bundled with plugins, which were essentially what
are now Connect middleware. Previously plugins would be utilized in a manor
similar to below:
app.get('/user/:id', loadUser, function(req, res){
res.render('user');
});
use(Logger);
use(MethodOverride);
use(Cookie);
### req.param(name[, defaultValue])
Which we can now _use()_ within our app, or pass to the _express.createServer()_ method:
Previously only _name_ was accepted, so some of you may have been doing the following:
var id = req.param('id') || req.user.id;
var app = express.createServer(
express.logger(),
express.methodOverride(),
express.cookieDecoder()
);
The new _defaultValue_ argument can handle this nicely:
var id = req.param('id', req.user.id);
or:
### app.helpers() / app.locals()
var app = express.createServer();
_app.locals()_ is now an alias of _app.helpers()_, as helpers makes more sense for functions.
app.use(express.logger());
app.use(express.methodOverride());
app.use(express.cookieDecoder());
### req.accepts(type)
For documentation on creating Connect middleware visit [Middleware Authoring](http://extjs.github.com/Connect/#Middleware-Authoring).
_req.accepts()_ now accepts extensions:
// Accept: text/html
req.accepts('html');
req.accepts('.html');
// => true
// Accept: text/*; application/json
req.accepts('html');
req.accepts('text/*');
req.accepts('text/plain');
req.accepts('application/json');
// => true
req.accepts('image/png');
req.accepts('png');
// => false
### Running Applications
### res.cookie()
Previously a global function _run()_, was available:
Previously only directly values could be passed, so for example:
run();
res.cookie('rememberme', 'yes', { expires: new Date(Date.now() + 900000) });
The new _express.Server_ has the same API as _http.Server_,
so we can do things like:
However now we have the alternative _maxAge_ property which may be used to set _expires_ relative to _Date.now()_ in milliseconds, so our example above can now become:
app.listen();
app.listen(3000);
res.cookie('rememberme', 'yes', { maxAge: 900000 });
### Route Parameters
### res.download() / res.sendfile()
Previously we could use _this.param()_ to attempt
fetching a route, query string, or request body parameter:
Both of these methods now utilize Connect's static file server behind the scenes (actually the previous Express code was ported to Connect 1.0). With this change comes a change to the callback as well. Previously the _path_ and _stream_ were passed, however now only an _error_ is passed, when no error has occurred the callback will be invoked indicating that the file transfer is complete. The callback remains optional:
res.download('/path/to/file');
get('/user/:id', function(){
this.param('id');
});
res.download('/path/to/file', function(err){
if (err) {
console.error(err);
} else {
console.log('transferred');
}
});
Polymorphic parameter access can be done using `req.param()`:
The _stream threshold_ setting was removed.
app.get('/user/:id', function(req, res){
req.param('id');
});
### res.render()
Route parameters are available via `req.params`:
Previously locals were passed as a separate key:
res.render('user', { layout: false, locals: { user: user }});
app.get('/user/:id', function(req, res){
req.params.id;
});
In Express 2.0 both the locals and the options are one in the same, meaning you cannot have a local variable named _layout_ as it is reserved for express, however this cleans up the API:
res.render('user', { layout: false, user: user });
### Passing Route Control
### res.partial()
Old express had a weak notion of route passing,
which did not support async, and was never properly
implemented for practical use:
Express 2.0 adds the _res.partial()_ method, helpful for rendering partial fragments over WebSockets or Ajax requests etc. The API is identical to the _partial()_ calls within views.
// render a collection of comments
res.partial('comment', [comment1, comment2]);
get('/', function(){
this.pass('/foobar');
});
// render a single comment
res.partial('comment', comment);
Now Express has access to Connect's _next()_ function,
which is passed as the third and final argument. Calling _next()_ will
pass control to the next _matching route_, or continue down the stack
of Connect middleware.
### Template Engine Compliance
app.get('/user/:id?', function(req, res, next){
next();
});
To comply with Express previously engines needed the following signature:
engine.render(str, options, function(err){});
app.get('/user', function(){
// ... respond
});
Now they must export a _compile()_ function, returning a function which when called with local variables will render the template. This allows Express to cache the compiled function in memory during production.
var fn = engine.compile(str, options);
fn(locals);
### View Rendering
### View Partial Lookup
View filenames no longer take the form _NAME_._TYPE_._ENGINE_,
the _Content-Type_ can be set via _res.contentType()_ or
_res.header()_. For example what was previously _layout.html.haml_,
should now be _layout.haml_.
Previously partials were loaded relative to the now removed _view partials_ directory setting, or by default _views/partials_, now they are relative to the view calling them, read more on [view lookup](guide.html#View-Lookup).
Previously a view render looked something like this:
### Mime Types
get('/', function(){
this.render('index.html.haml', {
locals: { title: 'My Site' }
});
});
We now have _res.render()_, however the options passed to [haml](http://github.com/visionmedia/haml.js), [jade](http://github.com/visionmedia/jade), and others
remain the same.
app.get('/', function(req, res){
res.render('index.haml', {
locals: { title: 'My Site' }
});
});
Previously rendering of a collection via _partial()_ would look something like this:
this.partial('comment.html.haml', { collection: comments });
Although this worked just fine, it was generally to verbose, the similar but new API
looks like this, as _partial()_ is _always_ passed as a local variable:
partial('comment.haml', { collection: comments });
To make things even less verbose we can assume the extension when omitted:
partial('comment', { collection: comments });
And once again even further, when rendering a collection we can simply pass
an array, if no other options are desired:
partial('comments', comments);
### Redirecting
Previously you would
this.redirect('/somewhere');
However you would now:
res.redirect('/somewhere');
res.redirect('/somewhere', 301);
### HTTP Client
Previously Express provided a high level http client, this library is no more
as it does not belong in Express, however it may be resurrected as a separate module.
### Core Extensions
Express is no longer dependent on the [JavaScript Extensions](http://github.com/visionmedia/ext.js) library, so those of you using the methods provided by it such as `Object.merge(a, b)` will need to
roll your own, or install the module via:
$ npm install ext
Express and Connect now utilize the _mime_ module in npm, so to add more use:
require('mime').define({ 'foo/bar': ['foo', 'bar'] });

28
docs/screencasts.1 Normal file
View File

@@ -0,0 +1,28 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "SCREENCASTS" "" "March 2011" "" ""
.
.SH "NAME"
\fBscreencasts\fR
.
.SS "Introduction"
This introduction screencast covers the basics of Express, and how to get started with your first application\.
.
.P
.
.SS "View Partials"
In this screencast we work with partials to display a collection of users using the Jade \fIhttp://jade\-lang\.com\fR template engine, and learn about view path resolution\.
.
.P
.
.SS "Route Specific Middleware"
In the screencast below we learn about the benefits of route\-specific middleware\.
.
.P
.
.SS "Route Param Preconditions"
Learn about route parameter (\fI/user/:id\fR) pre\-conditions, providing automated validation, and loading of data via the named route param segments\.
.
.P

226
docs/screencasts.html Normal file
View File

@@ -0,0 +1,226 @@
<html>
<head>
<title>Express - node web framework</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<style>
#tagline {
margin-left: 75px;
margin-bottom: 30px;
color: rgba(255,255,255,0.7); }
html {
background: #1c1c1c url(images/bg.tile.jpg); }
body {
margin: 0;
padding-bottom: 30px;
font: 14px/1.4 "Helvetica Neue", "Lucida Grande", "Arial";
font-size: 14px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
background: url(images/bg.jpg) 50% 0 no-repeat;
color: #8b8b8b; }
* {
outline: none; }
em {
color: white; }
a img {
border: none !important; }
a {
font-weight: bold;
text-decoration: none;
color: white;
-webkit-transition-property: opacity, -webkit-transform, color, background-color, padding, -webkit-box-shadow;
-webkit-transition-duration: 0.15s;
-webkit-transition-timing-function: ease-out; }
a:hover {
opacity: 0.8; }
h1, h2, h3, h4 {
margin: 45px 0 0 0;
color: white;
text-shadow: 1px 2px 2px rgba(0,0,0,0.6); }
h3 {
font-size: 18px; }
h4 {
margin-left: 10px;
font-size: 14px;
}
pre {
margin: 20px 10px;
padding: 25px 20px;
background: rgba(0,0,0,0.5);
border: 1px solid #323232;
-webkit-box-shadow: 1px 2px 2px rgba(0,0,0,0.6);
-moz-box-shadow: 1px 2px 2px rgba(0,0,0,0.6);
-webkit-border-radius: 5px;
-moz-border-radius: 5px; }
code {
font-family: "Helvetica Neue", "Lucida Grande", "Arial"; }
ul {
margin: 15px 0;
padding: 0 0 0 35px; }
ul li {
margin: 0;
padding: 2px 0;
list-style: square; }
ul li ul {
margin: 0;
padding-left: 12px;
}
.man-name, #Express { display:none; }
.sect {
margin-left: 40px; }
img {
margin-left: 20px;
margin-bottom: 15px;
}
#logo {
display: block;
margin-left: 30%;
margin-bottom: 30px;
width: 194px;
height: 51px;
background: url(images/logo.png) 0 0 no-repeat;
text-indent: -99999px; }
#logo:hover {
opacity: 0.7; }
#logo:active {
opacity: 0.3; }
#ribbon {
position: fixed;
top: 0;
right: 0;
z-index: 2; }
#wrapper {
width: 100%;
min-height: 800px;
background: url(images/top.png) 0 0 repeat-x; }
#container {
margin: 0 auto;
padding-top: 80px;
width: 550px; }
#toc {
position: fixed;
top: 0;
left: 0;
margin: 0 0 0 15px;
padding: 15px;
height: 100%;
background: rgba(0,0,0,0.2);
overflow: auto;
border-right: 1px solid rgba(255,255,255,0.05);
}
#toc li {
padding: 0;
list-style: none;
}
#toc li a {
font-size: 11px;
}
#menu {
margin-left: 75px;
padding: 0;
padding-bottom: 30px; }
#menu li {
display: inline;
list-style: none; }
#menu li a {
display: block;
float: left;
margin: 0 2px;
padding: 3px 15px;
background: rgba(0,0,0,0.2);
-webkit-border-radius: 8px;
-moz-border-radius: 8px;
-webkit-box-shadow: 1px 2px 2px rgba(0,0,0,0.6);
-moz-box-shadow: 1px 2px 2px rgba(0,0,0,0.6);
-webkit-transition-property: opacity, -webkit-transform, color, background-color, -webkit-box-shadow;
-webkit-transition-duration: 0.15s;
-webkit-transition-timing-function: ease-out; }
#menu li a:hover,
#menu li a.active {
background: rgba(0,0,0,0.5); }
#menu li a:active {
background: rgba(0,0,0,0.1);
-webkit-box-shadow: 1px 1px 1px rgba(0,0,0,0.4);
-moz-box-shadow: 1px 1px 1px rgba(0,0,0,0.4); }
</style>
<script>
$(function(){
$('.section').hide();
$('.toggle, a.section-title').toggle(function(){
$(this).siblings('ul').fadeIn(300);
return false;
}, function(){
$(this).siblings('ul').fadeOut(300);
return false;
});
});
</script>
</head>
<body>
<a href='http://github.com/visionmedia/express'>
<img alt='Fork me on GitHub' id='ribbon' src='http://s3.amazonaws.com/github/ribbons/forkme_right_white_ffffff.png' />
</a>
<div id="wrapper">
<div id="container">
<a href='http://github.com/visionmedia/express' id='logo'>Express</a>
<p id="tagline">
High performance, high class web development for
<a href="http://nodejs.org">Node.js</a>
</p>
<ul id="menu">
<li><a href="index.html">Home</a></li>
<li><a href="guide.html">Guide</a></li>
<li><a href="screencasts.html">Screencasts</a></li>
<li><a href="applications.html">Applications</a></li>
</ul>
<div class='mp'>
<h2 id="Express">Express</h2>
<p class="man-name">
<code>screencasts</code>
</p>
<h3 id="Introduction">Introduction</h3>
<p>This introduction screencast covers the basics of Express, and how to get started with your first application.</p>
<p><object height="345" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="560" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0"><param name="movie" value="http://screenr.com/Content/assets/screenr_1116090935.swf" /><param name="flashvars" value="i=139583" /><param name="allowFullScreen" value="true" /><embed pluginspage="http://www.macromedia.com/go/getflashplayer" allowfullscreen="true" src="http://screenr.com/Content/assets/screenr_1116090935.swf" height="345" flashvars="i=139583" width="560"></embed></object></p>
<h3 id="View-Partials">View Partials</h3>
<p>In this screencast we work with partials to display a collection of users using the <a href="http://jade-lang.com">Jade</a> template engine, and learn about view path resolution.</p>
<p><object height="345" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="560" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0"><param name="movie" value="http://screenr.com/Content/assets/screenr_1116090935.swf" /><param name="flashvars" value="i=139591" /><param name="allowFullScreen" value="true" /><embed pluginspage="http://www.macromedia.com/go/getflashplayer" allowfullscreen="true" src="http://screenr.com/Content/assets/screenr_1116090935.swf" height="345" flashvars="i=139591" width="560"></embed></object></p>
<h3 id="Route-Specific-Middleware">Route Specific Middleware</h3>
<p>In the screencast below we learn about the benefits of route-specific middleware.</p>
<p><object height="345" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="560" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0"><param name="movie" value="http://screenr.com/Content/assets/screenr_1116090935.swf" /><param name="flashvars" value="i=140296" /><param name="allowFullScreen" value="true" /><embed pluginspage="http://www.macromedia.com/go/getflashplayer" allowfullscreen="true" src="http://screenr.com/Content/assets/screenr_1116090935.swf" height="345" flashvars="i=140296" width="560"></embed></object></p>
<h3 id="Route-Param-Preconditions">Route Param Preconditions</h3>
<p>Learn about route parameter (<em>/user/:id</em>) pre-conditions, providing automated validation, and loading of data via the named route param segments.</p>
<p><object height="345" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="560" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0"><param name="movie" value="http://screenr.com/Content/assets/screenr_1116090935.swf" /><param name="flashvars" value="i=140300" /><param name="allowFullScreen" value="true" /><embed pluginspage="http://www.macromedia.com/go/getflashplayer" allowfullscreen="true" src="http://screenr.com/Content/assets/screenr_1116090935.swf" height="345" flashvars="i=140300" width="560"></embed></object></p>
</div>
</div>
</div>
</body>
</html>

24
docs/screencasts.md Normal file
View File

@@ -0,0 +1,24 @@
### Introduction
This introduction screencast covers the basics of Express, and how to get started with your first application.
<object classid='clsid:d27cdb6e-ae6d-11cf-96b8-444553540000' codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0' width='560' height='345'><param name='movie' value='http://screenr.com/Content/assets/screenr_1116090935.swf' /><param name='flashvars' value='i=139583' /><param name='allowFullScreen' value='true' /><embed src='http://screenr.com/Content/assets/screenr_1116090935.swf' flashvars='i=139583' allowFullScreen='true' width='560' height='345' pluginspage='http://www.macromedia.com/go/getflashplayer'></embed></object>
### View Partials
In this screencast we work with partials to display a collection of users using the [Jade](http://jade-lang.com) template engine, and learn about view path resolution.
<object classid='clsid:d27cdb6e-ae6d-11cf-96b8-444553540000' codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0' width='560' height='345'><param name='movie' value='http://screenr.com/Content/assets/screenr_1116090935.swf' /><param name='flashvars' value='i=139591' /><param name='allowFullScreen' value='true' /><embed src='http://screenr.com/Content/assets/screenr_1116090935.swf' flashvars='i=139591' allowFullScreen='true' width='560' height='345' pluginspage='http://www.macromedia.com/go/getflashplayer'></embed></object>
### Route Specific Middleware
In the screencast below we learn about the benefits of route-specific middleware.
<object classid='clsid:d27cdb6e-ae6d-11cf-96b8-444553540000' codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0' width='560' height='345'><param name='movie' value='http://screenr.com/Content/assets/screenr_1116090935.swf' /><param name='flashvars' value='i=140296' /><param name='allowFullScreen' value='true' /><embed src='http://screenr.com/Content/assets/screenr_1116090935.swf' flashvars='i=140296' allowFullScreen='true' width='560' height='345' pluginspage='http://www.macromedia.com/go/getflashplayer'></embed></object>
### Route Param Preconditions
Learn about route parameter (_/user/:id_) pre-conditions, providing automated validation, and loading of data via the named route param segments.
<object classid='clsid:d27cdb6e-ae6d-11cf-96b8-444553540000' codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0' width='560' height='345'><param name='movie' value='http://screenr.com/Content/assets/screenr_1116090935.swf' /><param name='flashvars' value='i=140300' /><param name='allowFullScreen' value='true' /><embed src='http://screenr.com/Content/assets/screenr_1116090935.swf' flashvars='i=140300' allowFullScreen='true' width='560' height='345' pluginspage='http://www.macromedia.com/go/getflashplayer'></embed></object>

View File

@@ -6,60 +6,60 @@ require.paths.unshift(__dirname + '/../../support');
* Module dependencies.
*/
var express = require('../../lib/express'),
crypto = require('crypto');
var express = require('../../lib/express')
, crypto = require('crypto');
var app = express.createServer();
var app = express.createServer(
express.bodyParser()
, express.cookieParser()
, express.session({ secret: 'keyboard cat' })
);
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(express.bodyDecoder());
app.use(express.cookieDecoder());
app.use(express.session());
// Message helper, ideally we would use req.flash()
// however this is more light-weight for an example
app.dynamicHelpers({
message: function(req){
var err = req.session.error,
msg = req.session.success;
delete req.session.error;
delete req.session.success;
if (err) return '<p class="msg error">' + err + '</p>';
if (msg) return '<p class="msg success">' + msg + '</p>';
}
message: function(req){
var err = req.session.error
, msg = req.session.success;
delete req.session.error;
delete req.session.success;
if (err) return '<p class="msg error">' + err + '</p>';
if (msg) return '<p class="msg success">' + msg + '</p>';
}
});
// Generate a salt for the user to prevent rainbow table attacks
var users = {
tj: {
name: 'tj',
salt: 'randomly-generated-salt',
pass: md5('foobar' + 'randomly-generated-salt')
}
tj: {
name: 'tj'
, salt: 'randomly-generated-salt'
, pass: md5('foobar' + 'randomly-generated-salt')
}
};
// Used to generate a hash of the plain-text password + salt
function md5(str) {
return crypto.createHash('md5').update(str).digest('hex');
return crypto.createHash('md5').update(str).digest('hex');
}
// Authenticate using our plain-object database of doom!
function authenticate(name, pass, fn) {
var user = users[name];
// query the db for the given username
if (!user) return fn(new Error('cannot find user'));
// apply the same algorithm to the POSTed password, applying
// the md5 against the pass / salt, if there is a match we
// found the user
if (user.pass == md5(pass + user.salt)) return fn(null, user);
// Otherwise password is invalid
fn(new Error('invalid password'));
var user = users[name];
// query the db for the given username
if (!user) return fn(new Error('cannot find user'));
// apply the same algorithm to the POSTed password, applying
// the md5 against the pass / salt, if there is a match we
// found the user
if (user.pass == md5(pass + user.salt)) return fn(null, user);
// Otherwise password is invalid
fn(new Error('invalid password'));
}
function restrict(req, res, next) {
@@ -72,54 +72,54 @@ function restrict(req, res, next) {
}
function accessLogger(req, res, next) {
console.log('/restricted accessed by %s', req.session.user.name);
next();
console.log('/restricted accessed by %s', req.session.user.name);
next();
}
app.get('/', function(req, res){
res.redirect('/login');
res.redirect('/login');
});
app.get('/restricted', restrict, accessLogger, function(req, res){
res.send('Wahoo! restricted area');
res.send('Wahoo! restricted area');
});
app.get('/logout', function(req, res){
// destroy the user's session to log them out
// will be re-created next request
req.session.destroy(function(){
res.redirect('home');
});
// destroy the user's session to log them out
// will be re-created next request
req.session.destroy(function(){
res.redirect('home');
});
});
app.get('/login', function(req, res){
if (req.session.user) {
req.session.success = 'Authenticated as ' + req.session.user.name
+ ' click to <a href="/logout">logout</a>. '
+ ' You may now access <a href="/restricted">/restricted</a>.';
}
res.render('login');
if (req.session.user) {
req.session.success = 'Authenticated as ' + req.session.user.name
+ ' click to <a href="/logout">logout</a>. '
+ ' You may now access <a href="/restricted">/restricted</a>.';
}
res.render('login');
});
app.post('/login', function(req, res){
authenticate(req.body.username, req.body.password, function(err, user){
if (user) {
// Regenerate session when signing in
// to prevent fixation
req.session.regenerate(function(){
// Store the user's primary key
// in the session store to be retrieved,
// or in this case the entire user object
req.session.user = user;
res.redirect('back');
});
} else {
req.session.error = 'Authentication failed, please check your '
+ ' username and password.'
+ ' (use "tj" and "foobar")';
res.redirect('back');
}
});
authenticate(req.body.username, req.body.password, function(err, user){
if (user) {
// Regenerate session when signing in
// to prevent fixation
req.session.regenerate(function(){
// Store the user's primary key
// in the session store to be retrieved,
// or in this case the entire user object
req.session.user = user;
res.redirect('back');
});
} else {
req.session.error = 'Authentication failed, please check your '
+ ' username and password.'
+ ' (use "tj" and "foobar")';
res.redirect('back');
}
});
});
app.listen(3000);

View File

@@ -6,20 +6,56 @@ require.paths.unshift(__dirname + '/../../support');
* Module dependencies.
*/
var express = require('./../../lib/express');
var express = require('../../lib/express')
, messages = require('express-contrib/messages');
// Our app IS the exports, this prevents require('./app').app,
// instead it is require('./app');
var app = module.exports = express.createServer();
// Illustrates that an app can be broken into
// several files, but yet extend the same app
require('./main');
require('./contact');
// Config
// Illustrates that one app (Server instance) can
// be "mounted" to another at the given route.
app.use('/blog', require('./blog'));
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.listen(3000);
console.log('Express started on port 3000');
// mount hook
app.mounted(function(other){
console.log('ive been mounted!');
});
// Flash message helper provided by express-contrib
// $ npm install express-contrib
app.dynamicHelpers({
messages: messages
, base: function(){
// return the app's mount-point
// so that urls can adjust. For example
// if you run this example /post/add works
// however if you run the mounting example
// it adjusts to /blog/post/add
return '/' == app.route ? '' : app.route;
}
});
// Middleware
app.configure(function(){
app.use(express.logger('\x1b[33m:method\x1b[0m \x1b[32m:url\x1b[0m :response-time'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.session({ secret: 'keyboard cat' }));
app.use(app.router);
app.use(express.static(__dirname + '/public'));
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
// Routes
require('./routes/site')(app);
require('./routes/post')(app);
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -1,45 +0,0 @@
/**
* Module dependencies.
*/
var express = require('./../../../lib/express'),
fs = require('fs');
// Export our app as the module
var app = module.exports = express.createServer();
// Set views directory
app.set('views', __dirname + '/views');
// Load our posts
var posts = JSON.parse(fs.readFileSync(__dirname + '/posts.json', 'utf8'));
// Set our default view engine to "ejs"
app.set('view engine', 'ejs');
app.dynamicHelpers({
basepath: function(){
// "this" is the app, we can
// dynamically provide the "home"
// setting to all views
return this.set('home');
}
});
app.get('/', function(req, res){
res.render('index', {
locals: {
posts: posts
}
});
});
app.get('/post/:id', function(req, res){
var id = req.params.id;
res.render('post', {
locals: {
post: posts[id]
}
});
});

View File

@@ -1,4 +0,0 @@
[
{ "id": 0, "title": "Post one", "body": "Just some lame post" },
{ "id": 1, "title": "Post two", "body": "Just another lame post" }
]

View File

@@ -1,2 +0,0 @@
<h2>Posts</h2>
<%- partial('post', { collection: posts, as: global }) %>

View File

@@ -1,9 +0,0 @@
<html>
<head>
<title>Blog Example</title>
</head>
<body>
<h1>Best Blog Ever</h1>
<%- body %>
</body>
</html>

View File

@@ -1,2 +0,0 @@
<h3><a href="<%= basepath %>/post/<%= id %>"><%= title %></a></h3>
<p><%= body %></p>

View File

@@ -1 +0,0 @@
<%- partial('post', { object: post, as: global }) %>

View File

@@ -1,9 +0,0 @@
// in ./app.js we did "module.exports", allowing
// us to grab the app from the parent module (the one
// which required it)
var app = module.parent.exports;
app.get('/contact', function(req, res){
res.send('<p>Wahoo contact page</p>');
});

View File

@@ -1,10 +0,0 @@
// in ./app.js we did "module.exports", allowing
// us to grab the app from the parent module (the one
// which required it)
var app = module.parent.exports;
app.get('/', function(req, res){
res.send('<p>Visit <a href="/blog">/blog</a>'
+ ' or <a href="/contact">/contact</a></p>');
});

View File

@@ -0,0 +1,67 @@
// Fake data store
var ids = 0
, db = {};
var Post = exports = module.exports = function Post(title, body) {
this.id = ++ids;
this.title = title;
this.body = body;
this.createdAt = new Date;
};
Post.prototype.save = function(fn){
db[this.id] = this;
fn();
};
Post.prototype.validate = function(fn){
if (!this.title) return fn(new Error('_title_ required'));
if (!this.body) return fn(new Error('_body_ required'));
if (this.body.length < 10) {
return fn(new Error(
'_body_ should be at least **10** characters long, was only _' + this.title.length + '_'));
}
fn();
};
Post.prototype.update = function(data, fn){
this.updatedAt = new Date;
for (var key in data) {
if (undefined != data[key]) {
this[key] = data[key];
}
}
this.save(fn);
};
Post.prototype.destroy = function(fn){
exports.destroy(this.id, fn);
};
exports.count = function(fn){
fn(null, Object.keys(db).length);
};
exports.all = function(fn){
var arr = Object.keys(db).reduce(function(arr, id){
arr.push(db[id]);
return arr;
}, []);
fn(null, arr);
};
exports.get = function(id, fn){
fn(null, db[id]);
};
exports.destroy = function(id, fn) {
if (db[id]) {
delete db[id];
fn();
} else {
fn(new Error('post ' + id + ' does not exist'));
}
};

View File

@@ -0,0 +1,58 @@
body {
font: 13px "Helvetica Neue", Arial, sans-serif;
color: #111;
padding: 60px 80px;
}
h1, h2 {
color: #c00;
}
a {
color: #c00;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
label {
padding: 6px 0;
display: block;
text-transform: lowercase;
}
textarea,
input {
outline: none;
padding: 5px;
background: #f1f1f1;
border: 1px solid #aaa;
}
textarea:focus,
input:focus {
background: #fff;
}
textarea {
width: 500px;
height: 200px;
}
a.edit {
margin-left: 10px;
font-size: 11px;
font-weight: normal;
}
.date {
font-size: 11px;
}
#messages ul {
padding: 10px;
border: 1px solid;
}
#messages ul li {
list-style: none;
}
#messages ul.info {
color: #2EBBE6;
background: #F7FBFD;
}
#messages ul.error {
color: #E4250C;
background: #FDEAE7;
}

View File

@@ -0,0 +1,95 @@
/**
* Module dependencies.
*/
var basicAuth = require('../../../lib/express').basicAuth
, Post = require('../models/post');
module.exports = function(app){
/**
* Apply basic auth to all post related routes
*/
app.all('/post(/*)?', basicAuth(function(user, pass){
return 'admin' == user && 'express' == pass;
}));
/**
* Map :post to the database, loading
* every time :post is present.
*/
app.param('post', function(req, res, next, id){
Post.get(id, function(err, post){
if (err) return next(err);
if (!post) return next(new Error('failed to load post ' + id));
req.post = post;
next();
});
});
/**
* Add a post.
*/
app.get('/post/add', function(req, res){
res.render('post/form', { post: {}});
});
/**
* Save a post.
*/
app.post('/post', function(req, res){
var data = req.body.post
, post = new Post(data.title, data.body);
post.validate(function(err){
if (err) {
req.flash('error', err.message);
return res.redirect('back');
}
post.save(function(err){
req.flash('info', 'Successfully created post _%s_', post.title);
res.redirect('/post/' + post.id);
});
});
});
/**
* Display the post.
*/
app.get('/post/:post', function(req, res){
res.render('post', { post: req.post });
});
/**
* Display the post edit form.
*/
app.get('/post/:post/edit', function(req, res){
res.render('post/form', { post: req.post });
});
/**
* Update post. Typically a data layer would handle this stuff.
*/
app.put('/post/:post', function(req, res, next){
var post = req.post;
post.validate(function(err){
if (err) {
req.flash('error', err.message);
return res.redirect('back');
}
post.update(req.body.post, function(err){
if (err) return next(err);
req.flash('info', 'Successfully updated post');
res.redirect('back');
});
});
});
};

View File

@@ -0,0 +1,19 @@
/**
* Module dependencies.
*/
var Post = require('../models/post');
module.exports = function(app){
app.get('/', function(req, res){
Post.count(function(err, count){
Post.all(function(err, posts){
res.render('index', {
count: count
, posts: posts
});
});
});
});
};

View File

@@ -0,0 +1,19 @@
h1 Blog
!= messages()
- if (count)
p Display all #{count} post(s)
#posts!= partial('post', posts)
- else
p
| It looks like you have no posts!
p
| Click
a(href=base + '/post/add') here
| to create a post. Login
| as
em "admin"
| and
em "express"
| .

View File

@@ -0,0 +1,7 @@
!!! 5
html
head
title Blog
link(rel='stylesheet', href=base + '/style.css')
body
#container!= body

View File

@@ -0,0 +1,20 @@
- if (post.title)
h1 Editing #{post.title}
- else
h1 New Post
!= messages()
form#post(action=base + '/post' + (post.title ? '/' + post.id : ''), method='post')
- if (post.title)
input(type='hidden', name='_method', value='put')
p
label(for='post[title]') Title:
input(type='text', name='post[title]', value=post.title)
p
label(for='post[body]') Body:
textarea(name='post[body]')= post.body || ''
p
input(type='submit', value=post.title ? 'Update' : 'Create')

View File

@@ -0,0 +1,16 @@
.post
// title
h2
= post.title
a.edit(href=base + '/post/' + post.id + '/edit') Edit
// flash messages
!= messages()
// dates
p.date.created Created at #{post.createdAt}
- if (post.updatedAt)
p.date.updated Updated at #{post.updatedAt}
// body
pre.body= post.body

View File

@@ -1,45 +1,48 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
var express = require('./../../lib/express');
var express = require('../../lib/express');
var app = express.createServer(
// Place default Connect favicon above logger so it is not in
// the logging output
express.favicon(),
// Place default Connect favicon above logger so it is not in
// the logging output
express.favicon(),
// Custom logger format
express.logger({ format: '\x1b[1m:method\x1b[0m \x1b[33m:url\x1b[0m :response-time' }),
// Custom logger format
express.logger({ format: '\x1b[1m:method\x1b[0m \x1b[33m:url\x1b[0m :response-time' }),
// Provides req.cookies
express.cookieDecoder(),
// Provides req.cookies
express.cookieParser(),
// Parses x-www-form-urlencoded request bodies (and json)
express.bodyDecoder()
// Parses x-www-form-urlencoded request bodies (and json)
express.bodyParser()
);
app.get('/', function(req, res){
if (req.cookies.remember) {
res.send('Remembered :). Click to <a href="/forget">forget</a>!.');
} else {
res.send('<form method="post"><p>Check to <label>'
+ '<input type="checkbox" name="remember"/> remember me</label> '
+ '<input type="submit" value="Submit"/>.</p></form>');
}
if (req.cookies.remember) {
res.send('Remembered :). Click to <a href="/forget">forget</a>!.');
} else {
res.send('<form method="post"><p>Check to <label>'
+ '<input type="checkbox" name="remember"/> remember me</label> '
+ '<input type="submit" value="Submit"/>.</p></form>');
}
});
app.get('/forget', function(req, res){
res.clearCookie('remember');
res.redirect('back');
res.clearCookie('remember');
res.redirect('back');
});
app.post('/', function(req, res){
if (req.body.remember) {
res.cookie('remember', '1', { path: '/', expires: new Date(Date.now() + 900000), httpOnly: true });
}
res.redirect('back');
if (req.body.remember) {
res.cookie('remember', '1', { path: '/', expires: new Date(Date.now() + 900000), httpOnly: true });
}
res.redirect('back');
});
app.listen(3000);

View File

@@ -1,31 +1,46 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
var express = require('./../../lib/express');
var express = require('../../lib/express');
var app = express.createServer();
app.get('/', function(req, res){
res.send('<ul>'
+ '<li>Download <a href="/files/amazing.txt">amazing.txt</a>.</li>'
+ '<li>Download <a href="/files/missing.txt">missing.txt</a>.</li>'
+ '</ul>');
res.send('<ul>'
+ '<li>Download <a href="/files/amazing.txt">amazing.txt</a>.</li>'
+ '<li>Download <a href="/files/missing.txt">missing.txt</a>.</li>'
+ '</ul>');
});
app.get('/files/*', function(req, res){
var file = req.params[0];
res.download(__dirname + '/files/' + file);
// /files/* is accessed via req.params[0]
// but here we name it :file
app.get('/files/:file(*)', function(req, res, next){
var file = req.params.file
, path = __dirname + '/files/' + file;
// either res.download(path) and let
// express handle failures, or provide
// a callback
res.download(path, function(err){
if (err) return next(err);
// the response has invoked .end()
// so you cannnot respond here (of course)
// but the callback is handy for statistics etc.
console.log('transferred %s', path);
});
});
app.error(function(err, req, res, next){
if (process.ENOENT == err.errno) {
res.send('Cant find that file, sorry!');
} else {
// Not a 404
next(err);
}
if ('ENOENT' == err.code) {
res.send('Cant find that file, sorry!');
} else {
// Not a 404
next(err);
}
});
app.listen(3000);

View File

@@ -1 +1 @@
wow supes cool are you glad you downloaded this?
what an amazing download

View File

@@ -6,27 +6,29 @@ require.paths.unshift(__dirname + '/../../support');
* Module dependencies.
*/
var express = require('./../../lib/express');
var express = require('../../lib/express');
var app = express.createServer();
// Register ejs as .html
app.register('.html', require('ejs'));
// Optional since express defaults to CWD/views
app.set('views', __dirname + '/views');
app.set('view engine', 'html');
// Dummy users
var users = [
{ name: 'tj', email: 'tj@sencha.com' },
{ name: 'ciaran', email: 'ciaranj@gmail.com' },
{ name: 'aaron', email: 'aaron.heckmann+github@gmail.com' }
{ name: 'tj', email: 'tj@sencha.com' }
, { name: 'ciaran', email: 'ciaranj@gmail.com' }
, { name: 'aaron', email: 'aaron.heckmann+github@gmail.com' }
];
app.get('/', function(req, res){
res.render('users.ejs', {
locals: {
users: users
}
});
res.render('users', { users: users });
});
app.listen(3000);
app.listen(3000);
console.log('Express app started on port 3000');

View File

@@ -1,6 +0,0 @@
<html>
<body>
<h1>Users</h1>
<%- body %>
</body>
</html>

View File

@@ -0,0 +1,6 @@
<html>
<body>
<h1>Users</h1>
<%- body %>
</body>
</html>

View File

@@ -1,3 +0,0 @@
<ul id="users">
<%- partial('user', users) %>
</ul>

View File

@@ -0,0 +1,3 @@
<ul id="users">
<%- partial('user', users) %>
</ul>

View File

@@ -6,10 +6,9 @@ require.paths.unshift(__dirname + '/../../support');
* Module dependencies.
*/
var express = require('./../../lib/express');
var express = require('../../lib/express');
var app = express.createServer(),
sys = require('sys');
var app = express.createServer();
// Serve default connect favicon
app.use(express.favicon());
@@ -32,7 +31,7 @@ app.use(app.router);
// exception
app.use(function(req, res, next){
next(new NotFound(req.url));
next(new NotFound(req.url));
});
app.set('views', __dirname + '/views');
@@ -40,17 +39,21 @@ app.set('views', __dirname + '/views');
// Provide our app with the notion of NotFound exceptions
function NotFound(path){
this.name = 'NotFound';
if (path) {
Error.call(this, 'Cannot find ' + path);
this.path = path;
} else {
Error.call(this, 'Not Found');
}
Error.captureStackTrace(this, arguments.callee);
this.name = 'NotFound';
if (path) {
Error.call(this, 'Cannot find ' + path);
this.path = path;
} else {
Error.call(this, 'Not Found');
}
Error.captureStackTrace(this, arguments.callee);
}
sys.inherits(NotFound, Error);
/**
* Inherit from `Error.prototype`.
*/
NotFound.prototype.__proto__ = Error.prototype;
// We can call app.error() several times as shown below.
// Here we check for an instanceof NotFound and show the
@@ -61,42 +64,32 @@ sys.inherits(NotFound, Error);
// in the development environment.
app.error(function(err, req, res, next){
if (err instanceof NotFound) {
res.render('404.jade', {
status: 404,
locals: {
error: err
}
});
} else {
next(err);
}
if (err instanceof NotFound) {
res.render('404.jade', { status: 404, error: err });
} else {
next(err);
}
});
// Here we assume all errors as 500 for the simplicity of
// this demo, however you can choose whatever you like
app.error(function(err, req, res){
res.render('500.jade', {
status: 500,
locals: {
error: err
}
});
res.render('500.jade', { status: 500, error: err });
});
// Routes
app.get('/', function(req, res){
res.render('index.jade');
res.render('index.jade');
});
app.get('/404', function(req, res){
throw new NotFound;
throw new NotFound(req.url);
});
app.get('/500', function(req, res, next){
next(new Error('keyboard cat!'));
next(new Error('keyboard cat!'));
});
app.listen(3000);

View File

@@ -1,25 +1,28 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
var express = require('./../../lib/express');
var express = require('../../lib/express');
var app = express.createServer();
app.get('/', function(req, res){
// Caught and passed down to the errorHandler middleware
throw new Error('something broke!');
// Caught and passed down to the errorHandler middleware
throw new Error('something broke!');
});
app.get('/next', function(req, res, next){
// We can also pass exceptions to next()
next(new Error('oh no!'))
// We can also pass exceptions to next()
next(new Error('oh no!'))
});
// The errorHandler middleware in this case will dump exceptions to stderr
// as well as show the stack trace in responses, currently handles text/plain,
// text/html, and application/json responses to aid in development
app.use('/', express.errorHandler({ dumpExceptions: true, showStack: true }));
app.use('/', express.errorHandler({ dump: true, stack: true }));
app.listen(3000);

View File

@@ -11,8 +11,8 @@ var express = require('../../lib/express');
// App with session support
var app = express.createServer(
express.cookieDecoder(),
express.session()
express.cookieParser()
, express.session({ secret: 'keyboard cat' })
);
// View settings
@@ -20,58 +20,29 @@ var app = express.createServer(
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
// Dynamic helpers are functions which are executed
// on each view render, unless dynamicHelpers is false.
// So for example we do not need to call messages() in our
// template, "messages" will be populated with the return
// value of this function.
app.dynamicHelpers({
messages: function(req, res){
// In the case of flash messages
// we return a function, allowing
// flash messages to only be flushed
// when called, otherwise every request
// will flush flash messages regardless.
return function(){
// Grab the flash messages
var messages = req.flash();
// We will render the "messages.ejs" partial
return res.partial('messages', {
// Our target object is our messages
object: messages,
// We want it to be named "types" in the partial
// since they are keyed like this:
// { info: ['foo'], error: ['bar']}
as: 'types',
// Pass a local named "hasMessages" so we can easily
// check if we have any messages at all
locals: { hasMessages: Object.keys(messages).length },
// We dont want dynamicHelpers in this partial, as
// it would cause infinite recursion
dynamicHelpers: false
});
}
}
// express-messages is a dynamicHelper that
// renders the flash messages to HTML for you
// $ npm install express-messages
messages: require('express-messages')
});
app.dynamicHelpers({
// Another dynamic helper example. Since dynamic
// helpers resolve at view rendering time, we can
// "inject" the "page" local variable per request
// providing us with the request url.
page: function(req, res){
return req.url;
}
// Another dynamic helper example. Since dynamic
// helpers resolve at view rendering time, we can
// "inject" the "page" local variable per request
// providing us with the request url.
page: function(req, res){
return req.url;
}
});
app.get('/', function(req, res){
// Not very realistic notifications but illustrates usage
req.flash('info', 'email queued');
req.flash('info', 'email sent');
req.flash('error', 'delivery failed');
res.render('index');
// Not very realistic notifications but illustrates usage
req.flash('info', 'email queued');
req.flash('info', 'email sent');
req.flash('error', 'delivery failed');
res.render('index');
});
app.listen(3000);

View File

@@ -1,5 +1,5 @@
<html>
<body>
<%- body %>
</body>
<body>
<%- body %>
</body>
</html>

View File

@@ -1,12 +0,0 @@
<% if (hasMessages) { %>
<div id="messages">
<% for (var type in types) { %>
<h2><%= type %> messages</h2>
<ul id="<%= type %>">
<% types[type].forEach(function(msg){ %>
<li><%= msg %></li>
<% }) %>
</ul>
<% } %>
</div>
<% } %>

View File

@@ -1,17 +1,19 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
var express = require('./../../lib/express'),
sys = require('sys');
var express = require('../../lib/express');
var app = express.createServer();
// Here we use the bodyDecoder middleware
// to parse urlencoded request bodies
// which populates req.body
app.use(express.bodyDecoder());
app.use(express.bodyParser());
// The methodOverride middleware allows us
// to set a hidden input of _method to an arbitrary
@@ -19,60 +21,60 @@ app.use(express.bodyDecoder());
app.use(express.methodOverride());
// Required by session
app.use(express.cookieDecoder());
app.use(express.cookieParser());
// Required by req.flash() for persistent
// notifications
app.use(express.session());
app.use(express.session({ secret: 'keyboard cat' }));
app.get('/', function(req, res){
// get ?name=foo
var name = req.param('name') || '';
// Switch the button label based if we have a name
var label = name ? 'Update' : 'Save';
// get ?name=foo
var name = req.param('name') || '';
// Switch the button label based if we have a name
var label = name ? 'Update' : 'Save';
// Buffer all flash messages.
// Typically this would all be done in a template
// however for illustration purposes we iterate
// here.
// The messages in req.flash() persist until called,
// at which time they are flushed from the session
var msgs = '<ul>',
flash = req.flash();
Object.keys(flash).forEach(function(type){
flash[type].forEach(function(msg){
msgs += '<li class="' + type + '">' + msg + '</li>';
});
// Buffer all flash messages.
// Typically this would all be done in a template
// however for illustration purposes we iterate
// here.
// The messages in req.flash() persist until called,
// at which time they are flushed from the session
var msgs = '<ul>',
flash = req.flash();
Object.keys(flash).forEach(function(type){
flash[type].forEach(function(msg){
msgs += '<li class="' + type + '">' + msg + '</li>';
});
msgs += '</ul>';
});
msgs += '</ul>';
// If we have a name, we are updating,
// so add the hidden _method input
res.send(msgs
+ '<form method="post">'
+ (name ? '<input type="hidden" value="put" name="_method" />' : '')
+ 'Name: <input type="text" name="name" value="' + name + '" />'
+ '<input type="submit" value="' + label + '" />'
+ '</form>');
// If we have a name, we are updating,
// so add the hidden _method input
res.send(msgs
+ '<form method="post">'
+ (name ? '<input type="hidden" value="put" name="_method" />' : '')
+ 'Name: <input type="text" name="name" value="' + name + '" />'
+ '<input type="submit" value="' + label + '" />'
+ '</form>');
});
app.post('/', function(req, res){
if (req.body.name) {
// Typically here we would create a resource
req.flash('info', 'Saved ' + req.body.name);
res.redirect('/?name=' + req.body.name);
} else {
req.flash('error', 'Error: name required');
res.redirect('/');
}
if (req.body.name) {
// Typically here we would create a resource
req.flash('info', 'Saved ' + req.body.name);
res.redirect('/?name=' + req.body.name);
} else {
req.flash('error', 'Error: name required');
res.redirect('/');
}
});
app.put('/', function(req, res){
// Typically here we would update a resource
req.flash('info', 'Updated ' + req.body.name);
res.redirect('/?name=' + req.body.name);
// Typically here we would update a resource
req.flash('info', 'Updated ' + req.body.name);
res.redirect('/?name=' + req.body.name);
});
app.listen(3000);

View File

@@ -1,4 +1,7 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
@@ -10,49 +13,54 @@ var app = express.createServer();
// Fake items
var items = [
{ name: 'foo' },
{ name: 'bar' },
{ name: 'baz' }
{ name: 'foo' }
, { name: 'bar' }
, { name: 'baz' }
];
// Routes
app.get('/', function(req, res, next){
res.send('Visit /item/2');
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.write('<p>Visit /item/2</p>');
res.write('<p>Visit /item/2.json</p>');
res.write('<p>Visit /item/2.xml</p>');
res.end();
});
app.get('/item/:id.:format?', function(req, res, next){
var id = req.params.id,
format = req.params.format,
item = items[id];
// Ensure item exists
if (item) {
// Serve the format
switch (format) {
case 'json':
// Detects json
res.send(item);
break;
case 'xml':
// Set contentType as xml then sends
// the string
var xml = ''
+ '<items>'
+ '<item>' + item.name + '</item>'
+ '</items>';
res.contentType('.xml');
res.send(xml);
break;
case 'html':
default:
// By default send some hmtl
res.send('<h1>' + item.name + '</h1>');
}
} else {
// We could simply pass route control and potentially 404
// by calling next(), or pass an exception like below.
next(new Error('Item ' + id + ' does not exist'));
var id = req.params.id
, format = req.params.format
, item = items[id];
// Ensure item exists
if (item) {
// Serve the format
switch (format) {
case 'json':
// Detects json
res.send(item);
break;
case 'xml':
// Set contentType as xml then sends
// the string
var xml = ''
+ '<items>'
+ '<item>' + item.name + '</item>'
+ '</items>';
res.contentType('.xml');
res.send(xml);
break;
case 'html':
default:
// By default send some hmtl
res.send('<h1>' + item.name + '</h1>');
}
} else {
// We could simply pass route control and potentially 404
// by calling next(), or pass an exception like below.
next(new Error('Item ' + id + ' does not exist'));
}
});
// Middleware

View File

@@ -6,14 +6,15 @@ require.paths.unshift(__dirname + '/../../support');
* Module dependencies.
*/
var express = require('./../../lib/express'),
http = require('http');
var express = require('../../lib/express')
, http = require('http');
var app = express.createServer();
// Expose our views
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
/**
* Request github json api `path`.
@@ -24,20 +25,20 @@ app.set('views', __dirname + '/views');
*/
function request(path, fn){
var client = http.createClient(80, 'github.com'),
req = client.request('GET', '/api/v2/json' + path, { Host: 'github.com' });
req.addListener('response', function(res){
res.body = '';
res.addListener('data', function(chunk){ res.body += chunk; });
res.addListener('end', function(){
try {
fn(null, JSON.parse(res.body));
} catch (err) {
fn(err);
}
});
var client = http.createClient(80, 'github.com')
, req = client.request('GET', '/api/v2/json' + path, { Host: 'github.com' });
req.on('response', function(res){
res.body = '';
res.on('data', function(chunk){ res.body += chunk; });
res.on('end', function(){
try {
fn(null, JSON.parse(res.body));
} catch (err) {
fn(err);
}
});
req.end();
});
req.end();
}
/**
@@ -48,11 +49,11 @@ function request(path, fn){
*/
function sort(repos){
return repos.sort(function(a, b){
if (a.watchers == b.watchers) return 0;
if (a.watchers > b.watchers) return -1;
if (a.watchers < b.watchers) return 1;
});
return repos.sort(function(a, b){
if (a.watchers == b.watchers) return 0;
if (a.watchers > b.watchers) return -1;
if (a.watchers < b.watchers) return 1;
});
}
/**
@@ -64,9 +65,9 @@ function sort(repos){
*/
function totalWatchers(repos) {
return repos.reduce(function(sum, repo){
return sum + repo.watchers;
}, 0);
return repos.reduce(function(sum, repo){
return sum + repo.watchers;
}, 0);
}
/**
@@ -74,7 +75,7 @@ function totalWatchers(repos) {
*/
app.get('/', function(req, res){
res.redirect('/repos/visionmedia');
res.redirect('/repos/visionmedia');
});
/**
@@ -82,37 +83,33 @@ app.get('/', function(req, res){
*/
app.get('/repos/*', function(req, res, next){
var names = req.params[0].split('/'),
users = [];
(function fetchData(name){
// We have a user name
if (name) {
console.log('... fetching \x1b[33m%s\x1b[0m', name);
request('/repos/show/' + name, function(err, user){
if (err) {
next(err)
} else {
user.totalWatchers = totalWatchers(user.repositories);
user.repos = sort(user.repositories);
user.name = name;
users.push(user);
fetchData(names.shift());
}
});
// No more users
var names = req.params[0].split('/')
, users = [];
(function fetchData(name){
// We have a user name
if (name) {
console.log('... fetching \x1b[33m%s\x1b[0m', name);
request('/repos/show/' + name, function(err, user){
if (err) {
next(err)
} else {
console.log('... done');
res.render('index.jade', {
locals: {
users: users
}
});
user.totalWatchers = totalWatchers(user.repositories);
user.repos = sort(user.repositories);
user.name = name;
users.push(user);
fetchData(names.shift());
}
})(names.shift());
});
// No more users
} else {
console.log('... done');
res.render('index', { users: users });
}
})(names.shift());
});
// Serve statics from ./public
app.use(express.staticProvider(__dirname + '/public'));
app.use(express.static(__dirname + '/public'));
// Listen on port 3000
app.listen(3000);

View File

@@ -1,18 +1,18 @@
body {
padding: 30px 50px;
font: 12px/1.4 "Helvetica Neue", Arial, sans-serif;
padding: 30px 50px;
font: 12px/1.4 "Helvetica Neue", Arial, sans-serif;
}
a {
color: #00AAFF;
text-decoration: none;
color: #00AAFF;
text-decoration: none;
}
a:hover {
text-decoration: underline;
text-decoration: underline;
}
.user {
margin: 0 10px;
float: left;
width: 25%;
width: 300px;
}
table td:nth-child(2) {
padding: 0 5px;

View File

@@ -3,12 +3,12 @@
* Module dependencies.
*/
var express = require('./../../lib/express');
var express = require('../../lib/express');
var app = express.createServer();
app.get('/', function(req, res){
res.send('Hello World');
res.send('Hello World');
});
app.listen(3000);

View File

@@ -6,7 +6,7 @@ require.paths.unshift(__dirname + '/../../support');
* Module dependencies.
*/
var express = require('./../../lib/express');
var express = require('../../lib/express');
// Path to our public directory
@@ -16,8 +16,8 @@ var pub = __dirname + '/public';
// and then serve with connect's staticProvider
var app = express.createServer(
express.compiler({ src: pub, enable: ['sass'] }),
express.staticProvider(pub)
express.compiler({ src: pub, enable: ['sass'] })
, express.static(pub)
);
// Optional since express defaults to CWD/views
@@ -25,23 +25,35 @@ var app = express.createServer(
app.set('views', __dirname + '/views');
// Set our default template engine to "jade"
// which prevents the need for extensions (although you can still mix and match)
// which prevents the need for extensions
// (although you can still mix and match)
app.set('view engine', 'jade');
// Dummy users
var users = [
{ name: 'tj', email: 'tj@sencha.com' },
{ name: 'ciaran', email: 'ciaranj@gmail.com' },
{ name: 'aaron', email: 'aaron.heckmann+github@gmail.com' }
{ name: 'tj', email: 'tj@vision-media.ca' }
, { name: 'ciaran', email: 'ciaranj@gmail.com' }
, { name: 'aaron', email: 'aaron.heckmann+github@gmail.com' }
];
app.get('/', function(req, res){
res.render('users', {
locals: {
users: users
}
});
res.render('users', { users: users });
});
app.get('/users', function(req, res){
// we can use res.partial() as if
// we were in a view, utilizing the same api
// to render a fragment
res.partial('users/user', users);
});
app.get('/users/list', function(req, res){
res.partial('users/list', { object: users });
});
app.get('/user/:id', function(req, res){
res.partial('users/user', users[req.params.id]);
});
app.listen(3000);
console.log('Express app started on port 3000');
console.log('Express app started on port 3000');

View File

@@ -0,0 +1,3 @@
ul#users
- each user in list
li!= partial('user', user)

41
examples/markdown/app.js Normal file
View File

@@ -0,0 +1,41 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
// $ npm install markdown
/**
* Module dependencies.
*/
var express = require('../../lib/express')
, md = require('markdown').markdown;
var app = express.createServer();
// register .md so that markdown may comply
// with the express view system by implementing
// a .compile() method
app.register('.md', {
compile: function(str, options){
var html = md.toHTML(str);
return function(locals){
return html.replace(/\{([^}]+)\}/g, function(_, name){
return locals[name];
});
};
}
});
// Optional since express defaults to CWD/views
app.set('views', __dirname + '/views');
app.set('view engine', 'md');
app.get('/', function(req, res){
res.render('index', { layout: false, title: 'Markdown Example' });
});
app.listen(3000);
console.log('Express app started on port 3000');

View File

@@ -0,0 +1,4 @@
# {title}
Just an example view rendered with _markdown_.

27
examples/mounting/app.js Normal file
View File

@@ -0,0 +1,27 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
var express = require('../../lib/express')
, blog = require('../blog/app');
var app = express.createServer();
app.use(express.cookieParser());
app.use(express.session({ secret: 'keyboard cat' }));
// mount the blog. the blog app is written using the "base"
// local variable, allowing it's urls to adjust to wherever
// we wish to mount it.
app.use('/blog', blog);
app.get('/', function(req, res){
res.send('Visit <a href="/blog">/blog</a>');
});
app.listen(3000);
console.log('Server listening on port 3000');

View File

@@ -6,46 +6,45 @@ require.paths.unshift(__dirname + '/../../support');
* Module dependencies.
*/
var express = require('../../lib/express'),
form = require('connect-form'),
sys = require('sys');
var express = require('../../lib/express')
, form = require('connect-form');
var app = express.createServer(
// connect-form (http://github.com/visionmedia/connect-form)
// middleware uses the formidable middleware to parse urlencoded
// and multipart form data
form({ keepExtensions: true })
// connect-form (http://github.com/visionmedia/connect-form)
// middleware uses the formidable middleware to parse urlencoded
// and multipart form data
form({ keepExtensions: true })
);
app.get('/', function(req, res){
res.send('<form method="post" enctype="multipart/form-data">'
+ '<p>Image: <input type="file" name="image" /></p>'
+ '<p><input type="submit" value="Upload" /></p>'
+ '</form>');
res.send('<form method="post" enctype="multipart/form-data">'
+ '<p>Image: <input type="file" name="image" /></p>'
+ '<p><input type="submit" value="Upload" /></p>'
+ '</form>');
});
app.post('/', function(req, res, next){
// connect-form adds the req.form object
// we can (optionally) define onComplete, passing
// the exception (if any) fields parsed, and files parsed
req.form.complete(function(err, fields, files){
if (err) {
next(err);
} else {
console.log('\nuploaded %s to %s',
files.image.filename,
files.image.path);
res.redirect('back');
}
});
// connect-form adds the req.form object
// we can (optionally) define onComplete, passing
// the exception (if any) fields parsed, and files parsed
req.form.complete(function(err, fields, files){
if (err) {
next(err);
} else {
console.log('\nuploaded %s to %s'
, files.image.filename
, files.image.path);
res.redirect('back');
}
});
// We can add listeners for several form
// events such as "progress"
req.form.addListener('progress', function(bytesReceived, bytesExpected){
var percent = (bytesReceived / bytesExpected * 100) | 0;
sys.print('Uploading: %' + percent + '\r');
});
// We can add listeners for several form
// events such as "progress"
req.form.on('progress', function(bytesReceived, bytesExpected){
var percent = (bytesReceived / bytesExpected * 100) | 0;
process.stdout.write('Uploading: %' + percent + '\r');
});
});
app.listen(3000);

View File

@@ -1,9 +1,9 @@
module.exports = {
// /
index: function(req, res){
res.render();
}
// /
index: function(req, res){
res.render();
}
};

View File

@@ -2,54 +2,54 @@
// Fake user database for example
var users = [
{ id: 0, name: 'TJ', email: 'tj@vision-media.ca' },
{ id: 1, name: 'Simon', email: 'simon@vision-media.ca' }
{ id: 0, name: 'TJ', email: 'tj@vision-media.ca' }
, { id: 1, name: 'Simon', email: 'simon@vision-media.ca' }
];
function get(id, fn) {
if (users[id]) {
fn(null, users[id]);
} else {
fn(new Error('User ' + id + ' does not exist'));
}
if (users[id]) {
fn(null, users[id]);
} else {
fn(new Error('User ' + id + ' does not exist'));
}
}
module.exports = {
// /users
index: function(req, res){
res.render(users);
},
// /users
index: function(req, res){
res.render(users);
},
// /users/:id
// /users/:id
show: function(req, res, next){
get(req.params.id, function(err, user){
if (err) return next(err);
res.render(user);
});
},
// /users/:id/edit
edit: function(req, res, next){
get(req.params.id, function(err, user){
if (err) return next(err);
res.render(user);
});
},
// PUT /users/:id
update: function(req, res, next){
var id = req.params.id;
get(id, function(err){
if (err) return next(err);
var user = users[id] = req.body.user;
user.id = id;
req.flash('info', 'Successfully updated _' + user.name + '_.');
res.redirect('back');
});
}
show: function(req, res, next){
get(req.params.id, function(err, user){
if (err) return next(err);
res.render(user);
});
},
// /users/:id/edit
edit: function(req, res, next){
get(req.params.id, function(err, user){
if (err) return next(err);
res.render(user);
});
},
// PUT /users/:id
update: function(req, res, next){
var id = req.params.id;
get(id, function(err){
if (err) return next(err);
var user = users[id] = req.body.user;
user.id = id;
req.flash('info', 'Successfully updated _' + user.name + '_.');
res.redirect('back');
});
}
};

View File

@@ -3,149 +3,146 @@
* Module dependencies.
*/
var fs = require('fs'),
express = require('../../lib/express');
var fs = require('fs')
, express = require('../../lib/express');
exports.boot = function(app){
bootApplication(app);
bootControllers(app);
bootApplication(app);
bootControllers(app);
};
// App settings and middleware
function bootApplication(app) {
app.use(express.logger({ format: ':method :url :status' }));
app.use(express.bodyDecoder());
app.use(express.methodOverride());
app.use(express.cookieDecoder());
app.use(express.session());
app.use(app.router);
app.use(express.staticProvider(__dirname + '/public'));
app.use(express.logger({ format: ':method :url :status' }));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.session({ secret: 'keyboard cat' }));
app.use(app.router);
app.use(express.static(__dirname + '/public'));
// Example 500 page
app.error(function(err, req, res){
console.dir(err)
res.render('500');
});
// Example 500 page
app.error(function(err, req, res){
console.dir(err)
res.render('500');
});
// Example 404 page via simple Connect middleware
app.use(function(req, res){
res.render('404');
});
// Example 404 page via simple Connect middleware
app.use(function(req, res){
res.render('404');
});
// Setup ejs views as default, with .html as the extension
app.set('views', __dirname + '/views');
app.register('.html', require('ejs'));
app.set('view engine', 'html');
// Setup ejs views as default, with .html as the extension
app.set('views', __dirname + '/views');
app.register('.html', require('ejs'));
app.set('view engine', 'html');
// Some dynamic view helpers
app.dynamicHelpers({
request: function(req){
return req;
},
// Some dynamic view helpers
app.dynamicHelpers({
request: function(req){
return req;
},
hasMessages: function(req){
return Object.keys(req.session.flash || {}).length;
},
hasMessages: function(req){
return Object.keys(req.session.flash || {}).length;
},
messages: function(req){
return function(){
var msgs = req.flash();
return Object.keys(msgs).reduce(function(arr, type){
return arr.concat(msgs[type]);
}, []);
}
}
});
messages: function(req){
return function(){
var msgs = req.flash();
return Object.keys(msgs).reduce(function(arr, type){
return arr.concat(msgs[type]);
}, []);
}
}
});
}
// Bootstrap controllers
function bootControllers(app) {
fs.readdir(__dirname + '/controllers', function(err, files){
if (err) throw err;
files.forEach(function(file){
bootController(app, file);
});
fs.readdir(__dirname + '/controllers', function(err, files){
if (err) throw err;
files.forEach(function(file){
bootController(app, file);
});
});
}
// Example (simplistic) controller support
function bootController(app, file) {
var name = file.replace('.js', ''),
actions = require('./controllers/' + name),
plural = name + 's', // realistically we would use an inflection lib
prefix = '/' + plural;
var name = file.replace('.js', '')
, actions = require('./controllers/' + name)
, plural = name + 's' // realistically we would use an inflection lib
, prefix = '/' + plural;
// Special case for "app"
if (name == 'app') {
prefix = '/';
// Special case for "app"
if (name == 'app') prefix = '/';
Object.keys(actions).map(function(action){
var fn = controllerAction(name, plural, action, actions[action]);
switch(action) {
case 'index':
app.get(prefix, fn);
break;
case 'show':
app.get(prefix + '/:id.:format?', fn);
break;
case 'add':
app.get(prefix + '/:id/add', fn);
break;
case 'create':
app.post(prefix + '/:id', fn);
break;
case 'edit':
app.get(prefix + '/:id/edit', fn);
break;
case 'update':
app.put(prefix + '/:id', fn);
break;
case 'destroy':
app.del(prefix + '/:id', fn);
break;
}
Object.keys(actions).map(function(action){
var fn = controllerAction(name, plural, action, actions[action]);
switch(action) {
case 'index':
app.get(prefix, fn);
break;
case 'show':
app.get(prefix + '/:id.:format?', fn);
break;
case 'add':
app.get(prefix + '/:id/add', fn);
break;
case 'create':
app.post(prefix + '/:id', fn);
break;
case 'edit':
app.get(prefix + '/:id/edit', fn);
break;
case 'update':
app.put(prefix + '/:id', fn);
break;
case 'destroy':
app.del(prefix + '/:id', fn);
break;
}
});
});
}
// Proxy res.render() to add some magic
function controllerAction(name, plural, action, fn) {
return function(req, res, next){
var render = res.render,
format = req.params.format,
path = __dirname + '/views/' + name + '/' + action + '.html';
res.render = function(obj, options, fn){
res.render = render;
// Template path
if (typeof obj === 'string') {
return res.render(obj, options, fn);
}
return function(req, res, next){
var render = res.render
, format = req.params.format
, path = __dirname + '/views/' + name + '/' + action + '.html';
res.render = function(obj, options, fn){
res.render = render;
// Template path
if (typeof obj === 'string') {
return res.render(obj, options, fn);
}
// Format support
if (action == 'show' && format) {
if (format === 'json') {
return res.send(obj);
} else {
throw new Error('unsupported format "' + format + '"');
}
}
// Format support
if (action == 'show' && format) {
if (format === 'json') {
return res.send(obj);
} else {
throw new Error('unsupported format "' + format + '"');
}
}
// Render template
res.render = render;
options = options || {};
options.locals = options.locals || {};
// Expose obj as the "users" or "user" local
if (action == 'index') {
options.locals[plural] = obj;
} else {
options.locals[name] = obj;
}
return res.render(path, options, fn);
};
fn.apply(this, arguments);
// Render template
res.render = render;
options = options || {};
// Expose obj as the "users" or "user" local
if (action == 'index') {
options[plural] = obj;
} else {
options[name] = obj;
}
return res.render(path, options, fn);
};
fn.apply(this, arguments);
};
}

View File

@@ -1,9 +1,9 @@
<html>
<head>
<title>Express - MVC Example</title>
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<%- body %>
</body>
<head>
<title>Express - MVC Example</title>
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<%- body %>
</body>
</html>

View File

@@ -0,0 +1,7 @@
<% if (hasMessages) { %>
<ul id="messages">
<% messages().forEach(function(msg){ %>
<li><%- msg %></li>
<% }) %>
</ul>
<% } %>

View File

@@ -1,7 +0,0 @@
<% if (hasMessages) { %>
<ul id="messages">
<% messages().forEach(function(msg){ %>
<li><%- msg %></li>
<% }) %>
</ul>
<% } %>

View File

@@ -1,5 +1,5 @@
<h1>Editing user <%= user.name %></h1>
<%- partial('messages') %>
<%- partial('../messages') %>
<form method="post" action="/users/<%= user.id %>">
<input type="hidden" name="_method" value="put" />
<p>Name: <input type="text" name="user[name]", value="<%= user.name %>" /></p>

View File

@@ -1,7 +1,7 @@
<h1>Users</h1>
<%- partial('messages') %>
<%- partial('../messages') %>
<ul>
<% users.forEach(function(user){ %>
<li><a href="/users/<%= user.id %>"><%= user.name %></a></li>
<% }) %>
<% users.forEach(function(user){ %>
<li><a href="/users/<%= user.id %>"><%= user.name %></a></li>
<% }) %>
</ul>

View File

@@ -1,5 +1,5 @@
<h1>Viewing user #<%= user.id %></h1>
<%- partial('messages') %>
<%- partial('../messages') %>
<ul>
<li><%- user.name %></li>
<li><%- user.email %></li>

64
examples/params/app.js Normal file
View File

@@ -0,0 +1,64 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
var express = require('../../lib/express')
, app = express.createServer();
// Faux database
var users = [
{ name: 'tj' }
, { name: 'tobi' }
, { name: 'loki' }
, { name: 'jane' }
, { name: 'bandit' }
];
// Convert :to and :from to integers
app.param(['to', 'from'], function(n){ return parseInt(n, 10); });
// Load user by id
app.param('user', function(req, res, next, id){
if (req.user = users[id]) {
next();
} else {
next(new Error('failed to find user'));
}
});
/**
* GET index.
*/
app.get('/', function(req, res){
res.send('Visit /user/0 or /users/0-2');
});
/**
* GET :user.
*/
app.get('/user/:user', function(req, res, next){
res.send('user ' + req.user.name);
});
/**
* GET users :from - :to.
*/
app.get('/users/:from-:to', function(req, res, next){
var from = req.params.from
, to = req.params.to
, names = users.map(function(user){ return user.name; });
res.send('users ' + names.slice(from, to).join(', '));
});
app.listen(3000);
console.log('Express application listening on port 3000');

40
examples/partials/app.js Normal file
View File

@@ -0,0 +1,40 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
var express = require('../../lib/express');
var app = express.createServer();
// Optional since express defaults to CWD/views
app.set('views', __dirname + '/views');
// Set our default template engine to "jade"
// which prevents the need for extensions
// (although you can still mix and match)
app.set('view engine', 'jade');
// Dummy record
var ninja = {
name: 'leonardo',
summary: { email: 'hunter.loftis+github@gmail.com', master: 'splinter', description: 'peaceful leader' },
weapons: ['katana', 'fists', 'shell'],
victims: ['shredder', 'brain', 'beebop', 'rocksteady']
};
app.get('/', function(req, res){
res.render('ninjas', { ninja: ninja });
});
app.get('/li', function(req, res){
res.partial('li', { object: 'Testing', as: 'value' });
});
app.listen(3000);
console.log('Express app started on port 3000');

View File

@@ -0,0 +1,5 @@
!!!
html
head
title Partials Example
body!= body

View File

@@ -0,0 +1 @@
li= value

View File

@@ -0,0 +1 @@
li.weapon= weapon

View File

@@ -0,0 +1,18 @@
h1= ninja.name
// file, partial name, and partial object all match ('summary')
// the partial filename prefix '_' is completely optional
#summary!= partial('summary', ninja.summary)
// file, partial name = '_weapon', resolves to 'weapon' object within partial
#weapons
h2 Weapons
// the weapon partial is rendered once per item in
// the weapons array or "collection"
ul!= partial('weapon', ninja.weapons)
// partial name 'victim' resolves to 'victim.jade'
// or 'victim/index.jade', providing the "victim" local
#victims
h2 Victims
ul!= partial('victim', ninja.victims)

View File

@@ -0,0 +1,4 @@
h2 Summary
p= summary.email
p= summary.description
p taught by master #{summary.master}

View File

@@ -0,0 +1,5 @@
// this is insane overkill, I do not recommend
// doing tiny partials like this as it gets expensive
// with collections, however this illustrates the new
// partial lookup mechanism
!= partial('../../li', { object: victim, as: 'value' })

View File

@@ -1,4 +1,7 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
@@ -10,45 +13,58 @@ var app = express.createServer();
// Ad-hoc example resource method
app.resource = function(path, obj) {
this.get(path, obj.index);
this.get(path + '/:a..:b', function(req, res){
var a = parseInt(req.params.a, 10),
b = parseInt(req.params.b, 10);
obj.range(req, res, a, b);
});
this.get(path + '/:id', obj.show);
this.del(path + '/:id', obj.destroy);
this.get(path, obj.index);
this.get(path + '/:a..:b.:format?', function(req, res){
var a = parseInt(req.params.a, 10)
, b = parseInt(req.params.b, 10)
, format = req.params.format;
obj.range(req, res, a, b, format);
});
this.get(path + '/:id', obj.show);
this.del(path + '/:id', obj.destroy);
};
// Fake records
var users = [
{ name: 'tj' },
{ name: 'ciaran' },
{ name: 'aaron' },
{ name: 'guillermo' },
{ name: 'simon' },
{ name: 'tobi' }
{ name: 'tj' }
, { name: 'ciaran' }
, { name: 'aaron' }
, { name: 'guillermo' }
, { name: 'simon' }
, { name: 'tobi' }
];
// Fake controller
// Fake controller.
var User = {
index: function(req, res){
res.send(users);
},
show: function(req, res){
res.send(users[req.params.id] || { error: 'Cannot find user' });
},
destroy: function(req, res){
var id = req.params.id;
var destroyed = id in users;
delete users[id];
res.send(destroyed ? 'destroyed' : 'Cannot find user');
},
range: function(req, res, a, b){
res.send(users.slice(a, b+1));
index: function(req, res){
res.send(users);
},
show: function(req, res){
res.send(users[req.params.id] || { error: 'Cannot find user' });
},
destroy: function(req, res){
var id = req.params.id;
var destroyed = id in users;
delete users[id];
res.send(destroyed ? 'destroyed' : 'Cannot find user');
},
range: function(req, res, a, b, format){
var range = users.slice(a, b + 1);
switch (format) {
case 'json':
res.send(range);
break;
case 'html':
default:
var html = '<ul>' + range.map(function(user){
return '<li>' + user.name + '</li>';
}).join('\n') + '</ul>';
res.send(html);
break;
}
}
};
// curl http://localhost:3000/users -- responds with all users
@@ -60,15 +76,16 @@ var User = {
app.resource('/users', User);
app.get('/', function(req, res){
res.send([
'<h1>Examples:</h1> <ul>',
'<li>GET /users</li>',
'<li>GET /users/1</li>',
'<li>GET /users/3</li>',
'<li>GET /users/1..3</li>',
'<li>DELETE /users/4</li>',
'</ul>',
].join('\n'));
res.send([
'<h1>Examples:</h1> <ul>'
, '<li>GET /users</li>'
, '<li>GET /users/1</li>'
, '<li>GET /users/3</li>'
, '<li>GET /users/1..3</li>'
, '<li>GET /users/1..3.json</li>'
, '<li>DELETE /users/4</li>'
, '</ul>'
].join('\n'));
});
app.listen(3000);

View File

@@ -1,4 +1,7 @@
// Expose modules in ./support for demo purposes
require.paths.unshift(__dirname + '/../../support');
/**
* Module dependencies.
*/
@@ -16,44 +19,44 @@ var app = express.createServer();
// Dummy users
var users = [
{ id: 0, name: 'tj', email: 'tj@vision-media.ca', role: 'member' },
{ id: 1, name: 'ciaran', email: 'ciaranj@gmail.com', role: 'member' },
{ id: 2, name: 'aaron', email: 'aaron.heckmann+github@gmail.com', role: 'admin' }
{ id: 0, name: 'tj', email: 'tj@vision-media.ca', role: 'member' }
, { id: 1, name: 'ciaran', email: 'ciaranj@gmail.com', role: 'member' }
, { id: 2, name: 'aaron', email: 'aaron.heckmann+github@gmail.com', role: 'admin' }
];
function loadUser(req, res, next) {
// You would fetch your user from the db
var user = users[req.params.id];
if (user) {
req.user = user;
next();
} else {
next(new Error('Failed to load user ' + req.params.id));
}
// You would fetch your user from the db
var user = users[req.params.id];
if (user) {
req.user = user;
next();
} else {
next(new Error('Failed to load user ' + req.params.id));
}
}
function andRestrictToSelf(req, res, next) {
// If our authenticated user is the user we are viewing
// then everything is fine :)
if (req.authenticatedUser.id == req.user.id) {
next();
} else {
// You may want to implement specific exceptions
// such as UnauthorizedError or similar so that you
// can handle these in app.error() specifically
// (view ./examples/pages for this)
next(new Error('Unauthorized'));
}
// If our authenticated user is the user we are viewing
// then everything is fine :)
if (req.authenticatedUser.id == req.user.id) {
next();
} else {
// You may want to implement specific exceptions
// such as UnauthorizedError or similar so that you
// can handle these in app.error() specifically
// (view ./examples/pages for this)
next(new Error('Unauthorized'));
}
}
function andRestrictTo(role) {
return function(req, res, next) {
if (req.authenticatedUser.role == role) {
next();
} else {
next(new Error('Unauthorized'));
}
return function(req, res, next) {
if (req.authenticatedUser.role == role) {
next();
} else {
next(new Error('Unauthorized'));
}
}
}
// Middleware for faux authentication
@@ -62,24 +65,24 @@ function andRestrictTo(role) {
// may interacte with middleware
app.use(function(req, res, next){
req.authenticatedUser = users[0];
next();
req.authenticatedUser = users[0];
next();
});
app.get('/', function(req, res){
res.redirect('/user/0');
res.redirect('/user/0');
});
app.get('/user/:id', loadUser, function(req, res){
res.send('Viewing user ' + req.user.name);
res.send('Viewing user ' + req.user.name);
});
app.get('/user/:id/edit', loadUser, andRestrictToSelf, function(req, res){
res.send('Editing user ' + req.user.name);
res.send('Editing user ' + req.user.name);
});
app.del('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){
res.send('Deleted user ' + req.user.name);
res.send('Deleted user ' + req.user.name);
});
app.listen(3000);

View File

@@ -16,9 +16,9 @@ var express = require('../../lib/express')
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');
app.use(express.bodyDecoder());
app.use(express.cookieParser());
app.use(express.methodOverride());
app.use(express.staticProvider(__dirname + '/public'));
app.use(express.static(__dirname + '/public'));
// General

View File

@@ -2,16 +2,11 @@
// Fake posts database
var posts = [
{ title: 'Foo', body: 'some foo bar' }
, { title: 'Foo bar', body: 'more foo bar' }
, { title: 'Foo bar baz', body: 'more foo bar baz' }
];
{ title: 'Foo', body: 'some foo bar' }
, { title: 'Foo bar', body: 'more foo bar' }
, { title: 'Foo bar baz', body: 'more foo bar baz' }
];
exports.list = function(req, res){
res.render('post/list', {
locals: {
title: 'Posts'
, posts: posts
}
});
res.render('posts', { title: 'Posts', posts: posts });
};

View File

@@ -1,8 +1,4 @@
exports.index = function(req, res){
res.render('index', {
locals: {
title: 'Route Separation Example'
}
});
res.render('index', { title: 'Route Separation Example' });
};

View File

@@ -2,17 +2,12 @@
// Fake user database
var users = [
{ name: 'TJ', email: 'tj@vision-media.ca' }
, { name: 'Tobi', email: 'tobi@vision-media.ca' }
];
{ name: 'TJ', email: 'tj@vision-media.ca' }
, { name: 'Tobi', email: 'tobi@vision-media.ca' }
];
exports.list = function(req, res){
res.render('user/list', {
locals: {
title: 'Users'
, users: users
}
});
res.render('users', { title: 'Users', users: users });
};
exports.load = function(req, res, next){
@@ -26,20 +21,16 @@ exports.load = function(req, res, next){
};
exports.view = function(req, res){
res.render('user/view', {
locals: {
title: 'Viewing user ' + req.user.name
, user: req.user
}
res.render('users/view', {
title: 'Viewing user ' + req.user.name
, user: req.user
});
};
exports.edit = function(req, res){
res.render('user/edit', {
locals: {
title: 'Editing user ' + req.user.name
, user: req.user
}
res.render('users/edit', {
title: 'Editing user ' + req.user.name
, user: req.user
});
};

Some files were not shown because too many files have changed in this diff Show More