├── test ├── fixtures │ ├── hello.ejs │ ├── hello.jade │ ├── index.jade │ ├── item.jade │ ├── layouts │ │ └── foo.jade │ ├── hello.haml │ ├── user.jade │ ├── user.json │ ├── video.jade │ ├── cool-layout.jade │ ├── invalid.jade │ ├── scope.jade │ ├── foo.bar │ ├── forum │ │ └── thread.jade │ ├── greeting.jade │ ├── layout.jade │ ├── person.jade │ ├── video.ejs │ ├── sub-templates │ │ └── item.jade │ ├── user │ │ └── role.ejs │ ├── movies.jade │ ├── pet-count.jade │ ├── user.ejs │ ├── dynamic-helpers.jade │ ├── items.jade │ ├── dynamic-helpers.ejs │ ├── movie.jade │ ├── pets.jade │ ├── greetings.jade │ ├── pet-land.jade │ ├── pet.jade │ ├── magic.jade │ └── large.json ├── utils.test.js └── request.test.js ├── index.js ├── examples ├── partials │ ├── views │ │ ├── ninjas │ │ │ ├── _weapon.jade │ │ │ ├── victim_name.jade │ │ │ ├── summary.jade │ │ │ └── show.jade │ │ └── layout.jade │ └── app.js ├── cache │ ├── views │ │ ├── user.ejs │ │ ├── users.ejs │ │ └── layout.ejs │ └── app.js ├── error-pages │ ├── views │ │ ├── 500.jade │ │ ├── 404.jade │ │ ├── layout.jade │ │ └── index.jade │ └── app.js ├── downloads │ ├── files │ │ └── amazing.txt │ └── app.js ├── ejs │ ├── views │ │ ├── users │ │ │ ├── user.ejs │ │ │ └── index.ejs │ │ └── layout.ejs │ └── app.js ├── jade │ ├── views │ │ ├── users │ │ │ ├── user.jade │ │ │ └── index.jade │ │ └── layout.jade │ ├── public │ │ └── stylesheets │ │ │ ├── style.sass │ │ │ └── style.css │ └── app.js ├── flash │ ├── views │ │ ├── layout.ejs │ │ ├── index.ejs │ │ └── messages.ejs │ └── app.js ├── mvc │ ├── views │ │ ├── 404.html │ │ ├── 500.html │ │ ├── messages.html │ │ ├── layout.html │ │ ├── user │ │ │ ├── index.html │ │ │ ├── show.html │ │ │ └── edit.html │ │ └── app │ │ │ └── index.html │ ├── controllers │ │ ├── app.js │ │ └── user.js │ ├── app.js │ ├── public │ │ └── style.css │ └── mvc.js ├── route-separation │ ├── views │ │ ├── users │ │ │ ├── view.ejs │ │ │ ├── index.ejs │ │ │ └── edit.ejs │ │ ├── index.ejs │ │ ├── posts │ │ │ └── index.ejs │ │ └── layout.ejs │ ├── site.js │ ├── post.js │ ├── public │ │ └── style.css │ ├── app.js │ └── user.js ├── github │ ├── views │ │ ├── repo.jade │ │ ├── layout.jade │ │ └── index.jade │ ├── public │ │ └── style.css │ └── app.js ├── helloworld │ └── app.js ├── auth │ ├── views │ │ ├── login.ejs │ │ └── layout.ejs │ └── app.js ├── error │ └── app.js ├── session │ └── app.js ├── cookies │ └── app.js ├── placeholders │ └── app.js ├── multipart │ └── app.js ├── format │ └── app.js ├── form │ └── app.js ├── resource │ └── app.js └── route-middleware │ └── app.js ├── .npmignore ├── docs ├── layout │ ├── foot.html │ └── head.html ├── images │ ├── bg.jpg │ ├── logo.png │ ├── top.png │ ├── bg.tile.jpg │ └── apps │ │ ├── nodeko.png │ │ ├── storify.png │ │ ├── markupio.png │ │ ├── scrabbly.png │ │ ├── clickdummy.png │ │ ├── e-resistable.png │ │ ├── learnboost.png │ │ ├── opowerjobs.png │ │ ├── widescript.png │ │ ├── developmentseed.png │ │ └── toptwittertrends.png ├── executable.md ├── executable.1 ├── screencasts.1 ├── contrib.md ├── index.md ├── contrib.1 ├── index.1 ├── applications.1 ├── applications.md ├── screencasts.md ├── migrate.md ├── executable.html ├── contrib.html ├── migrate.1 ├── index.html ├── screencasts.html └── applications.html ├── .gitignore ├── install.sh ├── .gitmodules ├── package.json ├── lib └── express │ ├── index.js │ ├── view │ ├── partial.js │ └── view.js │ ├── utils.js │ ├── view.js │ ├── request.js │ └── response.js ├── LICENSE ├── support └── toc.js ├── Makefile ├── Readme.md └── bin └── express /test/fixtures/hello.ejs: -------------------------------------------------------------------------------- 1 | Hello -------------------------------------------------------------------------------- /test/fixtures/hello.jade: -------------------------------------------------------------------------------- 1 | p :( -------------------------------------------------------------------------------- /test/fixtures/index.jade: -------------------------------------------------------------------------------- 1 | p Welcome -------------------------------------------------------------------------------- /test/fixtures/item.jade: -------------------------------------------------------------------------------- 1 | li= item -------------------------------------------------------------------------------- /test/fixtures/layouts/foo.jade: -------------------------------------------------------------------------------- 1 | foo -------------------------------------------------------------------------------- /test/fixtures/hello.haml: -------------------------------------------------------------------------------- 1 | %p Hello World -------------------------------------------------------------------------------- /test/fixtures/user.jade: -------------------------------------------------------------------------------- 1 | p= person.name -------------------------------------------------------------------------------- /test/fixtures/user.json: -------------------------------------------------------------------------------- 1 | {"name":"tj"} -------------------------------------------------------------------------------- /test/fixtures/video.jade: -------------------------------------------------------------------------------- 1 | p= director -------------------------------------------------------------------------------- /test/fixtures/cool-layout.jade: -------------------------------------------------------------------------------- 1 | cool!= body -------------------------------------------------------------------------------- /test/fixtures/invalid.jade: -------------------------------------------------------------------------------- 1 | p= doesNotExist -------------------------------------------------------------------------------- /test/fixtures/scope.jade: -------------------------------------------------------------------------------- 1 | p= this.method() -------------------------------------------------------------------------------- /test/fixtures/foo.bar: -------------------------------------------------------------------------------- 1 | p This is actually jade :) -------------------------------------------------------------------------------- /test/fixtures/forum/thread.jade: -------------------------------------------------------------------------------- 1 | h1 Forum Thread -------------------------------------------------------------------------------- /test/fixtures/greeting.jade: -------------------------------------------------------------------------------- 1 | p Welcome #{name} -------------------------------------------------------------------------------- /test/fixtures/layout.jade: -------------------------------------------------------------------------------- 1 | html 2 | body!= body -------------------------------------------------------------------------------- /test/fixtures/person.jade: -------------------------------------------------------------------------------- 1 | p #{label} #{this.name} -------------------------------------------------------------------------------- /test/fixtures/video.ejs: -------------------------------------------------------------------------------- 1 |

