I'm investigating using the Hapi framework on Node.js for building a website where the requirement is for it to work fully with javascript disabled in the browser, and also I can't use HTML 5.
When posting form data, I've had a look at Joi for performing validation on the server side which looks good, but instead of just returning an error code, I need to display this nicely in the front end, eg. red borders around the relevant fields with custom error messages for each field and an error summary.
Is there any way of doing this? Or is there perhaps a plugin that would help without using client side javascript?
Looks like I've found a good way of doing it.
Simply return the error data in the view along with all the other data that the page binds to by returning h.view on the post method. Then use whatever templating plugin you are using to render the error message or not depending on the data.
{
method: 'POST',
path: currentPath,
options: {
validate: {
options: { abortEarly: false },
payload: Joi.object({
isAgent: Joi.string().required()
}),
failAction: (request, h, err) => {
const errorList = []
const fieldError = findErrorList(err, [isAgent])[0]
if (fieldError) {
errorList.push({
text: fieldError,
href: `#${field}`
})
}
return h.view(viewTemplate, createModel(errorList, request.payload, null)).takeover()
}
},
handler: async (request, h) => {
return h.redirect(nextPath);
}
},
}
Related
All I'm trying to do here is to be able fetch paypal_api_key from backend api something like:
app.get("/api/keys/paypal", (req, res) => {
res.send(process.env.PAYPAL_CLIENT_ID || "sb");
});
frontend side:
const loadPaypalScript = async () => {
const { data: clientId } = await axios.get("/api/keys/paypal", {
headers: { authorization: `Bearer ${userInfo.token}` },
});
paypalDispatch({
type: "resetOptions",
value: {
"client-id": clientId,
currency: "GBP",
},
});
paypalDispatch({ type: "setLoadingStatus", value: "pending" });
};
loadPaypalScript();
ERROR:
Uncaught Error: Attempted to load sdk version 5.0.343 on page, but window.paypal at version undefined already loaded.
To load this sdk alongside the existing version, please specify a different namespace in the script tag, e.g. <script src="https://www.paypal.com/sdk/js?client-id=CLIENT_ID" data-namespace="paypal_sdk"></script>, then use the paypal_sdk namespace in place of paypal in your code.
at VM827 js:2
at Module.<anonymous> (VM827 js:2)
at t (VM827 js:2)
at VM827 js:2
at VM827 js:2
I've tried this
<script src="https://www.paypal.com/sdk/js?client-id=***"></script>
it works but I'm not interested in passing my api key in plain sight like that
The Client ID is and must be public information, it's required for the script to load. Fetching it asynchronously after page load and using it to load the script dynamically does not in any way hide it; it will be in "plain sight" the moment the browser makes its request and also the moment the resource is loaded. So if that's the reason you're loading the script asynchronously, it does not make any sense; you are accomplishing nothing and should just do what works, ether putting the script tag in your page as HTML or using the official react-paypal-js.
In any case, the cause of your error is you are doing something that loads the SDK more than once. You need to ensure it's only loaded once. (for the rare use case that requires multiple SDKs concurrently, data-namespace is available but this is not your case)
Long time reader first time question asker. My question is about dynamically accessing function names in express ^4.17.1. I have poured over both the internet and the express documentation with no luck.
I currently have input validation set up on all of my express middleware functions that ensures I'm getting the right information in the right format. When bad or incomplete information is given to a particular middleware I throw a nicely formatted error to the global error handler and I include the route that errored, the file in which the error occured, and the exact middleware in which it occured.
I've been able to dynamically retrieve both the route and the file name, but I'm having issues finding a way around hard coding in the name of the middleware. I can access the route stack and get the names of ALL middleware in the route, but there doesn't seem to be a way to determine which one you're in aside from keeping that list on res.locals and changing the array as you go (which isn't ideal). Here is a simple example of what I'm talking about.
const path = require("path");
const currFile = path.basename(__filename);
module.exports = {
getStudentAssets(req, res, next) {
const { studentName } = req.body;
if (typeof studentName !== "string") {
const iWouldLoveToGetThisDynamically = "getStudentAssets";
const error = {
status: 400,
log: `ERROR on route ${req.originalUrl} in ${currFile} ${iWouldLoveToGetThisDynamically} middleware`,
};
return next(error);
}
// do random stuff
},
};
I have a feeling there is a way to track which layer you're currently on as accessing req.route.stack looks something like this
[
Layer {
handle: [Function: getStudentAssets],
name: 'getStudentAssets',
params: undefined,
path: undefined,
keys: [],
regexp: /^\/?$/i { fast_star: false, fast_slash: false },
method: 'get'
},
Layer {
// other middleware
}
]
There has to be a way to identify which layer you're currently on, other than keeping a counter stored separately on res.locals that you update every middleware. If you've read this far thank you!
So I figured it out! What I'm doing is basically just rebuilding the trace so all I had to do was access error.stack and it had all the information I could possibly want.
I am writing my Node.js server using TypeScript and express framework.
This is how my controller and route looks like:
export class AuthController {
public async signUpNewUser(request: Request, response: Response) {
...
}
}
How can I receive a model class instead Request type like in ASP.NET ?
Something like:
public async signUpNewUser(input: SignUpModel, response: Response) {
Is this a good idea at all? I am not sure this is a common approach in the Node.JS
I just want to make sure I get the same model each time and write a code related to this model and not on dynamic JSON object.
My suggestion is to convert to strong type model at the beginning of the route, but I am not sure this is a good way.
Does somebody have a solution for such cases?
How can I receive a model class instead Request type like in ASP.NET
This was a perpetual pain to me in my projects (and at work), eventually we decided to build a custom router with its own default error handling and auth-header checks. The trick with this pattern is to keep it lightweight, because this is still express and middleware is where things should go - this wrapper just provides a way for us to cast the express request into a properly shaped type based on the middleware we actually use.
This is pared down example, the idea is that you can specify the shape of the req & res by passing an interface (or an inlined type shape) and have typescript enforce the return shape.
Wrapper class example:
import * as express from 'express';
export type ExpressMethods = "get" | "post" | "put" | "delete" | "patch";
export type JsonRouteInput<RequestBody, RouteParams, QueryParams> = {
body: RequestBody;
params: RouteParams;
query: QueryParams;
};
export type JsonRouteHandler<
RequestBody,
RouteParams,
QueryParams,
ResponseBody
> = (
request: JsonRouteInput<RequestBody, RouteParams, QueryParams>
) => Promise<ResponseBody> | ResponseBody;
export class JsonRouter {
router = express.Router();
private addHandler<M extends ExpressMethods>(
method: M,
...middleware: express.RequestHandler[]
) {
this.router.use(...middleware);
}
get route(): {
[K in ExpressMethods]: <
RequestBody,
ResponseBody,
RouteParams = never,
QueryParams = never
>(
path: string,
handler: JsonRouteHandler<
RequestBody,
RouteParams,
QueryParams,
ResponseBody
>
) => JsonRouter
} {
const addables = {} as any;
(["get", "post", "put", "delete", "patch"] as ExpressMethods[]).forEach(
<RequestBody, ResponseBody, RouteParams = never, QueryParams = never>(
method
) => {
addables[method] = (
path: string,
handler: JsonRouteHandler<
RequestBody,
RouteParams,
QueryParams,
ResponseBody
>
) => {
this.router[method](path, async (req, res) => {
try {
const responseBody: ResponseBody = await handler({
body: req.body,
params: req.params,
query: req.query
});
res.json(responseBody);
} catch (err) {
// do your standard error handling or whatever
res.status(500).end("ow");
}
});
return this;
};
}
);
return addables;
}
}
And then using it
const jsonRouter = new JsonRouter().route.get<{ request: number }, { response: number }>(
"/hello-world",
req => {
return { response: req.body.request + 1 }; // type-checked result
}
);
This can definitely be taken one step further - I have some prototypes that allow us to semi-fluently build the shape of the request/response body. The goal with this strategy long term lets us generate a typescript rest client for the frontend, generate input-validation that matches the type we're using to annotate, and also enforce that the response is the right type - example router using this strategy to build the type dynamically
EDIT: To plug this example into an express server
const app = express();
// custom middleware goes here
app.use('/', jsonRouter.router);
app.listen(8000)
So you seem to have a couple different questions in there. The core question is "how do I cast a JSON object to a specific type", but then you also ask if it's a good idea or not and if it's a common practice.
The answer to your first question is pretty simple, you can cast it in your route (or wherever) like so:
router.get('/whatever', (req, res) => {
const signup: SignupModel = JSON.parse(req.model) as SignupModel;
// Do whatever you want with the signup model
});
Now, your other questions are way more opinion-based. If I'm being honest, I would say "don't use Typescript". :) Joking aside, I don't know how to answer your opinion-based question (nor is it a good fit for this site)
I have a generic SendMail route which I want to create multiple remote methods to handle multiple request templates. Any ideas on how to return a Email_Type from the remote method back to the base route. I know I could add a default with a code in it, but would like a more elegant solution.
Mail.genericSendMail = function genericEmail(response, callback) {
console.log(response);
let templateId=0;
//PROBLEM: HOW TO KNOW WHICH REMOTE WAS USED
switch (response.emailType) {
case "Template-1":
templateId= 1234;
break;
case "Template-2":
tempalteId = 456;
break;
default:
templateId = 789l
} //switch
console.log(templateId);
};
//Want multiple routes like this to support various templates
Mail.remoteMethod("genericEmail", {
http: {
path: "/emailTemplate1",
verb: "POST"
},
accepts [
{arg: "request", type:"object",http: {source:"body"},
default: {firstName:"", lastName:"",emailAddress:""}
}],
returns: RESTResponseStatic.loopbackAdapterCommonRestResponseDefinition()
});
//Want multiple routes like this to support various templates
Mail.remoteMethod("genericEmail", {
http: {
path: "/emailTemplate2",
verb: "POST"
},
accepts [
{arg: "request", type:"object",http: {source:"body"},
default: {emailAddress:"", promoCode:""}
}],
returns: RESTResponseStatic.loopbackAdapterCommonRestResponseDefinition()
});
There are a couple of different ways to do this. Since it happens to be a POST request, I usually go with attaching data to the body using a before remote hook.
Let's say you have a model method for logging in users.
Say we have a multi realm platform, so we need to know what platform we are logging in. If you don't use realms or don't know what they are, don't worry. This just shows you how to populate the data to the model method.
User.login = function(data, cb) {
if (data.realm == 'platform1) {
return logUserIntoPlatform1(data, cb);
}
return logUserIntoDefaultPlatform(data, cb);
}
Now let's say you don't want the client/frontend to send the realm and you don't want to do the lookup for realm in the model. We can add a beforeRemote hook like so:
User.beforeRemote('login', function (context, user, next) {
context.args.data.realm = lookUpRealmSync(context); // 1
next();
});
This will be called before the login method. Note the next() call: this is how you could do error detection before actually hitting the model method. Something like next({ status: 422, message: 'Parameter missing: password }); would return an error and not execute the User.login method.
You may have to look carefully at your context object (i.e. the line marked with 1 may not work exactly as I've shown for you).
If you want to read more about this stuff, I LoopBack's docs are pretty good. It seems they've been updated since I've last used them so I can't link you to the more useful pages. I found the remote method documentation here though.
Edit: I took a closer look at your question. You should be able to retrieve the path from the context object and pass data accordingly. I'm not going to try to code that since I don't know where it would actually be within the object.
Has anyone been able to get the google-api-nodejs-client to successfully insert a moment?
Whatever I try, I get a generic 400 "Invalid value" error but am unable to narrow down the invalid value because the API Explorer doesn't work either.
Would it be because of the missing data-requestvisibleactions parameter? I'm using passport.js's require('passport-google-oauth').OAuth2Strategy for handling oauth access, and that part is working fine, but I have no idea how to incorporate requestvisibleactions into the oauth request flow since this is definitely not originating from a clientside form.
Here's a snippet of what I'm trying to do (using the latest version of googleapis, v1.0.2):
var google = require('googleapis')
var auth = new google.auth.OAuth2()
auth.setCredentials({
'access_token': user.token
})
google.plus('v1').moments.insert({
collection: 'vault',
userId: 'me',
debug: true,
resource: {
type: "http://schemas.google.com/AddActivity",
target: {
type: "http://schema.org/CreativeWork",
url: "...omitted...",
image: "...omitted...",
description: "test",
name: "test"
}
},
auth: auth
}, function (err, response) {
if (err) {
console.error(err)
res.send(err.code, err)
} else {
console.log(response)
res.send(200)
}
})
ref 1 (out-of-date w.r.t. an older version of googleapis)
ref 2 (client-side, where the use of data-requestvisibleactions is more obvious)
As you speculated, you need the request_visible_actions parameter as part of the URL calling the oauth endpoint.
It looks like the current version of passport-google-oauth doesn't support this parameter. Judging by several of the open issues and pull requests, it isn't clear that the author will respond to requests to add it either. You have two possible options:
Switch to using the OAuth support that is included in google-api-nodejs-client
Patch the passport-google-oauth code. (And possibly submit a pull request in the hopes it will be useful to someone else.)
I don't use passport.js or the passport module in question, so I can't test this, but based on the github repository, I think you can insert the following in lib/passport-google-oauth/oauth2.js after line 136 and before the return statement:
if (options.requestVisibleActions) {
// Space separated list of allowed app actions
// as documented at:
// https://developers.google.com/+/web/app-activities/#writing_an_app_activity_using_the_google_apis_client_libraries
// https://developers.google.com/+/api/moment-types/
params['request_visible_actions'] = options.requestVisibleActions;
}