Osprey RAML Validation Error Handling - javascript

I am trying to custom handle response sent to caller due to RAML specification failure. At the moment my code does the following.
const cfg = require("./cfg");
const log = require('./logging');
const RAML = require('osprey');
const startMessage = "My Service started on port " + cfg.SERVER_PORT + " at " + cfg.API_MOUNT_POINT;
// start an express server
const start = x => {
// server dependencies
const fs = require('fs'),
express = require('express'),
app = express(),
router = express.Router(),
bodyParser = require('body-parser'),
api = require('./api');
RAML.loadFile(cfg.API_SPEC).then(_raml => {
app.use(bodyParser.json({ extended: true }));
// hide the useless "powered by express" header
app.disable('x-powered-by');
// RAML validation
app.use(cfg.API_MOUNT_POINT, _raml);
app.use(cfg.API_MOUNT_POINT, api);
})
.then(v => {
app.listen(cfg.SERVER_PORT, function() {
log.info(startMessage);
});
})
.catch(e => log.error(e));
}
This works well but the response sent to caller when validation fails is shown below.
{
"errors": [
{
"type": "json",
"dataPath": "redeemtype",
"keyword": "required",
"schema": true,
"message": "Missing required property: redeemtype"
}
],
"stack": "BadRequestError: Request failed to validate against RAML definition\n at createValidationError (/Volumes/Devel/dollardine/node_modules/osprey-method-handler/osprey-method-handler.js:735:14)\n at ospreyJsonBody (/Volumes/Devel/dollardine/node_modules/osprey-method-handler/osprey-method-handler.js:448:21)\n at handle (/Volumes/Devel/dollardine/node_modules/compose-middleware/lib/index.js:56:16)\n at dispatch (/Volumes/Devel/dollardine/node_modules/compose-middleware/lib/index.js:39:20)\n at next (/Volumes/Devel/dollardine/node_modules/compose-middleware/lib/index.js:37:24)\n at jsonParser (/Volumes/Devel/dollardine/node_modules/body-parser/lib/types/json.js:94:7)\n at handle (/Volumes/Devel/dollardine/node_modules/compose-middleware/lib/index.js:56:16)\n at dispatch (/Volumes/Devel/dollardine/node_modules/compose-middleware/lib/index.js:39:20)\n at middleware (/Volumes/Devel/dollardine/node_modules/compose-middleware/lib/index.js:41:16)\n at /Volumes/Devel/dollardine/node_modules/compose-middleware/lib/index.js:10:16\n at ospreyContentType (/Volumes/Devel/dollardine/node_modules/osprey-method-handler/osprey-method-handler.js:325:17)\n at handle (/Volumes/Devel/dollardine/node_modules/compose-middleware/lib/index.js:56:16)\n at dispatch (/Volumes/Devel/dollardine/node_modules/compose-middleware/lib/index.js:39:20)\n at next (/Volumes/Devel/dollardine/node_modules/compose-middleware/lib/index.js:37:24)\n at ospreyMethodHeader (/Volumes/Devel/dollardine/node_modules/osprey-method-handler/osprey-method-handler.js:262:12)\n at handle (/Volumes/Devel/dollardine/node_modules/compose-middleware/lib/index.js:56:16)"
}
This is great, but I do not want to send all this info to caller. I want just log it locally and just sent {"code": 400, "message": "Invalid input"}
How can I make osprey to give me ability to handle the error response?

