Resolve one to many relation Nest.js / GraphQL Federation with Mercurius - graphql

I am using Nest.js / GraphQL Federation with Mercurius. I have two entities Products and Store that reside in different apps. The store should have multiple products as one to many relationship. I have a StoresQueries resolver that has a method findAll that returns a StoresPagination object. Each returned store object does not have the fields mentioned in the products app from the apps/products/src/products/resolvers/Store.resolver.ts and same apps/products/src/products/models/Store.model.ts I want to keep stuff decoupled and more or less the same structure works fine on other entities.
Project structure
apps/
store/
src/
store/
models/
Store.model.ts
resolvers/
Store.resolver.ts
StoresQueries.resolver.ts
services/
Store.service.ts
Store.module.ts
App.module.ts
main.ts
products/
src/
store/
models/
Store.model.ts
Product.model.ts
resolvers/
Store.resolver.ts
Product.resolver.ts
services/
Product.service.ts
Product.module.ts
App.module.ts
main.ts
apps/store/src/models/Store.model.ts
#ObjectType()
#Directive('#key(fields: "id")')
export class Store {
#Field(() => ID)
id: string;
#Field()
#TextSearch()
name: string;
}
apps/store/src/resolvers/Store.resolver.ts
#Resolver(() => Store)
export class StoreResolver {
constructor(private readonly storeService: StoreService) {}
#Query(() => StoresQueries, { name: 'stores' })
async storesQueries(): Promise<StoresQueries> {
return {};
}
#ResolveReference()
async resolveReference(reference: {
__typename: string;
id: string;
}): Promise<Store> {
return this.storeService.findById(reference.id);
}
}
apps/store/src/resolvers/StoresQueries.resolver.ts
#Resolver(() => StoresQueries)
export class StoresQueriesResolver {
constructor(private storeService: StoreService) {}
#ResolveField(() => StorePagination)
async findAll(
#CurrentUser() user: JwtUser,
#Args('input', { nullable: true }) input: PaginationInput | null,
): Promise<StorePagination> {
return this.storeService.findAll(input?.cursor, input?.limit);
}
}
app/products/src/products/models/Store.model.ts
#ObjectType()
#Directive('#key(fields: "id")')
#Directive('#extends')
export class Store {
#Directive('#external')
#Field(() => ID)
id: string;
// these both fields are not shown when querying StoresQueries for each store
#Field(() => ProductsPagination, { nullable: true })
products?: ProductsPagination;
#Field(() => Number, { nullable: true })
dummyCount?: number;
}
app/products/src/products/models/Product.model.ts
#ObjectType()
#Directive('#key(fields: "id")')
#Label('product', ['id'])
export class Product {
#Field(() => ID)
id: string;
}
app/products/src/products/resolvers/Product.resolver.ts
#Resolver(() => Product)
export class ProductsResolver {
constructor(
private productsService: ProductsService,
) {}
#ResolveReference()
async resolveReference(reference: {
__typename: string;
id: string;
}): Promise<Product> {
return this.productsService.findById(reference.id);
}
}
app/products/src/products/resolvers/Store.resolver.ts
#Resolver(() => Store)
export class StoreResolver {
constructor(
private productsService: ProductsService,
) {}
#ResolveField(() => ProductsPagination, { nullable: true })
async products(
#Parent() parent: Store,
#Args('input', { nullable: true }) input: PaginationInput | null,
): Promise<ProductsPagination> {
const wp = this.productsService.getProductsByStoreId(
parent.id,
kind,
input,
);
return { __typename: 'ProductsPagination', ...wp };
}
}

Related

Prisma / Graphql resolver

