Multiple methods on the same route in Express - express

I have 3 different method responses in the API I'm working on currently set up like this:
app.use('/image', require('./routes/image/get'));
app.post('/image', require('./routes/image/post'));
app.put('/image', require('./routes/image/put'));
Is there a better way to do this?

You may use .route() on your app's Express object to reduce some of the redundancy in your route definitions.
app.route('/image')
.post(require('./routes/image/post'))
.put(require('./routes/image/put'));
There is also .all(), which will invoke your handler regardless of the request http method.
No use()
I've omitted .use(), mentioned above, because it is not available on Route objects -- it sets up application middleware. Routers are another layer of middleware (see this question for an explanation of the difference). If the intent is really to call .use(), and not .get(), then that line would have to stay, before the call to .route() (middleware registration order matters).
Reusing the same handler for different http methods
If one would prefer to reuse the same handler for a set of methods, in the following style:
app.route("/image").allOf(["post", "put"], function (req, res) {
//req.method can be used to alter handler behavior.
res.send("/image called with http method: " + req.method);
});
then, the desired functionality can be obtained by adding a new property to express.Route's prototype:
var express = require('express');
express.Route.prototype.allOf = function (methods /*, ... */) {
"use strict";
var i, varargs, methodFunc, route = this;
methods = (typeof methods === "string") ? [methods] : methods;
if (methods.length < 1) {
throw new Error("You must specify at least one method name.");
}
varargs = Array.prototype.slice.call(arguments, 1);
for (i = 0; i < methods.length; i++) {
methodFunc = route[methods[i]];
if (! (methodFunc instanceof Function)) {
throw new Error("Unrecognized method name: " +
methods[i]);
}
route = methodFunc.apply(route, varargs);
}
return route;
};

Related

How to unregister middleware in Telegraf?

When I add bot.hears(...), it registers middleware for handling matching text messages. But now it will handle those messages even if they are sent any time, even if not expected.
So if I am creating a stateful service, I would like to listen to particular messages only at appropriate time.
How can I unregister middleware, so that it does not hear any more previously handled messages?
I turned out I was looking for Scenes. How to use them is described on Github.
I'll just post a slightly modified code from the links above:
const { Telegraf, Scenes, session } = require('telegraf')
const contactDataWizard = new Scenes.WizardScene(
'CONTACT_DATA_WIZARD_SCENE_ID', // first argument is Scene_ID, same as for BaseScene
(ctx) => {
ctx.reply('Please enter guest\'s first name', Markup.removeKeyboard());
ctx.wizard.state.contactData = {};
return ctx.wizard.next();
},
(ctx) => {
// validation example
if (ctx.message.text.length < 2) {
ctx.reply('Please enter real name');
return;
}
ctx.wizard.state.contactData.firstName = ctx.message.text;
ctx.reply('And last name...');
return ctx.wizard.next();
},
);
const stage = new Scenes.Stage();
stage.register(contactDataWizard);
bot.use(session());
bot.use(stage.middleware());
But I still don't know how to generally implement it, so I need to find it out in the Scenes code of Telegraf.

How to perform async ModelState validation with FluentValidation in Web API using .NET Core

