How to use graphql-type-json with NestJS - graphql

i Have following response that needs to go under GraphQL Query:
{
'0xF7446f0...9a496aE94Cf9d42': { balances: [ [Object], [Object] ] },
'0xc01679F6496...95c86f9DEc63a': { balances: [ [Object], [Object] ] }
}
Using nestjs together with graphql-type-json and my code looks like this
#ObjectType()
export class BalancesResponse {
#Field({ nullable: true })
error?: string;
#Field((type) => JSON)
balances: any;
}
but when i try to execute the requests i got this error:
"message": "Cannot return null for non-nullable field BalancesResponse.balances."
Any idea how to return it, i want to return all of the key-value pairs in the object and my key to be dynamic

GraphQL does not support dictionary types. You can only query defined (i.e. known in advance) fields.
You could, however, refactor the response object to be an array:
[
{ key: '0xF7446f0...9a496aE94Cf9d42', balances: [ ... ] },
{ key: '0xc01679F6496...95c86f9DEc63a', balances: [ ... ] }
]
The corresponding type definition would be:
#ObjectType()
export class BalanceResponse {
#Field()
key: string;
#Field({ nullable: true })
error?: string;
#Field((type) => JSON)
balances: any;
}
...
#Query(() => [BalanceResponse])
async getBalance(): Promise<BalanceResponse[]>
Alternatively, you are free to lose the typing by declaring the whole response object as a GraphQL JSON. Then you can return your original response from the endpoint:
// not a GraphQL class anymore
export class BalancesResponse {
error?: string;
balances: Balance;
}
...
#Query(() => JSON)
async getBalance(): Promise<Record<string, BalancesResponse>>

Related

How to set Apollo's cacheControl Directive in Nestjs code-first

I am trying to set a static cacheControl on some fields, as done here
From my understanding, I need to use a directive, so I used the following nest documentation to declare directives
So, I built a cacheControl directive, this is what my GraphQLModule.forRootAsync has in the buildSchemaOptions:
buildSchemaOptions: {
directives: [
new GraphQLDirective({
name: 'cacheControl',
locations: [
DirectiveLocation.FIELD_DEFINITION,
DirectiveLocation.OBJECT,
DirectiveLocation.INTERFACE,
DirectiveLocation.UNION
],
args: {
maxAge: { type: GraphQLInt },
scope: {
type: new GraphQLEnumType({
name: 'CacheControlScope',
values: {
PUBLIC: {
astNode: {
kind: 'EnumValueDefinition',
description: undefined,
name: {
kind: 'Name',
value: 'PUBLIC'
},
directives: []
}
},
PRIVATE: {
astNode: {
kind: 'EnumValueDefinition',
description: undefined,
name: {
kind: 'Name',
value: 'PRIVATE'
},
directives: []
}
}
}
})
},
inheritMaxAge: { type: GraphQLBoolean }
}
})
]
}
And it did create the directive in the schema:
directive #cacheControl(maxAge: Int, scope: CacheControlScope, inheritMaxAge: Boolean) on FIELD_DEFINITION | OBJECT | INTERFACE | UNION
enum CacheControlScope {
PUBLIC
PRIVATE
}
Now, I try to use it on my field declaration in my #ObjectType like so:
import { Directive, Field, Int, ObjectType } from '#nestjs/graphql'
#ObjectType('User')
export class User {
#Directive('#cacheControl(maxAge:60)')
#Field(() => Int)
id!: number
#Directive('#cacheControl(maxAge:60)')
#Field()
text!: string
}
But when doing some query to get all users, it does not seem to cache anything - and does not send a cache-control header and does the whole query each time.
I tried to do a transformer, but not sure how to implement caching for the resolvers.
What am I missing?

Get ManyToOne field using TypeORM and Graph QL query

