koa-router 404 on node.js - node.js

platform
node: v-4.4.5 koa: v-2.0.0 koa-router: v-7.0.0
here is my code
///<reference path="../typings/tsd.d.ts" />
//导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示:
var Koa = require('koa');
var router=require("koa-router")();
// 创建一个Koa对象表示web app本身:
var app = new Koa();
// parse request body:
//add url-route:
router.get('/hello/:name', (ctx, next) => {
var name = ctx.params.name;
ctx.response.body =`<h1>Hello, ${name}!</h1>`;
});
router.get('/', function *(ctx, next) {
ctx.response.body = '<h1>Index</h1>';
});
router.all('/login', function *() {
this.redirect('/');
this.status = 301;
});
app.use(function (ctx,next){
this.body = `Invalid URL!!!${ctx.request.method} ${ctx.request.url}`;
ctx.response.type = 'text/html';
ctx.response.body = this.body;
});
app.use(router.routes())
.use(router.allowedMethods());
// 在端口3000监听:
app.listen(3000);
console.log('app started at port 3000...');
when I Brower http://localhost:3000/,output 'Invalid URL!!!'. tell me why can't match '/' router ? thank you!

you can use '*' to match urls like 404 or invalid links:
router.all('*', function(ctx, next) {
//
});

upgrade your node version to 7.6.0. koa2 work on >=7.6.0
and code like
Koa=require('koa')
var app = new Koa();
router = require('koa-router')()
// parse request body:
//add url-route:
router.get('/hello/:name', (ctx, next) => {
var name = ctx.params.name;
ctx.response.body =`<h1>Hello, ${name}!</h1>`;
});
router.get('/', function (ctx, next) {
console.log(ctx.request);
ctx.body = '<h1>Index</h1>';
});
router.all('/login', function (ctx) {
ctx.redirect('/');
ctx.status = 301;
});
app.use(function (ctx,next){
this.body = `Invalid URL!!!${ctx.request.method} ${ctx.request.url}`;
ctx.response.type = 'text/html';
ctx.response.body = this.body;
next()
});
app.use(router.routes())
.use(router.allowedMethods());
// 在端口3000监听:
app.listen(3000);
console.log('app started at port 3000...');

Related

How do we pass parameters to a mounted route in nodeJS?

