graphqlnestjstypegraphql

How to get specific field only in case if graphql require it?


I've started to learn NestJs and GraphQL and created a simple backend:

I have posts:

import { Field, Int, ObjectType } from 'type-graphql';

@ObjectType()
export class Post {

    constructor ({ id, title }) {
        this.id = id;
        this.title = title;
    }

    @Field(type => Int, { nullable: true })
    id: number;

    @Field(type => String)
    title: string;

}

And I have Users

import { Field, Int, ObjectType } from 'type-graphql';
import { Post } from './post';

@ObjectType()
export class User {

    constructor({ id, name, postIds }) {
        this.id = id;
        this.name = name;
        this.postIds = postIds;
    }

    @Field(type => Int)
    id: number;

    @Field(type => String)
    name: string;

    @Field(type => [Int])
    postIds: number[];

    // in case 'postIds' populating
    @Field(type => [Post])
    posts: Post[];
}

And NestJS (I think type-graphql library did it) generate schema.gql file for me:

# -----------------------------------------------
# !!! THIS FILE WAS GENERATED BY TYPE-GRAPHQL !!!
# !!!   DO NOT MODIFY THIS FILE BY YOURSELF   !!!
# -----------------------------------------------

type Post {
  id: Int
  title: String!
}

type Query {
  user(id: Int!): User!
}

type User {
  id: Int!
  name: String!
  postIds: [Int!]!
  posts: [Post!]!
}

And the last file with UserResolver:

import { Int, Resolver } from 'type-graphql';
import { Args, Query } from '@nestjs/graphql';
import { User } from './models/user';
import { Post } from './models/post';

const post1 = new Post({ id: 1, title: 'title_1' });
const post2 = new Post({ id: 2, title: 'title_2' });
const post3 = new Post({ id: 3, title: 'title_3' });
const user1 = new User({ id: 1, name: 'Alex', postIds: [post1.id, post2.id] });
const user2 = new User({ id: 2, name: 'Kate', postIds: [post3.id] });
const allUsers = [user1, user2];
const allPosts = [post1, post2, post3];

@Resolver()
export class UserResolver {

    @Query(returns => User)
    user(@Args({ name: 'id', type: () => Int }) id) {
        const currentUser = allUsers.find(u => u.id === id);

        // please, remember next part of code
        currentUser.posts = [];
        currentUser.postIds.forEach(postId => {
            const currentPost = allPosts.find(p => p.id === postId);
            currentUser.posts.push(currentPost);
        });
        return currentUser;
    }

}

It's all. It is very simple. Now, I can request the next:

{        
    user(id:1) {
        name
        posts {
            title
        }
    }
}

And it works. But there is some problem. If the request would be next...

{
    user(id:1) {
        name
        postIds
        #posts aren't required anymore
    }
}

...the next code executes anyway

        currentUser.posts = [];
        currentUser.postIds.forEach(postId => {
            const currentPost = allPosts.find(p => p.id === postId);
            currentUser.posts.push(currentPost);
        });

I think it is not right. The idea of graphql is to take only the necessary data described in the query. And in my case, we get the posts and then cut them off. If in the future there will be a large nesting of User.Post.IntroImage.Comment.User then this will be ineffective.

Please tell me how to do it right?


Solution

  • I found the answer :)

        import { Args, Parent, Query, ResolveProperty, Resolver } from '@nestjs/graphql';
        import { User } from './models/user';
        import { Post } from './models/post';
        
        const post1 = new Post({ id: 1, title: 'title_1' });
        const post2 = new Post({ id: 2, title: 'title_2' });
        const post3 = new Post({ id: 3, title: 'title_3' });
        const user1 = new User({ id: 1, name: 'Alex', postIds: [post1.id, post2.id] });
        const user2 = new User({ id: 2, name: 'Kate', postIds: [post3.id] });
        const allUsers = [user1, user2];
        const allPosts = [post1, post2, post3];
        
        @Resolver(of => User)
        export class UserResolver {
            @Query(returns => User)
            user(@Args('id') id: number) {
                return allUsers.find(u => u.id === id);
            }
        
            @ResolveProperty()
            async posts(@Parent() user) {
                return user.postIds.map(postId => {
                    return allPosts.find(p => p.id === postId);
                });
            }
        }
    

    I just need add @ResolveProperty and graphql library exec it if in need.