No error shown in console when thrown from inside hapi plugin - hapi.js

For some reason no error shows up in the server console when I start my hapi server with nodemon and navigate to http://localhost:3000/hapi-ext-fetch and this makes debugging very difficult. Here is my code:
var Hapi = require('hapi');
var Joi = require('joi');
var fetch = require('isomorphic-fetch');
var debugMode = { debug: { request: [ 'error', 'request-internal' ] }};
var server = new Hapi.Server(debugMode);
server.connection({ port: 3000 });
var myPlugin = {
register: function (server, options, next) {
server.route([
{
method: 'GET',
path: '/{name}',
handler: function ( request, reply ) {
throw new Error('this error isnt shown!');
},
config: {
validate: {
params: {
name: Joi.string().min(3).max(10)
}
}
}
}
]);
next();
}
};
myPlugin.register.attributes = {
name: 'myPlugin',
version: '1.0.0'
};
server.register([
{
register: myPlugin,
routes: {
prefix: '/test'
}
}
], function() {
server.ext( 'onPreResponse', ( request, reply ) => {
if ( typeof request.response.statusCode !== 'undefined' ) {
return reply.continue();
}
fetch('http://localhost:3000/test/whatever')
.then(function(result) {
reply(result);
})
.catch(function(err) {
reply('error on server side: ' + err.stack);
});
});
server.start((err) => {
if (err) {
throw err;
}
console.log('Server running at:', server.info.uri);
});
});
I'm using hapi 13.0.0

Can't say I totally understand your use case here and if this question will be helpful to other people. But what you're trying to do it seems is:
Send a request to /hapi-fetch-ext
Have that request 404
And then in an onPreResponse go fetch another route /test/whatever
Hope to see the "this error isn't shown error"
Not sure if you're aware but this is going to cause an infinite cycle of requests (your fetch will cause another onPreResponse and so on and so on). So you should probably only go fetch on a 404:
server.ext( 'onPreResponse', ( request, reply ) => {
if (request.response.isBoom && request.response.output.statusCode === 404) {
return fetch('http://localhost:3000/test/whatever')
.then(function(result) {
reply(result);
})
.catch(function(err) {
reply('error on server side: ' + err.stack);
});
}
return reply.continue();
});

Related

Can't find mongoose validation error within express error object

I'm using mongoose js and trying to insert a new document into a mongodb. I'm passing the data object to express using Axios. When I get a validation error, the express server identifies the problem very easily. It returns an error productCode: ValidatorError: Path 'productCode' is required.
The error that is returned to express is a huge object and I can't find the validation error in it. Can someone tell me what I'm missing in my catch error?
UPDATED
Path: Express axios Post catch error
const product = {
productCode: '1a',
productName: 'product x'
};
async function createProduct(product) {
axios
.post(
`http://localhost:3000/myroute`,
product
)
.then((res) => {
console.log('res', res.data.productCode);
return res.data.productCode;
})
.catch((error) => {
// console.log('err', err.response);
if (error.response) {
/*
* The request was made and the server responded with a
* status code that falls out of the range of 2xx
*/
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
/*
* The request was made but no response was received, `error.request`
* is an instance of XMLHttpRequest in the browser and an instance
* of http.ClientRequest in Node.js
*/
console.log(error.request);
} else {
// Something happened in setting up the request and triggered an Error
console.log('Error', error.message);
}
// console.log(error);
});
}
UPDATE
Path: Server
const product = new Product(req.body);
product
.save()
.then((result) => {
res.status(200).json(result);
})
.catch((err) => {
console.log('err', err);
res.status(500).json({ error: err.reason });
});
Path: Model
var productSchema = new Schema(
{
productCode: {
type: String,
required: true
},
productName: { type: String, default: '' }
},
{ collection: 'products', timestamps: true, versionKey: false }
);

Apollo Server & 4xx status codes