I'm taking a course on NodeJS, there were a few assignments related to routing, everything works fine except this part which seems a little odd: For some reason, I cannot read the parameter ID being passed to the mounted router.
dish.js
const express = require('express');
const bodyParser = require('body-parser');
const dishRouter = express.Router();
dishRouter.use(bodyParser.json());
dishRouter.route('/')
.all((req,res,next) => {
res.statusCode = 200;
res.setHeader('Content-Type','text/plain');
next();
})
.get((req,res) => {
console.info('Info: ',req);
res.end(`Sending details of the dish back to you: ${req.params.dishId}`);
})
.post((req,res) => {
res.statusCode = 403;
res.end(`Operation not supported: ${req.params.dishId}`);
})
.put((req,res) => {
res.write(`Updating the dish...: ${req.params.dishId} \n` );
res.end(`Will update this dish: ${req.body.name} with details: ${req.body.description}`);
})
.delete((req,res) => {
res.end(`Deleting this dish: ${req.params.dishId}`);
});
exports.dish = dishRouter;
dishes.js
const express = require('express');
const bodyParser = require('body-parser');
const dishesRouter = express.Router();
dishesRouter.use(bodyParser.json());
dishesRouter.route('/')
.all((req,res,next) => {
res.statusCode = 200;
res.setHeader('Content-Type','text/plain');
next();
})
.get((req,res) => {
res.end('Sending all dishes back to you');
})
.post((req,res) => {
res.end(`Will add the dish: ${req.body.name} with details: ${req.body.description}`);
})
.put((req,res) => {
res.statusCode = 403;
res.end(`Operation not supported.`);
})
.delete((req,res) => {
res.end(`Deleting all dishes.....`);
});
exports.dishes = dishesRouter;
index.js
const express = require('express');
const morgan = require('morgan');
const bodyParser = require('body-parser');
const http = require('http');
const dishRouter = require('./routes/dish');
const dishesRouter = require('./routes/dishes');
const hostname = 'localhost';
const port = 3000;
const app = express();
app.use(morgan('dev'));
app.use(bodyParser.json());
app.use('/dishes',dishesRouter.dishes);
app.use('/dishes/:dishId',dishRouter.dish);
app.use(express.static(__dirname+'/public'));
app.use((req,res,next) => {
res.statusCode = 200;
res.setHeader('Content-Type','text/html');
res.end('<html><body><h1>This is an Express Server</h1></body></html>');
});
const server = http.createServer(app);
server.listen(port,hostname,(req,res) => {
console.info(`Server running on port: ${port}, at: ${hostname}`);
})
This GET localhost:3000/dishes/123 is calling the right route, but the parameter dishId comes back as "undefined". Again, just learning nodeJS, seems like my receiver/mounted route should receive those parameters just fine, the body can be read properly, but not the params. ... thanks.
Yeah the params don't flow between routers. You're on a new router, hence new route params object.
You can check out the code for this:
https://github.com/expressjs/express/blob/master/lib/router/index.js#L43
Check out line 43 and line 53 where route.params is set to an empty object.
Some examples:
index.js
app.use('/dishes/:dishId',(req, res) => {
console.log('now I get my dishId', req.params.dishId)
});
dish.js (version 1)
dishRouter.route('/')
.get((req, res) => {
console.log('now i get nothing', req.params)
})
dish.js (version 2)
dishRouter.route('/:anotherId')
.get((req, res) => {
console.log('now we get another parameter', req.params.anotherId)
})
// the path would be /dish/123/456
I'm not sure if there is a offical-expressjs-way to pass the params object between routers.
One solution would be to create a custom handler
index.js
app.use('/dishes/:dishId', handler)
handler.js
function handler (req, res, next) {
if (req.method === 'GET') {
console.log('now we get it', req.params)
}
}
module.exports = handler
Anoter way would be to add the dishId to the request object before calling the router:
index.js
app.use('/dishes/:dishId', (req, res, next) => {
req.dishId = req.params.dishId
router(req, res, next)
})
dish.js
const express = require('express')
const router = express.Router()
router.route('/')
.get((req, res) => {
console.log('nothing here', req.params)
console.log('dishId', req.dishId)
})
module.exports = router
Third way would be to send the params as options to a router function
index.js
app.use('/dishes/:dishId', (req, res, next) => {
router(req.params)(req, res, next)
})
dish.js
function createRouter (options) {
const router = express.Router()
router.route('/')
.get((req, res) => {
console.log('nothing here', req.params)
console.log('but alot here', options)
})
return router
}
module.exports = createRouter
If you want you could also just put the :dishId on the router as an optional parameter
index.js
app.use('/dishes', dishesRouter)
dishes.js
const express = require('express')
const router = express.Router()
router.route('/:dishId?')
.get((req, res) => {
if (req.params.dishId) {
res.end(`Sending details of the dish back to you: ${req.params.dishId}`)
} else {
res.end('Sending all dishes back to you');
}
})
module.exports = router

How to mock an Express/Node.js middleware on a post route?

