I'm working on a simple API that performs an INSERT query into a SQL database, using information supplied from a form clients would fill out on the front end. I'm curious if there is a preferred way when it comes to constructing the error message regarding query errors.
Using a concrete example, I have a form to be filled out by users with attributes A, B, and C. In the SQL database, they're all enforced with a "NOT NULL" constraint. Therefore, if an empty form was submitted, the following error object would be returned:
{
...
errors:
[ ValidateErrorItem {
message: "model.a cannot be null"
type: "..."
path: "..."
value: "..."
.. },
ValidateErrorItem {
message: "model.b cannot be null"
type: "..."
path: "..."
value: "..."
.. },
ValidateErrorItem {
message: "model.c cannot be null"
type: "..."
path: "..."
value: "..."
.. },
]
}
How would one typically construct an error message to be display to the user (other than simply showing "An error occurred with the query")?
Note: I know that this scenario can be prevented using front end validation, but other things can go wrong (besides non-null validation), so this is just a simple example when an error occurs, not necessarily what I'd do in practice.
These kind of validations should be performed for each api request. I prefer to use Joi.
Joi will throw error anytime the schema does not match with what you are expecting. For example
const schema = Joi.object().keys({
modelA: Joi.string().required(),
modelB: Joi.string().required(),
modelC: Joi.string().required(),
});
const myObj = { modelA: 'modelA' };
console.log(Joi.validate(myObj, schema));
The above code will throw an error { ValidationError: child "modelB" fails because ["modelB" is required]
You can also add customer error message like this
const schema = Joi.object().keys({
modelA: Joi.string().required(),
modelB: Joi.string().required().error(new Error('Was expecting a string')),
modelC: Joi.string().required(),
});
const myObj = { modelA: 'modelA' };
console.log(Joi.validate(myObj, schema));
This will throw an error
Error: Was expecting a string
These messages can be parsed by front-end and shown as such.
Related
I'm creating something using a nodejs/typescript stack for the server, and I'm trying to define custom generic error messages instead of per-field messages. Something like this:
routes.post(
'/points',
upload.single('image'),
celebrate({
body: Joi.object().keys({
name: Joi.string().required(),
email: Joi.string().required().email(),
whatsapp: Joi.number().required(),
latitude: Joi.number().not(0).required(),
longitude: Joi.number().not(0).required(),
city: Joi.string().required(),
uf: Joi.string().required().max(2),
items: Joi.string().required()
}),
}, {
abortEarly: false,
messages: {
'string.empty':'{context.label} cant be empty!'
}
}),
pointsController.create
);
As you can see, I'm trying to use a variable/value inside the custom message. I got that 'key' based on the error entry that comes out of celebrate/joi error, which is like this:
{
message: ' cant be empty!',
path: [ 'items' ],
type: 'string.empty',
context: { label: 'items', value: '', key: 'items' }
}
If there a way to do something like that?
The message is not 'parsing' the {context.label} as I though it would. I mean, this is a shot in the dark since I couldn't find anywhere if something like this is suported at all.
You can use {#label} to achieve what you want to.
Try:
.messages({
'string.empty': '{#label} cant be empty!',
'any.required': '{#label} is a required field for this operation'
})
and so on for all other types.
Other values are also accessible similarly. For ex, if you want to generalise the error message for string min/max:
.messages({
'string.min': '{#label} should have a minimum length of {#limit}'
})
Here, limit (min) was set when you created the schema for the string.
I am using Indicative in my project to validate my controller, but, Indicative don't have a "Unique" rule in "Validation Rules", but the framework Adonis have a rule call "unique" that does exactly what i need.
My project is made in Adonis, but i prefer to use "Indicative" and not "Validator" in Adonis, because i think is more easy and beautiful write the code direct in the Controller
code: 'required|string|max:255',
description: 'required|string|max:255|unique:tabela',
authors: 'string|max:255',
status: 'boolean',
user_id: 'integer',
created_at: [
importValidate.validations.dateFormat(['YYYY-MM-DD HH:mm:ss'])
],
updated_at: [
importValidate.validations.dateFormat(['YYYY-MM-DD HH:mm:ss'])
]
}
In the example above, I need the "code" to be "Unique" and return an error message and a response status. How can I do this?
The unique method of Validator will automatically search in the database. I don't think it's possible to do it with Indicative
I propose this solution (in your controller):
const { validate } = use('Validator')
...
const rules = {
code: 'unique:<table_name>,<field_name>'
}
const messages = {
'code.unique': '...'
}
const validation = await validate({ code: ... }, rules, messages)
if (validation.fails()) {
...
}
To use this command it is necessary to use Validator. I don't think there's an equivalent with Indicative
I have a two fields phone field and phoneCode field. I'm using "#hapi/joi" 16.1.8 version along with expressjs.
For custom error message I used .messages() method instead of .error() because its smiply straight foreward. This is the code I tried to validate with the Joi system:
const data = { phone, phoneCode };
const schema = Joi.object({
phone: Joi.string().pattern(/^[0-9]{10}$/).required().messages({
"string.base": "Sorry! It looks like something went wrong. Please try later.",
"string.pattern.base": "Phone number must be a 10 digits number.",
"string.empty": "Phone Number is not allowed to be empty."
}),
phoneCode: Joi.string().max(3).required().messages({
"number": "Want to send default message if any error"
})
});
let validate = schema.validate(data, { abortEarly: false });
I wanted to send only one default message whenerver error occoured in phoneCode keys. I also tried
"number.*": "Want to send default message if any error"
OR
Joi.string().max(3).required().message("Want to send default message if any error") // It give Error: Cannot apply rules to empty ruleset
OR
phoneCode: Joi.string().max(3).message("Want to send default message if any error" ).required() // This worked upto a limit, not working if i sent phoneCode empty.
This is how I achieved the custom error message in JOI. Simply get the message from validate object. Its simple and working good.
const data = { phone, phoneCode };
const schema = Joi.object({
phone: Joi.string().pattern(/^[0-9]{10}$/).required().messages({
"string.base": "Sorry! It looks like something went wrong. Please try later",
"string.pattern.base": "Phone number must be a 10 digits number",
"string.empty": "Phone Number is not allowed to be empty",
"any.required": "Phone Number is required"
}),
phoneCode: Joi.string().max(3).required().messages({
"string.base": "Phone code must be valid",
"string.empty": "Phone code must be valid",
"string.max": "Phone code must be valid",
"any.required": "Phone code must be valid"
})
});
let validate = schema.validate(data);
if (!validate || validate.hasOwnProperty("error")) {
console.error(`[URL: ${req.originalUrl}] [ERROR:${JSON.stringify(validate.error.details)}]`);
return SendResponse.sendErrorMessage(res, validate.error.details[0].message);
}
I suppose this is not directly possible right now from Joi. You can use .error(new Error(errMsg)) to send back single error in case of issue in phoneCode:
const Joi = require('#hapi/joi');
const joiSchema = Joi.object({
phone: Joi.string().pattern(/^[0-9]{10}$/).required().messages({
"string.base": "Sorry! It looks like something went wrong. Please try later.",
"string.pattern.base": "Phone number must be a 10 digits number.",
"string.empty": "Phone Number is not allowed to be empty."
}),
phoneCode: Joi.string().max(3).required().error(new Error("Want to send default message if any error"))
}).error((errors) => new Error(errors.join(' ')));
const { error, value } = joiSchema.validate({ phone: 123, phoneCode: null }, { abortEarly: false });
console.log(error, value);
Outputs:
Error: Sorry! It looks like something went wrong. Please try later. Error: Want to send default message if any error
Object {phone: 123, phoneCode: null}
But remember that Joi sends back ValidationError in case of .messages(), but now it will send back Error. You can format your errors as per your requirement, I have simply joined them with a space at the end.
RunKit with executable code: https://runkit.com/rvypandey/5e394a3b78e4df0013483771
I was thinking about ways of implementing graphql response that would contain both an error and data.
Is it possible to do so without creating a type that would contain error?
e.g.
Mutation addMembersToTeam(membersIds: [ID!]! teamId: ID!): [Member] adds members to some team. Suppose this mutation is called with the following membersIds: [1, 2, 3].
Members with ids 1 and 2 are already in the team, so an error must be thrown that these members cannot be added, but member with an id 3 should be added as he is not in the team.
I was thinking about using formatResponse but seems that I can't get an error there.
Is it possible to solve this problem without adding error field to the return type?
Is it possible to solve this problem without adding error field to the return type?
Unfortunately, no.
A resolver can either return data, or return null and throw an error. It cannot do both. To clarify, it is possible to get a partial response and some errors. A simple example:
const typeDefs = `
type Query {
foo: Foo
}
type Foo {
a: String
b: String
}
`
const resolvers = {
Query: {
foo: () => {},
}
Foo: {
a: () => 'A',
b: () => new Error('Oops!'),
}
}
In this example, querying both fields on foo will result in the following response:
{
"data": {
"foo": {
"a": "A",
"b": null
}
},
"errors": [
{
"message": "Oops",
"locations": [
{
"line": 6,
"column": 5
}
],
"path": [
"foo",
"b"
]
}
]
}
In this way, it's possible to send back both data and errors. But you cannot do so for the same field, like in your question. There's a couple of ways around this. As you point out, you could return the errors as part of the response, which is usually how this is done. You could then use formatResponse, walk the resulting data, extract any errors and combine them with them with any other GraphQL errors. Not optimal, but it may get you the behavior you're looking for.
Another alternative is to modify the mutation so it takes a single memberId. You can then request a separate mutation for each id you're adding:
add1: addMemberToTeam(memberId: $memberId1 teamId: $teamId): {
id
}
add2: addMemberToTeam(memberId: $memberId2 teamId: $teamId): {
id
}
add3: addMemberToTeam(memberId: $memberId3 teamId: $teamId): {
id
}
This can be trickier to handle client-side, and is of course less efficient, but again might get you the expected behavior.
If you think about combining the GraphQL error - there is a way to do it in Apollo.
You need to set errorPolicy to all. That will help you notify users about the error and at the same time have as much data as possible.
none: This is the default policy to match how Apollo Client 1.0
worked. Any GraphQL Errors are treated the same as network errors and
any data is ignored from the response.
ignore: Ignore allows you to
read any data that is returned alongside GraphQL Errors, but doesn’t
save the errors or report them to your UI.
all: Using the all policy
is the best way to notify your users of potential issues while still
showing as much data as possible from your server. It saves both data
and errors into the Apollo Cache so your UI can use them.
But according to best practices, you shouldn't manipulate it in this way.
This is a great article about handling errors in GraphQL.
So, preferable way is to add "errors" field as part of your response and handle it in JS code.
We can achieve this by using a union. I would recommend visiting the great article Handling GraphQL errors like a champ
Example:
Mutation part: We can return the union type for the response & capture the result according to types.
type MemberType {
id: ID!
name: String!
}
enum ErrorType {
BAD_REQUEST_ERROR
FORBIDDEN_ERROR
INTERNAL_SERVER_ERROR
NOT_FOUND_ERROR
UNAUTHORIZED_ERROR
}
type GraphqlError {
type: ErrorType!
code: String!
message: String!
helpLink: URL
}
union UserRegisterResult = MemberType | GraphqlError;
addMembersToTeam(membersIds: [ID!]! teamId: ID!): UserRegisterResult!
Response:
addMembersToTeam(membersIds: [ID!]! teamId: ID!): {
...on MemberType{
id,
name,
}
...on GraphqlError{
id,
message,
statusCode,
}
}
I am trying to insert a lot of data i database using Sequelize library
but it is showing some strange as I am passing an array of objects into the bulkCreate query .
I have function something like this
db.organizations
.findAll({
attributes: ['name'],
where: {
id: req.ORG_ID
}
})
.then(org => {
let i; let smsData = [];
for (i = 0; i < user.length; i++) {
let DefaultText = Utils.defaultMessage(
user[i].name,
user[i].dataValues.amount,
org[0].name
);
smsData.push({
organizationId:
user[i].dataValues.organizationEntries[0].organizationId,
mobile: user[i].mobile,
message: DefaultText,
status: 0
});
}
console.log(JSON.stringify(smsData));
db.logSms.bulkCreate([{
smsData
}]).then(() => { // Notice: There are no arguments here, as of right now you'll have to...
return db.logSms.findAll({ where: { status: 0 } });
}).then(sms => {
console.log(sms)
})
and I have also done console.log(smsData) which is showing array of objects but I am not able to understand why I am facing this error.
INSERT INTO log_sms (id,created_at,updated_at) VALUES (NULL,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP);
Unhandled rejection SequelizeDatabaseError: Field 'mobile' doesn't have a default value
This error is basically coming as while executing the query as u can see below the error as I have made mobile field compulsory and I am passing the array of objects which is having mobile number but still this error is coming. Please give some hint
I have got my mistake as I should remove the braces from this statement:
db.logSms.bulkCreate([{ smsData }])
it should be like this:
db.logSms.bulkCreate(smsData)