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?
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.