47
Building Web Apps using Node.js @DaveChubbuck https://github.com/davidchubbs

Build Web Apps using Node.js

Embed Size (px)

DESCRIPTION

Learn how to get started quickly with Node.js by leveraging the popular framework Express.

Citation preview

Building Web Apps using Node.js

@DaveChubbuck https://github.com/davidchubbs

What is Node.js?# file called: useless.jsconsole.log(‘Hello world’);

# if useless.js was run in the browser:

# if useless.js was run on Node.js, using Terminal:

# file called: useless.jsconsole.log(‘Hello world’);

# if useless.js was run in the browser:

# if useless.js was run on Node.js, using Terminal:

What is Node.js?

# file called: useless.jsconsole.log(‘Hello world’);

# if useless.js was run in the browser:

# if useless.js was run on Node.js, using Terminal:

What is Node.js?

Node.js executes Javascript files.

Node.js is a Platform (not a framework)

• Node.js includes some utilities to make it consistent with the browser, like:

• Timer API: setTimeout

• Console API: console.log

Node.js is a Platform (not a framework)

• Node.js also includes utilities for various types of network and file I/O. These include (among others):

• http, https

• Datagram (UDP)

• NET (TCP)

• fs (File System)

Node.js is a Platform (not a framework)

Frameworks are interested in organizing your code and/or providing sugar for faster development.

Frameworks are interested in organizing your code and/or providing sugar for faster development.

Node.js provides minimal, low-level APIs necessary to do things like build a web-server, command line tool, etc. Consequentially, we consider it to be a

platform, not a framework, though there are frameworks built on top of Node.js

Complete API: http://nodejs.org/api/

Node.js Modules

Scope, Exporting, & Importing

Including logic from another file does not affect the global scope. Each file has its own scope, keeping everything in the file private unless it is exported.

module.exports & exports

# exports-example.js

var private = ‘this is private since it is not exported’;exports.public = ‘this is public’;exports.method = function () {

return private;};

# module-exports-example.js

function Constructor () {}module.exports = Constructor;

use module.exports when exporting 1 value, exports when exporting 2+ values

caveats

Do not ever reassign exports (that’s what module.exports is for). exports = {}; // DO NOT DO THIS!

Do not use both exports & module.exports in the same module, else exports will be ignored. module.exports = ‘this will work’; exports.name = ‘this will be ignored’;

requireTo import/include a module, we use require()

require

// .js & .json can be omitted// filepaths can be relative or absolute (using __dirname)// module name can be a directory if the directory contains index.js or // ... if package.json `main` property points to alternative file// 3rd party modules can be just their name (explained later)

var obj = require(__dirname + ’./exports-example’);var Constructor = require(‘./module-exports-example’);var thirdParty = require(‘not-yours’);

obj.public //=> ‘this is public’typeof obj.method //=> ‘function’

typeof Constructor //=> ‘function’var instance = new Constructor();

To import/include a module, we use require()

Module Caching

# index.jsvar util = require(‘./util’),

example = require(‘./example’);console.log(util());

# example.jsvar util = require(‘./util’);util();

# util.jsvar counter = 0;module.exports = function () {

return ++counter;}

>> node index.js # what will this output?

# index.jsvar util = require(‘./util’),

example = require(‘./example’);console.log(util());

# example.jsvar util = require(‘./util’);util();

# util.jsvar counter = 0;module.exports = function () {

return ++counter;}

>> node index.js2

Modules is one of the best things about Node.js

Node’s implementation of modules is done so well that Node.js projects tend to be super modular,

promoting low coupling & high cohesion

(it also influenced Browserify, which lets us write front-end JS files using this same modular pattern)

{ "name": "package-name", "version": "1.0.0", "description": "", "author": { "name": "David Chubbs", "url": "https://github.com/davidchubbs" }, "private": true, "main": "./server.js", "scripts": { "test": "mocha", "start": "node server.js" }, "dependencies": { "express": "^4.0.0" }, "devDependencies": { "jshint": "~2.5", "mocha": "~1.21", "should": "~4.0" }, "engines": { "node": ">=0.10.0" }}