This question is a follow up to this post - How to perform async ModelState validation with FluentValidation in Web API?.
I was wondering if FluentValidation has a way to perform async ModelState validation in .net core web api. I have a FluentValidation Validator class which contains async validation methods such as "MustAsync", which means in my business service class I call the validator manually using "ValidateAsync". I also want to use this same validator class to validate the model coming in from the request. I went through the documents and read that the only way to do this is to manually call the "ValidateAsync()" method since the .net pipeline is synchronous. I would rather not manually have to call this method from within my controller, I would prefer to either register it in the startup (have the framework automatically call the the validator on my model) or decorate my request model with the validator.
Has anyone been able to achieve this?
Thanks!
Based on the linked question, I've adapted the code slightly to be compatible with ASP.NET Core (2.2, in my case). In general, this is using the IAsyncActionFilter interface. You can read about it in the official docs.
public class ModelValidationActionFilter : IAsyncActionFilter
{
private readonly IValidatorFactory _validatorFactory;
public ModelValidationActionFilter(IValidatorFactory validatorFactory) => _validatorFactory = validatorFactory;
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var allErrors = new Dictionary<string, object>();
// Short-circuit if there's nothing to validate
if (context.ActionArguments.Count == 0)
{
await next();
return;
}
foreach (var (key, value) in context.ActionArguments)
{
// skip null values
if (value == null)
continue;
var validator = _validatorFactory.GetValidator(value.GetType());
// skip objects with no validators
if (validator == null)
continue;
// validate
var result = await validator.ValidateAsync(value);
// if it's valid, continue
if (result.IsValid) continue;
// if there are errors, copy to the response dictonary
var dict = new Dictionary<string, string>();
foreach (var e in result.Errors)
dict[e.PropertyName] = e.ErrorMessage;
allErrors.Add(key, dict);
}
if (allErrors.Any())
{
// Do anything you want here, if the validation failed.
// For example, you can set context.Result to a new BadRequestResult()
// or implement the Post-Request-Get pattern.
}
else
await next();
}
}
If you want to apply this filter globally, you can add the filter to the AddMvc call in your Startup class. For example:
services.AddMvc(options =>
{
options.Filters.Add<ModelValidationActionFilter>();
// uncomment the following line, if you want to disable the regular validation
// options.ModelValidatorProviders.Clear();
});
I had trouble getting the code in #nachtjasmin's answer to work with newer versions of FluentValidation. Specifically, the trouble is that ValidateAsync now takes an IValidationContext instead of the model being validated, and the context can't be created without knowing the type of the model at compile time.
Eventually I stumbled upon this answer, which points out that the exact type is not important and uses object instead.
So, instead of:
var result = await validator.ValidateAsync(value);
You can use:
var context = new ValidationContext<object>(value);
var result = await validator.ValidateAsync(context);
Based on the answer above by #nachtjasmin, you can add this in two ways,
Using AddMvc
services.AddControllersWithViews(options =>
{
options.Filters.Add<FluentValidationActionFilter>();
});
Using AddControllersWithViews
services.AddControllersWithViews(options =>
{
options.Filters.Add<FluentValidationActionFilter>();
});
If your's is just a Web API and you don't have any Razor pages involved, then you can consider using AddControllersWithViews over AddMvc, as the AddMvc uses the AddControllersWithViews internally and add the services.AddRazorPages() on top of that.
You can see this info here for AddMvc and here for AddControllersWithViews

pass options to mongoose schema toJSON transform inline (in expressjs)?