Good morning all,
I m currently back in the famous world of web development and I have in mind to develop a tool by using Nest/Prisma/Graphl.
However, I'm struggling a little bit on key element like the following one.
Basically, I can see that, by using the "include" function in Prisma (module.service.ts), I'm getting subModules list: this is the expected behavior.
However, on Graphl side, to cover field resolver (module.resolver.ts), I can see that the same request is executing again to cover SubModules field.....
What am I missing?????
See below the code:
module.module.ts
import { Field, ID, ObjectType } from '#nestjs/graphql'
import { SubModule } from './submodule.model'
#ObjectType()
export class Module {
// eslint-disable-next-line #typescript-eslint/no-unused-vars
#Field((type) => ID)
id: number
name: string
description: string
icon: string
active: boolean
position: number
subModules?: SubModule[]
}
submodule.model.ts
import { Field, ObjectType, ID } from '#nestjs/graphql';
import { Module } from './module.model';
#ObjectType()
export class SubModule {
#Field((type) => ID)
id: number;
name: string;
description: string;
icon: string;
active: boolean;
position: number;
module: Module;
}
module.resolver.ts
import {
Resolver,
Query,
ResolveField,
Parent,
Args,
InputType,
} from '#nestjs/graphql'
import { Module } from 'src/models/module.model'
import { ModuleService } from './module.service'
import { SubModuleService } from './sub-module.service'
#InputType()
class FilterModules {
name?: string
description?: string
icon?: string
active?: boolean
}
// eslint-disable-next-line #typescript-eslint/no-unused-vars
#Resolver((of) => Module)
export class ModuleResolver {
constructor(
private moduleService: ModuleService,
private subModuleService: SubModuleService,
) {}
// eslint-disable-next-line #typescript-eslint/no-unused-vars
#Query((returns) => Module)
async module(#Args('ModuleId') id: number) {
return this.moduleService.module(id)
}
// eslint-disable-next-line #typescript-eslint/no-unused-vars
#Query((returns) => [Module], { nullable: true })
async modules(
#Args({ name: 'skip', defaultValue: 0, nullable: true }) skip: number,
#Args({ name: 'filterModules', defaultValue: '', nullable: true })
filterModules: FilterModules,
) {
return this.moduleService.modules({
skip,
where: {
name: {
contains: filterModules.name,
},
},
})
}
#ResolveField()
async subModules(#Parent() module: Module) {
const { id } = module
return this.subModuleService.subModules({ where: { moduleId: id } })
}
}
module.service.ts
import { Injectable } from '#nestjs/common'
import { PrismaService } from 'src/prisma.service'
import { Prisma, Module } from '#prisma/client'
#Injectable()
export class ModuleService {
constructor(private prisma: PrismaService) {
prisma.$on<any>('query', (event: Prisma.QueryEvent) => {
console.log('Query: ' + event.query)
console.log('Params' + event.params)
console.log('Duration: ' + event.duration + 'ms')
})
}
async module(id: number): Promise<Module | null> {
return this.prisma.module.findUnique({
where: {
id: id || undefined,
},
})
}
async modules(params: {
skip?: number
take?: number
cursor?: Prisma.ModuleWhereUniqueInput
where?: Prisma.ModuleWhereInput
orderBy?: Prisma.ModuleOrderByWithRelationInput
}): Promise<Module[]> {
const { skip, take, cursor, where, orderBy } = params
return this.prisma.module.findMany({
skip,
take,
cursor,
where,
orderBy,
})
}
async updateModule(params: {
where: Prisma.ModuleWhereUniqueInput
data: Prisma.ModuleUpdateInput
}): Promise<Module> {
const { where, data } = params
return this.prisma.module.update({
data,
where,
})
}
}
Thanks in advance for your help

Cannot return null for non-nullable field - Typegoose and TypeGraphQL