I´m writing some tests with chai and mocha and i am having some troubles.
For example, in the route that i paste down here, the LOGOUT calls the isLoggedIn middleware that checks if a user exists in the session.
For example, if a do this:
it('Logout', function(done) {
chai.request(baseURL)
.post('/logout')
.end(function(err, res) {
expect(err).to.be.null;
expect(res).to.have.status(204);
done();
});
});
the test faills cause i get a 401 status code. I am new on this test stuffs. I understand that i have to use sinon to get mi test pass, but i can´t get the solution.
This is my route:
'use strict';
const express = require('express');
const createError = require('http-errors');
const router = express.Router();
const bcrypt = require('bcrypt');
const User = require('../models/User');
const {isLoggedIn} = require('../helpers/middlewares');
router.post('/logout', isLoggedIn(), (req, res, next) => {
req.session.destroy();
return res.status(204).send();
});
This is the Middleware:
'use strict';
const createError = require('http-errors');
exports.isLoggedIn = () => (req, res, next) => {
if (req.session.user) {
next();
} else {
next(createError(401));
};
};
Thank you very much!!!
In your flow problem in that express middleware initialized during run express application and after becomes unavailable for stubbing. My solution is that would init stub before run express application.
test.spec.js:
const chai = require("chai"),
sinon = require("sinon"),
chaiHttp = require("chai-http"),
initServer = require("./initTestServer"),
isLoggedInMiddleware = require("./middleware");
chai.use(chaiHttp);
const { expect } = chai;
describe("Resource: /", function() {
before(function() {
sinon.stub(isLoggedInMiddleware, "isLoggedIn").callsFake(function() {
return (req, res, next) => {
next();
};
});
this.httpServer = initServer();
});
after(function() {
this.httpServer.close();
});
describe("#POST /login", function() {
beforeEach(function() {
this.sandbox = sinon.createSandbox();
});
afterEach(function() {
this.sandbox.restore();
});
it("- should login in system and return data", async function() {
return chai
.request(this.httpServer.server)
.post("/logout")
.end((err, res) => {
expect(err).to.be.null;
expect(res).to.have.status(204);
});
});
});
});
initTestServer.js:
const isLoggedInMiddleware = require("./middleware");
const initServer = () => {
const express = require("express");
const app = express();
app.post("/logout", isLoggedInMiddleware.isLoggedIn(), (req, res, next) => {
return res.status(204).send();
});
const server = require("http").createServer(app);
server.listen(3004);
const close = () => {
server.close();
global.console.log(`Close test server connection on ${process.env.PORT}`);
};
return { server, close };
};
module.exports = initServer;
Thank you #EduardS for then answer!!
I solved it in a similar way:
it('Logout', async function(done) {
sinon.stub(helpers, 'isLoggedIn')
helpers.isLoggedIn.callsFake((req, res, next) => {
return (req, res, next) => {
next();
};
})
app = require('../index')
chai.request(app)
.post('/api/auth/logout')
.end(function(err, res2) {
expect(res2).to.have.status(204);
helpers.isLoggedIn.restore()
})
done();
});

Node.js + Express - How to log the request body and response body

