{"id":23436993,"url":"https://github.com/amplication/extended-queries-example","last_synced_at":"2025-04-09T18:27:39.790Z","repository":{"id":196232921,"uuid":"695098941","full_name":"amplication/extended-queries-example","owner":"amplication","description":null,"archived":false,"fork":false,"pushed_at":"2023-11-11T07:32:42.000Z","size":1191,"stargazers_count":1,"open_issues_count":4,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-02-15T10:54:42.151Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/amplication.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-09-22T10:57:31.000Z","updated_at":"2023-10-14T12:32:59.000Z","dependencies_parsed_at":"2024-12-23T13:37:08.938Z","dependency_job_id":"47411b51-1b06-4cf8-9728-f440e834a3e1","html_url":"https://github.com/amplication/extended-queries-example","commit_stats":null,"previous_names":["amplication/example-extending-graphql-queries"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amplication%2Fextended-queries-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amplication%2Fextended-queries-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amplication%2Fextended-queries-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amplication%2Fextended-queries-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/amplication","download_url":"https://codeload.github.com/amplication/extended-queries-example/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248087048,"owners_count":21045460,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-23T13:36:24.923Z","updated_at":"2025-04-09T18:27:39.769Z","avatar_url":"https://github.com/amplication.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Extended Queries Example\n\n### Advanced Customization with Amplication-Generated Code\n\nThis repository provides insights into modifying and extending code that's generated by Amplication. While Amplication is efficient in generating a foundational scaffold for various data structures, there can be scenarios where the default generated code might not suffice for certain specific requirements.\n\nHere, we outline the steps to integrate custom code into an Amplication-generated project. Specifically, we'll focus on nested query scenarios, highlighting the path through structures like Order \u003e Customer \u003e Payment. The goal is to extend the default functionalities, such as retrieving all orders made using a specific payment method, like \"Paypal\".\n\n### Step 1 - Familiarize Yourself with the Prisma Schema\n\nBefore diving in, it's paramount to have a clear understanding of the prisma.schema file, especially the parts that align with the context of this guide. By examining it, you'll notice the interrelatedness of entities such as Order, Customer, and Payment.\n\n[prisma/schema.prisma](apps/extended-queries-server/prisma/schema.prisma)\n\n```prisma\nmodel Order {\n  id         String    @id @default(cuid())\n  customer   Customer? @relation(fields: [customerId], references: [id])\n  customerId String?\n}\n\nmodel Customer {\n  id        String    @id @default(cuid())\n  orders    Order[]\n  payments  Payment[]\n}\n\nmodel Payment {\n  id          String                  @id @default(cuid())\n  customer    Customer?               @relation(fields: [customerId], references: [id])\n  customerId  String?\n  paymentType EnumPaymentPaymentType?\n}\n\nenum EnumPaymentPaymentType {\n  Card\n  Cash\n  Paypal\n}\n```\n\n### Step 2 - Pinpointing the Gaps\n\n When inspecting the generated code, you might notice that the `${EntityName}WhereInput` for interrelated entities leverages the `${EntityName}WhereUniqueInput`, which is confined to returning just the ID field.\n\n[order/base/OrderWhereInput.ts\n](apps/extended-queries-server/src/order/base/OrderWhereInput.ts\n)\n\n```typescript \n@InputType()\nclass OrderWhereInput {\n  @ApiProperty({\n    required: false,\n    type: () =\u003e CustomerWhereUniqueInput,\n  })\n  @ValidateNested()\n  @Type(() =\u003e CustomerWhereUniqueInput)\n  @IsOptional()\n  @Field(() =\u003e CustomerWhereUniqueInput, {\n    nullable: true,\n  })\n  customer?: CustomerWhereUniqueInput;\n\n // and the rest of the file...\n}\n\nexport { OrderWhereInput as OrderWhereInput };\n\n```\n\nTo enrich our data-fetching capabilities, we must pivot from using `${EntityName}WhereUniqueInput` to `${EntityName}WhereInput`. Yet, it's imperative to avoid tampering with the OrderWhereInput DTO within the base folder. Instead, we'll craft a custom DTO outside this directory.\n\n```typescript\nimport { InputType, Field } from \"@nestjs/graphql\";\nimport { ApiProperty } from \"@nestjs/swagger\";\nimport { ValidateNested, IsOptional } from \"class-validator\";\nimport { Type } from \"class-transformer\";\nimport { CustomerWhereInput } from \"src/customer/base/CustomerWhereInput\";\nimport { FloatNullableFilter } from \"src/util/FloatNullableFilter\";\nimport { StringFilter } from \"src/util/StringFilter\";\nimport { ProductWhereUniqueInput } from \"src/product/base/ProductWhereUniqueInput\";\nimport { IntNullableFilter } from \"src/util/IntNullableFilter\";\n\n@InputType()\nclass OrderWhereInputWithExtendedCustomer {\n  @ApiProperty({\n    required: false,\n    type: () =\u003e CustomerWhereInput,\n  })\n  @ValidateNested()\n  @Type(() =\u003e CustomerWhereInput)\n  @IsOptional()\n  @Field(() =\u003e CustomerWhereInput, {\n    nullable: true,\n  })\n  customer?: CustomerWhereInput;\n\n  @ApiProperty({\n    required: false,\n    type: FloatNullableFilter,\n  })\n  @Type(() =\u003e FloatNullableFilter)\n  @IsOptional()\n  @Field(() =\u003e FloatNullableFilter, {\n    nullable: true,\n  })\n  discount?: FloatNullableFilter;\n\n  @ApiProperty({\n    required: false,\n    type: StringFilter,\n  })\n  @Type(() =\u003e StringFilter)\n  @IsOptional()\n  @Field(() =\u003e StringFilter, {\n    nullable: true,\n  })\n  id?: StringFilter;\n\n  @ApiProperty({\n    required: false,\n    type: () =\u003e ProductWhereUniqueInput,\n  })\n  @ValidateNested()\n  @Type(() =\u003e ProductWhereUniqueInput)\n  @IsOptional()\n  @Field(() =\u003e ProductWhereUniqueInput, {\n    nullable: true,\n  })\n  product?: ProductWhereUniqueInput;\n\n  @ApiProperty({\n    required: false,\n    type: IntNullableFilter,\n  })\n  @Type(() =\u003e IntNullableFilter)\n  @IsOptional()\n  @Field(() =\u003e IntNullableFilter, {\n    nullable: true,\n  })\n  quantity?: IntNullableFilter;\n\n  @ApiProperty({\n    required: false,\n    type: IntNullableFilter,\n  })\n  @Type(() =\u003e IntNullableFilter)\n  @IsOptional()\n  @Field(() =\u003e IntNullableFilter, {\n    nullable: true,\n  })\n  totalPrice?: IntNullableFilter;\n}\n\nexport { OrderWhereInputWithExtendedCustomer as OrderWhereInputWithExtendedCustomer };\n```\n\nTo further refine, we'll also tap into the Prisma implementation of OrderWhereInput that encompasses operators such as `AND`, `OR`, `NOT`.\n\n```typescript\nexport type OrderWhereInput = {\n    AND?: Enumerable\u003cOrderWhereInput\u003e\n    OR?: Enumerable\u003cOrderWhereInput\u003e\n    NOT?: Enumerable\u003cOrderWhereInput\u003e\n    createdAt?: DateTimeFilter | Date | string\n    customer?: XOR\u003cCustomerRelationFilter, CustomerWhereInput\u003e | null\n    customerId?: StringNullableFilter | string | null\n    discount?: FloatNullableFilter | number | null\n    id?: StringFilter | string\n    product?: XOR\u003cProductRelationFilter, ProductWhereInput\u003e | null\n    productId?: StringNullableFilter | string | null\n    quantity?: IntNullableFilter | number | null\n    totalPrice?: IntNullableFilter | number | null\n    updatedAt?: DateTimeFilter | Date | string\n  }\n```\n\n[customer/dtos/CustomerWhereInputWithOperator.ts](apps/extended-queries-server/src/customer/dtos/CustomerWhereInputWithOperator.ts)\n\n```typescript\nimport { InputType, Field } from \"@nestjs/graphql\";\nimport { IsOptional } from \"class-validator\";\nimport { Type } from \"class-transformer\";\nimport { CustomerWhereInput } from \"../base/CustomerWhereInput\";\n\n@InputType()\nclass CustomerWhereInputWithOperator extends CustomerWhereInput {\n  @Type(() =\u003e CustomerWhereInput)\n  @IsOptional()\n  @Field(() =\u003e CustomerWhereInput, {\n    nullable: true,\n  })\n  AND?: CustomerWhereInput;\n\n  @Type(() =\u003e CustomerWhereInput)\n  @IsOptional()\n  @Field(() =\u003e CustomerWhereInput, {\n    nullable: true,\n  })\n  OR?: CustomerWhereInput;\n\n  @Type(() =\u003e CustomerWhereInput)\n  @IsOptional()\n  @Field(() =\u003e CustomerWhereInput, {\n    nullable: true,\n  })\n  NOT?: CustomerWhereInput;\n}\n\nexport { CustomerWhereInputWithOperator as CustomerWhereInputWithOperator };\n```\n\nWith this foundation, we can now enhance the `OrderWhereInputWithExtendedCustomer` to employ the `CustomerWhereInputWithOperator` as opposed to the plain `CustomerWhereInput`.\n\n[order/dtos/OrderWhereInputWithExtendedCustomer.ts](apps/extended-queries-server/src/order/dtos/OrderWhereInputWithExtendedCustomer.ts)\n\n```typescript\nimport { InputType, Field } from \"@nestjs/graphql\";\nimport { ApiProperty } from \"@nestjs/swagger\";\nimport { ValidateNested, IsOptional } from \"class-validator\";\nimport { Type } from \"class-transformer\";\nimport { CustomerWhereInputWithOperator } from \"src/customer/dtos/CustomerWhereInputWithOperator\";\nimport { FloatNullableFilter } from \"src/util/FloatNullableFilter\";\nimport { StringFilter } from \"src/util/StringFilter\";\nimport { ProductWhereUniqueInput } from \"src/product/base/ProductWhereUniqueInput\";\nimport { IntNullableFilter } from \"src/util/IntNullableFilter\";\n\n@InputType()\nclass OrderWhereInputWithExtendedCustomer {\n  @ApiProperty({\n    required: false,\n    type: () =\u003e CustomerWhereInputWithOperator,\n  })\n  @ValidateNested()\n  @Type(() =\u003e CustomerWhereInputWithOperator)\n  @IsOptional()\n  @Field(() =\u003e CustomerWhereInputWithOperator, {\n    nullable: true,\n  })\n  customer?: CustomerWhereInputWithOperator;\n\n  @ApiProperty({\n    required: false,\n    type: FloatNullableFilter,\n  })\n  @Type(() =\u003e FloatNullableFilter)\n  @IsOptional()\n  @Field(() =\u003e FloatNullableFilter, {\n    nullable: true,\n  })\n  discount?: FloatNullableFilter;\n\n  @ApiProperty({\n    required: false,\n    type: StringFilter,\n  })\n  @Type(() =\u003e StringFilter)\n  @IsOptional()\n  @Field(() =\u003e StringFilter, {\n    nullable: true,\n  })\n  id?: StringFilter;\n\n  @ApiProperty({\n    required: false,\n    type: () =\u003e ProductWhereUniqueInput,\n  })\n  @ValidateNested()\n  @Type(() =\u003e ProductWhereUniqueInput)\n  @IsOptional()\n  @Field(() =\u003e ProductWhereUniqueInput, {\n    nullable: true,\n  })\n  product?: ProductWhereUniqueInput;\n\n  @ApiProperty({\n    required: false,\n    type: IntNullableFilter,\n  })\n  @Type(() =\u003e IntNullableFilter)\n  @IsOptional()\n  @Field(() =\u003e IntNullableFilter, {\n    nullable: true,\n  })\n  quantity?: IntNullableFilter;\n\n  @ApiProperty({\n    required: false,\n    type: IntNullableFilter,\n  })\n  @Type(() =\u003e IntNullableFilter)\n  @IsOptional()\n  @Field(() =\u003e IntNullableFilter, {\n    nullable: true,\n  })\n  totalPrice?: IntNullableFilter;\n}\n\nexport { OrderWhereInputWithExtendedCustomer as OrderWhereInputWithExtendedCustomer };\n```\n\n### Step 3 - Crafting a Customized OrderFindManyArgs DTO\n\nGiven that we're keen on safeguarding the integrity of the base folder and maintaining the versatility of our queries, it's apt to design a new findManyArgs DTO. This DTO will utilize the `OrderWhereInputWithExtendedCustomer` in lieu of the more restricted `OrderWhereInput`.\n\n[order/dtos/OrderFindManyExtendedWhereArgs.ts](apps/extended-queries-server/src/order/dtos/OrderFindManyExtendedWhereArgs.ts)\n\n```typescript\nimport { ArgsType, Field } from \"@nestjs/graphql\";\nimport { ApiProperty } from \"@nestjs/swagger\";\nimport { IsOptional, ValidateNested, IsInt } from \"class-validator\";\nimport { Type } from \"class-transformer\";\nimport { OrderWhereInputWithExtendedCustomer } from \"./OrderWhereInputWithExtendedCustomer\";\nimport { OrderOrderByInput } from \"../base/OrderOrderByInput\";\n\n@ArgsType()\nclass OrderFindManyExtendedWhereArgs {\n  @ApiProperty({\n    required: false,\n    type: () =\u003e OrderWhereInputWithExtendedCustomer,\n  })\n  @IsOptional()\n  @ValidateNested()\n  @Field(() =\u003e OrderWhereInputWithExtendedCustomer, { nullable: true })\n  @Type(() =\u003e OrderWhereInputWithExtendedCustomer)\n  where?: OrderWhereInputWithExtendedCustomer;\n\n  @ApiProperty({\n    required: false,\n    type: [OrderOrderByInput],\n  })\n  @IsOptional()\n  @ValidateNested({ each: true })\n  @Field(() =\u003e [OrderOrderByInput], { nullable: true })\n  @Type(() =\u003e OrderOrderByInput)\n  orderBy?: Array\u003cOrderOrderByInput\u003e;\n\n  @ApiProperty({\n    required: false,\n    type: Number,\n  })\n  @IsOptional()\n  @IsInt()\n  @Field(() =\u003e Number, { nullable: true })\n  @Type(() =\u003e Number)\n  skip?: number;\n\n  @ApiProperty({\n    required: false,\n    type: Number,\n  })\n  @IsOptional()\n  @IsInt()\n  @Field(() =\u003e Number, { nullable: true })\n  @Type(() =\u003e Number)\n  take?: number;\n}\n\nexport { OrderFindManyExtendedWhereArgs as OrderFindManyExtendedWhereArgs };\n\n```\n\n### Step 4 - Service-Level Implementation\n\nNow, we venture into the service layer. In our `order.service.ts`, we've integrated a method designed to retrieve orders based on the specified conditions.\n\n[order.service.ts](apps/extended-queries-server/src/order/order.service.ts)\n\n```typescript\nimport { Order, Prisma } from \"@prisma/client\";\n\n\nasync findManyOrdersWherePaymentMethod\u003cT extends Prisma.OrderFindManyArgs\u003e(\n    args: Prisma.SelectSubset\u003cT, Prisma.OrderFindManyArgs\u003e\n  ): Promise\u003cOrder[]\u003e {\n    return this.prisma.order.findMany(args);\n  }\n```\n\n### Step 5 - Bringing it to GraphQL\n\nWith our service layer primed, we shift our attention to the GraphQL resolver. The aim is to ensure that our GraphQL API can handle the complexities of our enhanced nested queries.\n\n[order/order.resolver.ts](apps/extended-queries-server/src/order/order.resolver.ts)\n\n```typescript\nimport { OrderFindManyExtendedWhereArgs } from \"./dtos/OrderFindManyExtendedWhereArgs\";\n\n@common.UseInterceptors(AclFilterResponseInterceptor)\n  @graphql.Query(() =\u003e [Order])\n  @nestAccessControl.UseRoles({\n    resource: \"Order\",\n    action: \"read\",\n    possession: \"any\",\n  })\n  async ordersWherePaymentMethod(\n    @graphql.Args() args: OrderFindManyExtendedWhereArgs\n  ): Promise\u003cOrder[]\u003e {\n    return this.service.findManyOrdersWherePaymentMethod(args);\n  }\n```\n\n#### Test it in [http://localhost:3000/grpahql](http://localhost:3000/grpahql)\n\nThe query:\n\n```graphql\nquery OrdersWherePaymentMethod($where: OrderWhereInputWithExtendedCustomer) {\n  ordersWherePaymentMethod(where: $where) {\n    id\n    customer {\n      id\n      email\n      payments {\n        paymentType\n      }\n    }\n  }\n}\n```\n\nThe variables:\n\n```graphql\n{\n  \"where\": {\n    \"customer\": {\n      \"payments\": {\n        \"some\": {\n          \"paymentType\": \"Paypal\"\n        }\n      }\n    }\n  }\n}\n```\n\nThe result:\n\n![image](./assets/graphql-example.png)\n\n### Step 6 - Embracing the REST Paradigm\n\nLastly, for those vested in RESTful services, we've got you covered. In our order controller, we've curated a method to retrieve orders based on their payment methods.\n\n```typescript\nimport { OrderFindManyExtendedWhereArgs } from \"./dtos/OrderFindManyExtendedWhereArgs\";\n\n  @common.UseInterceptors(AclFilterResponseInterceptor)\n  @common.Get(\"/customer/paymentMethod\")\n  @swagger.ApiOkResponse({ type: [Order] })\n  @ApiNestedQuery(OrderFindManyExtendedWhereArgs)\n  @nestAccessControl.UseRoles({\n    resource: \"Order\",\n    action: \"read\",\n    possession: \"any\",\n  })\n  @swagger.ApiForbiddenResponse({\n    type: errors.ForbiddenException,\n  })\n  async ordersWherePaymentMethod(@common.Req() request: Request): Promise\u003cOrder[]\u003e {\n    const args = plainToClass(OrderFindManyExtendedWhereArgs, request.query);\n    return this.service.findManyOrdersWherePaymentMethod({\n      ...args,\n      select: {\n        createdAt: true,\n        customer: {\n          select: {\n            id: true,\n            payments: {\n              select: {\n                paymentType: true, // \u003c= pay attention to this\n              },\n            }\n          },\n        },\n        discount: true,\n        id: true,\n        product: {\n          select: {\n            id: true,\n          },\n        },\n        quantity: true,\n        totalPrice: true,\n        updatedAt: true,\n      },\n    });\n  }\n```\n\n#### Test it in [http://localhost:3000/api](http://localhost:3000/api)\n\n![image](./assets/rest-example.png)\n\nThe response json:\n\n```json\n[\n  {\n    \"createdAt\": \"2023-10-13T09:20:09.222Z\",\n    \"customer\": {\n      \"id\": \"clnoedikh0000p5smo00rts3i\",\n      \"payments\": [\n        {\n          \"paymentType\": \"Paypal\"\n        }\n      ]\n    },\n    \"discount\": 10,\n    \"id\": \"clnoeeadi0002p5smxsicb8he\",\n    \"product\": null,\n    \"quantity\": 2,\n    \"totalPrice\": 300,\n    \"updatedAt\": \"2023-10-13T09:19:45.663Z\"\n  },\n  {\n    \"createdAt\": \"2023-10-13T09:50:40.568Z\",\n    \"customer\": {\n      \"id\": \"clnofg29u0000p506rfzmxeqd\",\n      \"payments\": [\n        {\n          \"paymentType\": \"Paypal\"\n        }\n      ]\n    },\n    \"discount\": null,\n    \"id\": \"clnofhjg80002p506btxsgw9q\",\n    \"product\": null,\n    \"quantity\": 8,\n    \"totalPrice\": 750,\n    \"updatedAt\": \"2023-10-13T09:50:10.159Z\"\n  }\n]\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famplication%2Fextended-queries-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Famplication%2Fextended-queries-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famplication%2Fextended-queries-example/lists"}