I have a mongoose (3.1) 'Thing' schema whose toJSON I can customize in the following manner...
Thing.options.toJSON = {};
Thing.options.toJSON.transform = function (doc, ret, options){
// do something to ret, depending on options
}
As noted in the code comment, I would like to change the JSON representation given the value of options. I would like to pass these options in an expressjs action, maybe...
app.get(..., function (req ,res){
Thing.find({}, function(err, things){
var myOptions = {...} // something application stateful
return response.send(things) // MAYBE ADD OPTIONS HERE?
});
});
How do I modify expressjs to allow me to supply options?
Thanks,
G
IMHO, the accepted answer (#VladStirbu's) is wrong because the options are being set at the schema level. It's changing the schema, so those options will be available in subsequent calls, even if you don't request so explicitly.
The options should be set inline, individually for that call:
Regular call using express:
app.get(..., function (req ,res){
Thing.find({}, function(err, things){
return response.send(things);
});
});
Call using express, but passing inline options to toJSON():
app.get(..., function (req ,res){
Thing.find({}, function(err, things){
let toJSONOptions; // may be undefined, it's fine
if ( /* whatever condition you decide */ ) {
// this keeps the schema's original options:
toJSONOptions = Object.assign({ }, Thing.schema.options.toJSON);
// request to use original transform function, if any:
toJSONOptions.transform = true;
// set your own options to be passed to toJSON():
toJSONOptions._options = {...}; // whatever you need here
}
return response.send( things.map(e => e.toJSON(toJSONOptions)) );
});
});
No problem if toJSONOptions = undefined, it would be like a regular call to toJSON(), which is what express does when stringifying.
If you're using findOne() or findById(), then just return:
return response.send( thing.toJSON(toJSONOptions) );
This is the Mongoose commit that made me think of this:
https://github.com/Automattic/mongoose/commit/1161f79effc074944693b1799b87bb0223103220
You could pass options in the route handler by passing them to the schema options:
app.get(..., function (req ,res){
Thing.find({}, function(err, things){
Thing.schema.options.toJSON.myOptions = {...} // something application stateful
return response.send(things) // MAYBE ADD OPTIONS HERE?
});
});
this way, the options will be available in the transform function as a property of the options object:
Thing.options.toJSON.transform = function (doc, ret, options){
console.log(options.myOptions); // prints the app specific data provided earlier
}

Hapi server methods vs server.app.doSomething

I am writing a hapi js plugin, and was wondering what's the difference between the two ways of exposing methods that other plugins can use.
Method 1:
server.method("doSomething",
function () {
// Something
});
Method 2:
server.app.doSomething = function () {
// Something
};
In the first approach, the function can later be called as server.doSomething(), while using the second approach as server.app.doSomething().
So why would I use one way instead of another?
Looking at the API docs it sounds like they intended server.methods to be used for functions and server.app to be used for app settings/configuration. My guess is you should stick with server.method if you want to expose server level methods to be used in your plugins.
server.methods
An object providing access to the server methods where each server
method name is an object property.
var Hapi = require('hapi');
var server = new Hapi.Server();
server.method('add', function (a, b, next) {
return next(null, a + b);
});
server.methods.add(1, 2, function (err, result) {
// result === 3
});
server.app
Provides a safe place to store server-specific run-time application
data without potential conflicts with the framework internals. The
data can be accessed whenever the server is accessible. Initialized
with an empty object.
var Hapi = require('hapi');
server = new Hapi.Server();
server.app.key = 'value';
var handler = function (request, reply) {
return reply(request.server.app.key);
};

Prevent duplicate routes in express.js

Is there a nice way to prevent duplicate routes from being registered in express? I have a pretty large application with hundreds of routes across different files, and it gets difficult to know if I've already registered a certain route when I go to add a new one. For example, I'd like to throw an error when express gets to routes487.js:
File: routes1.js
var ctrl = require('../controllers/testctrl');
var auth = require('../libs/authentication');
module.exports = function (app) {
app.get('/hi', auth.getToken, ctrl.hi);
app.get('/there', auth.getToken, ctrl.there);
};
File: routes487.js
var ctrl = require('../controllers/testctrl487');
var auth = require('../libs/authentication');
module.exports = function (app) {
app.get('/hi', auth.getToken, ctrl.hi487);
};
You could try a custom solution by wrapping express methods with the validation. Consider the following modification to your express app:
// route-validation.js
module.exports = function (app) {
var existingRoutes = {}
, originalMethods = [];
// Returns true if the route is already registered.
function routeExists(verb, path) {
return existingRoutes[verb] &&
existingRoutes[verb].indexOf(path) > -1;
}
function registerRoute(verb, path) {
if (!existingRoutes[verb]) existingRoutes[verb] = [];
existingRoutes[verb].push(path);
}
// Return a new app method that will check repeated routes.
function validatedMethod(verb) {
return function() {
// If the route exists, app.VERB will throw.
if (routeExists(verb, arguments[0]) {
throw new Error("Can't register duplicate handler for path", arguments[0]);
}
// Otherwise, the route is saved and the original express method is called.
registerRoute(verb, arguments[0]);
originalMethods[verb].apply(app, arguments);
}
}
['get', 'post', 'put', 'delete', 'all'].forEach(function (verb) {
// Save original methods for internal use.
originalMethods[verb] = app[verb];
// Replace by our own route-validator methods.
app[verb] = validatedMethod(verb);
});
};
You just need to pass your app to this function after creation and duplicate route checking will be implemented. Note that you might need other "verbs" (OPTIONS, HEAD).
If you don't want to mess with express' methods (we don't know whether or how express itself or middleware modules will use them), you can use an intermediate layer (i.e., you actually wrap your app object instead of modifying its methods). I actually feel that would be a better solution, but I feel lazy to type it right now :)