Currently, my Apollo Server(running on HapiJS) returns HTTP 200 for every request, including failed ones.
I would like the GraphQL server to return HTTP 4xx for unsuccessful requests. The primary reason for it is that I want to set up monitoring for my ELB.
I know that Apollo Server has an engine platform, but I want to implement it using my current infrastructure.
Any ideas of how I could accomplish that? I tried to capture 'onPreResponse' event for my HapiJS server but I couldn't modify status code there.
After reading this answer. Here is a solution by modifying the hapijs plugin graphqlHapi of hapiApollo.ts file.
server.ts:
import { makeExecutableSchema } from 'apollo-server';
import { ApolloServer, gql } from 'apollo-server-hapi';
import Hapi from 'hapi';
import { graphqlHapi } from './hapiApollo';
const typeDefs = gql`
type Query {
_: String
}
`;
const resolvers = {
Query: {
_: () => {
throw new Error('some error');
},
},
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
const port = 3000;
async function StartServer() {
const app = new Hapi.Server({ port });
graphqlHapi.register(app, { path: '/graphql', graphqlOptions: { schema } });
app.ext('onPreResponse', (request: any, h: any) => {
const response = request.response;
if (!response.isBoom) {
return h.continue;
}
return h.response({ message: response.message }).code(400);
});
await app.start();
}
StartServer()
.then(() => {
console.log(`apollo server is listening on http://localhost:${port}/graphql`);
})
.catch((error) => console.log(error));
hapiApollo.ts:
import Boom from 'boom';
import { Server, Request, RouteOptions } from 'hapi';
import { GraphQLOptions, runHttpQuery, convertNodeHttpToRequest } from 'apollo-server-core';
import { ValueOrPromise } from 'apollo-server-types';
export interface IRegister {
(server: Server, options: any, next?: Function): void;
}
export interface IPlugin {
name: string;
version?: string;
register: IRegister;
}
export interface HapiOptionsFunction {
(request?: Request): ValueOrPromise<GraphQLOptions>;
}
export interface HapiPluginOptions {
path: string;
vhost?: string;
route?: RouteOptions;
graphqlOptions: GraphQLOptions | HapiOptionsFunction;
}
const graphqlHapi: IPlugin = {
name: 'graphql',
register: (server: Server, options: HapiPluginOptions, next?: Function) => {
if (!options || !options.graphqlOptions) {
throw new Error('Apollo Server requires options.');
}
server.route({
method: ['GET', 'POST'],
path: options.path || '/graphql',
vhost: options.vhost || undefined,
options: options.route || {},
handler: async (request, h) => {
try {
const { graphqlResponse, responseInit } = await runHttpQuery([request, h], {
method: request.method.toUpperCase(),
options: options.graphqlOptions,
query:
request.method === 'post'
? // TODO type payload as string or Record
(request.payload as any)
: request.query,
request: convertNodeHttpToRequest(request.raw.req),
});
// add our custom error handle logic
const graphqlResponseObj = JSON.parse(graphqlResponse);
if (graphqlResponseObj.errors && graphqlResponseObj.errors.length) {
throw new Error(graphqlResponseObj.errors[0].message);
}
const response = h.response(graphqlResponse);
Object.keys(responseInit.headers as any).forEach((key) =>
response.header(key, (responseInit.headers as any)[key]),
);
return response;
} catch (error) {
// handle our custom error
if (!error.name) {
throw Boom.badRequest(error.message);
}
if ('HttpQueryError' !== error.name) {
throw Boom.boomify(error);
}
if (true === error.isGraphQLError) {
const response = h.response(error.message);
response.code(error.statusCode);
response.type('application/json');
return response;
}
const err = new Boom(error.message, { statusCode: error.statusCode });
if (error.headers) {
Object.keys(error.headers).forEach((header) => {
err.output.headers[header] = error.headers[header];
});
}
// Boom hides the error when status code is 500
err.output.payload.message = error.message;
throw err;
}
},
});
if (next) {
next();
}
},
};
export { graphqlHapi };
Now, when the GraphQL resolver throws an error, the client-side will receive our custom response with Http status code 400 instead of 200 status code with GraphQL errors response.
General from the browser:
Request URL: http://localhost:3000/graphql
Request Method: POST
Status Code: 400 Bad Request
Remote Address: 127.0.0.1:3000
Referrer Policy: no-referrer-when-downgrade
The response body is: {"message":"some error"}

Two custom methods/endpoints using loopBack, one works, the other gives a 401

I created two custom endpoints with Loopback.
Account.deleteAllHearingTests = function (req, callback) {
console.log('here comes the req to delete all hearing tests', req);
Account.findById(req.accessToken.userId)
.then(account => {
if (!account) {
throw new Error('cannot find user');
}
return app.models.HearingTest.updateAll({ accountId: account.id }, { isDeleted: new Date() });
})
.then(() => {
callback(null);
})
.catch(error => {
callback(error);
})
}
Account.remoteMethod(
'deleteAllHearingTests', {
http: {
path: '/clearHearingTests',
verb: 'post'
},
accepts: [
{ arg: 'req', type: 'object', http: { source: 'req' } }
],
returns: {}
}
);
the second one looks like this.
Account.deleteSingleHearingTest = function (req, callback) {
// console.log('accounts.js: deleteSingleHearingTest: are we being reached????', req)
Account.findById(req.accessToken.userId)
.then(account => {
if (!account) {
throw new Error('Cannot find user');
}
console.log('account.js: deleteSingleHearingTest: req.body.hearingTestId N: ', req.body.hearingTestId);
return app.models.HearingTest.updateAll({ accountId: account.id, id: req.body.hearingTestId }, { isDeleted: new Date() });
})
.then(() => {
callback(null);
})
.catch(error => {
callback(error);
});
}
Account.remoteMethod(
'deleteSingleHearingTest', {
http: {
path: '/deleteSingleHearingTest',
verb: 'post'
},
accepts: [
{ arg: 'req', type: 'object', description: 'removes a single hearing test', http: { source: 'req' } }
],
description: 'this is the end point for a single delete',
returns: {}
}
);
};
The first custom method returns a 401 status response when I make the initial fetch. The second returns a 200.
Inside my actions file the first method is called with something that looks like this:
export function deleteAllHearingTests() {
return (dispatch, getState) => {
let state = getState();
if (!state.user || !state.user.accessToken || !state.user.accessToken.id || !state.user.accessToken.userId) {
console.debug('deleteAllHearingTests', state.user);
// TODO: ERROR
return;
}
fetch(SERVERCONFIG.BASEURL + '/api/Accounts/clearHearingTests?access_token=' + state.user.accessToken.id, {
method: 'POST',
headers: SERVERCONFIG.HEADERS
})
.then(response => {
console.log('here is your response', response);
if (response.status !== 200) {
throw new Error('Something is wrong');
}
return response.json()
})
the second method is called with
export const deleteSingleHearingTest = (hearingTestNumber) => {
return (dispatch, getState) => {
let state = getState();
if (!state.user || !state.user.accessToken || !state.user.accessToken.id || !state.user.accessToken.userId) {
console.debug('writeTestResult', state.user);
// TODO: ERROR
return;
}
console.log('single delete ', SERVERCONFIG.BASEURL + '/api/Accounts/deleteSingleHearingTest?access_token=' + state.user.accessToken.id)
fetch(SERVERCONFIG.BASEURL + '/api/Accounts/deleteSingleHearingTest?access_token=' + state.user.accessToken.id, {
method: 'POST',
headers: SERVERCONFIG.HEADERS,
body: JSON.stringify({ "hearingTestId": hearingTestNumber })
})
.then(response => {
console.log('getting response from initial fetch inside deleteSingleReqport', response);
They are nearly identical, however, one works..the other fails. What are some possible causes for the 401?
Did you try to call those methods with external tool like a postman, so you would exactly know if you don't miss access_token or something else? Also, when you compare code from one function and another, you can see that you are colling the updateAll with different arguments. It's hard to say without original code, but maybe the issue is there? Compare below:
return app.models.HearingTest.updateAll(
{ accountId: account.id },
{ isDeleted: new Date() });
return app.models.HearingTest.updateAll(
{ accountId: account.id, id: req.body.hearingTestId },
{ isDeleted: new Date() });
Additionally, in fetch method they are also diffferences, you are missing in one case the below:
body: JSON.stringify({ "hearingTestId": hearingTestNumber })
What you could also do to debug and to provide more data is to run server in debug mode by calling:
export DEBUG=*; npm start

Node js: mssql [ConnectionError: Connection is closed.] name: 'ConnectionError', message: 'Connection is closed.', code: 'ECONNCLOSED'

i am getting error in npm mssql 3.0.0 with sqlserver 2012
i am creating single page application where i used restful using express .
there are 4 method which executing the query and returning the data to response.
for each method i am opening the connection and closing the connection.
but when savedquery is calling then connection close error occurs.
each method code is similar to savedquery method (copy pasted code only queries are changed) but they are executing savedquery is not executing
{ [ConnectionError: Connection is closed.]
name: 'ConnectionError',
message: 'Connection is closed.',
code: 'ECONNCLOSED' }
var savedquery=function(req,res){
dbConfig= {
user: 'XXX',
password: 'XXXXXXXXXX',
server: 'localhost', // You can use 'localhost\\instance' to connect to named instance
database: 'DEMO_ODS',
options: {
encrypt: true
}
};
sql.connect(dbConfig).then(function (err) {
var sqlrequest = new sql.Request();
sqlrequest.query("SELECT * from SavedQuery").then(function (recordset) {
sql.close(function (value) {
console.log("connection6 closed");
});
return res.status(200).send(recordset);
}).catch(function (err) {
console.log(err);
});
}).catch(function (err) {
console.log(err);
});
};
}
I know it is an old questionm but this answer is for the others who are facing the same isue. I had the same problem, What I did is, used promises as below.
function getData() {
try {
sqlInstance.connect(setUp)
.then(function () {
// Function to retrieve all the data - Start
new sqlInstance.Request()
.query("select * from Course")
.then(function (dbData) {
if (dbData == null || dbData.length === 0)
return;
console.dir('All the courses');
console.dir(dbData);
})
.catch(function (error) {
console.dir(error);
});
// Function to retrieve all the data - End
// To retrieve specicfic data - Start
var value = 1;
new sqlInstance.Request()
.input("param", sqlInstance.Int, value)
.query("select * from Course where CourseID = #param")
.then(function (dbData) {
if (dbData == null || dbData.length === 0)
return;
console.dir('Course with ID = 1');
console.dir(dbData);
})
.catch(function (error) {
console.dir(error);
});
// To retrieve specicfic data - End
}).catch(function (error) {
console.dir(error);
});
} catch (error) {
console.dir(error);
}
}
This solved my issue. You can find the fix here.
You should remove
options: {
encrypt: true
}
from your dbConfig
I just use promise to handle concurrent request:
const executeQuery = function (res, query, dbName) {
dbConfig = {
user: "********",
password: "********",
server: "*******",
database: dbName
}
sql.connect(dbConfig).then(pool => {
return pool.request().query(query)
}).then(result => {
res.send(result);
}).catch(err => {
res.send(err);
});
}
Hope it's help someone.

Invalidate session with custom authenticator

Using ember-cli 0.1.2 and ember-cli-simple-auth 0.7.0, I need to invalidate the session both on client and server. As explained here I need to do something similar to the authenticate method making an ajax request to the server and ensuring its success before emptying the session:
import Ember from 'ember';
import Base from "simple-auth/authenticators/base";
var CustomAuthenticator = Base.extend({
tokenEndpoint: 'http://127.0.0.1:3000/api/v1/auth/login',
restore: function(data) {
},
authenticate: function(credentials) {
var _this = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
url: _this.tokenEndpoint,
type: 'POST',
data: JSON.stringify({ email: credentials.identification, password: credentials.password }),
contentType: 'application/json'
}).then(function(response) {
Ember.run(function() {
resolve({ token: response.token });
});
}, function(xhr, status, error) {
var response = JSON.parse(xhr.responseText);
Ember.run(function() {
reject(response.error);
});
});
});
},
invalidate: function() {
var _this = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
url: _this.tokenEndpoint,
type: 'DELETE'
}).then(function(response) {
resolve();
}, function(xhr, status, error) {
var response = JSON.parse(xhr.responseText);
Ember.run(function() {
reject(response.error);
});
});
});
}
// invalidate: function() {
// var _this = this;
// return new Ember.RSVP.Promise(function(resolve) {
// Ember.$.ajax({ url: _this.tokenEndpoint, type: 'DELETE' }).always(function() {
// resolve();
// });
// });
// }
});
export default {
name : 'authentication',
before : 'simple-auth',
initialize : function(container) {
container.register('authenticator:custom', CustomAuthenticator);
}
};
My logout API endpoint need the token (in the headers). How do I pass it? I read this but my authorizer seems ignoring it and I got a 401:
import Ember from 'ember';
import Base from 'simple-auth/authorizers/base';
var CustomAuthorizer = Base.extend({
authorize: function(jqXHR, requestOptions){
Ember.debug("AUTHORIZING!");
}
});
export default {
name : 'authorization',
before : 'simple-auth',
initialize : function(container) {
container.register('authorizer:custom', CustomAuthorizer);
}
};
My environment.js:
/* jshint node: true */
module.exports = function(environment) {
var ENV = {
modulePrefix: 'wishhhh',
environment: environment,
baseURL: '/',
locationType: 'auto',
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build
// e.g. 'with-controller': true
}
},
APP: {
// Here you can pass flags/options to your application instance
// when it is created
}
};
// TODO: disabled because of https://github.com/stefanpenner/ember-cli/issues/2174
ENV.contentSecurityPolicyHeader = 'Disabled-Content-Security-Policy'
ENV['simple-auth'] = {
authorizer: 'authorizer:custom',
// crossOriginWhitelist: ['http://localhost:3000']
crossOriginWhitelist: ['*']
}
if (environment === 'development') {
// ENV.APP.LOG_RESOLVER = true;
ENV.APP.LOG_ACTIVE_GENERATION = true;
// ENV.APP.LOG_TRANSITIONS = true;
// ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
ENV.APP.LOG_VIEW_LOOKUPS = true;
}
if (environment === 'test') {
// Testem prefers this...
ENV.baseURL = '/';
ENV.locationType = 'auto';
// keep test console output quieter
ENV.APP.LOG_ACTIVE_GENERATION = false;
ENV.APP.LOG_VIEW_LOOKUPS = false;
ENV.APP.rootElement = '#ember-testing';
}
if (environment === 'production') {
}
return ENV;
};
The following is the Ember inspector output when, eventually, I try to logout:
Did you actually configure Ember Simple Auth to use your custom authorizer? In that case it should authorize the session invalidation request automatically.
Alternatively you could add the token in the authenticator's invalidate method which gets passed the session's contents.
Thanks to marcoow, I found out that it was actually a problem with every request not only the logout one. My authorizer never got called. Problem was environment setup of crossOriginWhitelist which, in order to work with my dev API, I had to set to ['http://127.0.0.1:3000']. Neither ['http://localhost:3000'] nor [*] worked.