I have a small api I have built using Node.js and express.
I am trying to create a logger and I need log the request body AND response body.
app.use((req, res) => {
console.log(req);
res.on("finish", () => {
console.log(res);
});
});
"express": "^4.16.3",
However, i am not able to find the body in the req or res object. Please tell me how i can get them. thanks.
For res.body try the following snippet:
const endMiddleware = (req, res, next) => {
const defaultWrite = res.write;
const defaultEnd = res.end;
const chunks = [];
res.write = (...restArgs) => {
chunks.push(new Buffer(restArgs[0]));
defaultWrite.apply(res, restArgs);
};
res.end = (...restArgs) => {
if (restArgs[0]) {
chunks.push(new Buffer(restArgs[0]));
}
const body = Buffer.concat(chunks).toString('utf8');
console.log(body);
defaultEnd.apply(res, restArgs);
};
next();
};
app.use(endMiddleware)
// test
// HTTP GET /
res.status(200).send({ isAlive: true });
You need body-parser that will create body object for you in your request. To do that
npm install body-parser
var bodyParser = require('body-parser')//add this
app.use(bodyParser())//add this before any route or before using req.body
app.use((req, res) => {
console.log(req.body); // this is what you want
res.on("finish", () => {
console.log(res);
});
});
Ran into this problem but didn't like the solutions. An easy way is to simply wrap the original res.send or res.json with your logger.
Put this as middleware before your routes.
app.use(function responseLogger(req, res, next) {
const originalSendFunc = res.send.bind(res);
res.send = function(body) {
console.log(body); // do whatever here
return originalSendFunc(body);
};
next();
});
https://github.com/expressjs/express/blob/master/lib/response.js
res.send has signature of function(body) { return this; }
Here is a working example using the built in PassThrough stream. Remember to use the express.json() built in middleware to enable request body parsing.
After that, you need to intercept all writes to the response stream. Writes will happen on calling write or end, so replace those functions and capture the arguments in a separate stream.
Use res.on('finish', ...) to gather all the written data into a Buffer using Buffer.concat and print it.
const express = require('express');
const { PassThrough } = require('stream')
const app = express();
app.use(express.json());
app.use((req, res, next) => {
const defaultWrite = res.write.bind(res);
const defaultEnd = res.end.bind(res);
const ps = new PassThrough();
const chunks = [];
ps.on('data', data => chunks.push(data));
res.write = (...args) => {
ps.write(...args);
defaultWrite(...args);
}
res.end = (...args) => {
ps.end(...args);
defaultEnd(...args);
}
res.on('finish', () => {
console.log("req.body", req.body);
console.log("res.body", Buffer.concat(chunks).toString());
})
next();
})
app.use('/', (req, res) => {
res.send("Hello");
});
app.listen(3000);
install npm install body-parser
and use this snippet,
var express = require('express')
var bodyParser = require('body-parser')
var app = express()
// create application/json parser
var jsonParser = bodyParser.json()
to get json response
app.use(jsonParser, function (req, res) {
console.log(req.body); // or console.log(res.body);
})
There is ready made module https://www.npmjs.com/package/morgan-body
const express = require('express')
const morganBody = require("morgan-body")
const bodyParser = require("body-parser")
const app = express()
const port = 8888
// must parse body before morganBody as body will be logged
app.use(bodyParser.json());
// hook morganBody to express app
morganBody(app, {logAllReqHeader:true, maxBodyLength:5000});
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
Hi was looking for same as complete log of request and response as middleware in express js. Found the solution as well w
/*Added by vikram parihar for log */
const moment = require('moment');
const rfs = require("rotating-file-stream");
const geoip = require('geoip-lite');
const { PassThrough } = require('stream')
let path = require('path');
const accessLogStream = rfs.createStream('access.log', {
interval: '1M', // rotate daily
compress: true,
path: path.join(__dirname, '../../log')
});
module.exports = function (req, res, next) {
try {
let geo = geoip.lookup(req.ip);
let country = geo ? geo.country : "Unknown";
let region = geo ? geo.region : "Unknown";
let log = {
"time": moment().format('YYYY/MM/DD HH:mm:ss'),
"host": req.hostname,
"ip": req.ip,
"originalUrl": req.originalUrl,
"geo": {
"browser": req.headers["user-agent"],
"Language": req.headers["accept-language"],
"Country": country,
"Region": region,
},
"method": req.method,
"path": req.path,
"url": req.url,
"body": req.body,
"params": req.params,
"query": req.query,
"response": {
"body": res.body
}
};
const defaultWrite = res.write.bind(res);
const defaultEnd = res.end.bind(res);
const ps = new PassThrough();
const chunks = [];
ps.on('data', data => chunks.push(data));
res.write = (...args) => {
ps.write(...args);
defaultWrite(...args);
}
res.end = (...args) => {
ps.end(...args);
defaultEnd(...args);
}
res.on('finish', () => {
log.response.body = Buffer.concat(chunks).toString()
accessLogStream.write(JSON.stringify(log) + "\n");
})
} catch (error) {
console.log(error)
next(error)
}
next();
}

TypeError: Cannot read property 'then' of undefined node js

