Rest with Express.js nested router - javascript

Suppose I want to have REST endpoints which look roughly like this:
/user/
/user/user_id
/user/user_id/items/
/user/user_id/items/item_id
CRUD on each if makes sense. For example, /user POST creates a new user, GET fetches all users. /user/user_id GET fetches just that one user.
Items are user specific so I put them under user_id, which is a particular user.
Now to make Express routing modular I made a few router instances. There is a router for user, and a router for the item.
var userRouter = require('express').Router();
userRouter.route('/')
.get(function() {})
.post(function() {})
userRouter.route('/:user_id')
.get(function() {})
var itemRouter = require('express').Router();
itemRouter.route('/')
.get(function() {})
.post(function() {})
itemRouter.route('/:item_id')
.get(function() {})
app.use('/users', userRouter);
// Now how to add the next router?
// app.use('/users/', itemRouter);
URL to item is descendents of the URL hierarchy of the user. Now how do I get URL with /users whatever to userRouter but the more specific route of /user/*user_id*/items/ to the itemRouter? And also, I would like user_id to be accessible to itemRouter as well, if possible.

You can nest routers by attaching them as middleware on an other router, with or without params.
You must pass {mergeParams: true} to the child router if you want to access the params from the parent router.
mergeParams was introduced in Express 4.5.0 (Jul 5 2014)
In this example the itemRouter gets attached to the userRouter on the /:userId/items route
This will result in following possible routes:
GET /user -> hello user
GET /user/5 -> hello user 5
GET /user/5/items -> hello items from user 5
GET /user/5/items/6 -> hello item 6 from user 5
var express = require('express');
var app = express();
var userRouter = express.Router();
// you need to set mergeParams: true on the router,
// if you want to access params from the parent router
var itemRouter = express.Router({mergeParams: true});
// you can nest routers by attaching them as middleware:
userRouter.use('/:userId/items', itemRouter);
userRouter.route('/')
.get(function (req, res) {
res.status(200)
.send('hello users');
});
userRouter.route('/:userId')
.get(function (req, res) {
res.status(200)
.send('hello user ' + req.params.userId);
});
itemRouter.route('/')
.get(function (req, res) {
res.status(200)
.send('hello items from user ' + req.params.userId);
});
itemRouter.route('/:itemId')
.get(function (req, res) {
res.status(200)
.send('hello item ' + req.params.itemId + ' from user ' + req.params.userId);
});
app.use('/user', userRouter);
app.listen(3003);

manageable nested routes...
I wanted a specific example of doing nested routes in a very manageable way in express 4 and this was the top search result for "nested routes in express". Here's an API that would have many routes that would need to be broken up for example.
./index.js:
var app = require('express')();
// anything beginning with "/api" will go into this
app.use('/api', require('./routes/api'));
app.listen(3000);
./routes/api/index.js:
var router = require('express').Router();
// split up route handling
router.use('/products', require('./products'));
router.use('/categories', require('./categories'));
// etc.
module.exports = router;
./routes/api/products.js:
var router = require('express').Router();
// api/products
router.get('/', function(req, res) {
res.json({ products: [] });
});
// api/products/:id
router.get('/:id', function(req, res) {
res.json({ id: req.params.id });
});
module.exports = router;
Nesting example in folder structure
I noticed some comments on "nesting folder structure". It is implied in this however not obvious so I added the section below. Here's a specific example of a nested folder structure for routes.
index.js
/api
index.js
/admin
index.js
/users
index.js
list.js
/permissions
index.js
list.js
This is more a general example of how node works. If you use "index.js" in folders similarly to how "index.html" works in web pages for a directory default, this will be easy to scale your organization based off of recursion without changing your entry points to code. "index.js" is the default document accessed when using require in a directory.
contents of index.js
const express = require('express');
const router = express.Router();
router.use('/api', require('./api'));
module.exports = router;
contents of /api/index.js
const express = require('express');
const router = express.Router();
router.use('/admin', require('./admin'));
module.exports = router;
contents of /api/admin/index.js
const express = require('express');
const router = express.Router();
router.use('/users', require('./users'));
router.use('/permissions', require('./permissions'));
module.exports = router;
contents of /api/admin/users/index.js
const express = require('express');
const router = express.Router();
router.get('/', require('./list'));
module.exports = router;
There is some DRY issues here possibly but it does lend itself well to encapsulation of concerns.
FYI, recently I got into actionhero and have found it to be full featured w/sockets and tasks, more like a true framework all-in-one flipping the REST paradigm on its head. You should probably check it out over going naked w/ express.

