###Scope
The only recommended way of creating a new variable scope is to use a function. So an IIFE is an appropriate method for handling scope.
###Closures
When a function is defined inside another function, the inner function has access to variables declared within the outer function, even when after the outer function returns.
###Revealing module pattern
The revealing module pattern uses a function to return an object (functionality and data):
function createThing() {
var message = 'this is a thing';
function doThingStuff1() {
//...
};
function doOtherThingStuff() {
//...
};
return {
doThingStuff: doThingStuff,
doOtherThingStuff: doOtherThingStuff,
message: message
};
}When not explicitly set, or when not
within a method, this refers to the
Node global object, just as it refers
to the window object when running code
in a browser. As always, the keyword
new in JavaScript when used with a function
call, creates a new object and applies that
object to the this within that called
function:
function foo() {
console.log(this);
}
var thing1 = foo(); // foo logs "global"
var thing2 = new foo(); // foo logs empty objectEvery object in JavaScript has an internal
link to another object: its prototype. An
object's prototype object is accessible via
the __proto__ property. When a property
is accessed on an object, if that object has
no such property, the prototype is checked,
then its prototype is checked, up to the root
JavaScript object. As soon as a property is
found, it is returned. If no property is
found, it is undefined.
var foo = {};
foo.__proto__.bar = 123;
console.log(foo.bar); // 123
console.log(global.bar); // 123It is poor practice to use any __ prefixed
properties, as those are essentially hidden
implementation details. But when the new
operator is used with a function the
__proto__ is set to the function's
.prototype. A function's .prototype
value is the object that is used for the
prototype of any new object created when
using the new keyword:
function foo() {};
foo.prototype = {
name = 'fooname'
};
var baz = new foo();
console.log(baz.name); // fooname
var qux = new foo();
console.log(qux.name); // fooname
qux.name = 'q';
console.log(qux.name); // q
console.log(baz.name); // foonameThe above example illustrates that prototypes are good for saving memory when attributes or behaviors are shared, but are problematic for attributes that need to be written to. So an object's methods (functions) are ideal candidates to be defined on a prototype because all objects created using the revealing module pattern will have the same shared functions/methods, but attributes are part of each individual object, where they belong. So an improved pattern to allow reproduction of objects would be:
function Thing() {
this.name = 'thing name'
}
Thing.prototype.doThingStuff = function() {
console.log('do ' + this.name);
}
var thing = new Thing();
console.log(thing.name); // "thing name"
thing.doThingStuff(); // "do thing name
var thing2 = new Thing();
thing2.name = "two";
console.log(thing2.name); // "two"
console.log(thing.name); // "thing name"
thing2.doThingStuff(); // "do twoMost "classes" inside core Node.js are written using the above pattern.
An asyncronous callback that throws an error after its calling code finishes executing will not be caught in a wrapping try/catch because that code has already finished executing when the error is thrown:
try {
setTimeout(function() {
throw new Error("something bad");
}, 1000);
} catch (err) {
console.log('will never happen');
}In the above example, the node process exits because the error is thrown outside of a try / catch. This situation must be handled by putting the try / catch inside of the callback itself:
setTimeout(function() {
try {
throw new Error("bad");
} catch (ex) {
console.log('caught bad');
}
}, 1000);However, this can cause a problem if a callback needs to be notified of an error. Consider:
function doThing(callback) {
try {
throw new Error('x');
// notify callback() somehow
} catch (ex) {
// notify callback() of error somehow
}
}The pattern used in Node.js for these situations is to send an error object to the callback as the first parameter if there is an error. A falsy value is passed as the first parameter if no error:
function doThing(callback) {
var otherData = { };
try {
throw new Error('x');
callback(null, otherData);
} catch (ex) {
callback(ex, null);
}
}When require() is used, Node.js runs
the specified destination JavaScript file
in a new, isolated scope, and returns the
value specified by module.exports from
the file. To store the value of the
module.exports, assign the return of
require to a variable:
var thing = require('./thing.js');Note that a call to require() is a
blocking call. Execution will wait until
the call is complete.
The return value of a call to require()
is cached so subsequent calls passing in
the same destination file (irrespective of
path) will get the cached module.exports
value returned originally. So if a mutable
object is returned, each call to require
for a particular file will return the same
shared object instance.
###Object factories
If the default behavior of returning the
same object for require() calls that
resolve to the same file is not desired,
a simple object factory setup can be used:
module.exports = function() {
return {
//object to be created with each call to require
};
}And "requiring" the module like this:
var foo = (require('./foo'))();The module.exports object is implicitly an
empty object that a module can add properties
to. The exports alias can be used as a
shorthand for module.exports. Node just
creates a variable like this:
var exports = module.exports;So it is important to note that the exports
variable could accidentally be reassigned:
exports.a = 123;
console.log(module.exports.a); // 123
exports = 'whoops!';
console.log(module.exports); // { a: 123 }So exports should be used for attaching
but never assigning.
Modules best practices
- Dont' use
.jsin calls torequire() - Use relative paths (e.g.
./foo) in calls torequire()for your own project modules. Leave out paths in calls torequire()for node_modules and core modules. - Use
require()to get a single file per folder, i.e.index.js, specifying the folder name.
The console is available in any code in a
node application. Typical methods are
available on the console object, such as
log.
The setTimeout and setInterval functions
are also globally available.
The __filename and __dirname are available
in every file in a node app. They store the
value of the current file and current file's
directory path.
The process variable is accessible anywhere
as well, and holds data and functionality
dealing with the Node.js process. The argv
property of process contains an array of
the arguments passed to the Node.js process.
For example this command:
node app.js foo -bar bazWould result in an argv value like:
['c:\\program files\\node.js\\node.exe',
'c:\\users\\username\\app.js',
'foo',
'-bar',
'baz']The Buffer class is also available globally.
Strings and buffers convert into each other
very easily:
var str = 'this is a string';
var buf = new Buffer(str, 'utf8');
var roundTrip = buf.toString('utf8');Apparently, 'utf-8' and 'utf8' both work.
All the global methods and properties are
members of global, much like the
window object in a browser:
console.log(process === global.process); // trueBut unlike window, a module's variables
are placed in an isolated scope. Members
can but shouldn't be added to global.
Core modules are included using require()
without a path to the module, but just
the module name itself.
The path module has useful methods to
resolve inconsistencies between filesystems.
For instance, path.normalize(str) will
change a path str to a valid path for the
current system. And path.join() takes
an arbitrary number of args and joins them
together into a valid path for the current
system. It also automatically handles
path delimiters:
var path = require('path');
console.log(path.join('foo', '/bar', 'baz'));
// logs "foo/bar/baz" or "foo\bar\baz"The dirname() method returns the directory
to a file's path, basename() returns a
file name without an extension, and extname()
returns a file's extension:
var path = require('path');
var f = 'c:\\dev\\test.html';
var d = path.dirname(f); // c:\dev
var b = path.basename(f); // test.html
var e = path.extname(f); // .htmlThe fs module has filesystem functionality
such as writeFileSync() and readFileSync():
var path = require('path');
var fs = require('fs');
fs.writeFileSync(path.join(__dirname, 'test.txt'), 'file contents', 'utf8');
var contents = fs.readFileSync(path.join(__dirname, 'test.txt'), 'utf8');
console.log(contents); // file contentsThe -Sync versions of these methods should
be used only in certain scenarios. Usually the
versions with callbacks are appropriate:
var fs = require('fs');
fs.readFile('test.txt', 'utf8', function(err, data) {
if (err) {
console.log(err);
return;
}
console.log(data);
});The os module has methods exposing system info,
such as totalmem() and freemem() and cpus():
var os = require('os');
console.log(os.totalmem());
console.log(os.freemem());
console.log(os.cpus().length);The util module has general functionality,
such as the log() method which prints a
timestamp with your message. The format()
method is similar to printf in C. Available
placeholders include %s for strings and %d
for numbers:
var util = require('util');
util.log('The %s is %d dollars', 'thing', 50);util also has methods isArray, isDate
and isError.
Scanning for a node module required via a call
to require() follows these rules:
- if the value passed to
requirestarts with./,../, or/it is assumed to be a file based module and the path specified is used to find the module - if the value passed to
requiredoes not start with one of those strings, it is assumed to be a core module or stored in node_modules.
- Searching for core modules is done first.
- If there is not a matching core module, the node_module folder in the current directory is searched.
- If there is not a matching module in the current directory's node_module folder, the parent directory's node_module folder is searched, and so on until the module is found or until the search hits the root and gives up.
So the only difference between a file based
module and a node_module module is the way each
one is referenced in the require call and the
way the filesystem is searched for the .js file.
Sometimes multiple files need to work together
as a module, in which case they can be put in
a folder together, with an index.js "entry point"
file. Then the module is required using a call
to require with the folder name as the parameter.
The scanning process for node_module modules
makes it simpler to use require anywhere in an
application and have it automatically resolve to
the right code. It also makes it easy to share
modules between different apps - the module can
be stored in a common ancestor directory's
node_module folder.
JSON files can be required just as a module is,
but instead of executing the code in the module,
when require is called, a JavaScript object is
returned:
{
"foo": "bar",
"baz": "boo"
}// if above file is foo.json
var obj = require('./foo');
console.log(obj.baz); // booThe above is much easier than opening files
and parsing them with JSON (which is also
available on global).
A typical inheritance pattern in JavaScript involves a "parent" such as:
function Animal(name) {
this.name = name;
}
Animal.prototype.walk = function(destination) {
console.log(this.name + ' is walking to ' + destination);
}
var animal = new Animal('elephant');
animal.walk('Melbourne'); // elephant is walking to melbourneAn inheriting type could then be used:
function Bird(name) {
Animal.call(this, name);
}
Bird.prototype.__proto__ = Animal.prototype;
Bird.prototype.fly = function(destination) {
console.log(this.name + ' is flying to ' + destination);
}
var b = new Bird('finch');
b.walk('slc');
b.fly('chandler')Using the __proto__ property is not recommended.
A better way to set up this prototype chain in Node.js:
var utils = require('utils');
function Bird(name) {
Animal.call(this, name);
}
utils.inherits(Bird, Animal);
Bird.prototype.fly = function() { /*...*/ };The utils module uses Object.create() behind the
scenes to "properly" create a prototype chain.
Events can be emitted using the events module (built-in)
and the associated EventEmitter object:
var EventEmitter = require('events').EventEmitter;
var emitter = new EventEmitter();
emitter.on('cheese', function(arg1, arg2) {
console.log('cheese raised', arg1, arg2);
});
emitter.emit('cheese', { a: 123 }, { b: 456 });Any arbitratry number of subscribers can handle a single emitted event, and they are called in the order that they registered for the event. Also, any arguments passed in to the event handlers are shared among all handlers.
To remove a handler, a reference to the exact
handling function must be passed to removeListener:
var EventEmitter = require('events').EventEmitter;
var emitter = new EventEmitter();
var fooHandler = function(a,b) {
console.log('foo handled');
emitter.removeListener('foo', fooHandler);
};
emitter.on('foo', fooHandler);
emitter.emit('foo'); // foo handled
emitter.emit('foo'); // no console messageThe handling of an event one time only need not
use removeListener. Instead, once can be used
in place of on:
var EventEmitter = require('events').EventEmitter;
var emitter = new EventEmitter();
emitter.once('foo', function() {
console.log('handled');
});
emitter.emit('foo'); // handled
emitter.emit('foo'); // not handledEventEmitter has a method listeners than returns
and array of functions that have been registered for
the name of the passed in event:
var funcs = emitter.listeners('foo');
console.log(funcs); // [[Function: a], [Function: b]]The EventEmitter raises a newListener event when
a new listener is added and a removeListener event
when one is removed.
Memory leaks occur when subscribing to events in a
callback or loop but forgetting to unsubscribe. EventEmitter
will print a warning to the console if more than 10
subscribers register for a single event.
The error event is raised when an unhandled exception
occurs during executions of a Node.js program. If there
is no listener for the error event registered,
the default behavior occurs: a stack trace is printed to
the console and the program is exited.
The EventEmitter class is the basis for many derived
types in open source software projects. These are
modules that inherit from EventEmitter and export
the resulting object.
var EE = require('events').EventEmitter;
var utils = require('utils');
var Thing = function() {
EE.call(this);
};
utils.inherits(Thing, EE);
Thing.prototype.connect = function() {
/*...*/
this.emit('connected');
};
module.exports = Thing;Such an object can then be used:
var Thing = require('./thing.js');
var thing = new Thing();
thing.on('connected', function() {
console.log('received a connected message');
});
thing.connect();The process global in Node.js is an example
of one such type that inherites from EventEmitter.
For instance, it emits an uncaughtException
event, which can be subscribed (but processing
should never continue after handling it because
it is raised only when execution is in an unreliable
state:
process.on('uncaughtException', function(err) {
console.log(err.exception);
console.log(err.stack);
process.exit(1); // return error status, no more processing
});
throw new Error('this will break something');The process global also emits the exit event
but the process is being torn down at that point
so it is not possible to cancel the exit or run
any async code:
process.on('exit', function() {
console.log('this should get logged at the end.');
});Streams are essential for performance, for example, serving a large file without the help of streams would require loading the entire file in memory before starting to send the first bits of the file to the client. With the help of a stream, however, the file can be sent without completely loading all the bits into memory, by sending the file chunk by chunk.
Different types of streams in Node.js include Readable, Writable, Duplex, and Transform.
- Readable: a stream that can be read but not
written to, for example,
process.stdin. - Writable: a stream that can be written to
but not read from, for example,
process.stdout. - Duplex: a stream that can be read and written to, for example, the network socket.
- Transform: a special case of a duplex stream, where the output is computed from the input, for example compression or encryption streams.
The building blocks for streams are available
via require('stream'). Streams are built on
the Stream type which in turn is based on the
EventEmitter.
var stream = require('stream');
var readable = new stream.Readable({});
var writable = new stream.Writable({});
var duplex = new stream.Duplex({});
var transform = new stream.Transform({});All streams support the pipe method which pipes
the output of the stream to the parameter given:
var fs = require('fs');
var readableStream = fs.createReadStream('./streamed.txt');
readableStream.pip(process.stdout);Returned streams can be piped in a chain pattern:
var fs = require('fs');
var gzip = require('zlib').createGzip();
var inp = fs.createReadStream('test.txt');
var out = fs.createWriteStream('test.txt.gz');
inp.pipe(gzip).pipe(out);Because streams are based on EventEmitter, it is
easy to listen for events on a stream object, such
as 'readable' which is raised when there is new data
available to be read from the stream, which is done
using the read method on the stream. When there is
nothing left to read from the stream the read method
returns null:
process.stdin.on('readable', function() {
var buf = process.stdin.read();
if (buf == null) {
console.log('read complete');
return;
}
console.log('got: ');
process.stdout.write(buf.toString());
});A writable stream has a write method and an end method.
var fs = require('fs');
var ws = fs.createWriteStream('message.txt');
ws.write('foo');
ws.write('bar');
ws.end();One of the core modules in Node.js is 'http' which has
a createServer method, whose callback argument is a function
that is called on each received HTTP request. The callback
takes a request and response as parameters which are a
readable and writable stream, respectively. The function
returns a server object which has a listen method that
starts the server listening to HTTP requests:
var http = require('http');
var server = http.createServer(function(req, res) {
console.log(req.headers);
res.write('hey');
res.end();
});
server.listen(8080);
console.log('listening on 8080');The HTTP header Transfer-Encoding: chunked notifies
the server that the client can accept a response in
a stream, chunk by chunk. When the response is
sent in these chunks the first line of each chunk is
a hexadecimal value indicated the size of the chunk
in bytes. The response is ended with a 0-sized chunk.
If the statusCode property of the response object
is not set, it defaults to 200. To set it explicitly,
use res.statusCode = 404;.
To set any header value for a response, use
setHeader('Header-Name', 'header value');, for
example res.setHeader('Content-Type', 'text/hmtl');.
There is a handy npm package named "mime" that can
lookup mime types based on file types:
var mime = require('mime');
console.log(mime.lookup('./somefile.txt'));
console.log(mime.lookup('./index.html'));Important request properties include req.method
and req.url.
Since the response object is a writable stream, data can be piped into it. So a file from the filesystem could be written into the response stream:
fs.createReadStream('./file.html').pipe(res);Connect is a middleware framework. npm i connect
will install only the core framework. Each piece
of middleware is separated into its own module.
The connect function is at the heart of a
Connect application. It returns a dispatcher
function that can be passed to http.createServer:
var connect = require('connect');
var http = require('http');
var app = connect();
http.createServer(app).listen(3000);As-is, the example above will return a 404 response
for every request, which is the default behavior.
To properly handle requests, the dispatcher must
be configured with the use method, which registers
a piece of middleware with Connect. The use method
takes three parameters:
- a request object which inherits from the core http request object
- a response objecct which inherits from the core http response object
- an optional "next" callback which allows passing control to the next registered middleware, or informing Connect about an error condition.
The simplest possible middleware would be one that ignores the request and response and simply forwards control to the next registered middleware:
var connect = require('connect');
var app = connect();
app.use(function(req, res, next) { next(); });
app.listen(3000);A slightly more useful example would be one that logs each request's URL and method:
var connect = require('connect');
var app = connect();
app.use(function(req, res, next) {
console.log(req.method, req.url);
next();
});
app.listen(3000);The use function takes an optional string first parameter
that specifies the endpoint to trigger the middleware. If
omitted, all endpoints trigger the middleware, if included,
only a matching endpoint triggers. This is sometimes
referred to as "mounting". For example, to use the logging
code above only when requests come in for '/log':
var connect = require('connect');
var app connect();
function logit(req, res, next) {
console.log(req.method, req.url);
next();
}
app.use('/log', logit);
app.listen(3000);In the above example, all requests starting with the
specified path (/log) are handled by the logit middleware.
All other requests do not trigger middleware. Note that
middleware code itself shouldn't check req.url. It should
assume that app.use mounted the middleware correctly so
that it is triggered if and only if appropriate.
Middleware is configurable by passing in arguments to a function that returns the middleware function. A closure around the variables allows configuration of the function's behavior:
var connect = require('connect');
function logit(includeTimestamps) {
return function(req, res, next) {
if (includeTimestamps) console.log(new Date(), req.method, req.url);
else console.log(req.method, req.url);
next();
};
}
var logit1 = logit(true);
var logit2 = logit(false);
var app = connect();
app.use(logit1);
app.use(logit2);
app.use(function(req, res, next) {
res.end();
});
app.listen(3000);Chaining allows different pieces of middleware to cooperate, because
request and response objects passed to each middleware are mutable.
For instance, one middleware could try to parse a request body from
JSON into an object and put that object in req.body for other
middlewares further down the line to use. Then any middlewares later
in the pipeline would be able to simply access req.body instead of
worrying about how to parse the JSON on its own.
Middleware has the responsibility of continuing the pipeline by
calling next(), but by the same token has the option of ending the
processing pipline by not calling next. For example, a middleware
could change for the header Authorization: Basic QWxhZGRbp...= and
decode the base64 string, checking for the correct credentials. If
the correct credentials were not supplied, the middleware could leave
out the call to next() and instead set the response status to 401
and call res.end().
Another way the processing pipeline can be short-circuited is by
passing an error object to next. This informs connect that an
error occurred, and no other middlewares are called, and the error
message is sent to the client with a response status of 500. So
in this example the second middleware never executes:
var connect = require('connect');
function mw1(req, res, next) {
next(new Error('something terrible happened'));
}
function mw2(req, res, next) {
console.log('this is the second mw');
}
var app = connect();
app.use(mw1);
app.use(mw2);
app.listen(3000);However, a middleware can specifically be used to handle middleware
errors. Such a middleware takes four arguments: error,req,res,next
and is called only in the case of an error. For example, this fourth
middleware is executed, but the third is not:
var connect = require('connect');
var app = connect();
app.use(function(req, res, next) { console.log('this is executed'); next();});
app.use(function(req, res, next) { next(new Error('something bad')); });
app.use(function(req, res, next) { console.log('this won\'t get called'); next(); });
app.use(function(error, req, res, next) { console.log(error); next(); });
app.listen(3000);Node web apps are built on the http module and
createServer. Connect builds on this foundation
adding a middleware pipeline. Express takes the
Connect additions a step further, supplying common
functionality necessary for a web application.
An application dispatcher is created in the same way Connect does it:
var express = require('express');
var app = express();
app.use(/*...*/);
express.listen(2000);Error handling and other middleware all function the same as with Connect. All Connect middleware are Express middlewares, but not all Express middlewares are Connect middlewares because Express modifies the request and response objects, and Express middleware likely depends on those modifications, which Connect does not provide.
One piece of middleware included in Express is static
which allows static content to be served from a
specific location:
var express = require('express');
var app = express();
var path = require('path');
var filesPath = path.join(__dirname, 'public');
app.use(express.static(filesPath));
app.listen(3000);Another middleware included in Express is the body
parser which parses posted HTML form data or JSON
data from a request body and puts the parsed object
into req.body:
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser());
app.use(function(req, res) {
if (req.body) console.log(JSON.stringify(req.body));
res.end();
});Cookie support also comes with Express. To set a cookie,
use the cookie property of the response:
var express = require('express');
var app = express();
app.use(function(req, res){
res.cookie('name', 'foo');
res.end();
});
app.listen(2000);To get cookies back out, they have to be parsed because
cookies are all piled together in one string. The
middleware to parse the cookie name and value pairs is
cookie-parser:
var express = require('express');
var app = express();
var cookieParser = require('cookie-parser');
app.use(cookieParser());
app.use(function(req, res) {
if (req.cookie.name == 'foo') {
/*...*/
}
res.end();
});
app.listen(3000);Compression is another useful features that comes with Express. To automatically compress HTTP responses > 1kB just add in the compression middleware:
var express = require('express');
var app = express();
var compression = require('compression');
var path = require('path');
app.use(express.static(__dirname));
app.listen(3000);I have been able to get the above to work only with
serving static content via express.static, not with
any dynamically generated responses.
The connect-timeout Express middleware is handy for
preventing a hung middleware from staying hung forever:
var express = require('express');
var app = express();
var timeout = require('connect-timeout');
app.use('/broken', timeout(5000), function(req, res, next) {
// don't call next, simulates a hang
});
app.listen(3000);Beware when using the above middleware, as a "hung" middleware
may suddenly come back to life and continue processing,
including a possible call to next even after the 503
response has been sent by the timeout middleware.
The express response object inherits from the standard Node.js
server response object described previously. It adds functions
though, for convenience and flexibility. For instance, the
chainable function .status() can be used to set the HTTP status
code of the response:
var express = require('express');
var app = express();
app.use(function(req, res) {
res.status(201).end();
});
app.listen(3000);Also, an improvement over .setHeader() is the .set() method
which takes an object argument to set multiple response
headers at once:
var express = require('express');
var app = express();
app.use(function(req, res) {
res.status(201)
.set({ "content-type": 'text/plain', "x-custom": 'custom value' })
.end();
});
app.listen(3000);Of course, if all you need to set is the header, an Express
response object has a method for that: res.type('text/plain').
The argument can be a variety of value types: text/html, html
and .html all work the same.
Redirect responses are easy with an optional status code as a first parameter (default is 302), and a url for the second:
res.redirect('http://www.google.com');
res.redirect(301, '/other');In previous examples, calls to res.write actually streamed
the response back to the client. The response header
Transfer-Encoding: Chunked was included (because of the
call to res.write and the response body contained a line
with the count of bytes in the chunk, then the chunk, then
a line with a zero indicating a final, empty chunk. For
instance, this server-side code:
var express = require('express');
var app = express();
app.use(function(req, res) {
for (var i = 0; i < 3; i++) {
res.write(i.toString() + '\r\n');
}
res.end();
});
app.listen(3000);would result in an HTTP response like the following (assuming the server believes the client can handle the chunked response based on the request headers):
HTTP/1.1 200 OK
X-Powered-By: Express
Date: Sun, 24 Jan 2016 03:22:42 GMT
Connection: keep-alive
Transfer-Encoding: chunked
3
0
3
1
3
2
0Notice that each chunk is 3 bytes, as indicated by the
"3" lines. These chunks were streamed to the client
as the bytes were written to the response using res.write.
A call to res.send on the other hand, will not stream,
rather, it sends all bytes at once. It also can accept a
status code as a first parameter, and if you pass it an
object, it will set the content type to application/json:
res.send(404, 'not the droids you\'re looking for');
res.send({name: "thing1"});The request object in Express also inherits from the
standard Node.js server request, and adds common
handy functionality. For instance, the case-insensitve
get method gets a header value:
var ctype = req.get('content-type');
var val = req.get('x-custom-header');Express parses URLs of requests into handy properties
such as the path and query:
var id = req.query.id;
var area = req.query.area;
var path = req.path;An important thing to note: the req.url property is
always relative to where the executing middleware was
mounted:
var express = require('express');
var app = express();
app.use('/one', function(req, res) {
res.send(req.path);
});
app.use('/', function(req, res) {
res.send(req.path);
});
app.listen(3000);So in the example code above, a request to /foo will show
"/foo" in the response, but a request to /one/two will
show "/two" in the response. If you need the complete
URL instead of the one relative to where the middleware
is mounted, use req.originalUrl.
Instead of always using app.use you can use app.VERB
where VERB is one of the common HTTP verbs: get, post,
put, delete. This registers a middleware that is called
only when the HTTP verb matches the method name.
var express = require('express');
var app = express();
app.get('/', function(req, res) {
res.send('you got it');
});
app.post('/', function(req, res) {
res.send('you posted it');
});
app.listen(3000);There is also an all method that is called for the
specified route, regardless of the HTTP verb used.
Express has a .route method that can be used to
specify a single route, then chain the verb methods so
the route doesn't have to be supplied each time:
var express = require('express');
var app = express();
app.route('/')
.get(function(req, res, next) {
res.send('got');
})
.post(function(req, res, next) {
res.send('posted');
})
.put(function(req, res, next) {
res.send('put');
})
.delete(function(req, res, next) {
res.send('deleted');
});
app.listen(3000);The use middleware registration takes an optional
string first parameter which is a path prefix,
i.e. '/foo' will match any incoming request that
starts with /foo (such as /foo/bar). But the
verb-based helper methods
match an exact path, i.e. '/foo' will match only
an incoming request for /foo and not for requests
such as /foo/bar. However, the verb methods will
allow a * character as a wildcard, e.g. '/*',
and can take a regex instead of a string, e.g.
app.use(/^[0-9]{2,3}$/, function(req, res) { ....
URL parameters are also supported, using the
:parametername and req.params['parametername']
syntax:
var express = require('express');
var app = express();
app.get('/foo/:bar', function(req, res) {
res.send('got ' + req.params['bar']);
});
app.get('/foo', function(req, res) {
res.send('just foo');
});
app.listen(3000);###Parameter-based routing
Parameterized routes not only match route patterns but also populate route variable values for you:
var express = require('express');
var app = express();
app.get('/user/:userid', function(req, res) {
res.send('User ' + req.params.userid);
});
app.listen(2000);There is also an optional middleware app.param()
function that is called each time a route with a
parameter is matched. The .param middleware is
called before the matching route method is called
however, so processing with the parameter can take
place first:
var express =require('express');
var app = express();
app.param('userid', function(req, res, next, userid) {
// look up the user based on userid
req.user = { userId: userid, name: null };
next();
});
app.get('/user/:userid', function(req, res) {
res.send('user ID is ' + req.user.userId);
});
app.listen(3000);
A router can be thought of as a mini Express app itself,
with support for isolated routes and middleware. For
instance, a router object has the use, get, post,
all, param, and route functions.
var express = require('express');
var router = express.Router();
router.route('/')
.get(function(req, res, next) {
res.send('got /');
})
.post(function(req, res, next) {
res.send('posted to / ');
});
router.route('/:foo')
.get(function(req, res, next) {
res.send('got ' + req.params.foo);
})
.post(function(req, res, next) {
res.send('posted to ' + req.params.foo);
});
express().use(router).listen(3000);Note that the router object must be registers via
as middleware via a call to use.
There is an offical MongoDB package maintained for Node.js
(npm i mongodb) for communicating with a MongoDB instance
from within a Node.js app. A simple example:
var MongoClient = require('mongodb').MongoClient;
var thing = { name: 'thing1', desc: 'desc1' };
MongoClient.connect('mongodb://127.0.0.1:27017/demo', function(err, db) {
if (err) throw err;
console.log('connected');
var collection = db.collection('things');
collection.insert(thing, function(err, docs) {
console.log('inserted: ' + thing._id); // object gets auto assigned _id
collection.find('thing1').toArray(function(err, results) {
console.log('found: ' + results);
collection.remove('thing1', function(err, results) {
console.log('removed');
db.close();
});
});
});
});There is also a save method on the collection that will overwrite
an entire document with the version passed
(collection.save(thing, function(err) { ...). A more common
situation is one where you want to update only specific properties
of an object document. For this, use update passing in the find
key, the update operator(s), and the onDone function.
The Mongoose ODM is another official MongoDB package, but it allows schemas and models to more tightly control the data.
The MongoDB example code above is a good example of callback issues: the asynchronous nature of Node.js means that performing subsequent actions after asynchronous calls are completed in turn results in arrow code that is hard to read and maintain. Another example:
function one(data, cb) {
console.log('one');
setTimeout(cb, 1000, data);
}
function two(data, cb) {
console.log('two');
setTimeout(cb, 1000, data);
}
function three(data, cb) {
console.log('three');
setTimeout(cb, 1000, data);
}
one('data', function(text1) {
two(text1, function(text2) {
three(text2, function(text3) {
console.log('done');
});
});
});One potential solution is to make named handlers:
function one(data, cb) {
console.log('one');
setTimeout(cb, 1000, data);
}
function two(data, cb) {
console.log('two');
setTimeout(cb, 1000, data);
}
function three(data, cb) {
console.log('three');
setTimeout(cb, 1000, data);
}
// named handlers
function handleThree(text3) {
console.log('done');
}
function handleTwo(text2) {
third(text2, handleThree);
}
function handleOne(text1) {
second(text1, handleTwo);
}
// start the chain
first('data', handleOne);