TypeError: Cannot read property 'then' of undefined
Can you help me fix this? Thank you.
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var mongodb = require('mongodb');
var dbConn = mongodb.MongoClient.connect('mongodb://localhost:27017',
function(err, db) {
if(err){
throw err;
}else{
console.log("connected");
}
})
var app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static(path.resolve(__dirname, './')));
app.post('/post-feedback', function (req, res) {
dbConn.then(function(db) {
delete req.body._id; // for safety reasons
db.collection('feedbacks').insertOne(req.body);
});
res.send('Data received:\n' + JSON.stringify(req.body));
});
app.get('/view-feedbacks', function(req, res) {
dbConn.then(function(db) {
db.collection('feedbacks').find({}).toArray().then(function(feedbacks) {
res.status(200).json(feedbacks);
});
});
});
app.listen(process.env.PORT || 3000, process.env.IP || '0.0.0.0' );
TypeError: Cannot read property 'then' of undefined
Can you help me fix this? Thank you.
The following approach should get you started but should not use this for production (Reference: How do I manage MongoDB connections in a Node.js web application?). Read through for another production starters.
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var mongodb = require('mongodb');
var dbConn = function() {
return new Promise((resolve, reject) => {
mongodb.MongoClient.connect('mongodb://localhost:27017',
function(err, db) {
if(err){
return reject(err);
}else{
return resolve(db);
}
});
});
}
var app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static(path.resolve(__dirname, './')));
app.post('/post-feedback', function (req, res) {
dbConn()
.then(function(db) {
delete req.body._id; // for safety reasons
db.collection('feedbacks').insertOne(req.body);
res.send('Data received:\n' + JSON.stringify(req.body));
})
.catch(err => {
console.log(err)
res.send('Error');
})
});
app.get('/view-feedbacks', function(req, res) {
dbConn()
.then(function(db) {
db.collection('feedbacks').find({}).toArray().then(function(feedbacks) {
res.status(200).json(feedbacks);
});
})
.catch(err => {
console.log(err);
res.status(500).json({});
});
});
app.listen(process.env.PORT || 3000, process.env.IP || '0.0.0.0' );
Production Starter:
Ideally you will have something like following say in a file db.js
let mongoClient = require('mongodb').MongoClient,
logger = require('winston');
function DATABASE() {
this.dbObj = null;
this.myCollection = null; // You will need to add more collections here
}
DATABASE.prototype.init = function (config, options) {
let self = this;
self.config = config; //can pass a config for different things like port, ip etc.
self.logger = logger;
return new Promise(function (resolve, reject) {
if (self.initialized) {
return resolve(self);
}
let connectionUri = "mongodb://localhost:27017"; //self.config.mongo.connectionUri;
mongoClient.connect(connectionUri, {native_parser: true}, function (err, db) {
if (err) {
reject(err);
}
else {
self.dbObj = db;
self.myCollection = db.collection('myCollection');
self.initialized = true;
self.logger.info("db init success");
return resolve(self);
}
});
});
};
var dbObj = null;
var getdbObj = function () {
if (!dbObj) {
dbObj = new DATABASE();
}
return dbObj;
}();
module.exports = getdbObj;
In your main app start file you will have something like:
let dbObj = require('./db.js');
dbObj.init()
.then(db => {
console.log('db initialized successfully');
//db.dbObj.collection('myCollection').find()
//or
//db.myCollection.find() because this has been already initialized in db.js
var app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static(path.resolve(__dirname, './')));
app.post('/post-feedback', function (req, res) {
delete req.body._id; // for safety reasons
db.dbObj.collection('feedbacks').insertOne(req.body);
res.send('Data received:\n' + JSON.stringify(req.body));
});
app.get('/view-feedbacks', function(req, res) {
//db.collection('feedbacks')
});
app.listen(process.env.PORT || 3000, process.env.IP || '0.0.0.0' )
})
.catch(err => console.log(err));
Try this, dbConn is not promise
app.post('/post-feedback', function (req, res) {
mongoose.connection.db.collection('feedbacks', function (err, collection) {
collection.insertOne(req.body);
res.send('Data received:\n' + JSON.stringify(req.body));
});
// OR
const Model = mongoose.model('feedbacks');
let model = new Model();
model = Object.assign(model, req.body);
model.save().then((result) => {
res.send('Data received:\n' + JSON.stringify(req.body));
});
});
Its working .
If you are getting any TypeError (UnhandledPromiseRejectionWarning: TypeError: db.collection is not a function) form mongodb. Just change the version of mongodb to -
"mongodb": "^2.2.33"
"use strict"
var express = require('express');
var mongodb = require('mongodb');
var app = express();
var MongoClient = mongodb.MongoClient;
var url = 'mongodb://localhost:27017/feedback';
// no need to call then() yet
var dbConn = MongoClient.connect(url);
app.set('port', 5000);
app.listen(app.get('port'), function() {
console.log('feedback is running on port', app.get('port'));
});
app.get('/view-feedback', function(req, res, next) {
// the connection is opened
dbConn.then(function(db) {
// var dbo = db.db("feedback");
db.collection('feedback').find({}).toArray().then(function(docs) {
// return docs;
res.json(docs)
});
});
});