var userRouter = require('express').Router();
var itemRouter = require('express').Router({ mergeParams: true });
userRouter.route('/')
.get(function(req, res) {})
.post(function(req, res) {})
userRouter.route('/:user_id')
.get(function() {})
itemRouter.route('/')
.get(function(req, res) {})
.post(function(req, res) {})
itemRouter.route('/:item_id')
.get(function(req, res) {
return res.send(req.params);
});
app.use('/user/', userRouter);
app.use('/user/:user_id/item', itemRouter);
The key to the second part of your question is the use of the mergeParams option
var itemRouter = require('express').Router({ mergeParams: true });
From /user/jordan/item/cat I get a reponse:
{"user_id":"jordan","item_id":"cat"}

Using #Jason Sebring solution, and adapting for Typescript.
server.ts
import Routes from './api/routes';
app.use('/api/', Routes);
/api/routes/index.ts
import { Router } from 'express';
import HomeRoutes from './home';
const router = Router();
router.use('/', HomeRoutes);
// add other routes...
export default router;
/api/routes/home.ts
import { Request, Response, Router } from 'express';
const router = Router();
router.get('/', (req: Request, res: Response) => {
res.json({
message: 'Welcome to API',
});
});
export default router;

In the spirit of Express modular routers, we should have a separate router for users and for items. That router isn't part of our top-level application logic. We can nest it in our users' router instead.
Users router
const users = require('express').Router();
const items = require('./items');
//...
// Our root route to /users
albums.get('/', function(req, res, next) {
// res.send() our response here
});
// A route to handle requests to any individual user, identified by an user id
users.get('/:userId', function(req, res, next) {
let userId = req.params.userId;
// retrieve user from database using userId
// res.send() response with user data
});
// Note, this route represents /users/:userId/items because our top-level router is already forwarding /users to our Users router!
users.use('/:userId/items', items);
//...
module.exports = users;
Items router
// We need to merge params to make userId available in our Items router
const items = require('express').Router({ mergeParams: true });
//...
// The root router for requests to our items path
items.get('/', function(req, res, next) {
let userId = req.params.userId; // Here is where mergeParams makes its magic
// retrieve user's track data and render items list page
});
// The route for handling a request to a specific item
items.get('/:itemId', function(req, res, next) {
let userId = req.params.userId; // <-- mergeParams magic
let itemId = req.params.itemId;
// retrieve individual item data and render on single item page
});
//...
module.exports = items;
Source

try to add { mergeParams: true } look to simple example which it middleware use it in controller file getUser at the same for postUser
const userRouter = require("express").Router({ mergeParams: true });
export default ()=>{
userRouter
.route("/")
.get(getUser)
.post(postUser);
userRouter.route("/:user_id").get(function () {});
}

Express router(express.Router()) keeps params seprate so you would explicitly have to tell express to merge these params.
eg:
express.Router({ mergeParams: true })
//above line is answer to your question.

You need only one router, and use it like this:
router.get('/users');
router.get('/users/:user_id');
router.get('/users/:user_id/items');
router.get('/users/:user_id/items/:item_id');
app.use('api/v1', router);

Related

The home router "/" in express-generator is being overwritten somewhere, or it's just not working. Why?

I am trying to connect mongodb with express-generator. My default home router "/" is in index.js, but it's not working. It's not invoked. It only works if I change the router to something else.
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
console.log('here')
const collection = req.app.locals.collection;
collection.find({})
.toArray()
.then(data => res.json(data))
});
module.exports = router;
This is my index.js.
And in my app.js, I have this:
var indexRouter = require('./routes/index');
MongoClient.connect(uri, (err, client)=>{
if(err) console.log('we have an error: ', err)
const db = client.db('MovieGoers');
const collection = db.collection('users')
app.locals.collection = collection;
})
app.use('/', indexRouter);
It's not working. The function is not invoked. If I change the router in index.js to "/movies" or anything else, it works in that router, but the default home router "/" is not working. Nothing is invoked. I keep seeing the "welcome to express" header from the index.html.
Is something overwriting this router?

Express middleware that adds more middleware and routes