I found answer to my own question. In case anyone in future gets stuck here.
const start = x => {
// server dependencies
const fs = require('fs'),
express = require('express'),
app = express(),
router = express.Router(),
bodyParser = require('body-parser'),
api = require('./api');
const ramlConfig = {
"server": {
"notFoundHandler": false
},
"disableErrorInterception": true
}
osprey.loadFile(cfg.API_SPEC, ramlConfig).then(_raml => {
app.use(bodyParser.json({ extended: true }));
// hide the useless "powered by express" header
app.disable('x-powered-by');
// RAML validation
app.use(cfg.API_MOUNT_POINT, _raml);
app.use(customNotFoundHandler);
app.use(ramlErrorChecker);
app.use(cfg.API_MOUNT_POINT, api);
//app.use(ramlErrorChecker);
})
.then(v => {
app.listen(cfg.SERVER_PORT, function() {
log.info(startMessage);
});
})
.catch(e => log.error(e));
}
const ramlErrorChecker = (err, req, res, next) => {
if (err) {
log.error("RAML validation failed. Reason: " + err);
res.status(400);
return res.json(buildResponseJSON(400));
}
return next();
}
const customNotFoundHandler = (req, res, next) => {
// Check for existence of the method handler.
if (req.resourcePath) {
return next()
}
res.status(404);
return res.json({"message": "The path is not found"});
}
The important part is ramlConfig which helps in putting some customisation. By setting "disableErrorInterception" to true, we are taking over the error handling which is much better for standardisation and most importantly hiding the fact the RAML is being used. Setting "notFoundHandler" to false means, the undeclared routes are reject gracefully instead of random html.

Related

Why Expres Async Errors send different result as expected

I found a nice npm package by the name of Express Async Errors which according to the documentation, it's really nice to use.
However, if I implement it, the server will crash.
Here is my Route handler code
Controller
const { Genre } = require("../models");
const { StatusCodes } = require("http-status-codes");
const getGenre = async (req, res) => {
const genre = await Genre.findById({ _id: req.params.id });
if (!genre) {
return res.status(StatusCodes.BAD_REQUEST).json({
message: "The genre with the given ID was not found.",
});
}
res.status(StatusCodes.OK).json({ status: "success", data: genre });
};
*router*
const express = require("express");
const { authenticate, admin } = require("../middleware");
const router = express.Router();
const { schemaValidator } = require("../middleware");
const validateRequest = schemaValidator(true);
const { genres } = require("../controllers");
const { getAllGenres, getGenre, createGenre, updateGenre, deleteGenre } =
genres;
.route("/genres")
.get(getAllGenres)
Main Application Entry
require("express-async-errors");
//Routers
const routers = require("./router");
const connectDB = require("./DB/connect");
const express = require("express");
const app = express();
app.use(config.get("URI"), routers);
app.use(notFoundMiddleware);
const start = async () => {
const port = process.env.PORT || 3000;
const connectionString = config.get("mongoDB.connString");
await connectDB(connectionString)
.then(() => DBdebug(`Connected to MongoDB: ${connectionString}`))
.catch(() => console.log("MongoDB connection failure"));
app.listen(port, () => debug(`Listening on port ${port}...`));
};
start();
Above code is imported into index.js together with express-async-errors.
According to the document, if I create an error, express-async-errors has to handle this without crashing the application. My question is what I'm doind wrong???
I shut down the Mongo-driver just to create a scenario that the server is down with a status 503.
MongooseServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017
at Function.Model.$wrapCallback (/Volumes/Seagate/lib/model.js:5087:32)
at /Volumes/Seagate/lib/query.js:4510:21
at /Volumes/Seagate/node_modules/mongoose/lib/helpers/promiseOrCallback.js:32:5
From previous event:
at promiseOrCallback (/Volumes/Seagate/node_modules/mongoose/lib/helpers/promiseOrCallback.js:31:10)
at model.Query.exec (/Volumes/Seagate/node_modules/mongoose/lib/query.js:4509:10)
at model.Query.Query.then (/Volumes/Seagate/node_modules/mongoose/lib/query.js:4592:15)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
Instead of above error, I should see "Something went wrong" as the default message in Postman without crashing the application.
Can someone point me to the right direction?
NB: The link which I had used us is https://www.npmjs.com/package/express-async-errors
Thank you very much in advanced.
After two days of researching my problem, I finally convinced my self that the problem was me and found a solution for this particular matter.
I have created an ErrorHandlerMiddleware and in the particular middleware I check if the error is an instance of MongooseError object, if this is the case, I just send for now an custom message.
See code below:
const { StatusCodes } = require("http-status-codes");
const { CustomApiError } = require("../errors");
const Mongoose = require("mongoose");
const errorHandlerMiddleware = (err, req, res, next) => {
console.log("errorMiddleWare");
if (err instanceof CustomApiError) {
return res.status(err.statusCode).json({ message: err.message });
}
if (err instanceof Mongoose.Error) {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
message: "There is a technical problem, please try again later",
});
}
};
module.exports = { errorHandlerMiddleware };
In main application entry, I just pass errorHandlerMiddleware as a argument reference in app.use.
See code below
require("express-async-errors");
//Routers
const routers = require("./router");
const connectDB = require("./DB/connect");
const express = require("express");
const app = express();
app.use(config.get("URI"), routers);
app.use(notFoundMiddleware);
app.use(errorHandlerMiddleware);
const start = async () => {
const port = process.env.PORT || 3000;
const connectionString = config.get("mongoDB.connString");
await connectDB(connectionString)
.then(() => DBdebug(`Connected to MongoDB: ${connectionString}`))
.catch(() => console.log("MongoDB connection failure"));
app.listen(port, () => debug(`Listening on port ${port}...`));
};
start();
And this is the final result in PostMan:
If there are any comments regarding this solution, feel free to do this.
By comments and can learn more!!!
Thank you in advanced and keep coding!!!!