I have this Album model set up using Typegoose and TypeGraphQL, which contains multiple songs from the Song model using the AlbumSong ObjectType:
import {
prop as Property,
getModelForClass,
modelOptions,
Ref,
} from "#typegoose/typegoose";
import { Field, ObjectType, ID } from "type-graphql";
import { AlbumCategory, } from "./albumCategory.model";
import { Song } from "./song.model";
#ObjectType()
export class AlbumSong {
#Field(() => ID)
#Property({ required: true })
id!: string;
#Field(() => Song)
#Property({ type: () => Song, ref: () => Song, required: true })
song!: Song;
}
#ObjectType({ description: "The Album Model" })
#modelOptions({ schemaOptions: { collection: "albums", timestamps: true } })
export class Album {
#Field(() => ID)
id: string;
#Field()
#Property({ type: () => String })
title: string;
#Field(() => [AlbumSong])
#Property({ type: AlbumSong })
albumSongs!: Partial<AlbumSong>[];
#Field()
#Property({ required: true, default: Date.now })
createdAt: Date;
#Field()
#Property({ required: true, default: Date.now })
updatedAt: Date;
}
export const AlbumModel = getModelForClass(Album);
When trying to query the album using:
#Query((_returns) => Album, { nullable: false, name: "album" })
async getAlbumById(#Arg("id") id: string) {
return await AlbumModel.findById({ _id: id });
}
With the following GraphQL:
query Album($albumId: String!) {
album(id: $albumId) {
id
albumSongs {
id
song {
id
}
}
}
}
I get: "Cannot return null for non-nullable field AlbumSong.song."
To me it seems like the reference is not working, when i only query the albumSong's id it returns just fine...
Setup a FieldResolver to resolve the song within an AlbumSong
#Resolver(() => AlbumSong)
export class AlbumSongFieldResolver {
#FieldResolver(() => Song)
async song(#Root() parent: AlbumSong): Promise<Song> {
return Song.findOne(parent.song);
}
}

how I can add a custom scalar with different formats in type-graphql to generate GraphQL schema

I tried to generate a GraphQl schema using type-graphql lib I want the field value to accept any data type but it only takes a string. I have created a custom scalar with the name SettingValueScalar but it's not working as well
import { ObjectID } from 'mongodb'
import 'reflect-metadata'
import { Field, InputType, Int, ObjectType } from 'type-graphql'
import { DateScalar } from '../scalars/date.scalar'
import { SettingValueScalar } from '../scalars/setting-value.scalar'
#ObjectType()
#InputType('SettingInput')
export class Setting implements MSetting {
readonly _id!: ObjectID
#Field(() => Int)
id!: number
#Field({ nullable: true })
slug?: string
#Field({ nullable: true })
domain?: string
#Field(()=>SettingValueScalar,{ nullable: true })
value?: string| number | string[] | number[]
#Field(() => DateScalar, { nullable: true })
createdAt?: Date
#Field(() => DateScalar, { nullable: true })
updatedAt?: Date
}
SettingValueScalar
import { GraphQLScalarType, Kind } from 'graphql'
export const SettingValueScalar = new GraphQLScalarType({
name: 'SettingValue',
description: 'Setting value scalar type',
serialize(value: any): any {
return value
},
parseValue(value: any): any {
return value
},
parseLiteral(ast) {
return last
},
})

Schema must contain uniquely named types named "Project"

