Can we return a string or object in grapqhl while resolving? - node.js

I wanted to check whether the user exists in database ... if user exists in the database then send
type user{
name
email
password
username
}
or if user doesn't exist in database then send a string which tells "user doesn't exists in database"
like.... if I am writing tyepDefs
type Query{
show_data(username: String!): #if user exists then# ***user!*** or #if user doesnot exists then# ***String!***
How to make such type of resolvers and typeDefs ?

You can create a new type to use in your query:
type UserPayload {
state: String! # success or other value
user: User # if state was not success this must be null
}
Then use that type directly by your query like this:
type Query{
show_data(username: String!):UserPayload
}

Related

How do you omit fields in TypegraphQL

Let's say we have the following User model.
{ id: ID, email: string, username: string }
Then I want to define 2 queries:
Used by the owner to get their settings page so it contains sensitive information such as email (perhaps sin number)
Used by other users to search for a user (by username) & we do NOT want to expose the email or sin number
I have been researching the documentation & cannot find how to accomplish this. I was thinking to grab the info for the fields manually & parse it per query but that seems like an oversight.
UPDATE:
Here is sort of what I am trying to do:
class User {
#Field(
() => ID
)
id: string;
#Authorized("CURRENT_USER")
#Field(
{
nullable: true
}
)
email: string;
#Field()
username: string;
}
Resolver:
export default class UserResolver {
#Authorized("CURRENT_USER")
#Query(
() => User
)
async user(#Arg('username', () => String) username: string) {
// TODO: if username is current user then allow email
// else do not allow email, (I need auth checker in here)
}
}
If I understand your question correctly you should be able to use the Authorized decorator in TypegraphQL. With this solution you should be able to add it to the email field in your User model. This should also be able to work with the sid field as well
Have a look here: https://typegraphql.com/docs/authorization.html
For example your User model could look something like this:
class User {
#Field()
id: ID;
#Authorized("LOGGEDINUSER")
#Field({nullable: true})
email: string;
#Field()
username: string;
}
You will have to allow the email field to be nullable
You will also need to define an authChecker, with this you can run your logic to check if the user is the owner of the data, therefore granting them access to the data.
An authChecker can look something like this:
export const customAuthChecker: AuthChecker<Context> = (
{ args, context, info, root },
roles
) => {
// roles is an array of string which contains the authorization required to access the current resource
// this is specified in the #Authorized decorator
if (roles.includes("LOGGEDINUSER")) {
// here check if the user is actually logged in and if they are allowed to access the resource
// return true if they are allowed to access the resource
}
return false;
};
You will also need to change your call to the buildSchema to include the custom authChecker and authMode.
For example:
const schema = await buildSchema({
resolvers: [UserResolver],
authChecker: customAuthChecker,
authMode: "null",
});
Note this will still return an email field but instead of returning the actual email it will return null when the user does not meet the authentication requirements

Typescript compiler error about incompatble types

I'm working on an API written in Typescript 3.9.7 running on Node 10. I've removed unnecessary details, but I'm basically performing the following operations:
Pulling user data from the database.
Adding a 'state' field to each user object
Sending the data to the UI.
I'm trying to use interfaces to add some type safety, but I seem to be misusing them since the TS compiler gives me some errors. Advice on how to resolve this would be helpful.
I've trimmed out other details, but here's my method where I fetch the user data and add the state field:
public async getUsers(
parameter: string
): Promise<AugmentedUser[]> {
//return an array of User objects based on some parameter
const userData = await this.userService.getAll<User>(parameter);
return userData.forEach((userRow: User) => {
userRow.state = "inactive";
});
}
and my interfaces are
export interface User {
id: number;
firstName: string;
//some other fields
}
export interface AugmentedUser extends User {
state: string;
}
I get the following error from the compiler:
error TS2322: Type 'UserData[]' is not assignable to type 'AugmentedUser[]'. Property 'state' is missing in type 'User' but required in type 'AugmentedUser'.
What am I doing wrong? I added the state field in the forEach loop so why am I getting this error? Thanks.
forEach does not return anything. Try the map function:
return userData.map((userRow: User) => {
return {...userRow, state: 'inactive'};
});
This will generate a list of objects with all the User properties plus the state present in AugmentedUser.

Skip GraphQL Resolvers to return data

I have a GraphQl resolvers that resolves nested data.
for eg. this is my type definitions
type Users {
_id: String
company: Company
}
For the post I have my resolver which resolves post._id as
Users: {
company: (instance, arguments, context, info) => {
return instance.company && Company.find({_id: instance.company});
}
}
The above example works perfectly fine when I query for
Query {
Users {
_id
name
username
company {
_id
PAN
address
}
}
}
But the problem is sometime I don't have to use the company resolver inside Users, because it is coming along with the user so I just need to pass what's in the user object (no need of database call here)
I can achieve this just by checking if instance.company is and _id or Object, if _id get from database otherwise resolve whatever coming in.
But the problem is I have these type of resolvers in many places so I don't think it's a good idea to have this check in all places wherever I have resolver.
Is there a better way where I can define a configuration just to skip this resolver check.
Any feedback or suggestions would be highly appreciated.
Thanks

CastError: Cast to string failed for value