TypeError: req.flash is not a function -- (NodeJs) (connect-flash) -- I can not open the page

When I tried to log in from page, I got error
TypeError: req.flash is not a function
I explain the errors I got and the methods I tried
If I delete this code console.log (req.flash ("validation_error")) code in the function named "registerFormunuGoster" in the
auth_controller file, I can make a successful link to the page in
the first step. If I do not delete this code, I cannot connect to
the page successfully in the first step.
The text I mentioned above is translated into code below.
const registerFormunuGoster = (req, res) => { res.render("register", { layout: "./layout/auth_layout" ,}) }
Let's say I write the code mentioned above and opened the page, after that I fill the form on my page and I get the same error whenever I press the submit button after filling out the form. To solve this problem, under the auth_controller.js file If I delete the code "req.flash (" validation_error ", errors)" in the function named "register" this time i get a different error.I am leaving the other error I get below. I think the reason I got such an error must be because I did res.redirect().
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
The events I mentioned in item 4 are translated into code below.
`const register = (req, res) => {
const hatalar = validationResult(req);
if (!hatalar.isEmpty()) {
res.redirect("/register")
}
res.render("register", { layout: "./layout/auth_layout" ,})
}`
Below are my app.js and auth_controller.js configuration
app.js
const express = require("express")
const app = express()
const dotenv = require("dotenv").config()
const session = require("express-session")
const flash = require("connect-flash")
// database bağlantısı - veritabanı bağlantısı
require("./src/config/database")
// TEMPALTE ENGİNE AYARALARI
const ejs = require("ejs")
const expressLayouts = require("express-ejs-layouts")
const path = require("path")
app.set("view engine", "ejs")
app.set("views", path.resolve(__dirname, "./src/views"))
app.use(express.static("public"))
app.use(expressLayouts)
// routerlar include edilip kullanılır
const authRouter = require("./src/routers/auth_router")
app.use("/", authRouter)
// formdan yollanılan verileri json a çevirip verilere dönüşmesi için bu şart
app.use(express.urlencoded({ extended: true }))
//* const session = require("express-session")
//* Seesion ve flash message
//* bunları yapmak için yukarıdaki modul lazım
//? önemli bir not çıldıurmak üzereydim kodlar çalışmıyordu kodların çalışması üçün üst sıralar çekince oldu bakalım neyden kaynaklanıyorumuş deneyerek bulucam
//? app.get gibi sunucu istekleri yönlendirmeden önce kullanılması lazımmış
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: {
maxAge:1000*5
}
//* maxAge: verilen cookienin ne kadar zaman sonra kendisini ihma etmesini söylüyor
//* saniye cinsinden verdik
}))
//? flash mesajlarının middleware olarak kullanılmasını sağladık yani aldığımız hatayı flash sayesinde kullanabilceğiz
app.use(flash())
let sayac = 0
app.get("/", (req, res) => {
if (!req.session.sayac) {
req.session.sayac = 1
} else {
req.session.sayac++
}
res.json({ message: "Hello World", sayac: req.session.sayac })
})
app.listen(process.env.PORT, _ => console.log(`Server started at ${process.env.PORT} port `))
auth_controller.js:
const { validationResult } = require("express-validator")
const loginFormunuGoster = (req, res) => {
res.render("login", { layout: "./layout/auth_layout" })
}
const login = (req, res) => {
res.render("login", { layout: "./layout/auth_layout" })
}
const registerFormunuGoster = (req, res) => {
// console.log(req.flash("validation_error"))
res.render("register", { layout: "./layout/auth_layout" ,})
}
const register = (req, res) => {
const hatalar = validationResult(req);
// req.body adı altında girilen veriler gözüküyor
// console.log(req.body)
// console.log(hatalar)
if (!hatalar.isEmpty()) {
// req.flash("validation_error",hatalar)
res.redirect("/register")
}
res.render("register", { layout: "./layout/auth_layout" ,})
}
const forgetPasswordFormunuGoster = (req, res) => {
res.render("forget_password", { layout: "./layout/auth_layout" })
}
const forgetPassword = (req, res) => {
res.render("forget_password", { layout: "./layout/auth_layout" })
}
module.exports = {
loginFormunuGoster,
registerFormunuGoster,
forgetPasswordFormunuGoster,
register,
login,
forgetPassword,
}
I would really appreciate everyone's help!!!
I hope I explained the error I received well
I think your problem come from a misunderstanding of how you should send you request back.
The second error you have 'Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client' indicates that the request you are trying to manipulate again is already finished (you can read a better explanation here: Error: Can't set headers after they are sent to the client)
So in your case, you cannot do a .redirect and a .render in the same request.
For your initial error, it seems that flash is not attached to the req object. I'm not sure but it might be because you are requiring it after your router and it is not ready when used in it.
There:
// 'use' it before you require your auth_router
app.use(flash());
// then require your auth_controller.js file somewhere in this
const authRouter = require("./src/routers/auth_router")
app.use("/", authRouter)
Read connect-flash doc for more indication on how to use it: https://www.npmjs.com/package/connect-flash

Next.js form works locally but not on live server

I have been implementing a Next.js app for a side project of mine. It is a basic brochure-style site with a contact form.
The form works perfectly fine when the site is run locally, however I have just published the site to Netlify and now when submitting a form I encounter the following error:
POST https://dux.io/api/form 404
Uncaught (in promise) Error: Request failed with status code 404
at e.exports (contact.js:9)
at e.exports (contact.js:16)
at XMLHttpRequest.d.(/contact/anonymous function) (https://dux.io/_next/static/cFeeqtpSGmy3dLZAZZWRt/pages/contact.js:9:4271)
Any help would be extremely appreciated!
This is my Form Submit function:
async handleSubmit(e) {
e.preventDefault();
const { name, email, option, message } = this.state;
const form = await axios.post('/api/form', {
name,
email,
option,
message
});
this.setState(initialState);}
This is my server.js file:
const express = require('express');
const next = require('next');
const bodyParser = require('body-parser');
const mailer = require('./mailer');
const compression = require('compression');
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = express();
server.use(compression());
server.use(bodyParser.json());
server.post('/api/form', (req, res) => {
const { email = '', name = '', option = '', message = '' } = req.body;
mailer({ email, name, option, text: message })
.then(() => {
console.log('success');
res.send('success');
})
.catch(error => {
console.log('failed', error);
res.send('badddd');
});
});
server.get('*', (req, res) => {
return handle(req, res);
});
server.listen(3000, err => {
if (err) throw err;
console.log('> Read on http://localhost:3000');
});
});
It looks like nextjs tries to render the /api/form page and you get a not found with that.
Please make sure you start the server with node server.js instead of next start.
What about try to use full endpoint http://~~~/api/form instead of just /api/form?
Or I think, you can solve this problem if you use process.env
const config = {
endpoint: 'http://localhost:8080/api'
}
if (process.env.NODE_ENV === 'production') {
config.endpoint = 'hostname/api'
}