I have a problem with a TypeORM entity from my project.
The code for entity is shown below.
import { Field, ObjectType } from "type-graphql";
import { BaseEntity, Column, Entity, ManyToOne, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
import { MusicProvider } from "./MusicProvider";
import { User } from "./User";
#ObjectType()
#Entity()
export class UserMusicProvider extends BaseEntity {
#Field()
#PrimaryGeneratedColumn()
id!: number;
#Field(() => User)
#ManyToOne(() => User, (user) => user.id, { onDelete: 'CASCADE', cascade: true })
user: User
#PrimaryColumn()
userId: number
#Field(() => MusicProvider)
#ManyToOne(() => MusicProvider, (musicProvider) => musicProvider.id, { onDelete: 'CASCADE', cascade: true })
musicProvider: MusicProvider
#Column()
musicProviderId: number
#Field()
#Column()
musicProviderUserId: string;
}
As you can see, it has a field for MusicProvider. Into my GraphQL query I want to be able to get the MusicProvider's data, because I want to show this data into my app. I want to do this whitin this query, not using two queries.
The problem is that when I call this query:
query{
getUserMusicProviders(userId: 27){
userMusicProviders{
id
musicProviderUserId
musicProvider{
id
name
}
}
errors{
field
message
}
}
}
i receive an error saying:
Cannot return null for non-nullable field
UserMusicProvider.musicProvider.
which is true, because, as I checked, the MusicProvider returned is undefined. I don't know why this happens and would be really helpful to solve this issue.
The code for my query is this:
#Query(() => UserMusicProvidersResponse)
async getUserMusicProviders(
#Arg("userId") userId: number
): Promise<UserMusicProvidersResponse | null> {
const resp = await UserMusicProvider.find({ where: { userId } })
return {
userMusicProviders: resp
}
}
and the code for the UserMusicProvidersResponse is:
#ObjectType()
export class UserMusicProvidersResponse {
#Field(() => [FieldError], { nullable: true })
errors?: FieldError[];
#Field(() => [UserMusicProvider], { nullable: true })
userMusicProviders?: UserMusicProvider[]
}
Thank you for your time and thank you for trying to help me. If you want to clarify something, feel free to send a comment.

Loopback4 hasMany not return the link array

I just try to use the hasMany relationship according to the loopback4 documentation .but it's not working as expected.
My Bus Model =>
export class Bus extends Entity {
#property({
type: 'number',
id: true,
generated: true,
})
id?: number;
#hasMany(() => BusStation, {keyTo: 'busId'})
stations?: BusStation[];
constructor(data?: Partial<Bus>) {
super(data);
}
}
export interface BusRelations {
// describe navigational properties here
}
export type BusWithRelations = Bus & BusRelations ;
Bus Station Model =>
export class BusStation extends Entity {
#property({
type: 'number',
id: true,
generated: true,
})
id?: number;
#property({
type: 'number',
})
busId: number;
#property({
type: 'string',
required: true,
})
name: string;
constructor(data?: Partial<BusStation>) {
super(data);
}
}
export interface BusStationRelations {
// describe navigational properties here
}
export type BusStationWithRelations = BusStation & BusStationRelations;
Bus Repository =>
export class BusRepository extends DefaultCrudRepository<
Bus,
typeof Bus.prototype.id,
BusRelations
> {
public stations: HasManyRepositoryFactory<
BusStation,
typeof Bus.prototype.id
>;
constructor(
#inject('datasources') dataSource: MyDataSource,
#repository.getter('BusStationRepository')
busStationRepositoryGetter: Getter<BusStationRepository>,
) {
super(Bus, dataSource);
this.stations = this.createHasManyRepositoryFactoryFor(
'stations',
busStationnRepositoryGetter,
);
}
}
My Expected Get Response of Bus =>
{
" id":1,
"stations":[
{
"id":1,
"busId":1,
"name":"Station 1"
}
]
}
I did the exactly same with the documentation but why I can't get the response as I expected. Please May I know what I am missing?
I saw that some solution is to create another controller to connect these two models. Is it the only way? if yes, what is the reason for the hasMany?
in repository you have to include inclusion resolver under
this.stations = this.createHasManyRepositoryFactoryFor('stations',busStationnRepositoryGetter,);
like this
this.registerInclusionResolver('stations', this.stations.inclusionResolver);
on the other hand, you can use relation generator to avoid all this stuff.

TypeGraphql Field and Arg decorators using custom type

