https://github.com/mberlanda/web-development-with-node-and-express
https://github.com/mberlanda/web-development-with-node-and-express
Last synced: 2 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/mberlanda/web-development-with-node-and-express
- Owner: mberlanda
- Created: 2016-08-24T13:31:41.000Z (almost 9 years ago)
- Default Branch: master
- Last Pushed: 2016-08-29T11:18:09.000Z (almost 9 years ago)
- Last Synced: 2024-10-19T11:29:18.253Z (8 months ago)
- Language: JavaScript
- Size: 445 KB
- Stars: 0
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Web Development with Node and Express
This repo is presenting the examples of the book [Web Development with Node and Express](http://shop.oreilly.com/product/0636920032977.do) by Ethan Brown.
- [ch01: Introducing Express](#ch01-introducing-express)
- [ch02: Getting Started with Node](#ch02-getting-started-with-node)
- [ch03: Saving Time with Express](#ch03-saving-time-with-express)
- [ch04: Tidying Up](#ch04-tidyng-up)
- [ch05: Quality Assurance](#ch05-quality-assurance)
- [ch06: The Request and Response Object](#ch06-the-request-and-response-object)
- [ch07: Templating with Handlebars](#ch07-templating-with-handlebars)
- [ch08: Form Handling](#ch08-form-handling)
- [ch09: Cookies and Sessions](#ch09-cookies-and-sessions)
- [ch10: Middleware](#ch10-middleware)
- [ch11: Sending Email](#ch11-sending-email)
- [ch12: Production Concerns](#ch12-production-concerns)
- [ch13: Persistence](#ch13-persistence)
- [ch14: Routing](#ch14-routing)
- [ch15: Rest APIs and JSON](#ch15-rest-apis-and-json)---
#### ch01: Introducing Express
> The [Express website](http://expressjs.com/) describes Express as “a minimal and flexible node.js web application framework, providing a robust set of features for building single and multipage and hybrid web applications.”#### ch02: Getting Started with Node
[https://nodejs.org/en/download/package-manager/](https://nodejs.org/en/download/package-manager/)```bash
curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
sudo apt-get install -y nodejs
/hello_world $ nodejs helloWorld.js
Server started on localhost:3000; press Ctrl-C to terminate....
```#### ch03: Saving Time with Express
```
$ npm install --save express
$ npm install --save express-handlebars
```#### ch04: Tidying Up
- created package.json
- moved fortuneCookies into a lib module#### ch05: Quality Assurance
- Page testing
- Cross-page testing
- Logic testing
- Linting
- [Link checking](https://wummel.github.io/linkchecker/)
```bash
# Page testing
$ npm install --save-dev mocha
$ mkdir public/vendor
$ cp node_modules/mocha/mocha.js public/vendor
$ cp node_modules/mocha/mocha.css public/vendor
$ npm install --save-dev chai
$ cp node_modules/chai/chai.js public/vendor# Cross-page testing
$ npm install --save-dev [email protected]
$ sudo npm install -g mocha
$ mocha -u tdd -R spec qa/tests-crosspage.js 2>/dev/null# Logic testing
$ mocha -u tdd -R spec qa/tests-unit.js# Linting
$ sudo npm install -g jshint
$ jshint meadowlark.js
```Automating with Grunt:
```bash
$ sudo npm install -g grunt-cli
$ npm install --save-dev grunt
# grunt plugins
$ npm install --save-dev grunt-cafe-mocha
npm ERR! peerinvalid The package [email protected] does not satisfy its siblings' peerDependencies requirements!
npm ERR! peerinvalid Peer [email protected] wants grunt@~0.4.1
npm ERR! peerinvalid Peer [email protected] wants grunt@>=0.4.0$ npm install --save-dev grunt-contrib-jshint
$ npm install --save-dev grunt-exec
```---
#### ch06: The Request and Response Object
```javascript
var req = 'The Request Object';req.params; // return []
req.param(name); // recommended to avoid
req.query; // querystring parameters
req.body;
req.route;
req.cookies; // req.signedCookies;
req.headers;
req.accepts([types]); // such as application/json for API
req.ip;
req.path;
req.host;
req.xhr; // true if request orginated from AJAX
req.protocol; // http - https
req.secure; // true if https
req.url // req.originalUrl
req.acceptedLanguages;var res = 'The Response Object';
res.status(code); // sets status code
res.set(name, value); // response header
res.cookie(name, value, [options]); // res.clearCookie(name, [options])
res.redirect([status], url);
res.send(body); // res.send(status, body); first res.set('Content-Type', 'text/plain')
res.json(json); // res.json(status, json)
res.jsonp(json); // res.jsonp(status, json)
res.type(type); // res.type('txt')
res.format(object); // res.format({'text/plain': 'hi there', 'text/html': 'hi there'})
res.attachment([filename]); // res.download(path, [filename], [callback])
res.sendFile(path, [options], [callback]);
res.links(links);
res.locals;
res.render(view, [locals], callback);
```Rendering Content:
```javascript
// basic usage
app.get('/about', function(req, res){
res.render('about');
});
// response codes other than 200
app.get('/error', function(req, res){
res.status(500);
res.render('error');
});
// or on one line...
app.get('/error', function(req, res){
res.status(500).render('error');
});// passing a context to a view, including querystring, cookie, and session values
app.get('/greeting', function(req, res){
res.render('about', {
message: 'welcome',
style: req.query.style,
userid: req.cookie.userid,
username: req.session.username,
});
});// the following layout doesn't have a layout file, so views/no-layout.handlebars
// must include all necessary HTML
app.get('/no-layout', function(req, res){
res.render('no-layout', { layout: null });
});// the layout file views/layouts/custom.handlebars will be used
app.get('/custom-layout', function(req, res){
res.render('custom-layout', { layout: 'custom' });
});// rendering plaintext output
app.get('/test', function(req, res){
res.type('text/plain');
res.send('this is a test');
});// this should appear AFTER all of your routes
// note that even if you don't need the "next"
// function, it must be included for Express
// to recognize this as an error handler
app.use(function(err, req, res, next){
console.error(err.stack);
res.status(500).render('error');
});// this should appear AFTER all of your routes
app.use(function(req, res){
res.status(404).render('not-found');
});
```Processing Forms:
```javascript
// body-parser middleware must be linked in
app.post('/process-contact', function(req, res){
console.log('Received contact from ' + req.body.name +
' <' + req.body.email + '>');
// save to database....
res.redirect(303, '/thank-you');
});// body-parser middleware must be linked in (more robust)
app.post('/process-contact', function(req, res){
console.log('Received contact from ' + req.body.name +
' <' + req.body.email + '>');
try {
// save to database....
return res.xhr ?
res.render({ success: true }) :
res.redirect(303, '/thank-you');
} catch(ex) {
return res.xhr ?
res.json({ error: 'Database error.' }) :
res.redirect(303, '/database-error');
}
});
```Providing an API:
```javascript
var tours = [
{ id: 0, name: 'Hood River', price: 99.99 },
{ id: 1, name: 'Oregon Coast', price: 149.95 },
];// simple GET endpoint returning only JSON
app.get('/api/tours', function(req, res){
res.json(tours);
});// GET endpoint that returns JSON, XML, or text
app.get('/api/tours', function(req, res){
var toursXml = '' +
products.map(function(p){
return '' + p.name + '';
}).join('') + '';var toursText = tours.map(function(p){
return p.id + ': ' + p.name + ' (' + p.price + ')';
}).join('\n');
res.format({
'application/json': function(){
res.json(tours);
},'application/xml': function(){
res.type('application/xml');
res.send(toursXml);
},'text/xml': function(){
res.type('text/xml');
res.send(toursXml);
},
'text/plain': function(){
res.type('text/plain');
res.send(toursXml);
}
});
});// API that updates a tour and returns JSON; params are passed using querystring
app.put('/api/tour/:id', function(req, res){
var p = tours.filter(function(p){ return p.id === req.params.id })[0];
if( p ) {
if( req.query.name ) p.name = req.query.name;
if( req.query.price ) p.price = req.query.price;
res.json({success: true});
} else {
res.json({error: 'No such tour exists.'});
}
});// API that deletes a product
api.del('/api/tour/:id', function(req, res){
var i;
for( var i=tours.length-1; i>=0; i-- )
if( tours[i].id == req.params.id ) break;
if( i>=0 ) {
tours.splice(i, 1);
res.json({success: true});
} else {
res.json({error: 'No such tour exists.'});
}
});
```---
#### ch07: Templating with Handlebars
[Jade](http://jade-lang.com/)Handlebars:
- comments
```html
{{! super-secret comment }}```
- blocks
```
{
currency: {
name: 'United States dollars',
abbrev: 'USD',
},
tours: [
{ name: 'Hood River', price: '$99.95' },
{ name: 'Oregon Coast', price, '$159.95' },
],
specialsUrl: '/january-specials',
currencies: [ 'USD', 'GBP', 'BTC' ],
}
```
```html
-
{{name}} - {{price}}
{{#if ../currencies}}
({{../../currency.abbrev}})
{{/if}}
{{#each tours}}
{{! I'm in a new block...and the context has changed }}
{{/each}}
{{#unless currencies}}
All prices in {{currency.name}}.
{{/unless}}
{{#if specialsUrl}}
{{! I'm in a new block...but the context hasn't changed (sortof) }}
Check out our specials!
{{else}}
Please check back often for specials.
{{/if}}
{{#each currencies}}
{{.}}
{{else}}
Unfortunately, we currently only accept {{currency.name}}.
{{/each}}
```
- Server-Side Templates
```javascript
// npm install --save express-handlebars
var handlebars = require('express-handlebars')
.create({ defaultLayout: 'main' });
app.engine('handlebars', handlebars.engine);
app.set('view engine', 'handlebars');
// layout on
app.get('/foo', function(req, res){
res.render('foo');
});
// layout off
app.get('/foo', function(req, res){
res.render('foo', { layout: null });
});
// custom layout
app.get('/foo', function(req, res){
res.render('foo', { layout: 'custom' });
});
```
- partials (see weather partial example)
- sections (see jquery-test example)
- Client-Side Handlebars(see nursery-rhyme example)
---
#### ch08: Form Handling
- HTML Forms
```html
Your favorite color:
Submit
```
- Form Handling with Express / Handling AJAX Forms (see newsletter example)
```bash
$ npm install --save body-parser
```
- File Uploads
```bash
$ npm install --save formidable
```
- jQuery File Upload
```bash
$ npm install --save jquery-file-upload-middleware
```
---
#### ch09: Cookies and Sessions
```bash
$ npm install --save cookie-parser
```
```javascript
app.use(require('cookie-parser')(credentials.cookieSecret));
// where you have access to a res object
res.cookie('monster', 'nom nom');
res.cookie('signed_monster', 'nom nom', { signed: true });
var monster = req.cookies.monster;
var signedMonster = req.signedCookies.signed_monster;
res.clearCookie('monster');
```
Options for cookies:
- domain
- path
- maxAge
- secure
- httpOnly
- signed
[cookie-session middleware](https://www.npmjs.org/package/cookie-session)
```bash
$ npm install --save express-session
```
express-session middleware configuration options:
- resave
- saveUnitialized
- secret
- key
- store
- cookie
```javascript
// Using Sessions
req.session.userName = 'Anonymous';
var colorScheme = req.session.colorScheme || 'dark';
req.session.userName = null; // this sets 'userName' to null, but doesn't remove it
delete req.session.colorScheme; // this removes 'colorScheme'
```
#### ch10: Middleware
- Intro (see ./middleware-examples)
Common Middleware:
```
npm install --save connect
npm install --save body-parser
npm install --save cookie-parser
npm install --save express-session
npm install --save csurf
npm install --save errorhandler
npm install --save static-favicon
npm install --save morgan
npm install --save response-time
npm install --save vhost
```
#### ch11: Sending Email
```
npm install --save nodemailer
```
```javascript
var nodeMailer = require('nodemailer');
// setup the mailer
var mailTransport = nodemailer.createTransport('SMTP',{
host: 'smtp.meadowlarktravel.com',
secureConnection: true,
port: 465, // use SSL
auth: {
user: credentials.meadowlarkSmtp.user,
pass: credentials.meadowlarkSmtp.password,
}
});
// sending mail
mailTransport.sendMail({
from: '"Meadowlark Travel" ',
to: '[email protected]',
subject: 'Your Meadowlark Travel Tour',
text: 'Thank you for booking your trip with Meadowlark Travel. ' +
'We look forward to your visit!',
}, function(err){
if(err) console.error( 'Unable to send email: ' + error );
});
// sending mail to multiple recipients
mailTransport.sendMail({
from: '"Meadowlark Travel" ',
to: '[email protected], "Jane Customer" , ' +
'[email protected]',
subject: 'Your Meadowlark Travel Tour',
text: 'Thank you for booking your trip with Meadowlark Travel.' +
'We look forward to your visit!',
}, function(err){
if(err) console.error( 'Unable to send email: ' + error );
});
// largeRecipientList is an array of email addresses
var recipientLimit = 100;
for(var i=0; i',
to: largeRecipientList
.slice(i*recipientLimit, i*(recipientLimit+1)).join(','),
subject: 'Special price on Hood River travel package!',
text: 'Book your trip to scenic Hood River now!',
}, function(err){
if(err) console.error( 'Unable to send email: ' + error );
});
}
// sending HTML email
mailTransport.sendMail({
from: '"Meadowlark Travel" ',
to: '[email protected], "Jane Customer" ' +
', [email protected]',
subject: 'Your Meadowlark Travel Tour',
html: '
Meadowlark Travel
\nThanks for book your trip with ' +
'Meadowlark Travel. We look forward to your visit!',
// text: 'Thank you for booking your trip with Meadowlark Travel. ' +
// 'We look forward to your visit!',
// instead of text:, simply
generateTextFromHtml: true,
}, function(err){
if(err) console.error( 'Unable to send email: ' + error );
});
// images in HTML email
```
Using Views to Send HTML Email
```javascript
// see view/email/cart-thank-you.handlebars
// see lib/email.js
var emailService = require('./lib/email.js')(credentials);
emailService.send('[email protected]', 'Hood River tours on sale today!',
'Get \'em while they\'re hot!');
// email as a site monitoring tool
if(err){
email.sendError('the widget broke down!', __filename);
// ... display error message to user
}
// or
try {
// do something iffy here....
} catch(ex) {
email.sendError('the widget broke down!', __filename, ex);
// ... display error message to user
}
```
#### ch12: Production Concerns
- execution environments `$ NODE_ENV=production node meadowlark.js`
- environment-specific configuration
```
// logging:
// morgan (npm install --save morgan) for development
// express-logger (npm install --save express-logger) for production
```
- scaling out with app clusters (see meadowlark_cluster.js)
- handling uncaught exceptions (using [domains](https://engineering.gosquared.com/error-handling-using-domains-node-js) see meadowlark_domain.js)
- scaling out with multiple servers
- monitoring your websit with third-party uptime monitors
- stress testing
```
$ npm install --save loadtest
$ mocha -u tdd -R spec qa/tests-stress.js
```
#### ch13: Persistence
- Filesystem Persistence
- Cloud Persistence (see [AWS](https://aws.amazon.com/sdk-for-node-js/) or [Azure](https://azure.microsoft.com/en-us/documentation/articles/storage-nodejs-how-to-use-blob-storage/) documentation)
```javascript
// AWS sample
var filename = 'customerUpload.jpg';
aws.putObject({
ACL: 'private',
Bucket: 'uploads',
Key: filename,
Body: fs.readFileSync(__dirname + '/tmp/' + filename)
});
// Azure sample
var filename = 'customerUpload.jpg';
var blobService = azure.createBlobService();
blobService.putBlockBlobFromFile('uploads', filename, __dirname + '/tmp/' + filename);
```
- Database Persistence (NoSQL)
... [MongoDB: The Definitive Guide](http://usuaris.tinet.cat/bertolin/pdfs/mongodb_%20the%20definitive%20guide%20-%20kristina%20chodorow_1401.pdf) by Kristina Chodorow
... [https://www.mongodb.com/](https://www.mongodb.com/): `npm install --save mongodb`
... 'sudo apt-get install mongodb'
... [Mongoose](http://mongoosejs.com/): `npm install --save mongoose`
... [RoboMongo](https://robomongo.org/)
... [Connecting and Working with MongoDB with Node & Express](https://www.terlici.com/2015/04/03/mongodb-node-express.html)
... `npm install --save session-mongoose` (not supported in the latest version of express and connect, see [repo](https://github.com/donpark/session-mongoose) )
#### ch14: Routing
- Subdomains
```javascript
// npm install --save vhost
// create "admin" subdomain...this should appear
// before all your other routes
var admin = express.Router();
app.use(vhost('admin.*', admin));
// create admin routes; these can be defined anywhere
admin.get('/', function(req, res){
res.render('admin/home');
});
admin.get('/users', function(req, res){
res.render('admin/users');
});
```
- Routes are Middleware
- Route Paths and Regular Expressions
- Route Parameters
- Declaring Routes in a Module
- Automatically Rendering Views
```javascript
var autoViews = {};
var fs = require('fs');
app.use(function(req,res,next){
var path = req.path.toLowerCase();
// check cache; if it's there, render the view
if(autoViews[path]) return res.render(autoViews[path]);
// if it's not in the cache, see if there's
// a .handlebars file that matches
if(fs.existsSync(__dirname + '/views' + path + '.handlebars')){
autoViews[path] = path.replace(/^\//, '');
return res.render(autoViews[path]);
}
// no view found; pass on to 404 handler
next();
});
```
#### ch15: Rest APIs and JSON
```javascript
// cross-origin resource sharing may expose the application to attacks
// npm install --save cors
// Test the API with restler
// npm install --save-dev restler
// Using a REST Plugin
// npm install --save connect-rest
var rest = require('connect-rest');
// website routes go here
// define API routes here with rest.VERB....
// API configuration
var apiOptions = {
context: '/api',
domain: require('domain').create(),
};
// link API into pipeline
app.use(rest.rester(apiOptions));
// 404 handler goes here
```