-------------------------------------------------------------------------------- /test/fixtures/sub-templates/item.jade: -------------------------------------------------------------------------------- 1 | li.item= item -------------------------------------------------------------------------------- /test/fixtures/user/role.ejs: -------------------------------------------------------------------------------- 1 |
  • Role: <%= role %>
  • -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require('./lib/express'); -------------------------------------------------------------------------------- /test/fixtures/movies.jade: -------------------------------------------------------------------------------- 1 | ul!= partial('movie', movies) -------------------------------------------------------------------------------- /examples/partials/views/ninjas/_weapon.jade: -------------------------------------------------------------------------------- 1 | li.weapon= weapon -------------------------------------------------------------------------------- /test/fixtures/pet-count.jade: -------------------------------------------------------------------------------- 1 | | We have #{pets.length} cool pets -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | docs/ 3 | examples/ 4 | support/ 5 | test/ 6 | -------------------------------------------------------------------------------- /docs/layout/foot.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/partials/views/ninjas/victim_name.jade: -------------------------------------------------------------------------------- 1 | li.victim= victimName -------------------------------------------------------------------------------- /test/fixtures/user.ejs: -------------------------------------------------------------------------------- 1 |

    {{= name }}

    2 |

    {{= email }}

    -------------------------------------------------------------------------------- /test/fixtures/dynamic-helpers.jade: -------------------------------------------------------------------------------- 1 | p #{greetings(session, lastName)} 2 | -------------------------------------------------------------------------------- /test/fixtures/items.jade: -------------------------------------------------------------------------------- 1 | ul!= partial('item.jade', { collection: items }) -------------------------------------------------------------------------------- /test/fixtures/dynamic-helpers.ejs: -------------------------------------------------------------------------------- 1 |

    <%= greetings(session, lastName) %>

    -------------------------------------------------------------------------------- /examples/cache/views/user.ejs: -------------------------------------------------------------------------------- 1 |
  • <%= user.name %> <<%= user.email %>>
  • -------------------------------------------------------------------------------- /examples/error-pages/views/500.jade: -------------------------------------------------------------------------------- 1 | h1 Error: #{error.message} 2 | pre= error.stack -------------------------------------------------------------------------------- /docs/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowboy/express/master/docs/images/bg.jpg -------------------------------------------------------------------------------- /examples/downloads/files/amazing.txt: -------------------------------------------------------------------------------- 1 | wow supes cool are you glad you downloaded this? -------------------------------------------------------------------------------- /examples/ejs/views/users/user.ejs: -------------------------------------------------------------------------------- 1 |
  • <%= user.name %> <<%= user.email %>>
  • -------------------------------------------------------------------------------- /examples/jade/views/users/user.jade: -------------------------------------------------------------------------------- 1 | .user 2 | h2= user.name 3 | .email= user.email -------------------------------------------------------------------------------- /test/fixtures/movie.jade: -------------------------------------------------------------------------------- 1 | li 2 | .title= movie.title 3 | .director= movie.director -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowboy/express/master/docs/images/logo.png -------------------------------------------------------------------------------- /docs/images/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowboy/express/master/docs/images/top.png -------------------------------------------------------------------------------- /examples/cache/views/users.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/pets.jade: -------------------------------------------------------------------------------- 1 | h1= site 2 | p!= partial('pet-count') 3 | ul!= partial('pet', pets) -------------------------------------------------------------------------------- /docs/images/bg.tile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowboy/express/master/docs/images/bg.tile.jpg -------------------------------------------------------------------------------- /examples/ejs/views/users/index.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/flash/views/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- body %> 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/greetings.jade: -------------------------------------------------------------------------------- 1 | h1= name 2 | != partial('greeting', { locals: { name: otherName }}) -------------------------------------------------------------------------------- /test/fixtures/pet-land.jade: -------------------------------------------------------------------------------- 1 | div!= partial('pet', { collection: pets, locals: { site: 'Animal land' }}) -------------------------------------------------------------------------------- /docs/images/apps/nodeko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowboy/express/master/docs/images/apps/nodeko.png -------------------------------------------------------------------------------- /docs/images/apps/storify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowboy/express/master/docs/images/apps/storify.png -------------------------------------------------------------------------------- /examples/jade/views/users/index.jade: -------------------------------------------------------------------------------- 1 | - if (users.length) 2 | h1 Users 3 | #users!= partial('user', users) -------------------------------------------------------------------------------- /examples/partials/views/layout.jade: -------------------------------------------------------------------------------- 1 | !!! 2 | html 3 | head 4 | title Partials Example 5 | body!= body -------------------------------------------------------------------------------- /docs/images/apps/markupio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowboy/express/master/docs/images/apps/markupio.png -------------------------------------------------------------------------------- /docs/images/apps/scrabbly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowboy/express/master/docs/images/apps/scrabbly.png -------------------------------------------------------------------------------- /test/fixtures/pet.jade: -------------------------------------------------------------------------------- 1 | - if (firstInCollection) 2 | li #{pet} is the coolest of #{site} 3 | - else 4 | li= pet -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | lib-cov 3 | *.seed 4 | *.log 5 | *.csv 6 | *.dat 7 | *.out 8 | *.pid 9 | benchmarks/graphs -------------------------------------------------------------------------------- /docs/images/apps/clickdummy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowboy/express/master/docs/images/apps/clickdummy.png -------------------------------------------------------------------------------- /docs/images/apps/e-resistable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowboy/express/master/docs/images/apps/e-resistable.png -------------------------------------------------------------------------------- /docs/images/apps/learnboost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowboy/express/master/docs/images/apps/learnboost.png -------------------------------------------------------------------------------- /docs/images/apps/opowerjobs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowboy/express/master/docs/images/apps/opowerjobs.png -------------------------------------------------------------------------------- /docs/images/apps/widescript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowboy/express/master/docs/images/apps/widescript.png -------------------------------------------------------------------------------- /examples/ejs/views/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Users

    4 | <%- body %> 5 | 6 | -------------------------------------------------------------------------------- /examples/flash/views/index.ejs: -------------------------------------------------------------------------------- 1 |

    Flash Message Example

    2 |

    on page <%- page %>

    3 | <%- messages() %> 4 | -------------------------------------------------------------------------------- /examples/cache/views/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Users

    4 | <%- body %> 5 | 6 | -------------------------------------------------------------------------------- /examples/error-pages/views/404.jade: -------------------------------------------------------------------------------- 1 | - if (error.path) 2 | h2 Cannot find #{error.path} 3 | - else 4 | h2 Page Not Found -------------------------------------------------------------------------------- /docs/images/apps/developmentseed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowboy/express/master/docs/images/apps/developmentseed.png -------------------------------------------------------------------------------- /docs/images/apps/toptwittertrends.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowboy/express/master/docs/images/apps/toptwittertrends.png -------------------------------------------------------------------------------- /examples/mvc/views/404.html: -------------------------------------------------------------------------------- 1 |

    Cannot find <%= request.url %>

    2 |

    Sorry we failed to locate this page or resource.

    -------------------------------------------------------------------------------- /examples/error-pages/views/layout.jade: -------------------------------------------------------------------------------- 1 | html 2 | head 3 | title Custom Pages Example 4 | body 5 | h1 My Site 6 | != body -------------------------------------------------------------------------------- /examples/partials/views/ninjas/summary.jade: -------------------------------------------------------------------------------- 1 | h2 Summary 2 | p= summary.email 3 | p= summary.description 4 | p taught by master #{summary.master} -------------------------------------------------------------------------------- /examples/route-separation/views/users/view.ejs: -------------------------------------------------------------------------------- 1 |

    <%= user.name %>

    2 |
    3 |

    Email: <%= user.email %>

    4 |
    -------------------------------------------------------------------------------- /examples/github/views/repo.jade: -------------------------------------------------------------------------------- 1 | tr.repo 2 | td.name 3 | a(href: repo.homepage || repo.url)= repo.name 4 | td.watchers 5 | = repo.watchers -------------------------------------------------------------------------------- /examples/jade/public/stylesheets/style.sass: -------------------------------------------------------------------------------- 1 | body 2 | :padding 50px 80px 3 | :font 14px "Helvetica Nueue", "Lucida Grande", Arial, sans-serif 4 | -------------------------------------------------------------------------------- /examples/route-separation/site.js: -------------------------------------------------------------------------------- 1 | 2 | exports.index = function(req, res){ 3 | res.render('index', { title: 'Route Separation Example' }); 4 | }; -------------------------------------------------------------------------------- /examples/jade/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px 80px; 3 | font: 14px "Helvetica Nueue", "Lucida Grande", Arial, sans-serif;} 4 | -------------------------------------------------------------------------------- /examples/mvc/controllers/app.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | 4 | // / 5 | 6 | index: function(req, res){ 7 | res.render(); 8 | } 9 | }; -------------------------------------------------------------------------------- /examples/jade/views/layout.jade: -------------------------------------------------------------------------------- 1 | !!! 2 | html 3 | head 4 | title Jade Example 5 | link(rel="stylesheet", href="/stylesheets/style.css") 6 | body!= body -------------------------------------------------------------------------------- /examples/error-pages/views/index.jade: -------------------------------------------------------------------------------- 1 | h2 Pages Example 2 | ul 3 | li 4 | | visit 5 | a(href="/500") 500 6 | li 7 | | visit 8 | a(href="/404") 404 -------------------------------------------------------------------------------- /examples/github/views/layout.jade: -------------------------------------------------------------------------------- 1 | !!! 2 | html 3 | head 4 | title Github Example 5 | link(rel="stylesheet", href="/style.css") 6 | body 7 | #container!= body -------------------------------------------------------------------------------- /examples/mvc/views/500.html: -------------------------------------------------------------------------------- 1 |

    Internal Server Error

    2 |

    Sorry an error has occurred, please try refreshing the page or navigate back to home.

    -------------------------------------------------------------------------------- /examples/route-separation/views/index.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/magic.jade: -------------------------------------------------------------------------------- 1 | - if (firstInCollection) 2 | li.first= word 3 | - else if (lastInCollection) 4 | li.last= word 5 | - else 6 | li(class: 'word-' + indexInCollection)= word -------------------------------------------------------------------------------- /examples/mvc/views/messages.html: -------------------------------------------------------------------------------- 1 | <% if (hasMessages) { %> 2 | 7 | <% } %> -------------------------------------------------------------------------------- /examples/route-separation/views/posts/index.ejs: -------------------------------------------------------------------------------- 1 |

    Posts

    2 |
    3 | <% posts.forEach(function(post){ %> 4 |
    <%= post.title %>
    5 |
    <%= post.body %>
    6 | <% }) %> 7 |
    -------------------------------------------------------------------------------- /examples/mvc/views/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Express - MVC Example 4 | 5 | 6 | 7 | <%- body %> 8 | 9 | -------------------------------------------------------------------------------- /examples/route-separation/views/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= title %> 4 | 5 | 6 | 7 | <%- body %> 8 | 9 | -------------------------------------------------------------------------------- /examples/mvc/views/user/index.html: -------------------------------------------------------------------------------- 1 |

    Users

    2 | <%- partial('../messages') %> 3 | -------------------------------------------------------------------------------- /examples/mvc/views/user/show.html: -------------------------------------------------------------------------------- 1 |

    Viewing user #<%= user.id %>

    2 | <%- partial('../messages') %> 3 | 8 | -------------------------------------------------------------------------------- /examples/helloworld/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('./../../lib/express'); 7 | 8 | var app = express.createServer(); 9 | 10 | app.get('/', function(req, res){ 11 | res.send('Hello World'); 12 | }); 13 | 14 | app.listen(3000); 15 | -------------------------------------------------------------------------------- /examples/route-separation/views/users/index.ejs: -------------------------------------------------------------------------------- 1 |

    Users

    2 | -------------------------------------------------------------------------------- /examples/flash/views/messages.ejs: -------------------------------------------------------------------------------- 1 | <% if (hasMessages) { %> 2 |
    3 | <% for (var type in types) { %> 4 |

    <%= type %> messages

    5 | 10 | <% } %> 11 |
    12 | <% } %> -------------------------------------------------------------------------------- /examples/github/views/index.jade: -------------------------------------------------------------------------------- 1 | - each user in users 2 | .user 3 | h2= user.name 4 | p.summary 5 | | #{user.name} has 6 | | #{user.repos.length} repositories 7 | | with a total of #{user.totalWatchers} watchers. 8 | table#repos!= partial('repo', user.repos) -------------------------------------------------------------------------------- /examples/route-separation/post.js: -------------------------------------------------------------------------------- 1 | 2 | // Fake posts database 3 | 4 | var posts = [ 5 | { title: 'Foo', body: 'some foo bar' } 6 | , { title: 'Foo bar', body: 'more foo bar' } 7 | , { title: 'Foo bar baz', body: 'more foo bar baz' } 8 | ]; 9 | 10 | exports.list = function(req, res){ 11 | res.render('posts', { title: 'Posts', posts: posts }); 12 | }; 13 | -------------------------------------------------------------------------------- /examples/mvc/app.js: -------------------------------------------------------------------------------- 1 | 2 | // Expose modules in ./support for demo purposes 3 | require.paths.unshift(__dirname + '/../../support'); 4 | 5 | /** 6 | * Module dependencies. 7 | */ 8 | 9 | var express = require('../../lib/express'); 10 | 11 | var app = express.createServer(); 12 | 13 | require('./mvc').boot(app); 14 | 15 | app.listen(3000); 16 | console.log('Express app started on port 3000'); -------------------------------------------------------------------------------- /examples/github/public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 30px 50px; 3 | font: 12px/1.4 "Helvetica Neue", Arial, sans-serif; 4 | } 5 | a { 6 | color: #00AAFF; 7 | text-decoration: none; 8 | } 9 | a:hover { 10 | text-decoration: underline; 11 | } 12 | .user { 13 | margin: 0 10px; 14 | float: left; 15 | width: 20%; 16 | } 17 | table td:nth-child(2) { 18 | padding: 0 5px; 19 | } -------------------------------------------------------------------------------- /examples/route-separation/views/users/edit.ejs: -------------------------------------------------------------------------------- 1 |

    Editing <%= user.name %>

    2 |
    3 |
    4 | 5 |

    Name:

    6 |

    Email:

    7 |

    8 |
    9 |
    -------------------------------------------------------------------------------- /examples/route-separation/public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Helvetica Neue", Arial, sans-serif; 4 | } 5 | a { 6 | color: #00AEFF; 7 | text-decoration: none; 8 | } 9 | a.edit { 10 | color: #000; 11 | opacity: .3; 12 | } 13 | a.edit::before { 14 | content: '[ '; 15 | } 16 | a.edit::after { 17 | content: ' ]'; 18 | } 19 | dt { 20 | font-weight: bold; 21 | } 22 | dd { 23 | margin: 15px; 24 | } -------------------------------------------------------------------------------- /examples/auth/views/login.ejs: -------------------------------------------------------------------------------- 1 |

    Login

    2 | <%- message %> 3 | Try accessing /restricted. 4 |
    5 |

    6 | 7 | 8 |

    9 |

    10 | 11 | 12 |

    13 |

    14 | 15 |

    16 |
    -------------------------------------------------------------------------------- /examples/mvc/views/user/edit.html: -------------------------------------------------------------------------------- 1 |

    Editing user <%= user.name %>

    2 | <%- partial('../messages') %> 3 |
    4 | 5 |

    Name:

    6 |

    Email:

    7 |

    8 |
    -------------------------------------------------------------------------------- /examples/auth/views/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Authentication Example 5 | 17 | 18 | 19 | <%- body %> 20 | 21 | -------------------------------------------------------------------------------- /examples/mvc/views/app/index.html: -------------------------------------------------------------------------------- 1 |

    Application Index

    2 |

    Try visiting one of the following urls:

    3 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | 2 | install() { 3 | mkdir -p /tmp/$2 \ 4 | && cd /tmp/$2 \ 5 | && echo "... installing $2" \ 6 | && curl -# -L "http://github.com/$1/$2/tarball/master" \ 7 | | tar xz --strip 1 \ 8 | && mkdir -p ~/.node_libraries \ 9 | && cp -fr lib/$2 ~/.node_libraries/$2 10 | } 11 | 12 | install visionmedia express \ 13 | && install senchalabs connect \ 14 | && cp -f /tmp/express/bin/express /usr/local/bin/express \ 15 | && echo "... installation complete" -------------------------------------------------------------------------------- /examples/mvc/public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 30px; 3 | font: 12px/1.4 "Helvetica Nueue", "Lucida Grande", Arial, sans-serif; 4 | } 5 | a { 6 | color: #00aaff; 7 | text-decoration: none; 8 | } 9 | a:hover { 10 | text-decoration: underline; 11 | } 12 | #messages { 13 | width: 95%; 14 | margin-bottom: 15px; 15 | padding: 10px; 16 | border: 1px solid #00DD00; 17 | -webkit-box-shadow: 1px 1px 4px rgba(0,0,0,0.2); 18 | } 19 | #messages li { 20 | list-style: none; 21 | } 22 | #messages em { 23 | font-weight: bold; 24 | } -------------------------------------------------------------------------------- /docs/executable.md: -------------------------------------------------------------------------------- 1 | 2 | ## Synopsis 3 | 4 | express [-h|--help] [-v|--version] [-c|-css ENGINE] [PATH] 5 | 6 | ## Description 7 | 8 | The `express` executable generates apps at the given **PATH** or the 9 | current working directory. Although Express is not bound to a specific 10 | application structure, this executable creates a maintainable base app. 11 | 12 | ## Options 13 | 14 | -s, --sessions Add session support 15 | -c, --css ENGINE Add css ENGINE support (less|sass). Defaults to plain css 16 | -v, --version Output framework version 17 | -h, --help Display help information 18 | 19 | -------------------------------------------------------------------------------- /examples/partials/views/ninjas/show.jade: -------------------------------------------------------------------------------- 1 | h1= ninja.name 2 | 3 | // file, partial name, and partial object all match ('summary') 4 | // the partial filename prefix '_' is completely optional 5 | #summary!= partial('summary', ninja.summary) 6 | 7 | // file, partial name = '_weapon', resolves to 'weapon' object within partial 8 | #weapons 9 | h2 Weapons 10 | // the weapon partial is rendered once per item in 11 | // the weapons array or "collection" 12 | ul!= partial('weapon', ninja.weapons) 13 | 14 | // partial name 'victim_name' resolves to '_victim_name.jade' file and 'victimName' object 15 | #victims 16 | h2 Victims 17 | ul!= partial('victim_name', ninja.victims) -------------------------------------------------------------------------------- /examples/ejs/app.js: -------------------------------------------------------------------------------- 1 | 2 | // Expose modules in ./support for demo purposes 3 | require.paths.unshift(__dirname + '/../../support'); 4 | 5 | /** 6 | * Module dependencies. 7 | */ 8 | 9 | var express = require('./../../lib/express'); 10 | 11 | var app = express.createServer(); 12 | 13 | // Optional since express defaults to CWD/views 14 | 15 | app.set('views', __dirname + '/views'); 16 | 17 | // Dummy users 18 | var users = [ 19 | { name: 'tj', email: 'tj@sencha.com' } 20 | , { name: 'ciaran', email: 'ciaranj@gmail.com' } 21 | , { name: 'aaron', email: 'aaron.heckmann+github@gmail.com' } 22 | ]; 23 | 24 | app.get('/', function(req, res){ 25 | res.render('users.ejs', { users: users }); 26 | }); 27 | 28 | app.listen(3000); 29 | console.log('Express app started on port 3000'); -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "support/expresso"] 2 | path = support/expresso 3 | url = git://github.com/visionmedia/expresso.git 4 | [submodule "support/haml"] 5 | path = support/haml 6 | url = git://github.com/visionmedia/haml.js.git 7 | [submodule "support/ejs"] 8 | path = support/ejs 9 | url = git://github.com/visionmedia/ejs.git 10 | [submodule "support/connect-form"] 11 | path = support/connect-form 12 | url = git://github.com/visionmedia/connect-form.git 13 | [submodule "support/connect"] 14 | path = support/connect 15 | url = git://github.com/senchalabs/connect.git 16 | [submodule "support/jade"] 17 | path = support/jade 18 | url = git://github.com/visionmedia/jade.git 19 | [submodule "support/should"] 20 | path = support/should 21 | url = git://github.com/visionmedia/should.js.git 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express", 3 | "description": "Sinatra inspired web development framework", 4 | "version": "1.0.0", 5 | "author": "TJ Holowaychuk ", 6 | "contributors": [ 7 | { "name": "TJ Holowaychuk", "email": "tj@vision-media.ca" }, 8 | { "name": "Aaron Heckmann", "email": "aaron.heckmann+github@gmail.com" }, 9 | { "name": "Ciaran Jessup", "email": "ciaranj@gmail.com" }, 10 | { "name": "Guillermo Rauch", "email": "rauchg@gmail.com" } 11 | ], 12 | "dependencies": { "connect": ">= 0.5.2" }, 13 | "keywords": ["framework", "sinatra", "web", "rest", "restful"], 14 | "directories": { "lib": "./lib/express" }, 15 | "scripts": { "test": "make test" }, 16 | "bin": { "express": "./bin/express" }, 17 | "engines": { "node": ">= 0.2.0" } 18 | } -------------------------------------------------------------------------------- /examples/error/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('./../../lib/express'); 7 | 8 | var app = express.createServer(); 9 | 10 | app.get('/', function(req, res){ 11 | // Caught and passed down to the errorHandler middleware 12 | throw new Error('something broke!'); 13 | }); 14 | 15 | app.get('/next', function(req, res, next){ 16 | // We can also pass exceptions to next() 17 | next(new Error('oh no!')) 18 | }); 19 | 20 | // The errorHandler middleware in this case will dump exceptions to stderr 21 | // as well as show the stack trace in responses, currently handles text/plain, 22 | // text/html, and application/json responses to aid in development 23 | app.use('/', express.errorHandler({ dumpExceptions: true, showStack: true })); 24 | 25 | app.listen(3000); -------------------------------------------------------------------------------- /examples/session/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('../../lib/express'); 7 | 8 | var app = express.createServer( 9 | express.logger(), 10 | 11 | // Required by session() middleware 12 | express.cookieDecoder(), 13 | 14 | // Populates: 15 | // - req.session 16 | // - req.sessionStore 17 | // - req.sessionID 18 | express.session() 19 | ); 20 | 21 | app.get('/', function(req, res){ 22 | var body = ''; 23 | if (req.session.views) { 24 | ++req.session.views; 25 | } else { 26 | req.session.views = 1; 27 | body += '

    First time visiting? view this page in several browsers :)

    '; 28 | } 29 | res.send(body + '

    viewed ' + req.session.views + ' times.

    '); 30 | }); 31 | 32 | app.listen(3000); 33 | console.log('Express app started on port 3000'); -------------------------------------------------------------------------------- /docs/executable.1: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn/v0.7.3 2 | .\" http://github.com/rtomayko/ronn/tree/0.7.3 3 | . 4 | .TH "EXECUTABLE" "" "November 2010" "" "" 5 | . 6 | .SH "NAME" 7 | \fBexecutable\fR 8 | . 9 | .SH "Synopsis" 10 | . 11 | .nf 12 | 13 | express [\-h|\-\-help] [\-v|\-\-version] [\-c|\-css ENGINE] [PATH] 14 | . 15 | .fi 16 | . 17 | .SH "Description" 18 | 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\. 19 | . 20 | .SH "Options" 21 | . 22 | .nf 23 | 24 | \-s, \-\-sessions Add session support 25 | \-c, \-\-css ENGINE Add css ENGINE support (less|sass)\. Defaults to plain css 26 | \-v, \-\-version Output framework version 27 | \-h, \-\-help Display help information 28 | . 29 | .fi 30 | 31 | -------------------------------------------------------------------------------- /examples/downloads/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('./../../lib/express'); 7 | 8 | var app = express.createServer(); 9 | 10 | app.get('/', function(req, res){ 11 | res.send(''); 15 | }); 16 | 17 | // /files/* is accessed via req.params[0] 18 | // but here we name it :file 19 | app.get('/files/:file(*)', function(req, res){ 20 | var file = req.params.file; 21 | res.download(__dirname + '/files/' + file); 22 | }); 23 | 24 | app.error(function(err, req, res, next){ 25 | if (process.ENOENT == err.errno) { 26 | res.send('Cant find that file, sorry!'); 27 | } else { 28 | // Not a 404 29 | next(err); 30 | } 31 | }); 32 | 33 | app.listen(3000); 34 | console.log('Express started on port 3000'); -------------------------------------------------------------------------------- /docs/screencasts.1: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn/v0.7.3 2 | .\" http://github.com/rtomayko/ronn/tree/0.7.3 3 | . 4 | .TH "SCREENCASTS" "" "December 2010" "" "" 5 | . 6 | .SH "NAME" 7 | \fBscreencasts\fR 8 | . 9 | .SS "Introduction" 10 | This introduction screencast covers the basics of Express, and how to get started with your first application\. 11 | . 12 | .P 13 | . 14 | .SS "View Partials" 15 | 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\. 16 | . 17 | .P 18 | . 19 | .SS "Route Specific Middleware" 20 | In the screencast below we learn about the benefits of route\-specific middleware\. 21 | . 22 | .P 23 | . 24 | .SS "Route Placeholder Preconditions" 25 | Learn about route placeholder (\fI/user/:id\fR) pre\-conditions, allowing validation, and loading of data via the named route placeholder segments\. 26 | . 27 | .P 28 | 29 | -------------------------------------------------------------------------------- /examples/partials/app.js: -------------------------------------------------------------------------------- 1 | // Expose modules in ./support for demo purposes 2 | require.paths.unshift(__dirname + '/../../support'); 3 | 4 | /** 5 | * Module dependencies. 6 | */ 7 | 8 | var express = require('../../lib/express'); 9 | 10 | var app = express.createServer(); 11 | 12 | // Optional since express defaults to CWD/views 13 | 14 | app.set('views', __dirname + '/views'); 15 | 16 | // Set our default template engine to "jade" 17 | // which prevents the need for extensions 18 | // (although you can still mix and match) 19 | app.set('view engine', 'jade'); 20 | 21 | // Dummy record 22 | var ninja = { 23 | name: 'leonardo', 24 | summary: { email: 'hunter.loftis+github@gmail.com', master: 'splinter', description: 'peaceful leader' }, 25 | weapons: ['katana', 'fists', 'shell'], 26 | victims: ['shredder', 'brain', 'beebop', 'rocksteady'] 27 | }; 28 | 29 | app.get('/', function(req, res){ 30 | res.render('ninjas/show', { ninja: ninja }); 31 | }); 32 | 33 | app.listen(3000); 34 | console.log('Express app started on port 3000'); 35 | -------------------------------------------------------------------------------- /examples/route-separation/app.js: -------------------------------------------------------------------------------- 1 | 2 | // Expose modules in ./support for demo purposes 3 | require.paths.unshift(__dirname + '/../../support'); 4 | 5 | /** 6 | * Module dependencies. 7 | */ 8 | 9 | var express = require('../../lib/express') 10 | , app = express.createServer() 11 | , site = require('./site') 12 | , post = require('./post') 13 | , user = require('./user'); 14 | 15 | // Config 16 | 17 | app.set('view engine', 'ejs'); 18 | app.set('views', __dirname + '/views'); 19 | app.use(express.bodyDecoder()); 20 | app.use(express.methodOverride()); 21 | app.use(express.staticProvider(__dirname + '/public')); 22 | 23 | // General 24 | 25 | app.get('/', site.index); 26 | 27 | // User 28 | 29 | app.all('/users', user.list); 30 | app.all('/user/:id/:op?', user.load); 31 | app.get('/user/:id', user.view); 32 | app.get('/user/:id/view', user.view); 33 | app.get('/user/:id/edit', user.edit); 34 | app.put('/user/:id/edit', user.update); 35 | 36 | // Posts 37 | 38 | app.get('/posts', post.list); 39 | 40 | app.listen(3000); 41 | console.log('Express app started on port 3000'); -------------------------------------------------------------------------------- /examples/route-separation/user.js: -------------------------------------------------------------------------------- 1 | 2 | // Fake user database 3 | 4 | var users = [ 5 | { name: 'TJ', email: 'tj@vision-media.ca' } 6 | , { name: 'Tobi', email: 'tobi@vision-media.ca' } 7 | ]; 8 | 9 | exports.list = function(req, res){ 10 | res.render('users', { title: 'Users', users: users }); 11 | }; 12 | 13 | exports.load = function(req, res, next){ 14 | var id = req.params.id; 15 | req.user = users[id]; 16 | if (req.user) { 17 | next(); 18 | } else { 19 | next(new Error('cannot find user ' + id)); 20 | } 21 | }; 22 | 23 | exports.view = function(req, res){ 24 | res.render('users/view', { 25 | title: 'Viewing user ' + req.user.name 26 | , user: req.user 27 | }); 28 | }; 29 | 30 | exports.edit = function(req, res){ 31 | res.render('users/edit', { 32 | title: 'Editing user ' + req.user.name 33 | , user: req.user 34 | }); 35 | }; 36 | 37 | exports.update = function(req, res){ 38 | // Normally you would handle all kinds of 39 | // validation and save back to the db 40 | var user = req.body.user; 41 | req.user.name = user.name; 42 | req.user.email = user.email; 43 | res.redirect('back'); 44 | }; -------------------------------------------------------------------------------- /lib/express/index.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Express 4 | * Copyright(c) 2010 TJ Holowaychuk 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Re-export connect auto-loaders. 10 | * 11 | * This prevents the need to `require('connect')` in order 12 | * to access core middleware, so for example `express.logger()` instead 13 | * of `require('connect').logger()`. 14 | */ 15 | 16 | var exports = module.exports = require('connect').middleware; 17 | 18 | /** 19 | * Framework version. 20 | */ 21 | 22 | exports.version = '2.0.0-pre'; 23 | 24 | /** 25 | * Module dependencies. 26 | */ 27 | 28 | var Server = exports.Server = require('./server'); 29 | 30 | /** 31 | * Shortcut for `new Server(...)`. 32 | * 33 | * @param {Function} ... 34 | * @return {Server} 35 | * @api public 36 | */ 37 | 38 | exports.createServer = function(){ 39 | return new Server(Array.prototype.slice.call(arguments)); 40 | }; 41 | 42 | /** 43 | * View extensions. 44 | */ 45 | 46 | require('./view'); 47 | 48 | /** 49 | * Response extensions. 50 | */ 51 | 52 | require('./response'); 53 | 54 | /** 55 | * Request extensions. 56 | */ 57 | 58 | require('./request'); 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2009-2010 TJ Holowaychuk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /docs/contrib.md: -------------------------------------------------------------------------------- 1 | 2 | ### Development Dependencies 3 | 4 | Express development dependencies are stored within the _./support_ directory. To 5 | update them execute: 6 | 7 | $ git submodule update --init 8 | 9 | ### Running Tests 10 | 11 | Express uses the [Expresso](http://github.com/visionmedia/expresso) TDD 12 | framework to write and run elegant test suites extremely fast. To run all test suites 13 | simply execute: 14 | 15 | $ make test 16 | 17 | To target specific suites we may specify the files via: 18 | 19 | $ make test TESTS=test/view.test.js 20 | 21 | To check test coverage run: 22 | 23 | $ make test-cov 24 | 25 | ### Contributions 26 | 27 | To accept a contribution, you should follow these guidelines: 28 | 29 | * All tests _must_ pass 30 | * Your alterations or additions _must_ include tests 31 | * Your commit(s) should be _focused_, do not commit once for several changes 32 | * Do _not_ alter release information such as the _version_, or _History.md_ 33 | * Indents are _2_ spaces. 34 | 35 | ### Documentation 36 | 37 | To contribute documentation edit the markdown files in _./docs_, however 38 | do _not_ run _make docs_, as they will be re-built and published with each release. -------------------------------------------------------------------------------- /examples/jade/app.js: -------------------------------------------------------------------------------- 1 | 2 | // Expose modules in ./support for demo purposes 3 | require.paths.unshift(__dirname + '/../../support'); 4 | 5 | /** 6 | * Module dependencies. 7 | */ 8 | 9 | var express = require('../../lib/express'); 10 | 11 | // Path to our public directory 12 | 13 | var pub = __dirname + '/public'; 14 | 15 | // Auto-compile sass to css with "compiler" 16 | // and then serve with connect's staticProvider 17 | 18 | var app = express.createServer( 19 | express.compiler({ src: pub, enable: ['sass'] }), 20 | express.staticProvider(pub) 21 | ); 22 | 23 | // Optional since express defaults to CWD/views 24 | 25 | app.set('views', __dirname + '/views'); 26 | 27 | // Set our default template engine to "jade" 28 | // which prevents the need for extensions 29 | // (although you can still mix and match) 30 | app.set('view engine', 'jade'); 31 | 32 | // Dummy users 33 | var users = [ 34 | { name: 'tj', email: 'tj@vision-media.ca' } 35 | , { name: 'ciaran', email: 'ciaranj@gmail.com' } 36 | , { name: 'aaron', email: 'aaron.heckmann+github@gmail.com' } 37 | ]; 38 | 39 | app.get('/', function(req, res){ 40 | res.render('users', { users: users }); 41 | }); 42 | 43 | app.listen(3000); 44 | console.log('Express app started on port 3000'); 45 | -------------------------------------------------------------------------------- /examples/mvc/controllers/user.js: -------------------------------------------------------------------------------- 1 | 2 | // Fake user database for example 3 | 4 | var users = [ 5 | { id: 0, name: 'TJ', email: 'tj@vision-media.ca' } 6 | , { id: 1, name: 'Simon', email: 'simon@vision-media.ca' } 7 | ]; 8 | 9 | function get(id, fn) { 10 | if (users[id]) { 11 | fn(null, users[id]); 12 | } else { 13 | fn(new Error('User ' + id + ' does not exist')); 14 | } 15 | } 16 | 17 | module.exports = { 18 | 19 | // /users 20 | 21 | index: function(req, res){ 22 | res.render(users); 23 | }, 24 | 25 | // /users/:id 26 | 27 | show: function(req, res, next){ 28 | get(req.params.id, function(err, user){ 29 | if (err) return next(err); 30 | res.render(user); 31 | }); 32 | }, 33 | 34 | // /users/:id/edit 35 | 36 | edit: function(req, res, next){ 37 | get(req.params.id, function(err, user){ 38 | if (err) return next(err); 39 | res.render(user); 40 | }); 41 | }, 42 | 43 | // PUT /users/:id 44 | 45 | update: function(req, res, next){ 46 | var id = req.params.id; 47 | get(id, function(err){ 48 | if (err) return next(err); 49 | var user = users[id] = req.body.user; 50 | user.id = id; 51 | req.flash('info', 'Successfully updated _' + user.name + '_.'); 52 | res.redirect('back'); 53 | }); 54 | } 55 | }; -------------------------------------------------------------------------------- /examples/cookies/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('../../lib/express'); 7 | 8 | var app = express.createServer( 9 | // Place default Connect favicon above logger so it is not in 10 | // the logging output 11 | express.favicon(), 12 | 13 | // Custom logger format 14 | express.logger({ format: '\x1b[1m:method\x1b[0m \x1b[33m:url\x1b[0m :response-time' }), 15 | 16 | // Provides req.cookies 17 | express.cookieDecoder(), 18 | 19 | // Parses x-www-form-urlencoded request bodies (and json) 20 | express.bodyDecoder() 21 | ); 22 | 23 | app.get('/', function(req, res){ 24 | if (req.cookies.remember) { 25 | res.send('Remembered :). Click to forget!.'); 26 | } else { 27 | res.send('

    Check to ' 29 | + '.

    '); 30 | } 31 | }); 32 | 33 | app.get('/forget', function(req, res){ 34 | res.clearCookie('remember'); 35 | res.redirect('back'); 36 | }); 37 | 38 | app.post('/', function(req, res){ 39 | if (req.body.remember) { 40 | res.cookie('remember', '1', { path: '/', expires: new Date(Date.now() + 900000), httpOnly: true }); 41 | } 42 | res.redirect('back'); 43 | }); 44 | 45 | app.listen(3000); 46 | console.log('Express started on port 3000'); -------------------------------------------------------------------------------- /test/utils.test.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var utils = require('express/utils') 7 | , should = require('should'); 8 | 9 | module.exports = { 10 | 'test .miniMarkdown()': function(){ 11 | utils.miniMarkdown('_foo_').should.equal('foo'); 12 | utils.miniMarkdown('*foo*').should.equal('foo'); 13 | utils.miniMarkdown('**foo**').should.equal('foo'); 14 | utils.miniMarkdown('__foo__').should.equal('foo'); 15 | utils.miniMarkdown('_foo__').should.equal('foo_'); 16 | utils.miniMarkdown('[User Login](/login)').should.equal('User Login'); 17 | }, 18 | 19 | 'test .parseRange()': function(){ 20 | utils.parseRange(1000, 'bytes=0-499').should.eql([{ start: 0, end: 499 }]); 21 | utils.parseRange(1000, 'bytes=40-80').should.eql([{ start: 40, end: 80 }]); 22 | utils.parseRange(1000, 'bytes=-500').should.eql([{ start: 500, end: 999 }]); 23 | utils.parseRange(1000, 'bytes=-400').should.eql([{ start: 600, end: 999 }]); 24 | utils.parseRange(1000, 'bytes=500-').should.eql([{ start: 500, end: 999 }]); 25 | utils.parseRange(1000, 'bytes=400-').should.eql([{ start: 400, end: 999 }]); 26 | utils.parseRange(1000, 'bytes=0-0').should.eql([{ start: 0, end: 0 }]); 27 | utils.parseRange(1000, 'bytes=-1').should.eql([{ start: 999, end: 999 }]); 28 | } 29 | }; -------------------------------------------------------------------------------- /examples/placeholders/app.js: -------------------------------------------------------------------------------- 1 | 2 | // Expose modules in ./support for demo purposes 3 | require.paths.unshift(__dirname + '/../../support'); 4 | 5 | /** 6 | * Module dependencies. 7 | */ 8 | 9 | var express = require('../../lib/express') 10 | , app = express.createServer(); 11 | 12 | // Faux database 13 | 14 | var users = [ 15 | { name: 'tj' } 16 | , { name: 'tobi' } 17 | , { name: 'loki' } 18 | , { name: 'jane' } 19 | , { name: 'bandit' } 20 | ]; 21 | 22 | // Convert :to and :from to integers 23 | 24 | app.param(['to', 'from'], function(n){ return parseInt(n, 10); }); 25 | 26 | // Load user by id 27 | 28 | app.param('user', function(req, res, next, id){ 29 | if (req.user = users[id]) { 30 | next(); 31 | } else { 32 | next(new Error('failed to find user')); 33 | } 34 | }); 35 | 36 | /** 37 | * GET index. 38 | */ 39 | 40 | app.get('/', function(req, res){ 41 | res.send('Visit /user/0 or /users/0-2'); 42 | }); 43 | 44 | /** 45 | * GET :user. 46 | */ 47 | 48 | app.get('/user/:user', function(req, res, next){ 49 | res.send('user ' + req.user.name); 50 | }); 51 | 52 | /** 53 | * GET users :from - :to. 54 | */ 55 | 56 | app.get('/users/:from-:to', function(req, res, next){ 57 | var from = req.params.from 58 | , to = req.params.to 59 | , names = users.map(function(user){ return user.name; }); 60 | res.send('users ' + names.slice(from, to).join(', ')); 61 | }); 62 | 63 | app.listen(3000); 64 | console.log('Express application listening on port 3000'); -------------------------------------------------------------------------------- /lib/express/view/partial.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Express - view - Partial 4 | * Copyright(c) 2010 TJ Holowaychuk 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | 12 | var View = require('./view') 13 | , basename = require('path').basename; 14 | 15 | /** 16 | * Memory cache. 17 | */ 18 | 19 | var cache = {}; 20 | 21 | /** 22 | * Initialize a new `Partial` for the given `view` and `options`. 23 | * 24 | * @param {String} view 25 | * @param {Object} options 26 | * @api private 27 | */ 28 | 29 | var Partial = exports = module.exports = function Partial(view, options) { 30 | options = options || {}; 31 | View.call(this, view, options); 32 | }; 33 | 34 | /** 35 | * Inherit from `View.prototype`. 36 | */ 37 | 38 | Partial.prototype.__proto__ = View.prototype; 39 | 40 | /** 41 | * Resolve partial object name from the view path. 42 | * 43 | * Examples: 44 | * 45 | * "user.ejs" becomes "user" 46 | * "forum thread.ejs" becomes "forumThread" 47 | * "forum/thread/post.ejs" becomes "post" 48 | * "blog-post.ejs" becomes "blogPost" 49 | * 50 | * @return {String} 51 | * @api private 52 | */ 53 | 54 | exports.resolveObjectName = function(view){ 55 | return cache[view] || (cache[view] = view 56 | .split('/') 57 | .slice(-1)[0] 58 | .split('.')[0] 59 | .replace(/^_/, '') 60 | .replace(/[^a-zA-Z0-9 ]+/g, ' ') 61 | .split(/ +/).map(function(word, i){ 62 | return i 63 | ? word[0].toUpperCase() + word.substr(1) 64 | : word; 65 | }).join('')); 66 | }; -------------------------------------------------------------------------------- /examples/multipart/app.js: -------------------------------------------------------------------------------- 1 | 2 | // Expose modules in ./support for demo purposes 3 | require.paths.unshift(__dirname + '/../../support'); 4 | 5 | /** 6 | * Module dependencies. 7 | */ 8 | 9 | var express = require('../../lib/express') 10 | , form = require('connect-form'); 11 | 12 | var app = express.createServer( 13 | // connect-form (http://github.com/visionmedia/connect-form) 14 | // middleware uses the formidable middleware to parse urlencoded 15 | // and multipart form data 16 | form({ keepExtensions: true }) 17 | ); 18 | 19 | app.get('/', function(req, res){ 20 | res.send('
    ' 21 | + '

    Image:

    ' 22 | + '

    ' 23 | + '
    '); 24 | }); 25 | 26 | app.post('/', function(req, res, next){ 27 | 28 | // connect-form adds the req.form object 29 | // we can (optionally) define onComplete, passing 30 | // the exception (if any) fields parsed, and files parsed 31 | req.form.complete(function(err, fields, files){ 32 | if (err) { 33 | next(err); 34 | } else { 35 | console.log('\nuploaded %s to %s' 36 | , files.image.filename 37 | , files.image.path); 38 | res.redirect('back'); 39 | } 40 | }); 41 | 42 | // We can add listeners for several form 43 | // events such as "progress" 44 | req.form.on('progress', function(bytesReceived, bytesExpected){ 45 | var percent = (bytesReceived / bytesExpected * 100) | 0; 46 | process.stdout.write('Uploading: %' + percent + '\r'); 47 | }); 48 | }); 49 | 50 | app.listen(3000); 51 | console.log('Express app started on port 3000'); -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | 2 | var app = express.createServer(); 3 | 4 | app.get('/', function(req, res){ 5 | res.send('Hello World'); 6 | }); 7 | 8 | app.listen(3000); 9 | 10 | ## Features 11 | 12 | * Robust routing 13 | * Redirection helpers 14 | * Dynamic view helpers 15 | * Application level view options 16 | * Content negotiation 17 | * Focus on high performance 18 | * View rendering and partials support 19 | * Environment based configuration 20 | * Session based flash notifications 21 | * Built on [Connect](http://github.com/senchalabs/connect) 22 | * [Executable](executable.html) for generating applications quickly 23 | * High test coverage 24 | 25 | ## Contributors 26 | 27 | The following are the major contributors of Express (in no specific order). 28 | 29 | * TJ Holowaychuk ([visionmedia](http://github.com/visionmedia)) 30 | * Ciaran Jessup ([ciaranj](http://github.com/ciaranj)) 31 | * Aaron Heckmann ([aheckmann](http://github.com/aheckmann)) 32 | * Guillermo Rauch ([guille](http://github.com/guille)) 33 | 34 | ## More Information 35 | 36 | * [Google Group](http://groups.google.com/group/express-js) for discussion 37 | * Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates 38 | * Annotated source [documentation](api.html) 39 | * View the [Connect](http://github.com/senchalabs/connect) repo for middleware usage 40 | * View the [Connect Wiki](http://wiki.github.com/senchalabs/connect/) for contrib middleware 41 | * View the [examples](http://github.com/visionmedia/express/tree/master/examples/) 42 | * View the [source](http://github.com/visionmedia/express) 43 | * View the [contrib guide](contrib.html) 44 | -------------------------------------------------------------------------------- /examples/format/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('../../lib/express'); 7 | 8 | var app = express.createServer(); 9 | 10 | // Fake items 11 | 12 | var items = [ 13 | { name: 'foo' } 14 | , { name: 'bar' } 15 | , { name: 'baz' } 16 | ]; 17 | 18 | // Routes 19 | 20 | app.get('/', function(req, res, next){ 21 | res.writeHead(200, { 'Content-Type': 'text/html' }); 22 | res.write('

    Visit /item/2

    '); 23 | res.write('

    Visit /item/2.json

    '); 24 | res.write('

    Visit /item/2.xml

    '); 25 | res.end(); 26 | }); 27 | 28 | app.get('/item/:id.:format?', function(req, res, next){ 29 | var id = req.params.id 30 | , format = req.params.format 31 | , item = items[id]; 32 | // Ensure item exists 33 | if (item) { 34 | // Serve the format 35 | switch (format) { 36 | case 'json': 37 | // Detects json 38 | res.send(item); 39 | break; 40 | case 'xml': 41 | // Set contentType as xml then sends 42 | // the string 43 | var xml = '' 44 | + '' 45 | + '' + item.name + '' 46 | + ''; 47 | res.contentType('.xml'); 48 | res.send(xml); 49 | break; 50 | case 'html': 51 | default: 52 | // By default send some hmtl 53 | res.send('

    ' + item.name + '

    '); 54 | } 55 | } else { 56 | // We could simply pass route control and potentially 404 57 | // by calling next(), or pass an exception like below. 58 | next(new Error('Item ' + id + ' does not exist')); 59 | } 60 | }); 61 | 62 | // Middleware 63 | 64 | app.use(express.errorHandler({ showStack: true })); 65 | 66 | app.listen(3000); 67 | console.log('Express app started on port 3000'); -------------------------------------------------------------------------------- /docs/contrib.1: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn/v0.7.3 2 | .\" http://github.com/rtomayko/ronn/tree/0.7.3 3 | . 4 | .TH "CONTRIB" "" "October 2010" "" "" 5 | . 6 | .SH "NAME" 7 | \fBcontrib\fR 8 | . 9 | .SS "Development Dependencies" 10 | Express development dependencies are stored within the \fI\./support\fR directory\. To update them execute: 11 | . 12 | .IP "" 4 13 | . 14 | .nf 15 | 16 | $ git submodule update \-\-init 17 | . 18 | .fi 19 | . 20 | .IP "" 0 21 | . 22 | .SS "Running Tests" 23 | Express uses the Expresso \fIhttp://github\.com/visionmedia/expresso\fR TDD framework to write and run elegant test suites extremely fast\. To run all test suites simply execute: 24 | . 25 | .IP "" 4 26 | . 27 | .nf 28 | 29 | $ make test 30 | . 31 | .fi 32 | . 33 | .IP "" 0 34 | . 35 | .P 36 | To target specific suites we may specify the files via: 37 | . 38 | .IP "" 4 39 | . 40 | .nf 41 | 42 | $ make test TESTS=test/view\.test\.js 43 | . 44 | .fi 45 | . 46 | .IP "" 0 47 | . 48 | .P 49 | To check test coverage run: 50 | . 51 | .IP "" 4 52 | . 53 | .nf 54 | 55 | $ make test\-cov 56 | . 57 | .fi 58 | . 59 | .IP "" 0 60 | . 61 | .SS "Contributions" 62 | To accept a contribution, you should follow these guidelines: 63 | . 64 | .IP "\(bu" 4 65 | All tests \fImust\fR pass 66 | . 67 | .IP "\(bu" 4 68 | Your alterations or additions \fImust\fR include tests 69 | . 70 | .IP "\(bu" 4 71 | Your commit(s) should be \fIfocused\fR, do not commit once for several changes 72 | . 73 | .IP "\(bu" 4 74 | Do \fInot\fR alter release information such as the \fIversion\fR, or \fIHistory\.md\fR 75 | . 76 | .IP "\(bu" 4 77 | Indents are \fI2\fR spaces\. 78 | . 79 | .IP "" 0 80 | . 81 | .SS "Documentation" 82 | To contribute documentation edit the markdown files in \fI\./docs\fR, however do \fInot\fR run \fImake docs\fR, as they will be re\-built and published with each release\. 83 | -------------------------------------------------------------------------------- /support/toc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'), 4 | file = process.argv[2]; 5 | 6 | if (file) { 7 | var js = fs.readFileSync(file, 'utf8'), 8 | headers = js.match(/

    (.*?)<\/h3>/g), 9 | toc = ['
      '], 10 | sections = {}; 11 | if (js.indexOf('id="toc"') < 0) { 12 | headers.forEach(function(header){ 13 | var captures = header.match(/id="(.*?)">(.*?)' + method + ''); 25 | } else { 26 | toc.push('
    • ' + title + '
    • '); 27 | } 28 | }); 29 | toc.push(renderSections()); 30 | toc.push('
    '); 31 | js = js.replace('
    ', '
    ' + toc.join('\n')); 32 | fs.writeFileSync(file, js); 33 | } 34 | } 35 | 36 | function renderSections() { 37 | var buf = []; 38 | Object.keys(sections).forEach(function(section){ 39 | var methods = sections[section], 40 | a = '+ ' + section + ''; 41 | buf.push('
  • ' + a + '
      '); 42 | buf.push(methods.join('\n')); 43 | buf.push('
  • '); 44 | }); 45 | return buf.join('\n'); 46 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | PREFIX ?= /usr/local 3 | LIB_PREFIX = ~/.node_libraries 4 | 5 | DOCS = docs/index.md \ 6 | docs/screencasts.md \ 7 | docs/executable.md \ 8 | docs/contrib.md \ 9 | docs/guide.md \ 10 | docs/migrate.md \ 11 | docs/applications.md 12 | 13 | MANPAGES =$(DOCS:.md=.1) 14 | HTMLDOCS =$(DOCS:.md=.html) 15 | 16 | install: install-docs 17 | @mkdir -p $(PREFIX)/bin 18 | @mkdir -p $(LIB_PREFIX) 19 | cp -f bin/express $(PREFIX)/bin/express 20 | cp -fr lib/express $(LIB_PREFIX)/express 21 | 22 | uninstall: uninstall-docs 23 | rm -f $(PREFIX)/bin/express 24 | rm -fr $(LIB_PREFIX)/express 25 | 26 | install-support: 27 | cd support/connect && $(MAKE) install 28 | 29 | uninstall-support: 30 | cd support/connect && $(MAKE) uninstall 31 | 32 | install-docs: 33 | @mkdir -p $(PREFIX)/share/man/man1 34 | cp -f docs/executable.1 $(PREFIX)/share/man/man1/express.1 35 | 36 | uninstall-docs: 37 | rm -f $(PREFIX)/share/man/man1/express.1 38 | 39 | test: 40 | @NODE_ENV=test ./support/expresso/bin/expresso \ 41 | -I lib \ 42 | -I support \ 43 | -I support/connect/lib \ 44 | -I support/haml/lib \ 45 | -I support/jade/lib \ 46 | -I support/ejs/lib \ 47 | $(TESTFLAGS) \ 48 | test/*.test.js 49 | 50 | test-cov: 51 | @TESTFLAGS=--cov $(MAKE) test 52 | 53 | docs: docs/api.html $(MANPAGES) $(HTMLDOCS) 54 | @ echo "... generating TOC" 55 | @./support/toc.js docs/guide.html 56 | 57 | docs/api.html: lib/express/*.js 58 | dox \ 59 | --private \ 60 | --title Express \ 61 | --desc "High performance web framework for [node](http://nodejs.org)." \ 62 | $(shell find lib/express/* -type f) > $@ 63 | 64 | %.1: %.md 65 | @echo "... $< -> $@" 66 | @ronn -r --pipe $< > $@ 67 | 68 | %.html: %.md 69 | @echo "... $< -> $@" 70 | @ronn -5 --pipe --fragment $< \ 71 | | cat docs/layout/head.html - docs/layout/foot.html \ 72 | | sed 's/NAME/Express/g' \ 73 | > $@ 74 | 75 | docclean: 76 | rm -f docs/*.{1,html} 77 | 78 | .PHONY: install uninstall install-docs install-support uninstall-support install-docs uninstall-docs test test-cov docs docclean -------------------------------------------------------------------------------- /examples/form/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('./../../lib/express'), 7 | sys = require('sys'); 8 | 9 | var app = express.createServer(); 10 | 11 | // Here we use the bodyDecoder middleware 12 | // to parse urlencoded request bodies 13 | // which populates req.body 14 | app.use(express.bodyDecoder()); 15 | 16 | // The methodOverride middleware allows us 17 | // to set a hidden input of _method to an arbitrary 18 | // HTTP method to support app.put(), app.del() etc 19 | app.use(express.methodOverride()); 20 | 21 | // Required by session 22 | app.use(express.cookieDecoder()); 23 | 24 | // Required by req.flash() for persistent 25 | // notifications 26 | app.use(express.session()); 27 | 28 | app.get('/', function(req, res){ 29 | // get ?name=foo 30 | var name = req.param('name') || ''; 31 | 32 | // Switch the button label based if we have a name 33 | var label = name ? 'Update' : 'Save'; 34 | 35 | // Buffer all flash messages. 36 | // Typically this would all be done in a template 37 | // however for illustration purposes we iterate 38 | // here. 39 | 40 | // The messages in req.flash() persist until called, 41 | // at which time they are flushed from the session 42 | var msgs = '
      ', 43 | flash = req.flash(); 44 | Object.keys(flash).forEach(function(type){ 45 | flash[type].forEach(function(msg){ 46 | msgs += '
    • ' + msg + '
    • '; 47 | }); 48 | }); 49 | msgs += '
    '; 50 | 51 | // If we have a name, we are updating, 52 | // so add the hidden _method input 53 | res.send(msgs 54 | + '
    ' 55 | + (name ? '' : '') 56 | + 'Name: ' 57 | + '' 58 | + '
    '); 59 | }); 60 | 61 | app.post('/', function(req, res){ 62 | if (req.body.name) { 63 | // Typically here we would create a resource 64 | req.flash('info', 'Saved ' + req.body.name); 65 | res.redirect('/?name=' + req.body.name); 66 | } else { 67 | req.flash('error', 'Error: name required'); 68 | res.redirect('/'); 69 | } 70 | }); 71 | 72 | app.put('/', function(req, res){ 73 | // Typically here we would update a resource 74 | req.flash('info', 'Updated ' + req.body.name); 75 | res.redirect('/?name=' + req.body.name); 76 | }); 77 | 78 | app.listen(3000); 79 | console.log('Express app started on port 3000'); -------------------------------------------------------------------------------- /examples/flash/app.js: -------------------------------------------------------------------------------- 1 | 2 | // Expose modules in ./support for demo purposes 3 | require.paths.unshift(__dirname + '/../../support'); 4 | 5 | /** 6 | * Module dependencies. 7 | */ 8 | 9 | var express = require('../../lib/express'); 10 | 11 | // App with session support 12 | 13 | var app = express.createServer( 14 | express.cookieDecoder() 15 | , express.session() 16 | ); 17 | 18 | // View settings 19 | 20 | app.set('views', __dirname + '/views'); 21 | app.set('view engine', 'ejs'); 22 | 23 | // Dynamic helpers are functions which are executed 24 | // on each view render, unless dynamicHelpers is false. 25 | 26 | // So for example we do not need to call messages() in our 27 | // template, "messages" will be populated with the return 28 | // value of this function. 29 | 30 | app.dynamicHelpers({ 31 | messages: function(req, res){ 32 | // In the case of flash messages 33 | // we return a function, allowing 34 | // flash messages to only be flushed 35 | // when called, otherwise every request 36 | // will flush flash messages regardless. 37 | return function(){ 38 | // Grab the flash messages 39 | var messages = req.flash(); 40 | // We will render the "messages.ejs" partial 41 | return res.partial('messages', { 42 | // Our target object is our messages 43 | object: messages, 44 | // We want it to be named "types" in the partial 45 | // since they are keyed like this: 46 | // { info: ['foo'], error: ['bar']} 47 | as: 'types', 48 | // Pass a local named "hasMessages" so we can easily 49 | // check if we have any messages at all 50 | locals: { hasMessages: Object.keys(messages).length }, 51 | // We dont want dynamicHelpers in this partial, as 52 | // it would cause infinite recursion 53 | dynamicHelpers: false 54 | }); 55 | } 56 | } 57 | }); 58 | 59 | app.dynamicHelpers({ 60 | // Another dynamic helper example. Since dynamic 61 | // helpers resolve at view rendering time, we can 62 | // "inject" the "page" local variable per request 63 | // providing us with the request url. 64 | page: function(req, res){ 65 | return req.url; 66 | } 67 | }); 68 | 69 | app.get('/', function(req, res){ 70 | // Not very realistic notifications but illustrates usage 71 | req.flash('info', 'email queued'); 72 | req.flash('info', 'email sent'); 73 | req.flash('error', 'delivery failed'); 74 | res.render('index'); 75 | }); 76 | 77 | app.listen(3000); 78 | console.log('Express app started on port 3000'); -------------------------------------------------------------------------------- /examples/resource/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('../../lib/express'); 7 | 8 | var app = express.createServer(); 9 | 10 | // Ad-hoc example resource method 11 | 12 | app.resource = function(path, obj) { 13 | this.get(path, obj.index); 14 | this.get(path + '/:a..:b.:format?', function(req, res){ 15 | var a = parseInt(req.params.a, 10) 16 | , b = parseInt(req.params.b, 10) 17 | , format = req.params.format; 18 | obj.range(req, res, a, b, format); 19 | }); 20 | this.get(path + '/:id', obj.show); 21 | this.del(path + '/:id', obj.destroy); 22 | }; 23 | 24 | // Fake records 25 | 26 | var users = [ 27 | { name: 'tj' } 28 | , { name: 'ciaran' } 29 | , { name: 'aaron' } 30 | , { name: 'guillermo' } 31 | , { name: 'simon' } 32 | , { name: 'tobi' } 33 | ]; 34 | 35 | // Fake controller. 36 | 37 | var User = { 38 | index: function(req, res){ 39 | res.send(users); 40 | }, 41 | show: function(req, res){ 42 | res.send(users[req.params.id] || { error: 'Cannot find user' }); 43 | }, 44 | destroy: function(req, res){ 45 | var id = req.params.id; 46 | var destroyed = id in users; 47 | delete users[id]; 48 | res.send(destroyed ? 'destroyed' : 'Cannot find user'); 49 | }, 50 | range: function(req, res, a, b, format){ 51 | var range = users.slice(a, b + 1); 52 | switch (format) { 53 | case 'json': 54 | res.send(range); 55 | break; 56 | case 'html': 57 | default: 58 | var html = '
      ' + range.map(function(user){ 59 | return '
    • ' + user.name + '
    • '; 60 | }).join('\n') + '
    '; 61 | res.send(html); 62 | break; 63 | } 64 | } 65 | }; 66 | 67 | // curl http://localhost:3000/users -- responds with all users 68 | // curl http://localhost:3000/users/1 -- responds with user 1 69 | // curl http://localhost:3000/users/4 -- responds with error 70 | // curl http://localhost:3000/users/1..3 -- responds with several users 71 | // curl -X DELETE http://localhost:3000/users/1 -- deletes the user 72 | 73 | app.resource('/users', User); 74 | 75 | app.get('/', function(req, res){ 76 | res.send([ 77 | '

    Examples:

      ' 78 | , '
    • GET /users
    • ' 79 | , '
    • GET /users/1
    • ' 80 | , '
    • GET /users/3
    • ' 81 | , '
    • GET /users/1..3
    • ' 82 | , '
    • GET /users/1..3.json
    • ' 83 | , '
    • DELETE /users/4
    • ' 84 | , '
    ' 85 | ].join('\n')); 86 | }); 87 | 88 | app.listen(3000); 89 | console.log('Express app started on port 3000'); -------------------------------------------------------------------------------- /docs/index.1: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn/v0.7.3 2 | .\" http://github.com/rtomayko/ronn/tree/0.7.3 3 | . 4 | .TH "INDEX" "" "December 2010" "" "" 5 | . 6 | .SH "NAME" 7 | \fBindex\fR 8 | . 9 | .IP "" 4 10 | . 11 | .nf 12 | 13 | var app = express\.createServer(); 14 | 15 | app\.get(\'/\', function(req, res){ 16 | res\.send(\'Hello World\'); 17 | }); 18 | 19 | app\.listen(3000); 20 | . 21 | .fi 22 | . 23 | .IP "" 0 24 | . 25 | .SH "Features" 26 | . 27 | .IP "\(bu" 4 28 | Robust routing 29 | . 30 | .IP "\(bu" 4 31 | Redirection helpers 32 | . 33 | .IP "\(bu" 4 34 | Dynamic view helpers 35 | . 36 | .IP "\(bu" 4 37 | Application level view options 38 | . 39 | .IP "\(bu" 4 40 | Content negotiation 41 | . 42 | .IP "\(bu" 4 43 | Focus on high performance 44 | . 45 | .IP "\(bu" 4 46 | View rendering and partials support 47 | . 48 | .IP "\(bu" 4 49 | Environment based configuration 50 | . 51 | .IP "\(bu" 4 52 | Session based flash notifications 53 | . 54 | .IP "\(bu" 4 55 | Built on Connect \fIhttp://github\.com/senchalabs/connect\fR 56 | . 57 | .IP "\(bu" 4 58 | Executable \fIexecutable\.html\fR for generating applications quickly 59 | . 60 | .IP "\(bu" 4 61 | High test coverage 62 | . 63 | .IP "" 0 64 | . 65 | .SH "Contributors" 66 | The following are the major contributors of Express (in no specific order)\. 67 | . 68 | .IP "\(bu" 4 69 | TJ Holowaychuk (visionmedia \fIhttp://github\.com/visionmedia\fR) 70 | . 71 | .IP "\(bu" 4 72 | Ciaran Jessup (ciaranj \fIhttp://github\.com/ciaranj\fR) 73 | . 74 | .IP "\(bu" 4 75 | Aaron Heckmann (aheckmann \fIhttp://github\.com/aheckmann\fR) 76 | . 77 | .IP "\(bu" 4 78 | Guillermo Rauch (guille \fIhttp://github\.com/guille\fR) 79 | . 80 | .IP "" 0 81 | . 82 | .SH "More Information" 83 | . 84 | .IP "\(bu" 4 85 | Google Group \fIhttp://groups\.google\.com/group/express\-js\fR for discussion 86 | . 87 | .IP "\(bu" 4 88 | Follow tjholowaychuk \fIhttp://twitter\.com/tjholowaychuk\fR on twitter for updates 89 | . 90 | .IP "\(bu" 4 91 | Annotated source documentation \fIapi\.html\fR 92 | . 93 | .IP "\(bu" 4 94 | View the Connect \fIhttp://github\.com/senchalabs/connect\fR repo for middleware usage 95 | . 96 | .IP "\(bu" 4 97 | View the Connect Wiki \fIhttp://wiki\.github\.com/senchalabs/connect/\fR for contrib middleware 98 | . 99 | .IP "\(bu" 4 100 | View the examples \fIhttp://github\.com/visionmedia/express/tree/master/examples/\fR 101 | . 102 | .IP "\(bu" 4 103 | View the source \fIhttp://github\.com/visionmedia/express\fR 104 | . 105 | .IP "\(bu" 4 106 | View the contrib guide \fIcontrib\.html\fR 107 | . 108 | .IP "" 0 109 | 110 | -------------------------------------------------------------------------------- /examples/cache/app.js: -------------------------------------------------------------------------------- 1 | 2 | // Expose modules in ./support for demo purposes 3 | require.paths.unshift(__dirname + '/../../support'); 4 | 5 | /** 6 | * Module dependencies. 7 | */ 8 | 9 | var express = require('./../../lib/express') 10 | , redis = require('redis') 11 | , _cache = redis.createClient() 12 | , app = express.createServer(); 13 | 14 | // $ npm install redis 15 | // if you do not have the redis client installed 16 | // and start redis via $ redis-server 17 | 18 | app.set('views', __dirname + '/views'); 19 | app.set('view engine', 'ejs'); 20 | 21 | // Generate 50 random users for display 22 | var n = 50; 23 | var users = []; 24 | while (n--) { 25 | users.push({ name: 'user ' + n, email: n + '@vision-media.ca' }); 26 | } 27 | 28 | // example cache middleware. we proxy writeHead etc to 29 | // cache the response, and we could perform additional 30 | // checks on headers etc to extend our caching logic. 31 | // 32 | // Please note in this example you would have to 33 | // handle write() encodings as well, this is just an example :) 34 | 35 | function cache(req, res, next){ 36 | _cache.get(req.url, function(err, data){ 37 | try { 38 | if (data) { 39 | data = JSON.parse(data.toString()); 40 | delete data.headers['X-Response-Time']; 41 | res.writeHead(data.status, data.headers); 42 | res.end(data.body); 43 | } else { 44 | proxyCache(req, res); 45 | next(); 46 | } 47 | } catch (err) { 48 | // Ignore 49 | next(); 50 | } 51 | }); 52 | } 53 | 54 | function proxyCache(req, res){ 55 | var writeHead = res.writeHead 56 | , write = res.write 57 | , end = res.end 58 | , ok = false 59 | , cache = { body: '' }; 60 | 61 | // Proxy writeHead 62 | res.writeHead = function(status, headers){ 63 | res.writeHead = writeHead; 64 | res.writeHead(status, headers); 65 | if (status >= 300) return ok = false; 66 | cache.status = status; 67 | cache.headers = headers; 68 | }; 69 | 70 | // Proxy write 71 | res.write = function(data){ 72 | res.write = write; 73 | cache.body += data; 74 | res.write(data); 75 | }; 76 | 77 | // Proxy write 78 | res.end = function(data){ 79 | res.end = end; 80 | cache.body += data; 81 | res.end(data); 82 | 83 | // Serialize / cache data 84 | _cache.set(req.url, JSON.stringify(cache)); 85 | }; 86 | } 87 | 88 | // Apply to any route(s) as route-specific middleware 89 | 90 | app.get('/', cache, function(req, res){ 91 | res.render('users', { users: users }); 92 | }); 93 | 94 | app.listen(3000); 95 | console.log('Express app started on port 3000'); -------------------------------------------------------------------------------- /docs/applications.1: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn/v0.7.3 2 | .\" http://github.com/rtomayko/ronn/tree/0.7.3 3 | . 4 | .TH "APPLICATIONS" "" "November 2010" "" "" 5 | . 6 | .SH "NAME" 7 | \fBapplications\fR 8 | . 9 | .P 10 | Learnboost \fIhttp://learnboost\.com\fR is a free online gradebook application, aimed to crush the competition with innovative, realtime, enjoyable features\. 11 | . 12 | .P 13 | \fIhttp://learnboost\.com\fR 14 | . 15 | .P 16 | Storify \fIhttp://storify\.com\fR lets you turn what people post on social media websites into compelling stories\. 17 | . 18 | .P 19 | \fIhttp://storify\.com\fR 20 | . 21 | .P 22 | Pakistan Survey \fIhttp://pakistansurvey\.org/\fR by Development Seed \fIhttp://developmentseed\.org\fR, provides in\-depth agency\-specific analysis from regional experts with data from 1,000 interviews across 120 villages in all seven tribal agencies and mapping of 142 reported drone strikes in FATA through July 2010\. 23 | . 24 | .P 25 | \fIhttp://pakistansurvey\.org\fR 26 | . 27 | .P 28 | Markup\.IO \fIhttp://markup\.io\fR allows you to draw directly on \fIany\fR website, then share with others to share your thoughts\. 29 | . 30 | .P 31 | \fIhttp://markup\.io\fR 32 | . 33 | .P 34 | Scrabb\.ly \fIhttp://scrabb\.ly\fR is a massively multiplayer scrabble game initially created for the Node Knockout \fIhttp://nodeknockout\.com/\fR competition\. 35 | . 36 | .P 37 | \fIhttp://scrabb\.ly\fR 38 | . 39 | .P 40 | ClickDummy \fIhttp://clickdummy\.net/\fR is a rapid mockup prototyping application for designers and dummies\. 41 | . 42 | .P 43 | \fIhttp://clickdummy\.net\fR 44 | . 45 | .P 46 | Node Knockout \fIhttp://nodeknockout\.com\fR organized the first ever node\-specific competition with hundreds of contestants\. 47 | . 48 | .P 49 | \fIhttp://nodeknockout\.com\fR 50 | . 51 | .P 52 | Widescript \fIhttp://widescript\.com\fR is an innovative app that helps you focus and interact with your texts \- on your desktop, your couch or on the go\. 53 | . 54 | .P 55 | \fIhttp://widescript\.com\fR 56 | . 57 | .P 58 | e\-resistable \fIhttp://www\.e\-resistible\.co\.uk/\fR is an online order takeaway system providing an intuitive way to fill your belly from your computer! 59 | . 60 | .P 61 | \fIhttp://www\.e\-resistible\.co\.uk\fR 62 | . 63 | .P 64 | Top Twitter Trends \fIhttp://toptwittertrends\.com\fR utilizes MongoDB, Socket\.IO, jQuery and many other exciting libraries to bring you trending tweets in realtime\. 65 | . 66 | .P 67 | \fIhttp://toptwittertrends\.com\fR 68 | . 69 | .P 70 | The applications shown above are not listed in any specific order\. To have an application added or removed please contact TJ Holowaychuk \fIhttp://github\.com/visionmedia\fR\. 71 | -------------------------------------------------------------------------------- /docs/applications.md: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 4 | [Learnboost](http://learnboost.com) is a free online gradebook application, aimed to crush the competition with innovative, realtime, enjoyable features. 5 | 6 | [![LearnBoost](images/apps/learnboost.png)](http://learnboost.com) 7 | 8 | [Storify](http://storify.com) lets you turn what people post on social media websites into compelling stories. 9 | 10 | [![Storify](images/apps/storify.png)](http://storify.com) 11 | 12 | 13 | [Pakistan Survey](http://pakistansurvey.org/) by [Development Seed](http://developmentseed.org), provides in-depth agency-specific analysis from regional experts with data from 1,000 interviews across 120 villages in all seven tribal agencies and mapping of 142 reported drone strikes in FATA through July 2010. 14 | 15 | [![Pakistan Survey](images/apps/developmentseed.png)](http://pakistansurvey.org) 16 | 17 | [Markup.IO](http://markup.io) allows you to draw directly on _any_ website, then share with others to share your thoughts. 18 | 19 | [![Markup.IO](images/apps/markupio.png)](http://markup.io) 20 | 21 | [Scrabb.ly](http://scrabb.ly) is a massively multiplayer scrabble game initially created for the [Node Knockout](http://nodeknockout.com/) competition. 22 | 23 | [![Online Realtime Scrabble](images/apps/scrabbly.png)](http://scrabb.ly) 24 | 25 | [ClickDummy](http://clickdummy.net/) is a rapid mockup prototyping application for designers and dummies. 26 | 27 | [![Mockup Prototying](images/apps/clickdummy.png)](http://clickdummy.net) 28 | 29 | [Node Knockout](http://nodeknockout.com) organized the first ever node-specific competition with hundreds of contestants. 30 | 31 | [![Node Knockout Competition Express](images/apps/nodeko.png)](http://nodeknockout.com) 32 | 33 | [Widescript](http://widescript.com) is an innovative app that helps you focus and interact with your texts - on your desktop, your couch or on the go. 34 | 35 | [![Widescript](images/apps/widescript.png)](http://widescript.com) 36 | 37 | [e-resistable](http://www.e-resistible.co.uk/) is an online order takeaway system providing an intuitive way to fill your belly from your computer! 38 | 39 | [![Online Takeaway](images/apps/e-resistable.png)](http://www.e-resistible.co.uk) 40 | 41 | [Top Twitter Trends](http://toptwittertrends.com) utilizes MongoDB, Socket.IO, jQuery and many other exciting libraries to bring you trending tweets in realtime. 42 | 43 | [![Twitter Trends](images/apps/toptwittertrends.png)](http://toptwittertrends.com) 44 | 45 |
    46 | 47 | The applications shown above are not listed in any specific order. To have an application added or removed please contact [TJ Holowaychuk](http://github.com/visionmedia). -------------------------------------------------------------------------------- /examples/route-middleware/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('../../lib/express'); 7 | 8 | var app = express.createServer(); 9 | 10 | // Example requests: 11 | // curl http://localhost:3000/user/0 12 | // curl http://localhost:3000/user/0/edit 13 | // curl http://localhost:3000/user/1 14 | // curl http://localhost:3000/user/1/edit (unauthorized since this is not you) 15 | // curl -X DELETE http://localhost:3000/user/0 (unauthorized since you are not an admin) 16 | 17 | // Dummy users 18 | var users = [ 19 | { id: 0, name: 'tj', email: 'tj@vision-media.ca', role: 'member' } 20 | , { id: 1, name: 'ciaran', email: 'ciaranj@gmail.com', role: 'member' } 21 | , { id: 2, name: 'aaron', email: 'aaron.heckmann+github@gmail.com', role: 'admin' } 22 | ]; 23 | 24 | function loadUser(req, res, next) { 25 | // You would fetch your user from the db 26 | var user = users[req.params.id]; 27 | if (user) { 28 | req.user = user; 29 | next(); 30 | } else { 31 | next(new Error('Failed to load user ' + req.params.id)); 32 | } 33 | } 34 | 35 | function andRestrictToSelf(req, res, next) { 36 | // If our authenticated user is the user we are viewing 37 | // then everything is fine :) 38 | if (req.authenticatedUser.id == req.user.id) { 39 | next(); 40 | } else { 41 | // You may want to implement specific exceptions 42 | // such as UnauthorizedError or similar so that you 43 | // can handle these in app.error() specifically 44 | // (view ./examples/pages for this) 45 | next(new Error('Unauthorized')); 46 | } 47 | } 48 | 49 | function andRestrictTo(role) { 50 | return function(req, res, next) { 51 | if (req.authenticatedUser.role == role) { 52 | next(); 53 | } else { 54 | next(new Error('Unauthorized')); 55 | } 56 | } 57 | } 58 | 59 | // Middleware for faux authentication 60 | // you would of course implement something real, 61 | // but this illustrates how an authenticated user 62 | // may interacte with middleware 63 | 64 | app.use(function(req, res, next){ 65 | req.authenticatedUser = users[0]; 66 | next(); 67 | }); 68 | 69 | app.get('/', function(req, res){ 70 | res.redirect('/user/0'); 71 | }); 72 | 73 | app.get('/user/:id', loadUser, function(req, res){ 74 | res.send('Viewing user ' + req.user.name); 75 | }); 76 | 77 | app.get('/user/:id/edit', loadUser, andRestrictToSelf, function(req, res){ 78 | res.send('Editing user ' + req.user.name); 79 | }); 80 | 81 | app.del('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){ 82 | res.send('Deleted user ' + req.user.name); 83 | }); 84 | 85 | app.listen(3000); 86 | console.log('Express app started on port 3000'); -------------------------------------------------------------------------------- /examples/error-pages/app.js: -------------------------------------------------------------------------------- 1 | 2 | // Expose modules in ./support for demo purposes 3 | require.paths.unshift(__dirname + '/../../support'); 4 | 5 | /** 6 | * Module dependencies. 7 | */ 8 | 9 | var express = require('./../../lib/express'); 10 | 11 | var app = express.createServer(); 12 | 13 | // Serve default connect favicon 14 | app.use(express.favicon()); 15 | 16 | // Logger is placed below favicon, so favicon.ico 17 | // requests will not be logged 18 | app.use(express.logger({ format: '":method :url" :status' })); 19 | 20 | // "app.router" positions our routes 21 | // specifically above the middleware 22 | // assigned below 23 | 24 | app.use(app.router); 25 | 26 | // When no more middleware require execution, aka 27 | // our router is finished and did not respond, we 28 | // can assume that it is "not found". Instead of 29 | // letting Connect deal with this, we define our 30 | // custom middleware here to simply pass a NotFound 31 | // exception 32 | 33 | app.use(function(req, res, next){ 34 | next(new NotFound(req.url)); 35 | }); 36 | 37 | app.set('views', __dirname + '/views'); 38 | 39 | // Provide our app with the notion of NotFound exceptions 40 | 41 | function NotFound(path){ 42 | this.name = 'NotFound'; 43 | if (path) { 44 | Error.call(this, 'Cannot find ' + path); 45 | this.path = path; 46 | } else { 47 | Error.call(this, 'Not Found'); 48 | } 49 | Error.captureStackTrace(this, arguments.callee); 50 | } 51 | 52 | /** 53 | * Inherit from `Error.prototype`. 54 | */ 55 | 56 | NotFound.prototype.__proto__ = Error.prototype; 57 | 58 | // We can call app.error() several times as shown below. 59 | // Here we check for an instanceof NotFound and show the 60 | // 404 page, or we pass on to the next error handler. 61 | 62 | // These handlers could potentially be defined within 63 | // configure() blocks to provide introspection when 64 | // in the development environment. 65 | 66 | app.error(function(err, req, res, next){ 67 | if (err instanceof NotFound) { 68 | res.render('404.jade', { status: 404, error: err }); 69 | } else { 70 | next(err); 71 | } 72 | }); 73 | 74 | // Here we assume all errors as 500 for the simplicity of 75 | // this demo, however you can choose whatever you like 76 | 77 | app.error(function(err, req, res){ 78 | res.render('500.jade', { status: 500, error: err }); 79 | }); 80 | 81 | // Routes 82 | 83 | app.get('/', function(req, res){ 84 | res.render('index.jade'); 85 | }); 86 | 87 | app.get('/404', function(req, res){ 88 | throw new NotFound(req.url); 89 | }); 90 | 91 | app.get('/500', function(req, res, next){ 92 | next(new Error('keyboard cat!')); 93 | }); 94 | 95 | app.listen(3000); 96 | console.log('Express app started on port 3000'); -------------------------------------------------------------------------------- /docs/screencasts.md: -------------------------------------------------------------------------------- 1 | 2 | ### Introduction 3 | 4 | This introduction screencast covers the basics of Express, and how to get started with your first application. 5 | 6 | 7 | 8 | ### View Partials 9 | 10 | 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. 11 | 12 | 13 | 14 | ### Route Specific Middleware 15 | 16 | In the screencast below we learn about the benefits of route-specific middleware. 17 | 18 | 19 | 20 | ### Route Placeholder Preconditions 21 | 22 | Learn about route placeholder (_/user/:id_) pre-conditions, allowing validation, and loading of data via the named route placeholder segments. 23 | 24 | -------------------------------------------------------------------------------- /examples/github/app.js: -------------------------------------------------------------------------------- 1 | 2 | // Expose modules in ./support for demo purposes 3 | require.paths.unshift(__dirname + '/../../support'); 4 | 5 | /** 6 | * Module dependencies. 7 | */ 8 | 9 | var express = require('./../../lib/express') 10 | , http = require('http'); 11 | 12 | var app = express.createServer(); 13 | 14 | // Expose our views 15 | 16 | app.set('views', __dirname + '/views'); 17 | app.set('view engine', 'jade'); 18 | 19 | /** 20 | * Request github json api `path`. 21 | * 22 | * @param {String} path 23 | * @param {Function} fn 24 | * @api public 25 | */ 26 | 27 | function request(path, fn){ 28 | var client = http.createClient(80, 'github.com') 29 | , req = client.request('GET', '/api/v2/json' + path, { Host: 'github.com' }); 30 | req.addListener('response', function(res){ 31 | res.body = ''; 32 | res.addListener('data', function(chunk){ res.body += chunk; }); 33 | res.addListener('end', function(){ 34 | try { 35 | fn(null, JSON.parse(res.body)); 36 | } catch (err) { 37 | fn(err); 38 | } 39 | }); 40 | }); 41 | req.end(); 42 | } 43 | 44 | /** 45 | * Sort repositories by watchers desc. 46 | * 47 | * @param {Array} repos 48 | * @api public 49 | */ 50 | 51 | function sort(repos){ 52 | return repos.sort(function(a, b){ 53 | if (a.watchers == b.watchers) return 0; 54 | if (a.watchers > b.watchers) return -1; 55 | if (a.watchers < b.watchers) return 1; 56 | }); 57 | } 58 | 59 | /** 60 | * Tally up total watchers. 61 | * 62 | * @param {Array} repos 63 | * @return {Number} 64 | * @api public 65 | */ 66 | 67 | function totalWatchers(repos) { 68 | return repos.reduce(function(sum, repo){ 69 | return sum + repo.watchers; 70 | }, 0); 71 | } 72 | 73 | /** 74 | * Default to my user name :) 75 | */ 76 | 77 | app.get('/', function(req, res){ 78 | res.redirect('/repos/visionmedia'); 79 | }); 80 | 81 | /** 82 | * Display repos. 83 | */ 84 | 85 | app.get('/repos/*', function(req, res, next){ 86 | var names = req.params[0].split('/') 87 | , users = []; 88 | (function fetchData(name){ 89 | // We have a user name 90 | if (name) { 91 | console.log('... fetching \x1b[33m%s\x1b[0m', name); 92 | request('/repos/show/' + name, function(err, user){ 93 | if (err) { 94 | next(err) 95 | } else { 96 | user.totalWatchers = totalWatchers(user.repositories); 97 | user.repos = sort(user.repositories); 98 | user.name = name; 99 | users.push(user); 100 | fetchData(names.shift()); 101 | } 102 | }); 103 | // No more users 104 | } else { 105 | console.log('... done'); 106 | res.render('index', { users: users }); 107 | } 108 | })(names.shift()); 109 | }); 110 | 111 | // Serve statics from ./public 112 | app.use(express.staticProvider(__dirname + '/public')); 113 | 114 | // Listen on port 3000 115 | app.listen(3000); 116 | console.log('Express app started on port 3000'); -------------------------------------------------------------------------------- /test/fixtures/large.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo":"bar", 3 | "foo":"bar", 4 | "foo":"bar", 5 | "foo":"bar", 6 | "foo":"bar", 7 | "foo":"bar", 8 | "foo":"bar", 9 | "foo":"bar", 10 | "foo":"bar", 11 | "foo":"bar", 12 | "foo":"bar", 13 | "foo":"bar", 14 | "foo":"bar", 15 | "foo":"bar", 16 | "foo":"bar", 17 | "foo":"bar", 18 | "foo":"bar", 19 | "foo":"bar", 20 | "foo":"bar", 21 | "foo":"bar", 22 | "foo":"bar", 23 | "foo":"bar", 24 | "foo":"bar", 25 | "foo":"bar", 26 | "foo":"bar", 27 | "foo":"bar", 28 | "foo":"bar", 29 | "foo":"bar", 30 | "foo":"bar", 31 | "foo":"bar", 32 | "foo":"bar", 33 | "foo":"bar", 34 | "foo":"bar", 35 | "foo":"bar", 36 | "foo":"bar", 37 | "foo":"bar", 38 | "foo":"bar", 39 | "foo":"bar", 40 | "foo":"bar", 41 | "foo":"bar", 42 | "foo":"bar", 43 | "foo":"bar", 44 | "foo":"bar", 45 | "foo":"bar", 46 | "foo":"bar", 47 | "foo":"bar", 48 | "foo":"bar", 49 | "foo":"bar", 50 | "foo":"bar", 51 | "foo":"bar", 52 | "foo":"bar", 53 | "foo":"bar", 54 | "foo":"bar", 55 | "foo":"bar", 56 | "foo":"bar", 57 | "foo":"bar", 58 | "foo":"bar", 59 | "foo":"bar", 60 | "foo":"bar", 61 | "foo":"bar", 62 | "foo":"bar", 63 | "foo":"bar", 64 | "foo":"bar", 65 | "foo":"bar", 66 | "foo":"bar", 67 | "foo":"bar", 68 | "foo":"bar", 69 | "foo":"bar", 70 | "foo":"bar", 71 | "foo":"bar", 72 | "foo":"bar", 73 | "foo":"bar", 74 | "foo":"bar", 75 | "foo":"bar", 76 | "foo":"bar", 77 | "foo":"bar", 78 | "foo":"bar", 79 | "foo":"bar", 80 | "foo":"bar", 81 | "foo":"bar", 82 | "foo":"bar", 83 | "foo":"bar", 84 | "foo":"bar", 85 | "foo":"bar", 86 | "foo":"bar", 87 | "foo":"bar", 88 | "foo":"bar", 89 | "foo":"bar", 90 | "foo":"bar", 91 | "foo":"bar", 92 | "foo":"bar", 93 | "foo":"bar", 94 | "foo":"bar", 95 | "foo":"bar", 96 | "foo":"bar", 97 | "foo":"bar", 98 | "foo":"bar", 99 | "foo":"bar", 100 | "foo":"bar", 101 | "foo":"bar", 102 | "foo":"bar", 103 | "foo":"bar", 104 | "foo":"bar", 105 | "foo":"bar", 106 | "foo":"bar", 107 | "foo":"bar", 108 | "foo":"bar", 109 | "foo":"bar", 110 | "foo":"bar", 111 | "foo":"bar", 112 | "foo":"bar", 113 | "foo":"bar", 114 | "foo":"bar", 115 | "foo":"bar", 116 | "foo":"bar", 117 | "foo":"bar", 118 | "foo":"bar", 119 | "foo":"bar", 120 | "foo":"bar", 121 | "foo":"bar", 122 | "foo":"bar", 123 | "foo":"bar", 124 | "foo":"bar", 125 | "foo":"bar", 126 | "foo":"bar", 127 | "foo":"bar", 128 | "foo":"bar", 129 | "foo":"bar", 130 | "foo":"bar", 131 | "foo":"bar", 132 | "foo":"bar", 133 | "foo":"bar", 134 | "foo":"bar", 135 | "foo":"bar", 136 | "foo":"bar", 137 | "foo":"bar", 138 | "foo":"bar", 139 | "foo":"bar", 140 | "foo":"bar", 141 | "foo":"bar", 142 | "foo":"bar", 143 | "foo":"bar", 144 | "foo":"bar", 145 | "foo":"bar", 146 | "foo":"bar", 147 | "foo":"bar", 148 | "foo":"bar", 149 | "foo":"bar", 150 | "foo":"bar" 151 | } -------------------------------------------------------------------------------- /lib/express/utils.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Express - Utils 4 | * Copyright(c) 2010 TJ Holowaychuk 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | 12 | var path = require('path') 13 | , basename = path.basename 14 | , dirname = path.dirname 15 | , extname = path.extname; 16 | 17 | /** 18 | * Memory cache. 19 | */ 20 | 21 | var cache = { 22 | basename: {} 23 | , dirname: {} 24 | , extname: {} 25 | }; 26 | 27 | /** 28 | * Cached basename. 29 | * 30 | * @param {String} path 31 | * @return {String} 32 | * @api private 33 | */ 34 | 35 | exports.basename = function(path){ 36 | return cache.basename[path] 37 | || (cache.basename[path] = basename(path)); 38 | }; 39 | 40 | /** 41 | * Cached dirname. 42 | * 43 | * @param {String} path 44 | * @return {String} 45 | * @api private 46 | */ 47 | 48 | exports.dirname = function(path){ 49 | return cache.dirname[path] 50 | || (cache.dirname[path] = dirname(path)); 51 | }; 52 | 53 | /** 54 | * Cached extname. 55 | * 56 | * @param {String} path 57 | * @return {String} 58 | * @api private 59 | */ 60 | 61 | exports.extname = function(path){ 62 | return cache.extname[path] 63 | || (cache.extname[path] = extname(path)); 64 | }; 65 | 66 | /** 67 | * Parse mini markdown implementation. 68 | * The following conversions are supported, 69 | * primarily for the "flash" middleware: 70 | * 71 | * _foo_ or *foo* become foo 72 | * __foo__ or **foo** become foo 73 | * [A](B) becomes A 74 | * 75 | * @param {String} str 76 | * @return {String} 77 | * @api private 78 | */ 79 | 80 | exports.miniMarkdown = function(str){ 81 | return String(str) 82 | .replace(/(__|\*\*)(.*?)\1/g, '$2') 83 | .replace(/(_|\*)(.*?)\1/g, '$2') 84 | .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); 85 | }; 86 | 87 | /** 88 | * Escape special characters in the given string of html. 89 | * 90 | * @param {String} html 91 | * @return {String} 92 | * @api private 93 | */ 94 | 95 | exports.htmlEscape = function(html) { 96 | return String(html) 97 | .replace(/&/g, '&') 98 | .replace(/"/g, '"') 99 | .replace(//g, '>'); 101 | }; 102 | 103 | /** 104 | * Clone `obj`. 105 | * 106 | * @param {Object} obj 107 | * @return {Object} 108 | * @api private 109 | */ 110 | 111 | exports.clone = function(obj){ 112 | var ret = {}; 113 | for (var key in obj) { 114 | ret[key] = obj[key]; 115 | } 116 | return ret; 117 | }; 118 | 119 | /** 120 | * Parse "Range" header `str` relative to the given file `size`. 121 | * 122 | * @param {Number} size 123 | * @param {String} str 124 | * @return {Array} 125 | * @api private 126 | */ 127 | 128 | exports.parseRange = function(size, str){ 129 | var valid = true; 130 | var arr = str.substr(6).split(',').map(function(range){ 131 | var range = range.split('-') 132 | , start = parseInt(range[0], 10) 133 | , end = parseInt(range[1], 10); 134 | 135 | // -500 136 | if (isNaN(start)) { 137 | start = size - end; 138 | end = size - 1; 139 | // 500- 140 | } else if (isNaN(end)) { 141 | end = size - 1; 142 | } 143 | 144 | // Invalid 145 | if (isNaN(start) || isNaN(end) || start > end) valid = false; 146 | 147 | return { start: start, end: end }; 148 | }); 149 | return valid ? arr : undefined; 150 | }; 151 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Express 3 | 4 | Insanely fast (and small) server-side JavaScript web development framework 5 | built on [node](http://nodejs.org) and [Connect](http://github.com/senchalabs/connect). 6 | 7 | var app = express.createServer(); 8 | 9 | app.get('/', function(req, res){ 10 | res.send('Hello World'); 11 | }); 12 | 13 | app.listen(3000); 14 | 15 | ## Installation 16 | 17 | npm: 18 | 19 | $ npm install express 20 | 21 | curl: 22 | 23 | $ curl -# http://expressjs.com/install.sh | sh 24 | 25 | ## Features 26 | 27 | * Robust routing 28 | * Redirection helpers 29 | * Dynamic view helpers 30 | * Content negotiation 31 | * Focus on high performance 32 | * View rendering and partials support 33 | * Environment based configuration 34 | * Session based flash notifications 35 | * Built on [Connect](http://github.com/senchalabs/connect) 36 | * High test coverage 37 | * Executable for generating applications quickly 38 | * Application level view options 39 | 40 | Via Connect: 41 | 42 | * Session support 43 | * Cache API 44 | * Mime helpers 45 | * ETag support 46 | * Persistent flash notifications 47 | * Cookie support 48 | * JSON-RPC 49 | * Logging 50 | * and _much_ more! 51 | 52 | ## Contributors 53 | 54 | The following are the major contributors of Express (in no specific order). 55 | 56 | * TJ Holowaychuk ([visionmedia](http://github.com/visionmedia)) 57 | * Ciaran Jessup ([ciaranj](http://github.com/ciaranj)) 58 | * Aaron Heckmann ([aheckmann](http://github.com/aheckmann)) 59 | * Guillermo Rauch ([guille](http://github.com/guille)) 60 | 61 | ## More Information 62 | 63 | * Express [Contrib](http://github.com/visionmedia/express-contrib) repo for additional functionality 64 | * Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates 65 | * [Google Group](http://groups.google.com/group/express-js) for discussion 66 | * Visit the [Wiki](http://github.com/visionmedia/express/wiki) 67 | * Screencast - [Introduction](http://bit.ly/eRYu0O) 68 | * Screencast - [View Partials](http://bit.ly/dU13Fx) 69 | * Screencast - [Route Specific Middleware](http://bit.ly/hX4IaH) 70 | * Screencast - [Route Path Placeholder Preconditions](http://bit.ly/eNqmVs) 71 | 72 | ## Node Compatibility 73 | 74 | The latest release of Express is compatible with node --version: 75 | 76 | v0.2.5 77 | 78 | and connect --version: 79 | 80 | 0.5.0 81 | 82 | Express 1.x is maintained in the _1.x_ branch, and _master_ is 2.0.0-pre, to view 2.x documentation open _docs/index.html_. 83 | 84 | ## License 85 | 86 | (The MIT License) 87 | 88 | Copyright (c) 2009-2010 TJ Holowaychuk <tj@vision-media.ca> 89 | 90 | Permission is hereby granted, free of charge, to any person obtaining 91 | a copy of this software and associated documentation files (the 92 | 'Software'), to deal in the Software without restriction, including 93 | without limitation the rights to use, copy, modify, merge, publish, 94 | distribute, sublicense, and/or sell copies of the Software, and to 95 | permit persons to whom the Software is furnished to do so, subject to 96 | the following conditions: 97 | 98 | The above copyright notice and this permission notice shall be 99 | included in all copies or substantial portions of the Software. 100 | 101 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 102 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 103 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 104 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 105 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 106 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 107 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 108 | -------------------------------------------------------------------------------- /examples/auth/app.js: -------------------------------------------------------------------------------- 1 | 2 | // Expose modules in ./support for demo purposes 3 | require.paths.unshift(__dirname + '/../../support'); 4 | 5 | /** 6 | * Module dependencies. 7 | */ 8 | 9 | var express = require('../../lib/express') 10 | , crypto = require('crypto'); 11 | 12 | var app = express.createServer(); 13 | 14 | app.set('views', __dirname + '/views'); 15 | app.set('view engine', 'ejs'); 16 | 17 | app.use(express.bodyDecoder()); 18 | app.use(express.cookieDecoder()); 19 | app.use(express.session()); 20 | 21 | // Message helper, ideally we would use req.flash() 22 | // however this is more light-weight for an example 23 | 24 | app.dynamicHelpers({ 25 | message: function(req){ 26 | var err = req.session.error 27 | , msg = req.session.success; 28 | delete req.session.error; 29 | delete req.session.success; 30 | if (err) return '

    ' + err + '

    '; 31 | if (msg) return '

    ' + msg + '

    '; 32 | } 33 | }); 34 | 35 | // Generate a salt for the user to prevent rainbow table attacks 36 | 37 | var users = { 38 | tj: { 39 | name: 'tj' 40 | , salt: 'randomly-generated-salt' 41 | , pass: md5('foobar' + 'randomly-generated-salt') 42 | } 43 | }; 44 | 45 | // Used to generate a hash of the plain-text password + salt 46 | 47 | function md5(str) { 48 | return crypto.createHash('md5').update(str).digest('hex'); 49 | } 50 | 51 | // Authenticate using our plain-object database of doom! 52 | 53 | function authenticate(name, pass, fn) { 54 | var user = users[name]; 55 | // query the db for the given username 56 | if (!user) return fn(new Error('cannot find user')); 57 | // apply the same algorithm to the POSTed password, applying 58 | // the md5 against the pass / salt, if there is a match we 59 | // found the user 60 | if (user.pass == md5(pass + user.salt)) return fn(null, user); 61 | // Otherwise password is invalid 62 | fn(new Error('invalid password')); 63 | } 64 | 65 | function restrict(req, res, next) { 66 | if (req.session.user) { 67 | next(); 68 | } else { 69 | req.session.error = 'Access denied!'; 70 | res.redirect('/login'); 71 | } 72 | } 73 | 74 | function accessLogger(req, res, next) { 75 | console.log('/restricted accessed by %s', req.session.user.name); 76 | next(); 77 | } 78 | 79 | app.get('/', function(req, res){ 80 | res.redirect('/login'); 81 | }); 82 | 83 | app.get('/restricted', restrict, accessLogger, function(req, res){ 84 | res.send('Wahoo! restricted area'); 85 | }); 86 | 87 | app.get('/logout', function(req, res){ 88 | // destroy the user's session to log them out 89 | // will be re-created next request 90 | req.session.destroy(function(){ 91 | res.redirect('home'); 92 | }); 93 | }); 94 | 95 | app.get('/login', function(req, res){ 96 | if (req.session.user) { 97 | req.session.success = 'Authenticated as ' + req.session.user.name 98 | + ' click to logout. ' 99 | + ' You may now access /restricted.'; 100 | } 101 | res.render('login'); 102 | }); 103 | 104 | app.post('/login', function(req, res){ 105 | authenticate(req.body.username, req.body.password, function(err, user){ 106 | if (user) { 107 | // Regenerate session when signing in 108 | // to prevent fixation 109 | req.session.regenerate(function(){ 110 | // Store the user's primary key 111 | // in the session store to be retrieved, 112 | // or in this case the entire user object 113 | req.session.user = user; 114 | res.redirect('back'); 115 | }); 116 | } else { 117 | req.session.error = 'Authentication failed, please check your ' 118 | + ' username and password.' 119 | + ' (use "tj" and "foobar")'; 120 | res.redirect('back'); 121 | } 122 | }); 123 | }); 124 | 125 | app.listen(3000); 126 | console.log('Express started on port 3000'); -------------------------------------------------------------------------------- /examples/mvc/mvc.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var fs = require('fs') 7 | , express = require('../../lib/express'); 8 | 9 | exports.boot = function(app){ 10 | bootApplication(app); 11 | bootControllers(app); 12 | }; 13 | 14 | // App settings and middleware 15 | 16 | function bootApplication(app) { 17 | app.use(express.logger({ format: ':method :url :status' })); 18 | app.use(express.bodyDecoder()); 19 | app.use(express.methodOverride()); 20 | app.use(express.cookieDecoder()); 21 | app.use(express.session()); 22 | app.use(app.router); 23 | app.use(express.staticProvider(__dirname + '/public')); 24 | 25 | // Example 500 page 26 | app.error(function(err, req, res){ 27 | console.dir(err) 28 | res.render('500'); 29 | }); 30 | 31 | // Example 404 page via simple Connect middleware 32 | app.use(function(req, res){ 33 | res.render('404'); 34 | }); 35 | 36 | // Setup ejs views as default, with .html as the extension 37 | app.set('views', __dirname + '/views'); 38 | app.register('.html', require('ejs')); 39 | app.set('view engine', 'html'); 40 | 41 | // Some dynamic view helpers 42 | app.dynamicHelpers({ 43 | request: function(req){ 44 | return req; 45 | }, 46 | 47 | hasMessages: function(req){ 48 | return Object.keys(req.session.flash || {}).length; 49 | }, 50 | 51 | messages: function(req){ 52 | return function(){ 53 | var msgs = req.flash(); 54 | return Object.keys(msgs).reduce(function(arr, type){ 55 | return arr.concat(msgs[type]); 56 | }, []); 57 | } 58 | } 59 | }); 60 | } 61 | 62 | // Bootstrap controllers 63 | 64 | function bootControllers(app) { 65 | fs.readdir(__dirname + '/controllers', function(err, files){ 66 | if (err) throw err; 67 | files.forEach(function(file){ 68 | bootController(app, file); 69 | }); 70 | }); 71 | } 72 | 73 | // Example (simplistic) controller support 74 | 75 | function bootController(app, file) { 76 | var name = file.replace('.js', '') 77 | , actions = require('./controllers/' + name) 78 | , plural = name + 's' // realistically we would use an inflection lib 79 | , prefix = '/' + plural; 80 | 81 | // Special case for "app" 82 | if (name == 'app') prefix = '/'; 83 | 84 | Object.keys(actions).map(function(action){ 85 | var fn = controllerAction(name, plural, action, actions[action]); 86 | switch(action) { 87 | case 'index': 88 | app.get(prefix, fn); 89 | break; 90 | case 'show': 91 | app.get(prefix + '/:id.:format?', fn); 92 | break; 93 | case 'add': 94 | app.get(prefix + '/:id/add', fn); 95 | break; 96 | case 'create': 97 | app.post(prefix + '/:id', fn); 98 | break; 99 | case 'edit': 100 | app.get(prefix + '/:id/edit', fn); 101 | break; 102 | case 'update': 103 | app.put(prefix + '/:id', fn); 104 | break; 105 | case 'destroy': 106 | app.del(prefix + '/:id', fn); 107 | break; 108 | } 109 | }); 110 | } 111 | 112 | // Proxy res.render() to add some magic 113 | 114 | function controllerAction(name, plural, action, fn) { 115 | return function(req, res, next){ 116 | var render = res.render 117 | , format = req.params.format 118 | , path = __dirname + '/views/' + name + '/' + action + '.html'; 119 | res.render = function(obj, options, fn){ 120 | res.render = render; 121 | // Template path 122 | if (typeof obj === 'string') { 123 | return res.render(obj, options, fn); 124 | } 125 | 126 | // Format support 127 | if (action == 'show' && format) { 128 | if (format === 'json') { 129 | return res.send(obj); 130 | } else { 131 | throw new Error('unsupported format "' + format + '"'); 132 | } 133 | } 134 | 135 | // Render template 136 | res.render = render; 137 | options = options || {}; 138 | // Expose obj as the "users" or "user" local 139 | if (action == 'index') { 140 | options[plural] = obj; 141 | } else { 142 | options[name] = obj; 143 | } 144 | return res.render(path, options, fn); 145 | }; 146 | fn.apply(this, arguments); 147 | }; 148 | } -------------------------------------------------------------------------------- /lib/express/view/view.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Express - View 4 | * Copyright(c) 2010 TJ Holowaychuk 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | 12 | var utils = require('../utils') 13 | , extname = utils.extname 14 | , dirname = utils.dirname 15 | , basename = utils.basename 16 | , fs = require('fs') 17 | , stat = fs.statSync; 18 | 19 | /** 20 | * Memory cache. 21 | */ 22 | 23 | var cache = {}; 24 | 25 | /** 26 | * Existance cache. 27 | */ 28 | 29 | var exists = {}; 30 | 31 | /** 32 | * Initialize a new `View` with the given `view` path and `options`. 33 | * 34 | * @param {String} view 35 | * @param {Object} options 36 | * @api private 37 | */ 38 | 39 | var View = exports = module.exports = function View(view, options) { 40 | options = options || {}; 41 | // TODO: more caching 42 | this.view = view; 43 | this.root = options.root; 44 | this.relative = false !== options.relative; 45 | this.defaultEngine = options.defaultEngine; 46 | this.parent = options.parentView; 47 | this.basename = basename(view); 48 | this.engine = this.resolveEngine(); 49 | this.extension = '.' + this.engine; 50 | this.path = this.resolvePath(); 51 | this.dirname = dirname(this.path); 52 | }; 53 | 54 | /** 55 | * Check if the view path exists. 56 | * 57 | * @return {Boolean} 58 | * @api public 59 | */ 60 | 61 | View.prototype.__defineGetter__('exists', function(){ 62 | var path = this.path; 63 | if (null != exists[path]) { 64 | return exists[path]; 65 | } else { 66 | try { 67 | stat(path); 68 | return exists[path] = true; 69 | } catch (err) { 70 | return exists[path] = false; 71 | } 72 | } 73 | }); 74 | 75 | /** 76 | * Resolve view engine. 77 | * 78 | * @return {String} 79 | * @api private 80 | */ 81 | 82 | View.prototype.resolveEngine = function(){ 83 | // Explicit 84 | if (~this.basename.indexOf('.')) return extname(this.basename).substr(1); 85 | // Inherit from parent 86 | if (this.parent) return this.parent.engine; 87 | // Default 88 | return this.defaultEngine; 89 | }; 90 | 91 | /** 92 | * Resolve view path. 93 | * 94 | * @return {String} 95 | * @api private 96 | */ 97 | 98 | View.prototype.resolvePath = function(){ 99 | var path = this.view; 100 | // Implicit engine 101 | if (!~this.basename.indexOf('.')) path += this.extension; 102 | // Absolute 103 | if ('/' == path[0]) return path; 104 | // Relative to parent 105 | if (this.relative && this.parent) return this.parent.dirname + '/' + path; 106 | // Relative to root 107 | return this.root 108 | ? this.root + '/' + path 109 | : path; 110 | }; 111 | 112 | /** 113 | * Get view contents. This is a one-time hit, so we 114 | * can afford to be sync. 115 | * 116 | * @return {String} 117 | * @api public 118 | */ 119 | 120 | View.prototype.__defineGetter__('contents', function(){ 121 | return fs.readFileSync(this.path, 'utf8'); 122 | }); 123 | 124 | /** 125 | * Get template engine api, cache exports to reduce 126 | * require() calls. 127 | * 128 | * @return {Object} 129 | * @api public 130 | */ 131 | 132 | View.prototype.__defineGetter__('templateEngine', function(){ 133 | var ext = this.extension; 134 | return cache[ext] || (cache[ext] = require(this.engine)); 135 | }); 136 | 137 | /** 138 | * Return index path alternative. 139 | * 140 | * @return {String} 141 | * @api public 142 | */ 143 | 144 | View.prototype.__defineGetter__('indexPath', function(){ 145 | return this.dirname 146 | + '/' + this.basename.replace(this.extension, '') 147 | + '/index' + this.extension; 148 | }); 149 | 150 | /** 151 | * Return _ prefix path alternative 152 | * 153 | * @return {String} 154 | * @api public 155 | */ 156 | 157 | View.prototype.__defineGetter__('prefixPath', function(){ 158 | return this.dirname + '/_' + this.basename; 159 | }); 160 | 161 | /** 162 | * Register the given template engine `exports` 163 | * as `ext`. For example we may wish to map ".html" 164 | * files to jade: 165 | * 166 | * app.register('.html', require('jade')); 167 | * 168 | * or 169 | * 170 | * app.register('html', require('jade')); 171 | * 172 | * This is also useful for libraries that may not 173 | * match extensions correctly. For example my haml.js 174 | * library is installed from npm as "hamljs" so instead 175 | * of layout.hamljs, we can register the engine as ".haml": 176 | * 177 | * app.register('.haml', require('haml-js')); 178 | * 179 | * For engines that do not comply with the Express 180 | * specification, we can also wrap their api this way. 181 | * 182 | * app.register('.foo', { 183 | * render: function(str, options) { 184 | * // perhaps their api is 185 | * // return foo.toHTML(str, options); 186 | * } 187 | * }); 188 | * 189 | * @param {String} ext 190 | * @param {Object} obj 191 | * @api public 192 | */ 193 | 194 | exports.register = function(ext, exports) { 195 | if ('.' != ext[0]) ext = '.' + ext; 196 | cache[ext] = exports; 197 | }; 198 | -------------------------------------------------------------------------------- /docs/layout/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Express - node web framework 4 | 5 | 163 | 175 | 176 | 177 | 178 | Fork me on GitHub 179 | 180 |
    181 |
    182 | 183 |

    184 | High performance, high class web development for 185 | Node.js 186 |

    187 | 193 | -------------------------------------------------------------------------------- /docs/migrate.md: -------------------------------------------------------------------------------- 1 | 2 | ### Built On Connect 3 | 4 | Express 1.x is written to run on-top of the [Connect](http://extjs.github.com/Connect) middlware 5 | framework, thus the _Plugin_ has been replaced by Connect's middleware. By abstracting our middleware 6 | to Connect we allow additional community frameworks to develop robust, high-level frameworks using 7 | the same technologies as Express. 8 | 9 | ### Creating Applications 10 | 11 | Previously due to legacy code implemented in the early days of node, 12 | Express unfortunately had some globals. The DSL would previously be 13 | accessed as shown below: 14 | 15 | require('express'); 16 | 17 | configure(function(){ 18 | // app configuration 19 | }); 20 | 21 | get('/', function(){ 22 | return 'hello world'; 23 | }); 24 | 25 | Now we utilize the CommonJS module system appropriately, and 26 | introduce _express.createServer()_ which accepts the same arguments 27 | as _http.createServer()_: 28 | 29 | var express = require('express'), 30 | app = express.createServer(); 31 | 32 | app.configure(function(){ 33 | // app configuration 34 | }); 35 | 36 | app.get('/', function(req, res){ 37 | res.send('hello world'); 38 | }); 39 | 40 | Express 1.x does _not_ currently allow returning of a string. 41 | 42 | ### Plugins vs Middleware 43 | 44 | Previously Express was bundled with plugins, which were essentially what 45 | are now Connect middleware. Previously plugins would be utilized in a manor 46 | similar to below: 47 | 48 | use(Logger); 49 | use(MethodOverride); 50 | use(Cookie); 51 | 52 | Which we can now _use()_ within our app, or pass to the _express.createServer()_ method: 53 | 54 | var app = express.createServer( 55 | express.logger(), 56 | express.methodOverride(), 57 | express.cookieDecoder() 58 | ); 59 | 60 | or: 61 | 62 | var app = express.createServer(); 63 | 64 | app.use(express.logger()); 65 | app.use(express.methodOverride()); 66 | app.use(express.cookieDecoder()); 67 | 68 | For documentation on creating Connect middleware visit [Middleware Authoring](http://extjs.github.com/Connect/#Middleware-Authoring). 69 | 70 | ### Running Applications 71 | 72 | Previously a global function _run()_, was available: 73 | 74 | run(); 75 | 76 | The new _express.Server_ has the same API as _http.Server_, 77 | so we can do things like: 78 | 79 | app.listen(); 80 | app.listen(3000); 81 | 82 | ### Route Parameters 83 | 84 | Previously we could use _this.param()_ to attempt 85 | fetching a route, query string, or request body parameter: 86 | 87 | get('/user/:id', function(){ 88 | this.param('id'); 89 | }); 90 | 91 | Polymorphic parameter access can be done using `req.param()`: 92 | 93 | app.get('/user/:id', function(req, res){ 94 | req.param('id'); 95 | }); 96 | 97 | Route parameters are available via `req.params`: 98 | 99 | app.get('/user/:id', function(req, res){ 100 | req.params.id; 101 | }); 102 | 103 | ### Passing Route Control 104 | 105 | Old express had a weak notion of route passing, 106 | which did not support async, and was never properly 107 | implemented for practical use: 108 | 109 | get('/', function(){ 110 | this.pass('/foobar'); 111 | }); 112 | 113 | Now Express has access to Connect's _next()_ function, 114 | which is passed as the third and final argument. Calling _next()_ will 115 | pass control to the next _matching route_, or continue down the stack 116 | of Connect middleware. 117 | 118 | app.get('/user/:id?', function(req, res, next){ 119 | next(); 120 | }); 121 | 122 | app.get('/user', function(){ 123 | // ... respond 124 | }); 125 | 126 | ### View Rendering 127 | 128 | View filenames no longer take the form _NAME_._TYPE_._ENGINE_, 129 | the _Content-Type_ can be set via _res.contentType()_ or 130 | _res.header()_. For example what was previously _layout.html.haml_, 131 | should now be _layout.haml_. 132 | 133 | Previously a view render looked something like this: 134 | 135 | get('/', function(){ 136 | this.render('index.html.haml', { 137 | locals: { title: 'My Site' } 138 | }); 139 | }); 140 | 141 | 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 142 | remain the same. 143 | 144 | app.get('/', function(req, res){ 145 | res.render('index.haml', { 146 | locals: { title: 'My Site' } 147 | }); 148 | }); 149 | 150 | Previously rendering of a collection via _partial()_ would look something like this: 151 | 152 | this.partial('comment.html.haml', { collection: comments }); 153 | 154 | Although this worked just fine, it was generally to verbose, the similar but new API 155 | looks like this, as _partial()_ is _always_ passed as a local variable: 156 | 157 | partial('comment.haml', { collection: comments }); 158 | 159 | To make things even less verbose we can assume the extension when omitted: 160 | 161 | partial('comment', { collection: comments }); 162 | 163 | And once again even further, when rendering a collection we can simply pass 164 | an array, if no other options are desired: 165 | 166 | partial('comments', comments); 167 | 168 | ### Redirecting 169 | 170 | Previously you would 171 | 172 | this.redirect('/somewhere'); 173 | 174 | However you would now: 175 | 176 | res.redirect('/somewhere'); 177 | res.redirect('/somewhere', 301); 178 | 179 | ### HTTP Client 180 | 181 | Previously Express provided a high level http client, this library is no more 182 | as it does not belong in Express, however it may be resurrected as a separate module. 183 | 184 | ### Core Extensions 185 | 186 | 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 187 | roll your own, or install the module via: 188 | 189 | $ npm install ext -------------------------------------------------------------------------------- /docs/executable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Express - node web framework 4 | 5 | 163 | 175 | 176 | 177 | 178 | Fork me on GitHub 179 | 180 |
    181 |
    182 | 183 |

    184 | High performance, high class web development for 185 | Node.js 186 |

    187 | 193 |
    194 |

    Express

    195 |

    196 | executable 197 |

    198 |

    Synopsis

    199 | 200 |
    express [-h|--help] [-v|--version] [-c|-css ENGINE] [PATH]
    201 | 
    202 | 203 |

    Description

    204 | 205 |

    The express executable generates apps at the given PATH or the 206 | current working directory. Although Express is not bound to a specific 207 | application structure, this executable creates a maintainable base app.

    208 | 209 |

    Options

    210 | 211 |
    -s, --sessions        Add session support
    212 | -c, --css ENGINE      Add css ENGINE support (less|sass). Defaults to plain css
    213 | -v, --version         Output framework version
    214 | -h, --help            Display help information
    215 | 
    216 | 217 |
    218 |
    219 |
    220 | 221 | 222 | -------------------------------------------------------------------------------- /docs/contrib.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Express - node web framework 4 | 5 | 163 | 175 | 176 | 177 | 178 | Fork me on GitHub 179 | 180 |
    181 |
    182 | 183 |

    184 | High performance, high class web development for 185 | Node.js 186 |

    187 | 193 |
    194 |

    Express

    195 |

    196 | contrib 197 |

    198 |

    Development Dependencies

    199 | 200 |

    Express development dependencies are stored within the ./support directory. To 201 | update them execute:

    202 | 203 |
    $ git submodule update --init
    204 | 
    205 | 206 |

    Running Tests

    207 | 208 |

    Express uses the Expresso TDD 209 | framework to write and run elegant test suites extremely fast. To run all test suites 210 | simply execute:

    211 | 212 |
    $ make test
    213 | 
    214 | 215 |

    To target specific suites we may specify the files via:

    216 | 217 |
    $ make test TESTS=test/view.test.js
    218 | 
    219 | 220 |

    To check test coverage run:

    221 | 222 |
    $ make test-cov
    223 | 
    224 | 225 |

    Contributions

    226 | 227 |

    To accept a contribution, you should follow these guidelines:

    228 | 229 |
      230 |
    • All tests must pass
    • 231 |
    • Your alterations or additions must include tests
    • 232 |
    • Your commit(s) should be focused, do not commit once for several changes
    • 233 |
    • Do not alter release information such as the version, or History.md
    • 234 |
    • Indents are 2 spaces.
    • 235 |
    236 | 237 | 238 |

    Documentation

    239 | 240 |

    To contribute documentation edit the markdown files in ./docs, however 241 | do not run make docs, as they will be re-built and published with each release.

    242 | 243 |
    244 |
    245 |
    246 | 247 | 248 | -------------------------------------------------------------------------------- /docs/migrate.1: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn/v0.7.3 2 | .\" http://github.com/rtomayko/ronn/tree/0.7.3 3 | . 4 | .TH "MIGRATE" "" "October 2010" "" "" 5 | . 6 | .SH "NAME" 7 | \fBmigrate\fR 8 | . 9 | .SS "Built On Connect" 10 | 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\. 11 | . 12 | .SS "Creating Applications" 13 | 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: 14 | . 15 | .IP "" 4 16 | . 17 | .nf 18 | 19 | require(\'express\'); 20 | 21 | configure(function(){ 22 | // app configuration 23 | }); 24 | 25 | get(\'/\', function(){ 26 | return \'hello world\'; 27 | }); 28 | . 29 | .fi 30 | . 31 | .IP "" 0 32 | . 33 | .P 34 | Now we utilize the CommonJS module system appropriately, and introduce \fIexpress\.createServer()\fR which accepts the same arguments as \fIhttp\.createServer()\fR: 35 | . 36 | .IP "" 4 37 | . 38 | .nf 39 | 40 | var express = require(\'express\'), 41 | app = express\.createServer(); 42 | 43 | app\.configure(function(){ 44 | // app configuration 45 | }); 46 | 47 | app\.get(\'/\', function(req, res){ 48 | res\.send(\'hello world\'); 49 | }); 50 | . 51 | .fi 52 | . 53 | .IP "" 0 54 | . 55 | .P 56 | Express 1\.x does \fInot\fR currently allow returning of a string\. 57 | . 58 | .SS "Plugins vs Middleware" 59 | 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: 60 | . 61 | .IP "" 4 62 | . 63 | .nf 64 | 65 | use(Logger); 66 | use(MethodOverride); 67 | use(Cookie); 68 | . 69 | .fi 70 | . 71 | .IP "" 0 72 | . 73 | .P 74 | Which we can now \fIuse()\fR within our app, or pass to the \fIexpress\.createServer()\fR method: 75 | . 76 | .IP "" 4 77 | . 78 | .nf 79 | 80 | var app = express\.createServer( 81 | express\.logger(), 82 | express\.methodOverride(), 83 | express\.cookieDecoder() 84 | ); 85 | . 86 | .fi 87 | . 88 | .IP "" 0 89 | . 90 | .P 91 | or: 92 | . 93 | .IP "" 4 94 | . 95 | .nf 96 | 97 | var app = express\.createServer(); 98 | 99 | app\.use(express\.logger()); 100 | app\.use(express\.methodOverride()); 101 | app\.use(express\.cookieDecoder()); 102 | . 103 | .fi 104 | . 105 | .IP "" 0 106 | . 107 | .P 108 | For documentation on creating Connect middleware visit Middleware Authoring \fIhttp://extjs\.github\.com/Connect/#Middleware\-Authoring\fR\. 109 | . 110 | .SS "Running Applications" 111 | Previously a global function \fIrun()\fR, was available: 112 | . 113 | .IP "" 4 114 | . 115 | .nf 116 | 117 | run(); 118 | . 119 | .fi 120 | . 121 | .IP "" 0 122 | . 123 | .P 124 | The new \fIexpress\.Server\fR has the same API as \fIhttp\.Server\fR, so we can do things like: 125 | . 126 | .IP "" 4 127 | . 128 | .nf 129 | 130 | app\.listen(); 131 | app\.listen(3000); 132 | . 133 | .fi 134 | . 135 | .IP "" 0 136 | . 137 | .SS "Route Parameters" 138 | Previously we could use \fIthis\.param()\fR to attempt fetching a route, query string, or request body parameter: 139 | . 140 | .IP "" 4 141 | . 142 | .nf 143 | 144 | get(\'/user/:id\', function(){ 145 | this\.param(\'id\'); 146 | }); 147 | . 148 | .fi 149 | . 150 | .IP "" 0 151 | . 152 | .P 153 | Polymorphic parameter access can be done using \fBreq\.param()\fR: 154 | . 155 | .IP "" 4 156 | . 157 | .nf 158 | 159 | app\.get(\'/user/:id\', function(req, res){ 160 | req\.param(\'id\'); 161 | }); 162 | . 163 | .fi 164 | . 165 | .IP "" 0 166 | . 167 | .P 168 | Route parameters are available via \fBreq\.params\fR: 169 | . 170 | .IP "" 4 171 | . 172 | .nf 173 | 174 | app\.get(\'/user/:id\', function(req, res){ 175 | req\.params\.id; 176 | }); 177 | . 178 | .fi 179 | . 180 | .IP "" 0 181 | . 182 | .SS "Passing Route Control" 183 | Old express had a weak notion of route passing, which did not support async, and was never properly implemented for practical use: 184 | . 185 | .IP "" 4 186 | . 187 | .nf 188 | 189 | get(\'/\', function(){ 190 | this\.pass(\'/foobar\'); 191 | }); 192 | . 193 | .fi 194 | . 195 | .IP "" 0 196 | . 197 | .P 198 | 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\. 199 | . 200 | .IP "" 4 201 | . 202 | .nf 203 | 204 | app\.get(\'/user/:id?\', function(req, res, next){ 205 | next(); 206 | }); 207 | 208 | app\.get(\'/user\', function(){ 209 | // \.\.\. respond 210 | }); 211 | . 212 | .fi 213 | . 214 | .IP "" 0 215 | . 216 | .SS "View Rendering" 217 | 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\. 218 | . 219 | .P 220 | Previously a view render looked something like this: 221 | . 222 | .IP "" 4 223 | . 224 | .nf 225 | 226 | get(\'/\', function(){ 227 | this\.render(\'index\.html\.haml\', { 228 | locals: { title: \'My Site\' } 229 | }); 230 | }); 231 | . 232 | .fi 233 | . 234 | .IP "" 0 235 | . 236 | .P 237 | 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\. 238 | . 239 | .IP "" 4 240 | . 241 | .nf 242 | 243 | app\.get(\'/\', function(req, res){ 244 | res\.render(\'index\.haml\', { 245 | locals: { title: \'My Site\' } 246 | }); 247 | }); 248 | . 249 | .fi 250 | . 251 | .IP "" 0 252 | . 253 | .P 254 | Previously rendering of a collection via \fIpartial()\fR would look something like this: 255 | . 256 | .IP "" 4 257 | . 258 | .nf 259 | 260 | this\.partial(\'comment\.html\.haml\', { collection: comments }); 261 | . 262 | .fi 263 | . 264 | .IP "" 0 265 | . 266 | .P 267 | 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: 268 | . 269 | .IP "" 4 270 | . 271 | .nf 272 | 273 | partial(\'comment\.haml\', { collection: comments }); 274 | . 275 | .fi 276 | . 277 | .IP "" 0 278 | . 279 | .P 280 | To make things even less verbose we can assume the extension when omitted: 281 | . 282 | .IP "" 4 283 | . 284 | .nf 285 | 286 | partial(\'comment\', { collection: comments }); 287 | . 288 | .fi 289 | . 290 | .IP "" 0 291 | . 292 | .P 293 | And once again even further, when rendering a collection we can simply pass an array, if no other options are desired: 294 | . 295 | .IP "" 4 296 | . 297 | .nf 298 | 299 | partial(\'comments\', comments); 300 | . 301 | .fi 302 | . 303 | .IP "" 0 304 | . 305 | .SS "Redirecting" 306 | Previously you would 307 | . 308 | .IP "" 4 309 | . 310 | .nf 311 | 312 | this\.redirect(\'/somewhere\'); 313 | . 314 | .fi 315 | . 316 | .IP "" 0 317 | . 318 | .P 319 | However you would now: 320 | . 321 | .IP "" 4 322 | . 323 | .nf 324 | 325 | res\.redirect(\'/somewhere\'); 326 | res\.redirect(\'/somewhere\', 301); 327 | . 328 | .fi 329 | . 330 | .IP "" 0 331 | . 332 | .SS "HTTP Client" 333 | 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\. 334 | . 335 | .SS "Core Extensions" 336 | 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: 337 | . 338 | .IP "" 4 339 | . 340 | .nf 341 | 342 | $ npm install ext 343 | . 344 | .fi 345 | . 346 | .IP "" 0 347 | 348 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Express - node web framework 4 | 5 | 163 | 175 | 176 | 177 | 178 | Fork me on GitHub 179 | 180 |
    181 |
    182 | 183 |

    184 | High performance, high class web development for 185 | Node.js 186 |

    187 | 193 |
    194 |

    Express

    195 |

    196 | index 197 |

    198 |
    var app = express.createServer();
    199 | 
    200 | app.get('/', function(req, res){
    201 |     res.send('Hello World');
    202 | });
    203 | 
    204 | app.listen(3000);
    205 | 
    206 | 207 |

    Features

    208 | 209 |
      210 |
    • Robust routing
    • 211 |
    • Redirection helpers
    • 212 |
    • Dynamic view helpers
    • 213 |
    • Application level view options
    • 214 |
    • Content negotiation
    • 215 |
    • Focus on high performance
    • 216 |
    • View rendering and partials support
    • 217 |
    • Environment based configuration
    • 218 |
    • Session based flash notifications
    • 219 |
    • Built on Connect
    • 220 |
    • Executable for generating applications quickly
    • 221 |
    • High test coverage
    • 222 |
    223 | 224 | 225 |

    Contributors

    226 | 227 |

    The following are the major contributors of Express (in no specific order).

    228 | 229 | 235 | 236 | 237 |

    More Information

    238 | 239 | 249 | 250 | 251 |
    252 |
    253 |
    254 | 255 | 256 | -------------------------------------------------------------------------------- /lib/express/view.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Express - view 4 | * Copyright(c) 2010 TJ Holowaychuk 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | 12 | var path = require('path') 13 | , extname = path.extname 14 | , dirname = path.dirname 15 | , basename = path.basename 16 | , utils = require('connect').utils 17 | , clone = require('./utils').clone 18 | , View = require('./view/view') 19 | , Partial = require('./view/partial') 20 | , merge = utils.merge 21 | , http = require('http') 22 | , mime = utils.mime; 23 | 24 | /** 25 | * Memory cache. 26 | * 27 | * @type Object 28 | */ 29 | 30 | var cache = {}; 31 | 32 | /** 33 | * Expose constructors. 34 | */ 35 | 36 | exports = module.exports = View; 37 | exports.Partial = Partial; 38 | 39 | /** 40 | * Export template engine registrar. 41 | */ 42 | 43 | exports.register = View.register; 44 | 45 | /** 46 | * Render `view` partial with the given `options`. 47 | * 48 | * Options: 49 | * - `object` Single object with name derived from the view (unless `as` is present) 50 | * 51 | * - `as` Variable name for each `collection` value, defaults to the view name. 52 | * * as: 'something' will add the `something` local variable 53 | * * as: this will use the collection value as the template context 54 | * * as: global will merge the collection value's properties with `locals` 55 | * 56 | * - `collection` Array of objects, the name is derived from the view name itself. 57 | * For example _video.html_ will have a object _video_ available to it. 58 | * 59 | * @param {String} view 60 | * @param {Object|Array} options, collection, or object 61 | * @return {String} 62 | * @api public 63 | */ 64 | 65 | http.ServerResponse.prototype.partial = function(view, options, locals, parent){ 66 | var self = this; 67 | 68 | // Inherit parent view extension when not present 69 | if (parent && !~view.indexOf('.')) { 70 | view += parent.extension; 71 | } 72 | 73 | // Allow collection to be passed as second param 74 | if (options) { 75 | if ('length' in options) { 76 | options = { collection: options }; 77 | } else if (!options.collection && !options.locals && !options.object) { 78 | options = { object: options }; 79 | } 80 | } else { 81 | options = {}; 82 | } 83 | 84 | // Inherit locals from parent 85 | merge(options, locals); 86 | 87 | // Merge locals 88 | if (options.locals) { 89 | merge(options, options.locals); 90 | } 91 | 92 | // Partials dont need layouts 93 | options.renderPartial = true; 94 | options.layout = false; 95 | 96 | // Deduce name from view path 97 | var name = options.as || Partial.resolveObjectName(view); 98 | 99 | // Render partial 100 | function render(){ 101 | if (options.object) { 102 | if ('string' == typeof name) { 103 | options[name] = options.object; 104 | } else if (name === global) { 105 | merge(options, options.object); 106 | } else { 107 | options.scope = options.object; 108 | } 109 | } 110 | return self.render(view, options, null, parent); 111 | } 112 | 113 | // Collection support 114 | var collection = options.collection; 115 | if (collection) { 116 | var len = collection.length 117 | , buf = ''; 118 | delete options.collection; 119 | options.collectionLength = len; 120 | for (var i = 0; i < len; ++i) { 121 | var val = collection[i]; 122 | options.firstInCollection = i === 0; 123 | options.indexInCollection = i; 124 | options.lastInCollection = i === len - 1; 125 | options.object = val; 126 | buf += render(); 127 | } 128 | return buf; 129 | } else { 130 | return render(); 131 | } 132 | }; 133 | 134 | /** 135 | * Render `view` with the given `options` and optional callback `fn`. 136 | * When a callback function is given a response will _not_ be made 137 | * automatically, however otherwise a response of _200_ and _text/html_ is given. 138 | * 139 | * Options: 140 | * 141 | * Most engines accept one or more of the following options, 142 | * both _haml_ and _jade_ accept all: 143 | * 144 | * - `scope` Template evaluation context (the value of `this`) 145 | * - `debug` Output debugging information 146 | * - `status` Response status code, defaults to 200 147 | * - `headers` Response headers object 148 | * 149 | * @param {String} view 150 | * @param {Object|Function} options or callback function 151 | * @param {Function} fn 152 | * @api public 153 | */ 154 | 155 | http.ServerResponse.prototype.render = function(view, options, fn, parent){ 156 | // Support callback function as second arg 157 | if (typeof options === 'function') { 158 | fn = options, options = {}; 159 | } 160 | 161 | var self = this 162 | , app = this.app 163 | , options = options || {} 164 | , helpers = app.viewHelpers 165 | , dynamicHelpers = app.dynamicViewHelpers 166 | , viewOptions = app.settings['view options'] 167 | , cacheTemplates = app.settings['cache views']; 168 | 169 | // Merge "view options" 170 | if (viewOptions) options = merge(clone(viewOptions), options); 171 | 172 | // Defaults 173 | var self = this 174 | , root = app.settings.views || process.cwd() + '/views' 175 | , partial = options.renderPartial 176 | , layout = options.layout; 177 | 178 | // Layout support 179 | if (true === layout || undefined === layout) { 180 | layout = 'layout'; 181 | } 182 | 183 | // Default execution scope to the response 184 | options.scope = options.scope || this.req; 185 | 186 | // Populate view 187 | options.parentView = parent; 188 | 189 | // "views" setting 190 | options.root = root; 191 | 192 | // "view engine" setting 193 | options.defaultEngine = app.settings['view engine']; 194 | 195 | // Populate view 196 | // TODO: cache 197 | var orig = view = partial 198 | ? new Partial(view, options) 199 | : new View(view, options); 200 | 201 | // Ensure view exists, otherwise try _ prefix 202 | if (!view.exists) { 203 | view = partial 204 | ? new Partial(view.prefixPath, options) 205 | : new View(view.prefixPath, options); 206 | } 207 | 208 | // Ensure view _ prefix exists, otherwise try index 209 | if (!view.exists) { 210 | view = partial 211 | ? new Partial(orig.indexPath, options) 212 | : new View(orig.indexPath, options); 213 | } 214 | 215 | // Merge res.locals 216 | if (this.locals) { 217 | options = merge(this.locals, options); 218 | } 219 | 220 | // Dynamic helper support 221 | if (false !== options.dynamicHelpers) { 222 | // cache 223 | if (!this.__dynamicHelpers) { 224 | this.__dynamicHelpers = {}; 225 | for (var key in dynamicHelpers) { 226 | this.__dynamicHelpers[key] = dynamicHelpers[key].call( 227 | this.app 228 | , this.req 229 | , this); 230 | } 231 | } 232 | 233 | // apply 234 | merge(options, this.__dynamicHelpers); 235 | } 236 | 237 | // Merge view helpers 238 | options = merge(clone(helpers), options); 239 | 240 | // Always expose partial() as a local 241 | options.partial = function(path, opts){ 242 | return self.partial(path, opts, options, view); 243 | }; 244 | 245 | function error(err) { 246 | if (fn) { 247 | fn(err); 248 | } else { 249 | self.req.next(err); 250 | } 251 | } 252 | 253 | // Attempt render 254 | try { 255 | var engine = view.templateEngine 256 | , template = cacheTemplates 257 | ? cache[view.path] || (cache[view.path] = engine.compile(view.contents, options)) 258 | : engine.compile(view.contents, options) 259 | , str = template.call(options.scope, options); 260 | } catch (err) { 261 | return error(err); 262 | } 263 | 264 | // Layout support 265 | if (layout) { 266 | options.isLayout = true; 267 | options.layout = false; 268 | options.body = str; 269 | options.relative = false; 270 | self.render(layout, options, fn, view); 271 | } else if (partial) { 272 | return str; 273 | } else if (fn) { 274 | fn(null, str); 275 | } else { 276 | this.send(str, options.headers, options.status); 277 | } 278 | }; 279 | -------------------------------------------------------------------------------- /lib/express/request.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Express - request 4 | * Copyright(c) 2010 TJ Holowaychuk 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | 12 | var http = require('http') 13 | , utils = require('./utils') 14 | , mime = require('connect').utils.mime; 15 | 16 | /** 17 | * Default flash formatters. 18 | * 19 | * @type Object 20 | */ 21 | 22 | var flashFormatters = exports.flashFormatters = { 23 | s: function(val){ 24 | return String(val); 25 | } 26 | }; 27 | 28 | /** 29 | * Return request header or optional default. 30 | * 31 | * Examples: 32 | * 33 | * req.header('Content-Type'); 34 | * // => "text/plain" 35 | * 36 | * req.header('content-type'); 37 | * // => "text/plain" 38 | * 39 | * req.header('Accept'); 40 | * // => undefined 41 | * 42 | * req.header('Accept', 'text/html'); 43 | * // => "text/html" 44 | * 45 | * @param {String} name 46 | * @param {String} defaultValue 47 | * @return {String} 48 | * @api public 49 | */ 50 | 51 | http.IncomingMessage.prototype.header = function(name, defaultValue){ 52 | return this.headers[name.toLowerCase()] || defaultValue; 53 | }; 54 | 55 | /** 56 | * Check if the _Accept_ header is present, and includes the given `type`. 57 | * 58 | * When the _Accept_ header is not present `true` is returned. Otherwise 59 | * the given `type` is matched by an exact match, and then subtypes. You 60 | * may pass the subtype such as "html" which is then converted internally 61 | * to "text/html" using the mime lookup table. 62 | * 63 | * Examples: 64 | * 65 | * // Accept: text/html 66 | * req.accepts('html'); 67 | * // => true 68 | * 69 | * // Accept: text/*; application/json 70 | * req.accepts('html'); 71 | * req.accepts('text/html'); 72 | * req.accepts('text/plain'); 73 | * req.accepts('application/json'); 74 | * // => true 75 | * 76 | * req.accepts('image/png'); 77 | * req.accepts('png'); 78 | * // => false 79 | * 80 | * @param {String} type 81 | * @return {Boolean} 82 | * @api public 83 | */ 84 | 85 | http.IncomingMessage.prototype.accepts = function(type){ 86 | var accept = this.header('Accept'); 87 | 88 | // Normalize extensions ".json" -> "json" 89 | if (type && '.' == type[0]) type = type.substr(1); 90 | 91 | // When Accept does not exist, or is '*/*' return true 92 | if (!accept || '*/*' == accept) { 93 | return true; 94 | } else if (type) { 95 | // Allow "html" vs "text/html" etc 96 | if (type.indexOf('/') < 0) { 97 | type = mime.types['.' + type]; 98 | } 99 | 100 | // Check if we have a direct match 101 | if (~accept.indexOf(type)) return true; 102 | 103 | // Check if we have type/* 104 | type = type.split('/')[0] + '/*'; 105 | return accept.indexOf(type) >= 0; 106 | } else { 107 | return false; 108 | } 109 | }; 110 | 111 | /** 112 | * Return the value of param `name` when present or `defaultValue`. 113 | * 114 | * - Checks route placeholders, ex: _/user/:id_ 115 | * - Checks query string params, ex: ?id=12 116 | * - Checks urlencoded body params, ex: id=12 117 | * 118 | * To utilize urlencoded request bodies, `req.body` 119 | * should be an object. This can be done by using 120 | * the `connect.bodyDecoder` middleware. 121 | * 122 | * @param {String} name 123 | * @param {Mixed} defaultValue 124 | * @return {String} 125 | * @api public 126 | */ 127 | 128 | http.IncomingMessage.prototype.param = function(name, defaultValue){ 129 | // Route params like /user/:id 130 | if (this.params[name] !== undefined) { 131 | return this.params[name]; 132 | } 133 | // Query string params 134 | if (this.query[name] !== undefined) { 135 | return this.query[name]; 136 | } 137 | // Request body params via connect.bodyDecoder 138 | if (this.body && this.body[name] !== undefined) { 139 | return this.body[name]; 140 | } 141 | return defaultValue; 142 | }; 143 | 144 | /** 145 | * Queue flash `msg` of the given `type`. 146 | * 147 | * Examples: 148 | * 149 | * req.flash('info', 'email sent'); 150 | * req.flash('error', 'email delivery failed'); 151 | * req.flash('info', 'email re-sent'); 152 | * // => 2 153 | * 154 | * req.flash('info'); 155 | * // => ['email sent', 'email re-sent'] 156 | * 157 | * req.flash('info'); 158 | * // => [] 159 | * 160 | * req.flash(); 161 | * // => { error: ['email delivery failed'], info: [] } 162 | * 163 | * Formatting: 164 | * 165 | * Flash notifications also support arbitrary formatting support. 166 | * For example you may pass variable arguments to `req.flash()` 167 | * and use the %s specifier to be replaced by the associated argument: 168 | * 169 | * req.flash('info', 'email has been sent to %s.', userName); 170 | * 171 | * To add custom formatters use the `exports.flashFormatters` object. 172 | * 173 | * @param {String} type 174 | * @param {String} msg 175 | * @return {Array|Object|Number} 176 | * @api public 177 | */ 178 | 179 | http.IncomingMessage.prototype.flash = function(type, msg){ 180 | var msgs = this.session.flash = this.session.flash || {}; 181 | if (type && msg) { 182 | var i = 2 183 | , args = arguments 184 | , formatters = this.app.flashFormatters || {}; 185 | formatters.__proto__ = flashFormatters; 186 | msg = utils.miniMarkdown(utils.htmlEscape(msg)); 187 | msg = msg.replace(/%([a-zA-Z])/g, function(_, format){ 188 | var formatter = formatters[format]; 189 | if (formatter) return formatter(args[i++]); 190 | }); 191 | return (msgs[type] = msgs[type] || []).push(msg); 192 | } else if (type) { 193 | var arr = msgs[type]; 194 | delete msgs[type]; 195 | return arr || []; 196 | } else { 197 | this.session.flash = {}; 198 | return msgs; 199 | } 200 | }; 201 | 202 | /** 203 | * Check if the incoming request contains the "Content-Type" 204 | * header field, and it contains the give mime `type`. 205 | * 206 | * Examples: 207 | * 208 | * // With Content-Type: text/html; charset=utf-8 209 | * req.is('html'); 210 | * req.is('text/html'); 211 | * // => true 212 | * 213 | * // When Content-Type is application/json 214 | * req.is('json'); 215 | * req.is('application/json'); 216 | * // => true 217 | * 218 | * req.is('html'); 219 | * // => false 220 | * 221 | * Ad-hoc callbacks can also be registered with Express, to perform 222 | * assertions again the request, for example if we need an expressive 223 | * way to check if our incoming request is an image, we can register "an image" 224 | * callback: 225 | * 226 | * app.is('an image', function(req){ 227 | * return 0 == req.headers['content-type'].indexOf('image'); 228 | * }); 229 | * 230 | * Now within our route callbacks, we can use to to assert content types 231 | * such as "image/jpeg", "image/png", etc. 232 | * 233 | * app.post('/image/upload', function(req, res, next){ 234 | * if (req.is('an image')) { 235 | * // do something 236 | * } else { 237 | * next(); 238 | * } 239 | * }); 240 | * 241 | * @param {String} type 242 | * @return {Boolean} 243 | * @api public 244 | */ 245 | 246 | http.IncomingMessage.prototype.is = function(type){ 247 | var fn = this.app.is(type); 248 | if (fn) return fn(this); 249 | var contentType = this.headers['content-type']; 250 | if (!contentType) return; 251 | if (!~type.indexOf('/')) type = mime.type('.' + type); 252 | // TODO: remove when connect no longer appends charset... 253 | if (~type.indexOf(';')) type = type.split(';')[0]; 254 | if (~type.indexOf('*')) { 255 | type = type.split('/') 256 | contentType = contentType.split('/'); 257 | if ('*' == type[0] && type[1] == contentType[1]) return true; 258 | if ('*' == type[1] && type[0] == contentType[0]) return true; 259 | } 260 | return ~contentType.indexOf(type); 261 | }; 262 | 263 | // Callback for isXMLHttpRequest / xhr 264 | 265 | function isxhr() { 266 | return this.header('X-Requested-With', '').toLowerCase() === 'xmlhttprequest'; 267 | } 268 | 269 | /** 270 | * Check if the request was an _XMLHttpRequest_. 271 | * 272 | * @return {Boolean} 273 | * @api public 274 | */ 275 | 276 | http.IncomingMessage.prototype.__defineGetter__('isXMLHttpRequest', isxhr); 277 | http.IncomingMessage.prototype.__defineGetter__('xhr', isxhr); 278 | -------------------------------------------------------------------------------- /docs/screencasts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Express - node web framework 4 | 5 | 163 | 175 | 176 | 177 | 178 | Fork me on GitHub 179 | 180 |
    181 |
    182 | 183 |

    184 | High performance, high class web development for 185 | Node.js 186 |

    187 | 193 |
    194 |

    Express

    195 |

    196 | screencasts 197 |

    198 |

    Introduction

    199 | 200 |

    This introduction screencast covers the basics of Express, and how to get started with your first application.

    201 | 202 |

    203 | 204 |

    View Partials

    205 | 206 |

    In this screencast we work with partials to display a collection of users using the Jade template engine, and learn about view path resolution.

    207 | 208 |

    209 | 210 |

    Route Specific Middleware

    211 | 212 |

    In the screencast below we learn about the benefits of route-specific middleware.

    213 | 214 |

    215 | 216 |

    Route Placeholder Preconditions

    217 | 218 |

    Learn about route placeholder (/user/:id) pre-conditions, allowing validation, and loading of data via the named route placeholder segments.

    219 | 220 |

    221 | 222 |
    223 |
    224 |
    225 | 226 | 227 | -------------------------------------------------------------------------------- /docs/applications.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Express - node web framework 4 | 5 | 163 | 175 | 176 | 177 | 178 | Fork me on GitHub 179 | 180 |
    181 |
    182 | 183 |

    184 | High performance, high class web development for 185 | Node.js 186 |

    187 | 193 |
    194 |

    Express

    195 |

    196 | applications 197 |

    198 | 199 | 200 |
    201 | 202 | 203 |

    Learnboost is a free online gradebook application, aimed to crush the competition with innovative, realtime, enjoyable features.

    204 | 205 |

    LearnBoost

    206 | 207 |

    Storify lets you turn what people post on social media websites into compelling stories.

    208 | 209 |

    Storify

    210 | 211 |

    Pakistan Survey by Development Seed, provides in-depth agency-specific analysis from regional experts with data from 1,000 interviews across 120 villages in all seven tribal agencies and mapping of 142 reported drone strikes in FATA through July 2010.

    212 | 213 |

    Pakistan Survey

    214 | 215 |

    Markup.IO allows you to draw directly on any website, then share with others to share your thoughts.

    216 | 217 |

    Markup.IO

    218 | 219 |

    Scrabb.ly is a massively multiplayer scrabble game initially created for the Node Knockout competition.

    220 | 221 |

    Online Realtime Scrabble

    222 | 223 |

    ClickDummy is a rapid mockup prototyping application for designers and dummies.

    224 | 225 |

    Mockup Prototying

    226 | 227 |

    Node Knockout organized the first ever node-specific competition with hundreds of contestants.

    228 | 229 |

    Node Knockout Competition Express

    230 | 231 |

    Widescript is an innovative app that helps you focus and interact with your texts - on your desktop, your couch or on the go.

    232 | 233 |

    Widescript

    234 | 235 |

    e-resistable is an online order takeaway system providing an intuitive way to fill your belly from your computer!

    236 | 237 |

    Online Takeaway

    238 | 239 |

    Top Twitter Trends utilizes MongoDB, Socket.IO, jQuery and many other exciting libraries to bring you trending tweets in realtime.

    240 | 241 |

    Twitter Trends

    242 | 243 |
    244 | 245 | 246 |

    The applications shown above are not listed in any specific order. To have an application added or removed please contact TJ Holowaychuk.

    247 | 248 |
    249 |
    250 |
    251 | 252 | 253 | -------------------------------------------------------------------------------- /test/request.test.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('express') 7 | , connect = require('connect') 8 | , assert = require('assert') 9 | , should = require('should') 10 | , MemoryStore = require('connect/middleware/session/memory'); 11 | 12 | // Prevent reap timer 13 | var memoryStore = new MemoryStore({ reapInterval: -1 }); 14 | 15 | module.exports = { 16 | 'test #isXMLHttpRequest': function(){ 17 | var app = express.createServer(); 18 | 19 | app.get('/isxhr', function(req, res){ 20 | assert.equal(req.xhr, req.isXMLHttpRequest); 21 | res.send(req.isXMLHttpRequest 22 | ? 'yeaaa boy' 23 | : 'nope'); 24 | }); 25 | 26 | assert.response(app, 27 | { url: '/isxhr' }, 28 | { body: 'nope' }); 29 | 30 | assert.response(app, 31 | { url: '/isxhr', headers: { 'X-Requested-With': 'XMLHttpRequest' } }, 32 | { body: 'yeaaa boy' }); 33 | }, 34 | 35 | 'test #header()': function(){ 36 | var app = express.createServer(); 37 | 38 | app.get('/', function(req, res){ 39 | req.header('Host').should.equal('foo.com'); 40 | req.header('host').should.equal('foo.com'); 41 | res.send('wahoo'); 42 | }); 43 | 44 | assert.response(app, 45 | { url: '/', headers: { Host: 'foo.com' }}, 46 | { body: 'wahoo' }); 47 | }, 48 | 49 | 'test #accepts()': function(){ 50 | var app = express.createServer(); 51 | 52 | app.get('/all', function(req, res){ 53 | req.accepts('html').should.be.true; 54 | req.accepts('.html').should.be.true; 55 | req.accepts('json').should.be.true; 56 | req.accepts('.json').should.be.true; 57 | res.send('ok'); 58 | }); 59 | 60 | app.get('/', function(req, res){ 61 | req.accepts('html').should.be.true; 62 | req.accepts('.html').should.be.true; 63 | req.accepts('text/html').should.be.true; 64 | req.accepts('text/*').should.be.true; 65 | req.accepts('json').should.be.true; 66 | req.accepts('application/json').should.be.true; 67 | 68 | req.accepts('xml').should.be.false; 69 | req.accepts('image/*').should.be.false; 70 | req.accepts('png').should.be.false; 71 | req.accepts().should.be.false; 72 | res.send('ok'); 73 | }); 74 | 75 | app.get('/type', function(req, res){ 76 | req.accepts('html').should.be.true; 77 | req.accepts('text/html').should.be.true; 78 | req.accepts('json').should.be.true; 79 | req.accepts('application/json').should.be.true; 80 | 81 | req.accepts('png').should.be.false; 82 | req.accepts('image/png').should.be.false; 83 | res.send('ok'); 84 | }); 85 | 86 | assert.response(app, 87 | { url: '/all', headers: { Accept: '*/*' }}, 88 | { body: 'ok' }); 89 | assert.response(app, 90 | { url: '/type', headers: { Accept: 'text/*; application/*' }}, 91 | { body: 'ok' }); 92 | assert.response(app, 93 | { url: '/', headers: { Accept: 'text/html; application/json; text/*' }}, 94 | { body: 'ok' }); 95 | }, 96 | 97 | 'test #param()': function(){ 98 | var app = express.createServer( 99 | connect.bodyDecoder() 100 | ); 101 | 102 | app.get('/user/:id?', function(req, res){ 103 | res.send('user ' + req.param('id', 'unknown')); 104 | }); 105 | 106 | app.post('/user', function(req, res){ 107 | res.send('user ' + req.param('id')); 108 | }); 109 | 110 | assert.response(app, 111 | { url: '/user/12' }, 112 | { body: 'user 12' }); 113 | 114 | assert.response(app, 115 | { url: '/user?id=5' }, 116 | { body: 'user 5' }); 117 | 118 | assert.response(app, 119 | { url: '/user' }, 120 | { body: 'user unknown' }); 121 | 122 | assert.response(app, 123 | { url: '/user', method: 'POST', data: 'id=1', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }}, 124 | { body: 'user 1' }); 125 | }, 126 | 127 | 'test #flash()': function(){ 128 | var app = express.createServer( 129 | connect.cookieDecoder(), 130 | connect.session({ store: memoryStore }) 131 | ); 132 | 133 | app.flashFormatters = { 134 | u: function(val){ 135 | return String(val).toUpperCase(); 136 | } 137 | }; 138 | 139 | app.get('/', function(req, res){ 140 | req.flash('info').should.be.empty; 141 | req.flash('error').should.be.empty; 142 | req.flash().should.eql({}); 143 | req.session.flash.should.eql({}); 144 | 145 | req.flash('info', 'one').should.equal(1); 146 | req.flash('info', 'two').should.equal(2); 147 | req.flash('info').should.eql(['one', 'two']); 148 | req.flash('info').should.eql([]); 149 | 150 | req.flash('info', 'one').should.equal(1); 151 | req.flash('info').should.eql(['one']); 152 | 153 | req.flash('info', 'Email _sent_.'); 154 | req.flash('info', '