package.json

# install dependencies - will show up in ./node_modules/npm install # install packages in package.json filenpm install --production # do not install devDependencies

npm install name # install name package# to save that dependency in your package.json file, use:npm install name --save-dev # saves to devDependenciesnpm install name --save # saves to dependencies

npm rm name # remove packagenpm rm name --save # remove from package.json also

npm outdated # check if dependencies are outdatednpm outdated name # check if name is outdated

npm run name # execute scripts.name in package.jsonnpm start # shortcut for `npm run start`npm test # shortcut for `npm run test`

npm rm --help # get help on a commandnpm install name -g # run command globally

express

Middleware

var express = require('express');var app = express();

app.use(function (req, res, next) { res.set('X-Header', 'From Express :)'); next();});

app.use(function (req, res) { res.send('this is where you will stop');});

app.use(function (req, res, next) { res.send('you will never see this');});

app.listen(3000);

Middleware is a design pattern akin to a pipeline. Requests start at the top and flow until a particular middleware fails to use next()

>> curl -i http://localhost:3000HTTP/1.1 200 OKX-Header: From Express :)Content-Type: text/html; charset=utf-8Content-Length: 27Connection: keep-alive

this is where you will stop

Middleware

var express = require('express');var app = express();

app.use(function (req, res, next) { res.set('X-Header', 'From Express :)'); next();});

app.use(function (req, res) { res.send('this is where you will stop');});

app.use(function (req, res, next) { res.send('you will never see this');});

app.listen(3000);

Middleware is a design pattern akin to a pipeline. Requests start at the top and flow until a particular middleware fails to use next()

Mounting

...

app.use('/users', function (req, res, next) { res.set('X-Role', 'User'); next();});

app.use('/admins', function (req, res, next) { res.set('X-Role', 'Admin'); next();});

app.use(function (req, res, next) { res.send('this is where you will stop');});

...

Middleware can be mounted to base directories.

Mounting

...

app.use('/users', function (req, res, next) { res.set('X-Role', 'User'); next();});

app.use('/admins', function (req, res, next) { res.set('X-Role', 'Admin'); next();});

app.use(function (req, res, next) { res.send('this is where you will stop');});

...

Middleware can be mounted to base directories.

>> curl -i http://localhost:3000/admins/indexHTTP/1.1 200 OKX-Role: AdminContent-Type: text/html; charset=utf-8Content-Length: 27Connection: keep-alive

this is where you will stop

Mounting

...

app.use('/users', function (req, res, next) { res.set('X-Role', 'User'); next();});

app.use('/admins', function (req, res, next) { res.set('X-Role', 'Admin'); next();});

app.use(function (req, res, next) { res.send('this is where you will stop');});

...

Middleware can be mounted to base directories.

>> curl -i http://localhost:3000/admins/indexHTTP/1.1 200 OKX-Role: AdminContent-Type: text/html; charset=utf-8Content-Length: 27Connection: keep-alive

this is where you will stop

>> curl -i http://localhost:3000/usersHTTP/1.1 200 OKX-Role: UserContent-Type: text/html; charset=utf-8Content-Length: 27Connection: keep-alive

this is where you will stop

Mounting

...

app.use('/admins', function (req, res) { res.send(req.url);});

...

The base directory is stripped from the mounted middleware function, meaning the mounted middleware does not have to understand where it is mounted.

Mounting

...

app.use('/admins', function (req, res) { res.send(req.url);});

...

The base directory is stripped from the mounted middleware function, meaning the mounted middleware does not have to understand where it is mounted.

>> curl -i http://localhost:3000/admins/indexHTTP/1.1 200 OKContent-Type: text/html; charset=utf-8Content-Length: 27Connection: keep-alive

/index

Routing

app.get('/profile', function (req, res) { res.send('view profiles');});

app.post('/profile', function (req, res) { res.send('update profiles');});

app.get('/profile', function (req, res) { res.send('view profiles');});

app.post('/profile', function (req, res) { res.send('update profiles');});

app.get('/profile/:id', function (req, res) { res.send('profile w/ id: ' + req.params.id);});