I wanted to created a transaction module where after a successful transaction the users document(in this case user sends money to another user) will be updated as well.
a. in user.js(this is user model) besides name, pw, email (etc) I created this property which will hold the transaction related history of respective user. Please look at how I’ve used it:
transaction_history:[{
transactionid:String,
timestamp:String,
type:String,
balance:Number,
status:String
}]
b. when sender clicks send button in the form, a transaction document is created, & after this the user document(here the sender) should be updated along with transaction info.
//create transaction document, works fine
Transaction.create({transactionid:uniqid(),
timestamp:moment().format(datemask),
amount:balance,
sender:sendfrom,
receiver:sendto,
status:"done"
}, function(err, tr){
if(err) throw err;
else {
//I want sender document to update with transaction info
User.findOne({email:sendfrom}, function(err, sendfrom){
if(err) {console.log("error at sender side");}
else
if(sendfrom!=null){
// console.log("tr: "+tr); //fine
sendfrom.balance-=balance;
sendfrom.transaction_history.push({
transactionid:tr.transactionid,
// timestamp:tr.timestamp,
// type:"debit",
// balance:tr.amount,
// status:tr.status
}
);
sendfrom.save();
console.log("sender's current balance is: "+sendfrom.balance);
};
});
}});
c. But then I get this:
events.js:163
throw er; // Unhandled 'error' event
^
CastError: Cast to string failed for value "{ transactionid: '1amhrummxjhnhv0w4' }" at path "transaction_history"
Why this error occurs?I want your suggestion please! Thank you
You can't use the word 'type' as an object. Just rename to something else like 'something_type'.
Root of the problem is in the way I defined transaction_history property. This was supposed to be an array of objects syntactically[{}], but then I wrote its type to be String. So when I tried to insert a string as a value of ’type’, it throws error being unable to push string to ‘transaction_history’ object. To solve it, just need to remove type property. It’s my mistake to use a reserved word as a key in object.So I replaced 'type' by something else in model. that’s it!
At my case:
CastError: Cast to String failed for value "[ 'whateverValue_1', 'whateverValue_2' ]" at path "name"
Issue was that I have in form html name same word...
I.e:
<input name = "name"> Name
<input city = "name"> City
Looking for my mistake, I leave you other that could help for newbies as me:
words like type, model, never have to be in model Schema as main keywords!
There is a workaround provided by Mongoose:
You can use the field "type" in the object as long as you define its type, as such:
transaction_history: [
{
transactionId: String,
timestamp: String,
type: { type: String },
balance: Number,
status: String
}
]
source: https://www.typeerror.org/docs/mongoose/schematypes

NodeJs GraphQL enum type value as dynamic

https://launchpad.graphql.com/9qvqz3v5r
Here is my example graphQL Schema. i am trying to use enum type. How do i get enum values from backend and give it into schema?
// Construct a schema, using GraphQL schema language
const typeDefs = `
type User {
userId: Int
firstName: String
lastName: String
pincode:String
state:String
country:String
}
type Query {
hello: String
user: User
}
type CreateUserLoad {
user: User
}
enum Role {
writer
reader
author
admin
superAdmin
}
type Mutation{
createUser(firstName: String, lastName: String, role: Role): User
}
`;
I want to populate enum Role value from dynamic variable as
const roleData = ['writer','reader','author','admin','superAdmin'];
Can anyone help me?
You can simply use string interpolation:
// Construct a schema, using GraphQL schema language
const typeDefs = `
type User {
userId: Int
firstName: String
lastName: String
pincode:String
state:String
country:String
}
type Query {
hello: String
user: User
}
type CreateUserLoad {
user: User
}
enum Role { ${roles.join(' ')} }
type Mutation{
createUser(firstName: String, lastName: String, role: Role): User
}
`;
In fact, on every incoming grahpql query you have to pass the parsed schema to the graphql server, so you can even change it for every request. In that case, it would be better to change the object representation that the schema parsing returned.
For creating enum types directly, say you have an array of values userRoles and want a RolesEnum type, then you can create it like so:
const roleValues = {}
for (const value of userRoles) {
roleValues[value] = {value}
}
const RolesEnum = new GraphQLEnumType({
name: 'UserRoles',
values: roleValues,
})
you can then assign that directly as a type in your schema.
If your enum values are loaded from a database or any other back-end source, or if the enum list is dynamic, then you can't have a static enum definition in your schema.
The biggest problem with setting enums dynamically is that your schema is meant to be a contract between back-end and front-end and is not suppose to be altered.
If you are going to use your role as an arbitrary string then define it as such!
type Mutation {
createUser(firstName: String, lastName: String, role: String): User
}
The only difference here would be that your resolver will have to check if the role exists, which is what graphql previously did for you when you were using an enum.
Alternatively
If the role is used on a lot of queries / mutations, you can define a scalar Role and in the resolved for the scalar you can check if the role exists and simply throw an error if it doesn't.
In this case your mutation would look the same as if you had a dynamic enum Role, but you will not need to alter the schema at all.
type Mutation {
createUser(firstName: String, lastName: String, role: Role): User
}
To add dynamic enums along with documentation string using string interpolation.
Example: I have a list of countries and their ISO2 Codes
const countryData = [
{name:'India', code: 'IN'},
{name:'Afghanistan', code: 'AF'},
{name:'Algeria', code: 'DZ'},
{name: 'Ireland', code: 'IE'
];
const countryCodes = countryData.flatMap(country => [
`"${country.name}"`,
country.code
]);
Without using Array.join()
enum CountryCode { ${countryCodes} }

Resources