Express handling URIError: Failed to decode param

var express = require('express');
var app = express();
app.get('*', function (req, res) {
var host = req.get('Host');
return res.redirect(['https://', host, req.url].join(''));
});
var server = app.listen(8080, function () {
console.log('starting');
});
I have a simple script that redirects http to https. This is working fine except when there is a malformed url for example: website.com/%c0%ae%c0%ae. It displays something like:
URIError: Failed to decode param '/%c0%ae%c0%ae'
at decodeURIComponent (native)
at decode_param (/...<PROJECT DIRECTORY>.../node_modules/express/lib/router/layer.js:167:12)
at Layer.match (/.../node_modules/express/lib/router/layer.js:143:15)
at matchLayer (/.../node_modules/express/lib/router/index.js:557:18)
at next (/.../node_modules/express/lib/router/index.js:216:15)
at expressInit (/.../node_modules/express/lib/middleware/init.js:33:5)
at Layer.handle [as handle_request] (/.../node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (/.../node_modules/express/lib/router/index.js:312:13)
at /.../node_modules/express/lib/router/index.js:280:7
at Function.process_params (/.../node_modules/express/lib/router/index.js:330:12)
It's not nice when a user can randomly see where my project files are in the server. Any way to handle this error?
Thanks #Oleg for the tip. But somehow your solution wasn't logging error for me. Here's what I have come up with:
var express = require('express');
var app = express();
app.use(function(req, res, next) {
var err = null;
try {
decodeURIComponent(req.path)
}
catch(e) {
err = e;
}
if (err){
console.log(err, req.url);
return res.redirect(['https://', req.get('Host'), '/404'].join(''));
}
next();
});
app.get('*', function (req, res) {
return res.redirect(['https://', req.get('Host'), req.url].join(''));
});
var server = app.listen(8080, function () {
console.log('Starting');
});
Possible workaround:
var express = require('express');
var app = express();
app.get('*', function (req, res) {
// redirect regular paths
var host = req.get('Host');
return res.redirect(['https://', host, req.url].join(''));
});
// your express error handler
app.use(function(err, req, res, next) {
// in case of specific URIError
if (err instanceof URIError) {
err.message = 'Failed to decode param: ' + req.url;
err.status = err.statusCode = 400;
// .. your redirect here if still needed
return res.redirect(['https://', req.get('Host'), req.url].join(''));
} else {
// ..
}
// ..
});
var server = app.listen(8080, function () {
console.log('starting');
});
var express = require('express');
var app = express();
// handles 400 error
app.use((err, req, res, next) => {
if (!err) return next();
return res.status(400).json({
status: 400,
error: 'OOps! Bad request',
});
});
Edited:
The code snippet should be placed as the last route, it checks if there is an error that was skipped by other routes, which obviously there is, and sends a default response. This error happens when you add % as last character of an API endpoint..

Resources