How to make a POST request for each file in array

I have an array of drag'n'dropped files inside angular component. I would like to make a POST request to http://some.url for each of them. I'm trying to do the following:
drop.component.ts
public drop(event) {
* somehow set droppedFiles *
let observables = [];
this.droppedFiles.forEach(file => observables.push(this.uploadFile(file)));
forkJoin(observables);
}
public uploadFile(image) {
return this.imagesService.saveImage(image, this.tigerId).pipe(first()).subscribe(
(data: ISaveImageResponse) => {
console.log(data);
return;
},
error => {
console.error(error);
return;
}
);
}
images.service.ts
public saveImage(image: File): Observable<ISaveImageResponse> {
let imageInfo = {
name: null, type: null, image: null
};
imageInfo.name = [image.name, Validators.required];
imageInfo.type = [image.type, Validators.required];
imageInfo.image = null;
let form = this.formBuilder.group(imageInfo);
form.get('image').setValue(image);
const formModel = this.prepareFormData(form);
return this.http.post<any>(
'http://some.url',
formModel
).pipe(
map((imageInfo: any) => {
return imageInfo
}),
catchError((error, caught) => {
return EMPTY;
})
);
}
If I drop single file, this works fine. But if there are multiple files, requests become pending but I can't see them logged to server (which is express.js server).
What is the problem?
UPDATE
I've updated code to be actual: now uploadImage() returns Observable and requests are called from forkJoin()
UPDATE 2
After some time requests being pending I get the following error in server console:
(node:1291) MaxListenersExceededWarning: Possible EventEmitter memory leak detected.
11 field listeners added. Use emitter.setMaxListeners() to increase limit
But no info about request happening at all (for any request I do, for example console.log('POST /images');)
UPDATE 3
server-side code for handling POST requests:
server.js
const server = express();
const fs = require("fs");
const path = require('path');
const passport = require('passport');
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
server.use(
session({
store: new RedisStore({
url: config.redisStore.url
}),
secret: config.redisStore.secret,
resave: false,
saveUninitialized: false
})
);
server.use( passport.initialize() );
server.use( passport.session() );
server.use( cors({ origin: '*' }) );
server.use( bp.json() );
server.use( express.static('uploads') );
server.use( require('./image.routes') );
const port = 9901;
server.listen(port, () => {
const dir = __dirname + '/uploads';
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
console.log('We are live on ' + port);
});
image.routes.js
const fs = require('fs');
const formidable = require('express-formidable');
const path = require('path');
let router = express.Router();
router.post('/images',
formidable({
encoding: 'utf-8',
uploadDir: path.resolve(__dirname, 'uploads'),
multiples: true,
keepExtensions: true
}),
(req, res, next) => {
console.log('\nPOST /images');
const image = req.fields;
const data = req.files;
image.path = data.image.path;
const file = fs.createReadStream(image.path);
createImage(image).then( // createImage() saves image info to db
result => {
if (result) {
res.status(200).send(result);
} else {
console.error("Cannot save image");
res.status(400).send("Cannot save image");
}
}).catch(e => console.error(e.stack));
});
module.exports = router;
You cant use Promise.all to handle Rxjs requests.
You can use forkJoin to make multiple Observale request at once,
public drop(event) {
* somehow set droppedFiles *
let observables = []
this.droppedFiles.forEach(file => observables.push(this.uploadFile(file)));
Rx.Observable.forkJoin(observables)
}
Also your uploadFile function is not returning an observable
public uploadFile(image) {
return this.imagesService.saveImage(image, this.tigerId).pipe(first())
}
check out example number 5 here
Try using 'map' or 'for' instead forEach.
public drop(event) {
* somehow set droppedFiles *
Promise.all(this.droppedFiles.map(file => this.uploadFile(file)));
}

