I've to implement a custom mini router and have been looking at express js for inspirations.
Can anyone guide me on express js creates route handlers under the hood.
For instance, how the following can be implemented?
Node server code
http.createServer(function (req, res) { //how routing is done after?
//req???
req.get("/customers", function(){})
req.get("/customers/:id", function(){})
}).listen(9615);
is express js using regular expressions? Please also point to the right script in the github repository.
You need to check this https://github.com/expressjs/express/blob/master/lib/router/index.js. This is a separate module of expressjs for routing and you can pluck it out for your personal use without reinventing the wheel all over.
[EDIT] - To give idea how it might be done.
var routes = [];
var app = {};
app.get = function(pattern, cb) {
var splits = pattern.split("/");
var route = "";
var regex = "";
for(i=0; i < splits.length; i++) {
if (splits[i].indexOf(':') === -1) {
route += splits[i]+"/";
} else {
regex = splits[i].replace(":", "");
}
}
routes.push({ route : routes, regex : regex, cb: cb });
}
app.get("/customers", callback);
.
.
.
// handle incoming request. requestPath comes from server
var requestPath = "/customers"; // example only.
app.handleRequest(requestPath) {
for(i = 0; i < routes.length; i++) {
if(routes[i].route === requestPath) {
cb = routes[i].cb;
}
}
cb();
}
Kudos to robertklep.
Under the hood express js uses path-to-regexp.
I also found the regular expression from their page that is used by express js to parse the URL link
/^(?:\/(?=$))?$/i
There's not much re-inventing the wheel involved in here when all I wanted is a basic router without getting the whole framework and all of it's dependencies in my project.
Please take a look at the Express JS documentation. It would be pretty much it. For example, a piece of code using Express JS:
// GET method route
app.get('/', function (req, res) {
res.send('GET request to the homepage');
});
// POST method route
app.post('/', function (req, res) {
res.send('POST request to the homepage');
});
As for regular expressions, yes, you can use them. For example:
app.get('/ab(cd)?e', function(req, res) {
res.send('ab(cd)?e');
});
Besides that, please take look at one example in the Express JS GitHub repository.
As for the Router implementation in Express JS, look at their GitHub code (router script).
Related
I'm relatively new to web development, brand new to node.js.
I'm trying to use express to make a site that updates an api with the chef-node api client, using this tutorial
If I create a stand alone node.js app, it works as expected, and the value 'bacon' is set to 'good'
app1.js
var fs = require('fs'),
chef = require('chef'),
key = fs.readFileSync('/Users/foo.pem'),
chef_client = chef.createClient('foo', key, 'https://chef.example.com/organizations/dev');
var mybreakfast = { "id":"food","bacon":"good"}
chef_client.put('/data/breakfast/food', mybreakfast, function(err,res,body) {
if (err) { return console.log(err); }
console.log(body)
});
Full code is here.
The app basically looks like this:
app.js
var fs = require('fs'),
chef = require('chef'),
key = fs.readFileSync('/Users/foo.pem'),
chef_client = chef.createClient('foo', key, 'https://chef.example.com/organizations/dev');
...
//tutorial says this isn't ideal, but it is quickest way to get working
app.use(function(req,res,next){
req.client = chef_client;
next();
});
routes/index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
router.get('/databags', function(req, res) {
req.client.get('/data/breakfast/food', function(err,res,body) {
if (err) { return console.log(err); }
console.log("Found breakfast")
console.log(body)
bag_data = body; //TODO: Global variable is bad and you should feel bad, how do I use correct variable?
});
res.render('databags.jade', { title: 'Databags', somedata: bag_data });
});
router.post('/databags', function(req, res) {
var mybreakfast = { "id":"food","bacon":"good"}
req.client.put('/data/breakfast/food', mybreakfast, function(err,res,body) {
if (err) { return console.log(err); }
console.log(body)
});
});
vies/databags.jade
html
body
form(action='/databags', id='derp', method='POST')
each value, key in somedata
label #{key}
input(type='text',name="#{key}", value="#{value}")
br
br
input(type='submit', value='Submit', form='derp')
When I press the submit button in /databags, I get a 301 and no data is uploaded.
<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>openresty/1.7.10.1</center>
</body>
</html>
I suspect the problem has to do with the fact that I'm adding client to every request.
//tutorial says this isn't ideal, but it is quickest way to get working
app.use(function(req,res,next){
req.client = chef_client;
next();
});
What is the proper way to make the chef_client variable in app.js available in routes/index.js?
If that is the correct way to do this, then what could be making the app work by itself, but not when used with the express framework?
Since this is my first node.js / express site, any other suggestions to get this app working would be very appreciated.
Update
Solution has been found, working code snippet available here: https://gist.github.com/spuder/1e39868b6a9a0c3cdb13
If route_index.js is your routes/index.js then your problem is that you're not actually exporting anything from it. When you require('routes/index') you're going to get back whatever you set module.exports to to in that file.
That means your routes/index.js file should end with:
module.exports = router;
To address your question regarding how to share chef_client without a global, you can return a factory from routes/index.js that returns an instantiated router using the parameters you pass to it. That would look something like this:
routes/chef.js
function addChefRoutes(router, chefClient) {
router.get('databags', function(res,req){
// ...
}
});
module.exports = addChefRoutes;
app.js
var chefClient = chef.createClient('foo', key, 'https://chef.example.com/organizations/dev');
var addChefRoutes = require('./routes/chef');
var router = express.Router();
addChefRoutes(router, chefClient);
I am using express to make a web app in node.js. This is a simplification of what I have:
var express = require('express');
var jade = require('jade');
var http = require("http");
var app = express();
var server = http.createServer(app);
app.get('/', function(req, res) {
// Prepare the context
res.render('home.jade', context);
});
app.post('/category', function(req, res) {
// Process the data received in req.body
res.redirect('/');
});
My problem is the following:
If I find that the data sent in /category doesn't validate, I would like pass some additional context to the / page. How could I do this? Redirect doesn't seem to allow any kind of extra parameter.
There are a few ways of passing data around to different routes. The most correct answer is, of course, query strings. You'll need to ensure that the values are properly encodeURIComponent and decodeURIComponent.
app.get('/category', function(req, res) {
var string = encodeURIComponent('something that would break');
res.redirect('/?valid=' + string);
});
You can snag that in your other route by getting the parameters sent by using req.query.
app.get('/', function(req, res) {
var passedVariable = req.query.valid;
// Do something with variable
});
For more dynamic way you can use the url core module to generate the query string for you:
const url = require('url');
app.get('/category', function(req, res) {
res.redirect(url.format({
pathname:"/",
query: {
"a": 1,
"b": 2,
"valid":"your string here"
}
}));
});
So if you want to redirect all req query string variables you can simply do
res.redirect(url.format({
pathname:"/",
query:req.query,
});
});
And if you are using Node >= 7.x you can also use the querystring core module
const querystring = require('querystring');
app.get('/category', function(req, res) {
const query = querystring.stringify({
"a": 1,
"b": 2,
"valid":"your string here"
});
res.redirect('/?' + query);
});
Another way of doing it is by setting something up in the session. You can read how to set it up here, but to set and access variables is something like this:
app.get('/category', function(req, res) {
req.session.valid = true;
res.redirect('/');
});
And later on after the redirect...
app.get('/', function(req, res) {
var passedVariable = req.session.valid;
req.session.valid = null; // resets session variable
// Do something
});
There is also the option of using an old feature of Express, req.flash. Doing so in newer versions of Express will require you to use another library. Essentially it allows you to set up variables that will show up and reset the next time you go to a page. It's handy for showing errors to users, but again it's been removed by default. EDIT: Found a library that adds this functionality.
Hopefully that will give you a general idea how to pass information around in an Express application.
The easiest way I have found to pass data between routeHandlers to use next() no need to mess with redirect or sessions.
Optionally you could just call your homeCtrl(req,res) instead of next() and just pass the req and res
var express = require('express');
var jade = require('jade');
var http = require("http");
var app = express();
var server = http.createServer(app);
/////////////
// Routing //
/////////////
// Move route middleware into named
// functions
function homeCtrl(req, res) {
// Prepare the context
var context = req.dataProcessed;
res.render('home.jade', context);
}
function categoryCtrl(req, res, next) {
// Process the data received in req.body
// instead of res.redirect('/');
req.dataProcessed = somethingYouDid;
return next();
// optionally - Same effect
// accept no need to define homeCtrl
// as the last piece of middleware
// return homeCtrl(req, res, next);
}
app.get('/', homeCtrl);
app.post('/category', categoryCtrl, homeCtrl);
I had to find another solution because none of the provided solutions actually met my requirements, for the following reasons:
Query strings: You may not want to use query strings because the URLs could be shared by your users, and sometimes the query parameters do not make sense for a different user. For example, an error such as ?error=sessionExpired should never be displayed to another user by accident.
req.session: You may not want to use req.session because you need the express-session dependency for this, which includes setting up a session store (such as MongoDB), which you may not need at all, or maybe you are already using a custom session store solution.
next(): You may not want to use next() or next("router") because this essentially just renders your new page under the original URL, it's not really a redirect to the new URL, more like a forward/rewrite, which may not be acceptable.
So this is my fourth solution that doesn't suffer from any of the previous issues. Basically it involves using a temporary cookie, for which you will have to first install cookie-parser. Obviously this means it will only work where cookies are enabled, and with a limited amount of data.
Implementation example:
var cookieParser = require("cookie-parser");
app.use(cookieParser());
app.get("/", function(req, res) {
var context = req.cookies["context"];
res.clearCookie("context", { httpOnly: true });
res.render("home.jade", context); // Here context is just a string, you will have to provide a valid context for your template engine
});
app.post("/category", function(req, res) {
res.cookie("context", "myContext", { httpOnly: true });
res.redirect("/");
}
use app.set & app.get
Setting data
router.get(
"/facebook/callback",
passport.authenticate("facebook"),
(req, res) => {
req.app.set('user', res.req.user)
return res.redirect("/sign");
}
);
Getting data
router.get("/sign", (req, res) => {
console.log('sign', req.app.get('user'))
});
we can use express-session to send the required data
when you initialise the app
const express = require('express');
const app = express();
const session = require('express-session');
app.use(session({secret: 'mySecret', resave: false, saveUninitialized: false}));
so before redirection just save the context for the session
app.post('/category', function(req, res) {
// add your context here
req.session.context ='your context here' ;
res.redirect('/');
});
Now you can get the context anywhere for the session. it can get just by req.session.context
app.get('/', function(req, res) {
// So prepare the context
var context=req.session.context;
res.render('home.jade', context);
});
Here s what I suggest without using any other dependency , just node and express, use app.locals, here s an example :
app.get("/", function(req, res) {
var context = req.app.locals.specialContext;
req.app.locals.specialContext = null;
res.render("home.jade", context);
// or if you are using ejs
res.render("home", {context: context});
});
function middleware(req, res, next) {
req.app.locals.specialContext = * your context goes here *
res.redirect("/");
}
You can pass small bits of key/value pair data via the query string:
res.redirect('/?error=denied');
And javascript on the home page can access that and adjust its behavior accordingly.
Note that if you don't mind /category staying as the URL in the browser address bar, you can just render directly instead of redirecting. IMHO many times people use redirects because older web frameworks made directly responding difficult, but it's easy in express:
app.post('/category', function(req, res) {
// Process the data received in req.body
res.render('home.jade', {error: 'denied'});
});
As #Dropped.on.Caprica commented, using AJAX eliminates the URL changing concern.
Update 2021:
i tried url.format and querystring and both of them are deprecated, instead we can use URLSearchParams
const {URLSearchParams} = require('url')
app.get('/category', (req, res) =>{
const pathname = '/?'
const components ={
a:"a",
b:"b"
}
const urlParameters = new URLSearchParams(components)
res.redirect(pathname + urlParameters)
})
I use a very simple but efficient technique
in my app.js ( my entry point )
I define a variable like
let authUser = {};
Then I assign to it from my route page ( like after successful login )
authUser = matchedUser
It May be not the best approach but it fits my needs.
app.get('/category', function(req, res) {
var string = query
res.redirect('/?valid=' + string);
});
in the ejs you can directly use valid:
<% var k = valid %>
I am using parse.com cloud code with express to setup my routes. I have done this in the past with node, and I have my routes in separate files. So, in node I do
app.js
express = require("express");
app = exports.app = express();
require("./routes/js/account");
account.js
app = module.parent.exports.app;
app.get("/api/account/twitter", passport.authenticate("twitter"));
All the examples on parses site https://parse.com/docs/cloud_code_guide#webapp show this being done as follows.
app.js
var express = require('express');
var app = express();
app.get('/hello', function(req, res) {
res.render('hello', { message: 'Congrats, you just set up your app!' });
});
So, I would like to change the bottom to include a routes folder with separate routes files, but am not sure how to do this in parse.
I know this post is a little old, but I just wanted to post a solution for anyone still looking to get this to work.
What you need to do, is create your route file, I keep them in 'routes' forlder, for example <my_app_dir>/cloud/routes/user.js
Inside user.js you will have something that looks like this:
module.exports = function(app) {
app.get("/users/login", function(req, res) {
.. do your custom logic here ..
});
app.get("/users/logout", function(req, res) {
.. do your custom logic here ..
});
}
Then, in app.js you just include your file, but remember that you need to append cloud to the path, and pass the reference to your app instance:
require('cloud/routes/user')(app);
Also, remember that express evaluates routes in order, so you should take that into consideration when importing several route files.
I'm using a different method, have the routes in app.js, but you can probably include them in file if you prefer. Take a look at the example app,
anyblog on github
The way it works:
Set up a controller:
// Controller code in separate files.
var postsController = require('cloud/controllers/posts.js');
Add the controller route
// Show all posts on homepage
app.get('/', postsController.index);
// RESTful routes for the blog post object.
app.get('/posts', postsController.index);
app.get('/posts/new', postsController.new);
And then in posts.js, you can use exports, ex.
var Post = Parse.Object.extend('Post');
// Display all posts.
exports.index = function(req, res) {
var query = new Parse.Query(Post);
query.descending('createdAt');
query.find().then(function(results) {
res.render('posts/index', {
posts: results
});
},
function() {
res.send(500, 'Failed loading posts');
});
};
// Display a form for creating a new post.
exports.new = function(req, res) {
res.render('posts/new', {});
};
Pass the app reference to the post controller, and add the routes from there
I am writing a web app in node.js using Express. I have defined a route as follows:
app.get("/firstService/:query", function(req,res){
//trivial example
var html = "<html><body></body></html>";
res.end(html)
});
How do I reuse that route from within express?
app.get("/secondService/:query", function(req,res){
var data = app.call("/firstService/"+query);
//do something with the data
res.end(data);
});
I couldn't find anything in the API documentation and would rather not use another library like "request" because that seems kludgey. I am trying to keep my app as modular as possible. Thoughts?
Thanks
Similar to what Gates said, but I would keep the function(req, res){} in your routes file. So I would do something like this instead:
routes.js
var myModule = require('myModule');
app.get("/firstService/:query", function(req,res){
var html = myModule.firstService(req.params.query);
res.end(html)
});
app.get("/secondService/:query", function(req,res){
var data = myModule.secondService(req.params.query);
res.end(data);
});
And then in your module have your logic split up like so:
myModule.js
var MyModule = function() {
var firstService= function(queryParam) {
var html = "<html><body></body></html>";
return html;
}
var secondService= function(queryParam) {
var data = firstService(queryParam);
// do something with the data
return data;
}
return {
firstService: firstService
,secondService: secondService
}
}();
module.exports = MyModule;
Can you simply break this out into another function, put it in a shared spot and go from there?
var queryHandler = require('special_query_handler');
// contains a method called firstService(req, res);
app.get('/firstService/:query', queryHandler.firstService);
// second app
app.get('/secondService/:query', queryHandler.secondService);
Honestly, this whole business of nesting the call back inside of the app.get(...) is not really a great practice. You end up with a giant file containing all of the core code.
What you really want is a file filled with app.get() and app.post() statements with all of the callback handlers living in different, better organized files.
If you have a lot of middleware on your route, you can benefit from spreading:
const router = express.Router();
const myMiddleware = [
authenticationMiddleware(),
validityCheckMiddleware(),
myActualRequestHandler
];
router.get( "/foo", ...myMiddleware );
router.get( "/v1/foo", ...myMiddleware );
You can use run-middleware module exactly for that
app.runMiddleware('/firstService/query',function(responseCode,body,headers){
// Your code here
})
More info:
Module page in Github & NPM;
Examples of use run-middleware module
Disclosure: I am the maintainer & first developer of this module.
I have used following way:
at userpage.js
router.createSitemap = function(req, res, callback) { code here callback(value); }
at product.js
var userPageRouter = require('userpages');
userPageRouter.createSitemap(req, res, function () {
//console.log('sitemap');
});
Also can use in same userpage.js router I can use for other routing as well. eg.
router.get('/sitemap', function (req, res, next) {
router.createSitemap(req, res, function () {
res.redirect('/sitemap.xml');
}); });
Hope this will help.
So I'm starting to use Node.js. I saw the video with Ryan Dahl on Nodejs.org and heard he recommended Express-js for websites.
I downloaded the latest version of Express, and began to code. I have a fully fledged static view up on /, but as soon as I try sending parameters, I get errors like this:
Cannot GET /wiki
I tried following the guide on expressjs.com but the way one uses routes has changed in the latest version, which makes the guide unusable.
Guide:
app.get('/users/:id?', function(req, res, next){
var id = req.params.id;
if (id) {
// do something
} else {
next();
}
});
Generated by Express:
app.get('/', routes.index);
My problem arises when I try and add another route.
app.get('/wiki', routes.wiki_show);
I've tried a bunch of approaches, but I keep getting the Cannot GET /wiki (404) error.
routes/index.js looks like this:
exports.index = function(req, res) {
res.render('index', { title: 'Test', articles: articles, current_article: current_article, sections: sections })
};
The only thing I did there was add some parameters (arrays in the same file) and this i working. But when I copy the contents and change exports.index to exports.wiki or exports.wiki_show I still get the Cannot GET /wiki error.
Can anyone explain to me what I'm missing here? - Thanks.
So, after I created my question, I got this related list on the right with a similar issue: Organize routes in Node.js.
The answer in that post linked to the Express repo on GitHub and suggests to look at the 'route-separation' example.
This helped me change my code, and I now have it working. - Thanks for your comments.
My implementation ended up looking like this;
I require my routes in the app.js:
var express = require('express')
, site = require('./site')
, wiki = require('./wiki');
And I add my routes like this:
app.get('/', site.index);
app.get('/wiki/:id', wiki.show);
app.get('/wiki/:id/edit', wiki.edit);
I have two files called wiki.js and site.js in the root of my app, containing this:
exports.edit = function(req, res) {
var wiki_entry = req.params.id;
res.render('wiki/edit', {
title: 'Editing Wiki',
wiki: wiki_entry
})
}
The route-map express example matches url paths with objects which in turn matches http verbs with functions. This lays the routing out in a tree, which is concise and easy to read. The apps's entities are also written as objects with the functions as enclosed methods.
var express = require('../../lib/express')
, verbose = process.env.NODE_ENV != 'test'
, app = module.exports = express();
app.map = function(a, route){
route = route || '';
for (var key in a) {
switch (typeof a[key]) {
// { '/path': { ... }}
case 'object':
app.map(a[key], route + key);
break;
// get: function(){ ... }
case 'function':
if (verbose) console.log('%s %s', key, route);
app[key](route, a[key]);
break;
}
}
};
var users = {
list: function(req, res){
res.send('user list');
},
get: function(req, res){
res.send('user ' + req.params.uid);
},
del: function(req, res){
res.send('delete users');
}
};
var pets = {
list: function(req, res){
res.send('user ' + req.params.uid + '\'s pets');
},
del: function(req, res){
res.send('delete ' + req.params.uid + '\'s pet ' + req.params.pid);
}
};
app.map({
'/users': {
get: users.list,
del: users.del,
'/:uid': {
get: users.get,
'/pets': {
get: pets.list,
'/:pid': {
del: pets.del
}
}
}
}
});
app.listen(3000);
Seems that only index.js get loaded when you require("./routes") .
I used the following code in index.js to load the rest of the routes:
var fs = require('fs')
, path = require('path');
fs.readdirSync(__dirname).forEach(function(file){
var route_fname = __dirname + '/' + file;
var route_name = path.basename(route_fname, '.js');
if(route_name !== 'index' && route_name[0] !== "."){
exports[route_name] = require(route_fname)[route_name];
}
});
You could also organise them into modules. So it would be something like.
./
controllers
index.js
indexController.js
app.js
and then in the indexController.js of the controllers export your controllers.
//indexController.js
module.exports = function(){
//do some set up
var self = {
indexAction : function (req,res){
//do your thing
}
return self;
};
then in index.js of controllers dir
exports.indexController = require("./indexController");
and finally in app.js
var controllers = require("./controllers");
app.get("/",controllers.indexController().indexAction);
I think this approach allows for clearer seperation and also you can configure your controllers by passing perhaps a db connection in.
No one should ever have to keep writing app.use('/someRoute', require('someFile')) until it forms a heap of code.
It just doesn't make sense at all to be spending time invoking/defining routings. Even if you do need custom control, it's probably only for some of the time, and for the most bit you want to be able to just create a standard file structure of routings and have a module do it automatically.
Try Route Magic
As you scale your app, the routing invocations will start to form a giant heap of code that serves no purpose. You want to do just 2 lines of code to handle all the app.use routing invocations with Route Magic like this:
const magic = require('express-routemagic')
magic.use(app, __dirname, '[your route directory]')
For those you want to handle manually, just don't use pass the directory to Magic.