I'm trying to build a resolver using type-graphql library and found that I can't define custom argument type. Here is my code:
type Hits = { [K: string]: string | number }
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
#UseMiddleware(isAuthenticated)
async searchAssetList(#Arg('hits') hits: Hits) {
return [];
}
}
I got an error:
NoExplicitTypeError: Unable to infer GraphQL type from TypeScript reflection system. You need to provide explicit type for argument named 'hits' of 'searchAssetList' of 'SearchResolver' class.
I also tried to define an input class:
type Hits = { [K: string]: string | number }
#ObjectType()
class SearchListInput {
#Field(() => GraphQLObjectType)
hits: Hits;
}
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
async searchAssetList(#Arg('input') input: SearchListInput) {
return []
}
}
and got another error:
UnhandledPromiseRejectionWarning: Error: Cannot determine GraphQL input type for argument named 'input' of 'searchAssetList' of 'SearchResolver' class. Is the value, that is used as its TS type or explicit type, decorated with a proper decorator or is it a proper input value?
Replacing #ObjectType with #InputType also doesn't help. How to define decorators like #Field, #Arg correctly?
Any help is appreciated. Thanks.
I think you are trying to have an Arg of an array whit numbers and strings in that case you should do
#InputType()
class HitsInput
{
#Field()
Hits: [number | string];
}
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
async searchAssetList(#Arg('input') input: HitsInput) {
return []
}
}
if this is not the case and you want an object whit dynamic fields you need to define the object, let say if hits have Name and Id_Hits,
#InputType()
class HitsInput
{
#Field()
Id_Hits: number;
#Field()
Name: string;
}
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
async searchAssetList(#Arg('input') input: HitsInput) {
return []
}
}
and if you want to have a dynamic args base in the user request I am not quite sure that it is possible, but it is possible to do this
#InputType()
class HitsInput
{
[key: string]: any;
}
I had exactly the same issue, a couple of days ago, and I solve it using GraphQLScalarType by creating my own custom type
here how to do it
import { GraphQLScalarType} from "graphql";
export const GraphQLAny = new GraphQLScalarType({
name: 'Any',
serialize: (value) => value,
parseValue: (value) => value,
parseLiteral: (ast) => ast
});
and in your class you can use your customTpe like this:
#ObjectType()
class SearchListInput {
#Field(() => GraphQLAny)
hits: typeof GraphQLAny;
}

type-graphql make exception from Field Resolver

I have a following Entity and Resolver for example,
export class SampleA extends BaseEntity {
#Field()
#PrimaryGeneratedColumn()
id!: number;
#Field()
#Column()
title!: string
#Field()
#Column()
answer!: string;
}
#Resolver(SampleA)
export class SampleAResolver {
#FieldResolver(() => String)
async answer(#Root() sampleA: SampleA, #Ctx() {req}: MyContext) {
const msg = "answer is not public";
if(!req.session.userId) return msg;
return sampleA.answer;
}
}
It worked out well. As long as the condition does not meet, the answer field will be always "answer is not public" across the entire app. However, if sometimes I want to make a exception out of it,
For example, if I have the following
#ObjectType()
class ExceptedResponse {
#Field(() => String, {nullable: true})
errors?: string;
#Field(() => SampleA, {nullable: true})
result?: SampleA;
}
When I try to return the ExceptedResponse, the SampleA field will always be filtered by the answer #FieldResolver() so I cannot get the real answer.
So I wonder if I can make it to an exception out of it without manually re-create a whole new field like
#ObjectType()
class ExceptedResponse {
#Field(() => String, {nullable: true})
errors?: string;
#Field(() => {id: Number, title: String, answer: String }, {nullable: true})
result?: {id: number, title: string, answer: string };
}
//then I just manually assign each value inside my graphql method
Or ideally if I can make a SpecialSampleA extends from SampleA without that limitation.
After some random tests, it seems like it's extremely simple to achieve my goal.
Just simply create a new class and then assign the SampleA result to it. It's auto handled by type-graphql and escape the resolve field check because Class is changed;
#ObjectType()
class ExceptedSampleA {
#Field()
inspiration: string;
#Field()
answer: string;
}
//inside response
#Field(() => ExceptedSampleA, {nullable: true})
result?: ExceptedSampleA;
//inside graphql method like Mutation() then return response
const sampleA = SampleA.findOne();
return { result: sampleA, error: someError }

Resources