I have a Node/Express app that looks like this:
app.use(foo)
...
app.get('/foo/bar', ...)
...
app.get('/index', ...)
And I want to extract the middleware and the routes so that now I can do:
app.use(myMiddlewareAndRoutes)
...
app.get('/index', ...)
So that myMiddlewareAndRoutes adds the middleware foo and the route '/foo/bar' that belongs to it.
How can I do this?
var express = require('express')
var router = express.Router()
// middleware that is specific to this router
router.use(function timeLog (req, res, next) {
console.log('Time: ', Date.now())
next()
})
// define the home page route
router.get('/', function (req, res) {
res.send('Birds home page')
})
// define the about route
router.get('/about', function (req, res) {
res.send('About birds')
})
module.exports = router
var birds = require('./birds')
// ...
app.use('/birds', birds)
//or if you need it on root level
app.use('/', birds)
You want to create a separate Router object then add it as a middleware with the .use() function.
In the foo.js file I'm creating a new router and exporting it:
foo.js
const { Router } = require('express');
const router = Router();
router.get('/bar', (req, res, next) => {
return res.send('bar');
});
module.exports = router;
Then importing it in the index.js file to add it as a middleware:
index.js
const express = require('express');
const foo = require('./foo.js');
const app = express();
app.use('/foo', foo);
app.get('/index', ...)
Now every route you define in foo.js will use the /foo prefix like /foo/bar.

How to Avoid Repeating Passport.JS Code Used in Multiple Express.JS Routes

