'missing routes' error handling in node.js express app (failing) - javascript

I am simply trying to work within my app.js or router file to handle possible missing router errors. (similar stackoverflow solutions have not worked for me)
my app.js or router files is thus (works fine as is; but am trying to handle missing router error properly):
'use strict'
var path = require('path');
var film = require('./healthymeals.js');
var express = require('express');
var router = express.Router();
var app = express();
module.exports = function(app) {
app.get('/allMeals', film.getAllMeals);
app.get('/meals/:id', film.getMealInfo);
app.get('/meals/:id/options',meal.getHealthyMealoptions);
app.get('*', function(req, res) {
res.send("Healthy meals");
});
I have installed npm's/express error handling package 'http-status-codes' .
And tried it's implementation:
var HttpStatus = require('http-status-codes');
response
.status(HttpStatus.OK)
.send('ok');
response
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.send({
error: HttpStatus.getStatusText(HttpStatus.INTERNAL_SERVER_ERROR)
});
when the above is ran; I get a build error in terminal thrown on the 'response' word. I had no errors when installing relevant npm package.
ReferenceError: response is not defined
I have then tried a few suggestions I found on stackover, ie.
function getAllApps(request, response) {
appService.getApps(request.query.$expand).then(function (apps) {
response.status(200).send(apps);
})
.catch(function (err) {
console.error('Error occurred in Apps Api: ' + err);
response.status(500).send("" + err);
});
}
which is not effective and seems to just get ignored. any pointers appreciated (first time using this stack). cheers
Update, with the answer below from Jonas w - I was first getting time out error as follows:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
And then after increasing time out, I am getting below error:
TypeError: Cannot read property 'status' of undefined
For more context here is the test in which it is not passing with my attempts:
describe('error handling', function() {
it('handles missing routes', function(done) {
request
.get('/meals/brokenroute')
.expect(404)
.expect(function(res) {
ok('message' in res.body, '"message" key missing');
})
.end(done);
});

The best solution ive found yet ( using plain express only):
app.get('/allMeals', film.getAllMeals, END);
app.get('/meals/:id', film.getMealInfo, END);
app.get('/meals/:id/options',meal.getHealthyMealoptions,END);
function END(req,res){
res.end();
//note that this wont call next...
}
//all errors are handled below
app.get('*', function(req, res) {
res.status(404).end();
});

Related

how to attach middleware in swagger express like express-validator

I am trying to edit my express-swagger project and following this tutorial to add input validation into an express app
I used swagger-express-mw package to generate a boilerplate using swagger project create app but its not clear where I can add my middlewares as explained in the tutorial I mentioned above. Specifically i cant intercept my request, I get a typeError:
TypeError: req.checkBody is not a function
at saveNote
Here is my entry file. Everything apart from the bodyParser and validator function is coming from the boilerplate so I dont understand
SwaggerExpress.create(config, function(err, swaggerExpress) {
if (err) { throw err }
// install middleware
swaggerExpress.register(app)
// middleware
app.use(bodyParser.urlencoded({ extended: false })); // my code
app.use(validator()) // my d
const port = process.env.PORT || 10010
app.listen(port)
// if (swaggerExpress.runner.swagger.paths['/notes']) {
// console.log('try this:\nlocalhost:10010/notes to get all notes')
// }
})
I know this is an old question, but this might help someone along the way.Here is the simple NodeJS code showing how to integrate swagger-tools with swagger-express-mw or swagger-node-runner npm packages.
var SwaggerExpress = require('swagger-express-mw');
var swaggerTools = require('swagger-tools');
var app = require('express')();
module.exports = app; // for testing
var config = {
appRoot: __dirname // required config
};
SwaggerExpress.create(config, function(err, swaggerExpress) {
if (err) {
throw err;
}
// install middleware
swaggerExpress.register(app);
//swaggerobject is created by the swagger-express-mw module which is stored in runner object.
var swaggerObjectLoaded = swaggerExpress.runner.swagger;
//pass the runner object to initializeMiddleware function
swaggerTools.initializeMiddleware(swaggerObjectLoaded, function(middleware) {
app.use(middleware.swaggerMetadata());
// Validate Swagger requests
app.use(middleware.swaggerValidator());
// Route validated requests to appropriate controller
app.use(middleware.swaggerRouter(options));
// Serve the Swagger documents and Swagger UI
app.use(middleware.swaggerUi());
var port = process.env.PORT || 10010;
app.listen(port);
if (swaggerExpress.runner.swagger.paths['/hello']) {
console.log('try this:\ncurl http://127.0.0.1:' + port + '/hello?name=Scott');
}
});
});

TypeError: Cannot read property 'title' of null

I am getting this JavaScript error:
TypeError: Cannot read property 'title' of null
Here is the code:
Mds-iMac:cmscart imac$ nodemon app
[nodemon] 1.11.0
[nodemon] to restart at any time, enter rs
[nodemon] watching: .
[nodemon] starting node app app.js
(node:2274) DeprecationWarning: open() is deprecated in mongoose >= 4.11.0, use openUri() instead, or set the useMongoClient option if using connect() or createConnection(). See http://mongoosejs.com/docs/4.x/docs/connections.html#use-mongo-client
(node:2274) DeprecationWarning: Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html
Server started on port 3200
Db.prototype.authenticate method will no longer be available in the next major release 3.x as MongoDB 3.6 will only allow auth against users in the admin db and will no longer allow multiple credentials on a socket. Please authenticate using MongoClient.connect with auth credentials.
Connected to MongoDB
events.js:182
throw er; // Unhandled 'error' event
^
TypeError: Cannot read property 'title' of null
at /Users/imac/Desktop/cmscart/routes/pages.js:17:24
at model.Query.<anonymous> (/Users/imac/Desktop/cmscart/node_modules/mongoose/lib/model.js:4074:16)
at /Users/imac/Desktop/cmscart/node_modules/kareem/index.js:273:21
at /Users/imac/Desktop/cmscart/node_modules/kareem/index.js:131:16
at _combinedTickCallback (internal/process/next_tick.js:131:7)
at process._tickCallback (internal/process/next_tick.js:180:9)
[nodemon] app crashed - waiting for file changes before starting...
The page.js code is:
var express = require('express');
var router = express.Router();
// Get Page model
var Page = require('../models/page');
/*
* GET /
*/
router.get('/', function (req, res) {
Page.findOne({slug: 'home'}, function (err, page) {
if (err)
console.log(err);
res.render('index', {
title: page.title,
content: page.content
});
});
});
/*
* GET a page
*/
router.get('/:slug', function (req, res) {
var slug = req.params.slug;
Page.findOne({slug: slug}, function (err, page) {
if (err)
console.log(err);
if (!page) {
res.redirect('/');
} else {
res.render('index', {
title: page.title,
content: page.content
});
}
});
});
// Exports
module.exports = router;
The error is occuring inside the JavaScript functions at title: page.title, above.
Please help me out.
router.get('/edit-page/:slug', (req, res) => {
Page.findOne({slug : req.params.slug}).then((page) => {
if(!page) { //if page not exist in db
return res.status(404).send('Page not found');
}
res.render('admin/edit_page', { //page exist
title: page.title,
slug: page.slug,
content: page.content,
id: page._id
});
}).catch((e) => {//bad request
res.status(400).send(e);
});
});
Use this code it will work the logic is same but i have handled the conditions using promises it definitely worked for me. Hope it helps you :)
It means that when
function (err, page) {
was called, inside of Page.findOne, the page argument did not have a property of title.
(without context of how Page.findOne is being used, hard to say how to fix it)
if (err)
console.log(err);
Is one issue. When you get an error you should return an http error then exit the function eg:
if (err) {
console.log(err);
res.statusCode = 500;
res.end('error');
}
When there is an error the page variable will be null, which explains the exception thrown.

Passing in Async functions to Node.js Express.js router

This seems like a straightforward google, but I can't seem to find the answer...
Can you pass in ES6 ES7 async functions to the Express router?
Example:
var express = require('express');
var app = express();
app.get('/', async function(req, res){
// some await stuff
res.send('hello world');
});
If not, can you point me in the right direction on how to handle this problem ES7 style? Or do I just have to use promises?
Thanks!
May be you didn't found results because async/await is an ES7 not ES6 feature, it is available in node >= 7.6.
Your code will work in node.
I have tested the following code
var express = require('express');
var app = express();
async function wait (ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms)
});
}
app.get('/', async function(req, res){
console.log('before wait', new Date());
await wait(5 * 1000);
console.log('after wait', new Date())
res.send('hello world');
});
app.listen(3000, err => console.log(err ? "Error listening" : "Listening"))
And voila
MacJamal:messialltimegoals dev$ node test.js
Listening undefined
before wait 2017-06-28T22:32:34.829Z
after wait 2017-06-28T22:32:39.852Z
^C
Basicaly you got it, you have to async a function in order to await on a promise inside its code.
This is not supported in node LTS v6, so may be use babel to transpile code.
Hope this helps.
Update
Since ExpressJs 5, async functions are supported, and throw errors as expected
Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error
source
In Express 4 or less, it sort of works, but not really
While it seems to work, it stops handling errors thrown inside the async function, and as a result, if an error is not handled, the server never responds and the client keeps waiting until it timeout.
The correct behavior should be to respond with a 500 status code.
Solutions
express-promise-router
const router = require('express-promise-router')();
// Use it like a normal router, it will handle async functions
express-asyncify
const asyncify = require('express-asyncify')
To fix routes set in the app object
Replace var app = express(); with
var app = asyncify(express());
To fix routes set in router objects
Replace var router = express.Router(); with
var router = asyncify(express.Router());
Note
You only need to apply the asyncify function in the objects where you set the routes directly
https://www.npmjs.com/package/express-asyncify
I think you can't do it directly because exceptions are not caught and the function won't return if one is thrown. This article explains how to create a wrapper function to make it work: http://thecodebarbarian.com/using-async-await-with-mocha-express-and-mongoose.html
I haven't tried it but was investigating this recently.
Use express-promise-router.
const express = require('express');
const Router = require('express-promise-router');
const router = new Router();
const mysql = require('mysql2');
const pool = mysql.createPool({
host: 'localhost',
user: 'myusername',
password: 'mypassword',
database: 'mydb',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
}).promise();
router.get('/some_path', async function(req, res, next) {
const [rows, ] = await pool.execute(
'SELECT * ' +
'FROM mytable ',
[]
);
res.json(rows);
});
module.exports = router;
(The above is an example of using mysql2's promise interface with express-promise-router.)
Express 5 will automatically handle async errors for you
https://expressjs.com/en/guide/error-handling.html currently says it clearly:
Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error. For example:
app.get('/user/:id', async function (req, res, next) {
var user = await getUserById(req.params.id)
res.send(user)
})
If getUserById throws an error or rejects, next will be called with either the thrown error or the rejected value. If no rejected value is provided, next will be called with a default Error object provided by the Express router.
We can test that as follows:
const assert = require('assert')
const http = require('http')
const express = require('express')
const app = express()
app.get('/error', async (req, res) => {
throw 'my error'
})
const server = app.listen(3000, () => {
// Test it.
function test(path, method, status, body) {
const options = {
hostname: 'localhost',
port: server.address().port,
path: path,
method: method,
}
http.request(options, res => {
console.error(res.statusCode);
assert(res.statusCode === status);
}).end()
}
test('/error', 'GET', 500)
})
The terminal output on express#5.0.0-alpha.8 is the expected:
500
Error: my error
at /home/ciro/test/express5/main.js:10:9
at Layer.handle [as handle_request] (/home/ciro/test/node_modules/router/lib/layer.js:102:15)
at next (/home/ciro/test/node_modules/router/lib/route.js:144:13)
at Route.dispatch (/home/ciro/test/node_modules/router/lib/route.js:109:3)
at handle (/home/ciro/test/node_modules/router/index.js:515:11)
at Layer.handle [as handle_request] (/home/ciro/test/node_modules/router/lib/layer.js:102:15)
at /home/ciro/test/node_modules/router/index.js:291:22
at Function.process_params (/home/ciro/test/node_modules/router/index.js:349:12)
at next (/home/ciro/test/node_modules/router/index.js:285:10)
at Function.handle (/home/ciro/test/node_modules/router/index.js:184:3)
If you visit it on the browser, you will see an HTML page that says my error.
If you run the exact same code on express#4.17.1, you see on the terminal only:
UnhandledPromiseRejectionWarning: my error
but not the 500 nor my error. This is because the request just hangs forever. If you try to open it on the browser, you will see it hang more clearly.
TODO: how to make it show the stack trace as well instead of just my error? Getting the stack trace in a custom error handler in Express?
Express 4 solution
The simplest solution for Express 4 is to just wrap every single route in a try/catch as follows:
app.get('/error', async (req, res, next) => {
try {
throw new Error('my error'
res.send('never returned')
} catch(error) {
next(error);
}
})
which produces the same correct behavior as Express 5.
You can also factor this out further with some of the methods discussed at: express.js async router and error handling
Tested on Node.js v14.16.0.
To handle async requests in express routes use a try catch, which helps you to try for any errors in function and catch them. try{await stuff} catch{err}
An other less intrusive option is using express-async-errors
This will patch express to correctly handle async/await.
import express from 'express'
import 'express-async-errors'

Mocha API Testing: getting 'TypeError: app.address is not a function'

My Issue
I've coded a very simple CRUD API and I've started recently coding also some tests using chai and chai-http but I'm having an issue when running my tests with $ mocha.
When I run the tests I get the following error on the shell:
TypeError: app.address is not a function
My Code
Here is a sample of one of my tests (/tests/server-test.js):
var chai = require('chai');
var mongoose = require('mongoose');
var chaiHttp = require('chai-http');
var server = require('../server/app'); // my express app
var should = chai.should();
var testUtils = require('./test-utils');
chai.use(chaiHttp);
describe('API Tests', function() {
before(function() {
mongoose.createConnection('mongodb://localhost/bot-test', myOptionsObj);
});
beforeEach(function(done) {
// I do stuff like populating db
});
afterEach(function(done) {
// I do stuff like deleting populated db
});
after(function() {
mongoose.connection.close();
});
describe('Boxes', function() {
it.only('should list ALL boxes on /boxes GET', function(done) {
chai.request(server)
.get('/api/boxes')
.end(function(err, res){
res.should.have.status(200);
done();
});
});
// the rest of the tests would continue here...
});
});
And my express app files (/server/app.js):
var mongoose = require('mongoose');
var express = require('express');
var api = require('./routes/api.js');
var app = express();
mongoose.connect('mongodb://localhost/db-dev', myOptionsObj);
// application configuration
require('./config/express')(app);
// routing set up
app.use('/api', api);
var server = app.listen(3000, function () {
var host = server.address().address;
var port = server.address().port;
console.log('App listening at http://%s:%s', host, port);
});
and (/server/routes/api.js):
var express = require('express');
var boxController = require('../modules/box/controller');
var thingController = require('../modules/thing/controller');
var router = express.Router();
// API routing
router.get('/boxes', boxController.getAll);
// etc.
module.exports = router;
Extra notes
I've tried logging out the server variable in the /tests/server-test.js file before running the tests:
...
var server = require('../server/app'); // my express app
...
console.log('server: ', server);
...
and I the result of that is an empty object: server: {}.
You don't export anything in your app module. Try adding this to your app.js file:
module.exports = server
It's important to export the http.Server object returned by app.listen(3000) instead of just the function app, otherwise you will get TypeError: app.address is not a function.
Example:
index.js
const koa = require('koa');
const app = new koa();
module.exports = app.listen(3000);
index.spec.js
const request = require('supertest');
const app = require('./index.js');
describe('User Registration', () => {
const agent = request.agent(app);
it('should ...', () => {
This may also help, and satisfies #dman point of changing application code to fit a test.
make your request to the localhost and port as needed
chai.request('http://localhost:5000')
instead of
chai.request(server)
this fixed the same error message I had using Koa JS (v2) and ava js.
The answers above correctly address the issue: supertest wants an http.Server to work on. However, calling app.listen() to get a server will also start a listening server, this is bad practice and unnecessary.
You can get around by this by using http.createServer():
import * as http from 'http';
import * as supertest from 'supertest';
import * as test from 'tape';
import * as Koa from 'koa';
const app = new Koa();
# add some routes here
const apptest = supertest(http.createServer(app.callback()));
test('GET /healthcheck', (t) => {
apptest.get('/healthcheck')
.expect(200)
.expect(res => {
t.equal(res.text, 'Ok');
})
.end(t.end.bind(t));
});
Just in case, if someone uses Hapijs the issue still occurs, because it does not use Express.js, thus address() function does not exist.
TypeError: app.address is not a function
at serverAddress (node_modules/chai-http/lib/request.js:282:18)
The workaround to make it work
// this makes the server to start up
let server = require('../../server')
// pass this instead of server to avoid error
const API = 'http://localhost:3000'
describe('/GET token ', () => {
it('JWT token', (done) => {
chai.request(API)
.get('/api/token?....')
.end((err, res) => {
res.should.have.status(200)
res.body.should.be.a('object')
res.body.should.have.property('token')
done()
})
})
})
Export app at the end of the main API file like index.js.
module.exports = app;
We had the same issue when we run mocha using ts-node in our node + typescript serverless project.
Our tsconfig.json had "sourceMap": true . So generated, .js and .js.map files cause some funny transpiling issues (similar to this). When we run mocha runner using ts-node. So, I will set to sourceMap flag to false and deleted all .js and .js.map file in our src directory. Then the issue is gone.
If you have already generated files in your src folder, commands below would be really helpful.
find src -name ".js.map" -exec rm {} \;
find src -name ".js" -exec rm {} \;
I am using Jest and Supertest, but was receiving the same error. It was because my server takes time to setup (it is async to setup db, read config, etc). I needed to use Jest's beforeAll helper to allow the async setup to run. I also needed to refactor my server to separate listening, and instead use #Whyhankee's suggestion to create the test's server.
index.js
export async function createServer() {
//setup db, server,config, middleware
return express();
}
async function startServer(){
let app = await createServer();
await app.listen({ port: 4000 });
console.log("Server has started!");
}
if(process.env.NODE_ENV ==="dev") startServer();
test.ts
import {createServer as createMyAppServer} from '#index';
import { test, expect, beforeAll } from '#jest/globals'
const supertest = require("supertest");
import * as http from 'http';
let request :any;
beforeAll(async ()=>{
request = supertest(http.createServer(await createMyAppServer()));
})
test("fetch users", async (done: any) => {
request
.post("/graphql")
.send({
query: "{ getQueryFromGqlServer (id:1) { id} }",
})
.set("Accept", "application/json")
.expect("Content-Type", /json/)
.expect(200)
.end(function (err: any, res: any) {
if (err) return done(err);
expect(res.body).toBeInstanceOf(Object);
let serverErrors = JSON.parse(res.text)['errors'];
expect(serverErrors.length).toEqual(0);
expect(res.body.data.id).toEqual(1);
done();
});
});
Edit:
I also had errors when using data.foreach(async()=>..., should have use for(let x of... in my tests

Mocha/Should 'undefined is not a function'

I'm going off of this tutorial, trying to make tests with Mocha, Supertest, and Should.js.
I have the following basic test to create a user through a PUT endpoint that accepts it's data in headers.
describe('User Routes', function () {
it('should allow me to make my user', function (done) {
request(url)
.put('/users')
.set(myCreds)
// end handles the response
.end(function(err, res) {
if (err) {
throw err;
}
// this is should.js syntax, very clear
res.should.have.status(201);
done();
});
});
However, while the endpoint does trigger, and the user does get made, the code throws an error that should is undefined... Uncaught TypeError: undefined is not a function.
I have
var should = require('should');
var assert = require('assert');
var request = require('supertest');
At the top of the file, so why would it be undefined?
You are calling should incorrectly, try this:
res.should.have.property('status', 201)
or
res.status.should.be.equal(201)
or
should.equal(res.status, 201)
or install should-http.

Categories

Resources