How to connect Node.js to existing Postgres? error: Unhandled promise rejection

I have an existing postgresql database with Rails, now I'm making a Node.js app which is using the same database. I already have users in my db and now I would like to list them all.
I successfully created an express app and then I did as follows:
✗ npm install --save sequelize pg pg-hstore
✗ sequelize init
index.js
const express = require('express');
const logger = require('morgan');
const bodyParser = require('body-parser');
const pg = require('pg');
var conString = 'postgres://localhost:5432/db_name';
var client = new pg.Client(conString);
const app = express();
client.connect(err => {
if (err) {
console.error('connection error', err.stack);
} else {
console.log('connected');
}
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.get('/', (req, res) => {
res.send(models.User.findAll);
});
const PORT = process.env.PORT || 5000;
app.listen(PORT);
In my config.json I have:
"development": {
"username": "my_username",
"password": null,
"database": "database_name",
"host": "127.0.0.1",
"dialect": "postgres"
}
I get this error: UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()
I'm probably missing a big step but I don't know what it is, I have never done this before.
Example Query
const query = {
text: 'CREATE TABLE IF NOT EXISTS coverages ('+
'vaccine VARCHAR(64),' +
'country VARCHAR(255),' +
'region VARCHAR(255),' +
'year VARCHAR(4),' +
'value VARCHAR(12),' +
'PRIMARY KEY(vaccine, country, region, year, value))'
};
client.query(query)
.then(function(res) {
console.log(res);
})
.catch(function(err) {
console.log('\nError executing query', err.stack);
});
Here are some example queries using async / await (which requires Node 8+, I believe, so make sure your version supports this):
var express = require('express');
var pg = require('pg');
var router = express.Router();
let conString = 'postgres://localhost:5432/db_name';
var postgrespool = new pg.Pool({
connectionString: conString
});
router.get('/checkdbconnection', function(req, res, next) {
(async () => {
// Here is the query!
// alter it to query a table in your db
// this example just confirms a connection
var { rows } = await postgrespool.query(`
SELECT
'Hello from Postgres' AS pg_val;`);
if (rows.length) {
return res.send(rows);
} else {
res.status(404);
return res.send('No response from database.');
}
})().catch(e =>
setImmediate(() => {
res.status(500);
console.log(e);
return res.send('Error: ' + e.message);
})
);
});
router.get('/checkdbconnection/:name', function(req, res, next) {
let param_name = req.params.name;
(async () => {
// this example demonstrates how to pass parameters to your query with $1, $2, etc.
// usually, the cast of "::text" won't be necessary after the "$1"
var { rows } = await postgrespool.query(`
SELECT
'Hello from Postgres' AS pg_val,
$1::text AS parameter;`, [param_name]);
if (rows.length) {
return res.send(rows);
} else {
res.status(404);
return res.send('No response from database.');
}
})().catch(e =>
setImmediate(() => {
res.status(500);
console.log(e);
return res.send('Error: ' + e.message);
})
);
});
module.exports = router;
If you visit http://localhost:5000/checkdbconnection , you'll get this response:
[
{
"pg_val": "Hello from Postgres"
}
]
And if you visit, say, http://localhost:5000/checkdbconnection/Al-josh , you'll get this:
[
{
"pg_val": "Hello from Postgres",
"parameter": "Al-josh"
}
]
Hopefully my comments in the code have made it clear how the queries work, so you can alter them to your purpose. If not, provide some more detail about your tables and I can amend this answer.
Note also that I am using pg.Pool here to connect to Postgres. This is totally secondary to your question, but the documentation is worth reading.

Categories

Resources