I am creating a Apollo Graphql backend using type-orm. I create an entity called Project:
import { Field, ObjectType } from "type-graphql";
import { BaseEntity, Column, Entity, ObjectID, ObjectIdColumn } from "typeorm";
#ObjectType()
#Entity()
export class Project extends BaseEntity {
#Field(() => String)
#ObjectIdColumn()
id: ObjectID;
#Field()
#Column({ unique: true })
name!: string;
#Field()
#Column()
startDate!: Date;
#Field()
#Column({nullable: true})
endDate!: Date
#Field()
#Column({unique:true})
githubUrl: string;
}
and the resolver project:
import { Arg, Mutation, Query, Resolver } from 'type-graphql'
import {Project} from '../entities/project'
import {ProjectInput, ProjectResponse} from '../types/ProjectTypes'
#Resolver()
export class ProjectResolver {
#Query(() => [Project])
async getProjects(): Promise<Project[] | null> {
let projects = await Project.getRepository().find();
return projects;
}
#Mutation(() => ProjectResponse)
async createProject(
#Arg("input") input: ProjectInput
): Promise<ProjectResponse>{
let project : Project;
if(input.name == ""){
throw Error("Invalid input")
}
try{
project = await Project.create({
name: input.name,
startDate: input.startDate,
}).save();
}catch (error) {
if (error.code === 11000) {
return {
errors: [
{
field: "project",
message: "The project name is already in use",
},
],
};
} else return error;
}
return {project: project};
}
#Mutation(() => ProjectResponse)
async setProjectEndDate(
#Arg("projectId") projectId: string,
#Arg("endDate") endDate: Date
): Promise<ProjectResponse>{
let project = await Project.getRepository().findOne(projectId)
if(project){
if(project?.startDate > endDate){
return {
errors:[{
field:"EndDate",
message:"The end date must be a date after the start date of a project."
}]
}
}
project.endDate = endDate;
project.save();
}
return {
errors:[{
field:"Project",
message:"Project could not be found."
}]
}
}
}
this is the code of the 2 auxiliary classes for the input and response of the resolver:
#InputType()
export class ProjectInput{
#Field()
name: string
#Field()
startDate: Date
#Field(()=> Date,{nullable:true})
endDate?: Date | null
#Field(()=> String, {nullable:true})
githubUrl?: string
}
#ObjectType()
export class ProjectResponse{
#Field(() => [FieldError], { nullable: true })
errors?: FieldError[]
#Field(() => Project, { nullable: true })
project?: Project | null
}
this is the code I use to create the ApolloServer object:
const apolloServer = new ApolloServer({
introspection: true,
playground: true,
schema: await buildSchema({
resolvers: [ProjectResolver],
validate: false, // Disable default GraphQL errors
}),
context: ({ req, res }) => ({ req, res}), // Enables use of context (with request) in resolvers
})
And the error I get is the following:
Error: Schema must contain uniquely named types but contains multiple types named "Project".
at new GraphQLSchema (C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\node_modules\graphql\type\schema.js:194:15)
at Function.generateFromMetadataSync (C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\node_modules\type-graphql\dist\schema\schema-generator.js:31:32)
at Function.generateFromMetadata (C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\node_modules\type-graphql\dist\schema\schema-generator.js:16:29)
at Object.buildSchema (C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\node_modules\type-graphql\dist\utils\buildSchema.js:10:61)
at C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\dist\index.js:42:38
at Generator.next ()
at fulfilled (C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\dist\index.js:5:58)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
I don't know what the problem is so I would appreciate your help
I have managed to solve the problem by changing the import of the class 'Project' made in the 'ProjectResolver' class.
Instead of:
import {Project} from '../entities/project'
Now looks like this:
import {Project} from '../../src/entities/project'

TypeORM GraphQL nested query returns null

I am trying to use a GraphQL nested query to get information about project a and customer. I can get the information about project but I am unable to get the customer info.
How do I properly get the nested information?
Project Model
import { Entity, BaseEntity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm";
import { ObjectType, Field, ID } from "type-graphql";
import {Customer} from "./Customer";
#Entity('project')
#ObjectType()
export class Project extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn()
id: number;
#Field(() => String)
#Column()
name: string;
#Field(() => String)
#Column()
short_name: string;
#Field(() => String)
#Column()
description: string;
#Field(() => Customer, { nullable: true })
#ManyToOne(type => Customer, customer => customer.projects)
customer: Customer;
}
User Model
import { Entity, BaseEntity, PrimaryGeneratedColumn, Column, OneToMany, JoinTable } from "typeorm";
import { ObjectType, Field, ID } from "type-graphql";
import {Project} from "./Project";
import {ProjectUser} from "./Project_handled_by_user";
#Entity('customer')
#ObjectType()
export class Customer extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn()
id: number;
#Field(() => String)
#Column()
name: string;
#Field(() => String)
#Column()
short_name: string;
#Field(() => String)
#Column()
active: string;
#Field(() => [Project], { nullable: true })
#OneToMany(type => Project, project => project.customer)
projects: Project[];
}
Project Resolver
import { Resolver, Query, Mutation, Arg } from "type-graphql";
import { Project } from "../models/Project";
#Resolver()
export class ProjectResolver {
#Query(() => [Project])
projects() {
return Project.find();
}
#Query(() => Project)
project(#Arg("id") id: string) {
return Project.findOne({ where: { id } });
}
}
For example, my query is:
{
project(id: "1"){
id
name
customer{
id
name
}
}
}
The query returns:
{
"data": {
"project": {
"id": "1",
"name": "KFS 5.1",
"customer": null
}
}
}
Thanks for help

Resources