app.get('/profile', function (req, res) { res.send('view profiles');});

app.post('/profile', function (req, res) { res.send('update profiles');});

app.get('/profile/:id', function (req, res) { res.send('profile w/ id: ' + req.params.id);});

app.param('name', function (req, res, next, name) { // load user before hand using `name` - add to req.user next();});

app.get('/user/:name', function (req, res) { // use req.user});

app.all(‘/any-http-method’, function (req, res) { // no matter the http-method (GET, POST, PUT, etc.) // this will catch all requests @ /any-http-method ...});

app.route(‘/write/path/once’) .all(function (req, res, next) { // do something BEFORE method-specific handling next(); .get(function (req, res) { ... }) .post(function (req, res) { ... });

// Routers are isolated, making them akin to mini-appsvar router = express.Router(); // create a router

// add custom middleware to routerrouter.use(function (req, res, next) { next(); // use like any other middleware});

// add routesrouter.route(‘/profile’) .get(function (req, res) { ... });

app.use(‘/users’, router);

// catching 404’s involves having a route like this after your routesapp.use(function (req, res) { res.status(404).send(‘Page not found’);});

// then catch 500’s// (all 4 arguments are required)app.use(function (err, req, res, next) { console.error(err); res.status(500).send(‘Server error occurred’);});

// if an error occurs, you can immediately pass control to// your 500/error route by returning next(err)

app.param(‘id’, function (req, res, next, id) {

User.find(id, function (err, user) { if (err) { return next(new Error(‘User could not be found’)); }

req.user = user; next(); });

});

Popular Middleware& Configuration

// setup templating// ================

var cons = require(‘consolidate’);

// set .ejs files will use EJS templating engineapp.engine(‘ejs’, cons.ejs);

// set .ejs as the default extensionapp.set(‘view engine’, ‘ejs’);// set templates pathapp.set(‘views’, __dirname + ‘/views’);

// render template// ===============

app.get(‘/path’, function (req, res) {

// renders template: __dirname + ’/views/template-file.ejs’ res.render(‘template-file’, { dynamicData: 1 });

});

var compress = require('compression');var bodyParser = require('body-parser');var logger = require('morgan');

app.use(compress());app.use(bodyParser());app.use(express.static(__dirname + '/public'));app.use(logger('dev'));// or write custom syntax - :method :url :status :res[content-length]

var cookie = require('cookie-parser');var session = require('express-session');var RedisSession = require("connect-redis")(session);

// option 1: basic cookiesapp.use(cookie());// in middleware, use res.cookie('name', 'value') to set, req.cookies.name to get

// option 2: signed cookiesapp.use(cookie("secret-used-to-sign-cookies"));// res.cookie('name', 'value', {signed: true}), req.signedCookies.name

// option 3: sessionsapp.use(cookie());app.use(session({ store : new RedisSession({ port:6379, host:'localhost' }), name : 'sessions', secret: 'secrect-session-hash', cookie: { maxAge : 10800 * 1000 } // seconds * milliseconds}));

// option 4: see example using passport for auth

req & res

req.params //=> {object} route paramatersreq.query //=> {object} parsed query-string ?n=value => {n:value}req.body //=> {object} parsed form input

req.route //=> {object} matched route details

req.get(header) //=> {string} get header field (case insensitive)req.ipreq.pathreq.url //=> {string} .path + .queryreq.baseUrl //=> {string} prefix when mountedreq.originalUrl //=> {string} .baseUrl + .url (untouched)req.secure //=> {boolean} if SSL is usedreq.hostname //=> {string} header Host valuereq.subdomains //=> {array}

res.set(headerName, value)res.status(404) // set status code of responseres.cookie(name, value, [options])res.clearCookie(name, [options])

// generating a response packet// ============================res.send(data)res.render(template, [data, callback])res.json(data)res.redirect([status=302], url)// url can be host relative (‘/path’) or path relative (‘add-to-path’)

Go to http://expressjs.com/4x/api.html for the full API docs.

This will flesh out the remaining `req` & `res` object properties.