I just started using Passport.js in my Express app for creating a API with authenticated routes.
Question: If I have 2 routes defined in routes/animals.js and routes/locations.js which uses Passport to protect certain API requests, where I need to create a passport object and run passport.use(new BearerStrategy...) before i can use passport.authenticate later in the code.
How can I avoid repeating the passport.use(new BearerStrategy...) code in all the routes file? Or is it better to repeat them?
routes/animals.js
var passport = require("passport");
passport.use(new BearerStrategy(
function(token, done) {
User.findOne({ token: token }, function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
return done(null, user, { scope: 'all' });
});
}
));
router.get('/',
passport.authenticate('bearer', { session: false }),
function(req, res, next) {
Animal.find(function(err, animals) {
if (err) return next(err);
res.json(animals)
})
})
app.js
var animalsRouter = require('./routes/animals');
app.use('/api/animals', animalsRouter);
...
Node.js modules are Singleton-like. Meaning whenever you do require('passportjs'), you are getting back the same instance you imported in another file.
For your case, in every route file, you are importing the same Passport instance every time. So you only need to define your Passport configuration once.
So for example, define you BearerStrategy in another file:
bearer-strategy.js
module.exports = new BearerStrategy((token, done) => {
// ...
});
Then in your main app.js, configure Passport only once:
app.js
const express = require('express');
const passport = require('passport');
const bearerStrategy = require('./bearer-strategy.js');
const app = express();
// Only need to do this once
passport.use(bearerStrategy);
But now you need to protect your API routes and avoid repeating yourself. You can do this, by applying the Passport middleware to /api/** instead of /api/myRouter for every router. So your animals router can be:
routes/animals.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => res.json(req.user));
module.exports = router;
Now we need an API router which just mounts all the other routers:
routes/api.js
const express = require('express');
const router = express.Router();
const animalRouter = require('./animals.js');
router.use('/animals', animalRouter);
// other routers
module.exports = router;
Now finally, we can mount them in one shot and secure them with Passport once:
app.js
const express = require('express');
const passport = require('passport');
const bearerStrategy = require('./bearer-strategy.js');
const apiRouter = require('./routes/api.js')
const app = express();
passport.use(bearerStrategy);
app.use('/api', passport.authenticate('bearer', { session: false }), apiRouter);
As an aside, all plugins/whatever for Express are just middleware.

`Cannot GET /en/first` error with routing module in Node.js

I'm trying to create routing module in Node.js & express and I don't get what I'm doing wrong.
The error is Cannot GET /en/first
The point is to have many folders like the en, each folder with it's routing.
Combine them together in index.js and call it from app.js.
My files structure:
public
-js
-css
views
-index.js
-en
--about.html
--en.js
--home.html
app.js
My en.js file
var express = require('express')
, router = express.Router()
//en page1
router.get('/about1', function(req, res) {
res.render('about1')
})
//en page2
router.get('/first', function(req, res) {
res.render('first')
})
module.exports = router
/views/index.js
var express = require('express')
var router = express.Router()
router.use('./en', require('./en/'))
router.use('./fr', require('./fr/'))
module.exports = router
app.js
var express = require("express");
var app = express();
var path = require('path');
var router = express.Router();
app.use(router);
app.engine('ejs', require('ejs').__express)
app.set('view engine', 'ejs')
app.use(express.static(path.join(__dirname, 'public')));
app.use(require('./views'))
app.listen(3000,function(){
console.log("Live at Port 3000");
});
Also there is an option to use an array instead of routing like in the en.js file?
It should be
router.use('/en', require('./en/en'))
router.use('/fr', require('./fr/fr'))
There shouldn't be any dots in the route.
EDIT
Regarding your second question yes you can use an array:
const routes = ['about1', 'first']; // array of routes
routes.forEach(route => {
router.get('/' + route, function (req, res) {
res.render(route)
});
});
I don' think a router can use another router. Instead in app.js do
app.use('./en', require('./view/en/en.js'))
app.use('./fr', require('./view/fr/fr.js'))
personally I don't like your way of putting router files in the view folder as they don't define the view. They should be in their own folder.
For the question you raised in the end. The router is only a mini-app, and is there to make it easier for you to define sub routes. For example you can do:
var express = require('express')
var router = express.Router()
// define the home page route
router.get('/', function (req, res) {
res.send('Birds home page')
})
// define the about route
router.get('/about', function (req, res) {
res.send('About birds')
})
module.exports = router
And load this in app:
var birds = require('./birds')
// ...
app.use('/birds', birds) // basically adding prefix to all the routes in bird router :)
The resulting routes will be /birds/ and /birds/about
You basically need three things to complete definition of a route:
HTTP request method such as GET, PUT, or POST)
Route (string such as "/about")
Middlewares (functions with 2-4 parameter. (req,res,next)=>{})
You can do whatever you want to store them and construct the routes. Maybe you can have a array of objects that contains all the info and do:
arrayOfRouteObjects.forEach((element)=>{
app[element.method](element.route, element.middlewares)
})
You can also have a function that construct the middlewares if you know what they look like:
function renderView(viewName){
return (req,res,next)=>{
res.render(viewName);
}
}
And basically you have 2 arrays that stores all route and all matching view names. Loop through them and app.get them all.

app.use(path, router) matches but how do I extract named parameters in path

I am trying to separate routes into individual files with the app.use('/', router) pattern employed by default in an express initialized application. For example:
var users = require('./routes/users');
// ...
app.use('/users', users);
I've added a posts router for a blog, which works fine:
var posts = require('./routes/posts');
// ...
app.use('/posts', posts);
However, I'm now adding comments subroutes on posts. I need to employ a named parameter in the path supplied to app.use so that I can identify the post in addition to the comment:
var comments = require('./routes/comments');
// ...
app.use('/posts/:pid/comments', comments);
The routes/comments.js file looks like:
var express = require('express');
var router = express.Router();
router.get('/', function(req, res) {
res.send('all comments + ', req.params.pid);
});
// ...
A path such as /posts/1/comments/34 correctly matches and the callback in router/comments.js is executed, but req.params.pid is undefined.
Is it possible to employ the app.use(path, router) pattern with named parameters in the path? If so how do I get at that :pid named parameter?
How about this
comments.js
var express = require('express');
var router = express.Router();
router.get('/:pid/comments', function(req,res){
res.send('all comments for post id: ' + req.params.pid);
});
module.exports = router;
posts.js
var express = require('express');
var router = express.Router();
router.get('/', function(req,res){
res.send('show all posts');
});
router.get('/:pid', function(req,res){
res.send('show post with id: ' + req.params.pid);
});
module.exports = router;
finally app.js
var routes = require('./routes/index');
var users = require('./routes/users');
var comments = require('./routes/comments');
var posts = require('./routes/posts');
app.use('/posts', comments);
app.use('/posts', posts);
Points to note:
In comments.js, I use '/:pid/comments' as the path to match, so that the post id is available there.
In app.js, I pass the comments routes first to the app and the posts routes after that, so that the comment routes are matched first
Both comments and posts have the root path '/posts'
Sorry for the brevity. I just find this self-explanatory. If anything is confusing, please ask in the comments.
When you set the router to use comments for /posts/:pid/comments/, you're effectively throwing away the :pid parameter, because that parameter isn't available in the routing function defined in comments.js. Instead, you should define this route inside posts.js:
router.get("/:pid/comments", function(req, res) {
res.send("all comments " + req.params.pid);
});
Naturally, one would expect more complex logic to be required, which should be defined in a separate file and required in posts.js:
var comments = require("comments");
router.use("/:pid/comments", function(req, res) {
comments.renderGet(req.params.what, req, res);
});
Define your logic in comments.js:
module.exports = {
renderGet: function(id, req, res) {
res.send("all comments " + id);
},
// ... other routes, logic, etc.
};

Categories