I am working in NestJS Project, there is one situation where I need to implement conditional Validation.
So my Payload looks like this:
{
user_id: "123"
user_type: "M" //Value Can be M or F
inner_details: {
name: {
firstname:"akshay",
lastname:"nikte"
},
email: "ak#test.com"
},
user_co: "Tesla"
}
So in above payload(body) there are some validation requirement as below:
If user_type = M then Firstname and Lastname both cannot be Empty
If user_type = F then Firstname cannnot be Empty but Lastname can be Empty
For this I created 3 DTO classes:
Filename: create-user.dto.ts
export Class CreateUserDTO {
#IsNotEmpty()
#ApiProperty({
required:true
})
user_id: string
#IsNotEmpty()
#ApiProperty({
required:true
})
user_type: string
#ValidateNested({each:true})
#Type(()=>InnerDetailsDTO)
inner_details: InnerDetailsDTO
#IsNotEmpty()
user_co: string
}
filname: inner-details.dto.ts
export class InnerDetailsDTO {
#ValidateNested({each: true})
#Type: NameDTO
name: NameDTO
#IsNotEmpty
#ApiProperty({
required: true
})
email: string
}
filename name.dto.ts
export class NameDTO {
#IsNotEmpty()
#ApiProperty({required: true})
firstname: string
#Validateif(check if UserType==="M") // How to access UserType from CreateUser DTO
#IsNotEmprty()
lastname: string
}
I have have 3 different DTOs in 3 different Files, how to use attribute from 1 DTO in another DTO ?
In my NameDTO I want apply conditional validation, when user_type==M then validate both Firstname and Lastname but when user_type==F Then only validate Firstname and not Lastname
I recommend yup. There you can also make conditional validation. But therefore you have to setup yup as a Validation Pipeline in Nestjs as well. See this npm module here. But this package is not really popular, so I would implement it myself (I actually did that and it works pretty well). This video explains it really good.
Related
with Spring-GraphQl if I have following two schemas in the resources/graphql folder:
schema1:
type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
schema2:
type Query {
personByName(name: String): Person
}
type Person {
id: ID
firstName: String
lastName: String
}
Spring-GraphQL seems to be merging them into one GraphQL schema file and starting of Spring-Boot Graphql app ends with following error:
Caused by: graphql.schema.idl.errors.SchemaProblem: errors=['Query' type [#1:1] tried to redefine existing 'Query' type [#1:1]]
When I change it to:
schema1:
type Query {
bookById(id: ID): Book
personByName(name: String): Person
}
schema2:
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
type Person {
id: ID
firstName: String
lastName: String
}
it works perfectly good and I am able to call both queries with graphiql. How graphql spring works with multiple schemas? It seems spring-graphql merges files into one schema so multiple Query types per file breaks the app.
Thanks for answer.
Spring GraphQL is loading all schema resources under the configured location and is using TypeDefinitionRegistry::merge to create a single schema out of them.
I think that redifining any type (even the Query one) should raise an error, otherwise this could hide important issues and conflicting schema definitions. That's what GraphQL Java's TypeDefinitionRegistry is doing.
You can organize your schema files like this:
graphql/schema.graphqls
type Query {
}
// add common directives, scalars, etc
graphql/books.graphqls
extend type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
graphql/person.graphqls
extend type Query {
personByName(name: String): Person
}
type Person {
id: ID
firstName: String
lastName: String
}
I faced with an issue that can't resolve on my own. Let's go through it step by step to point out the problem.
I have a mutation bookAppointment which returns an Appointment object
GraphQL schema says that this object should return 4 properties: id, date, specialist, client.
To follow the GraphQL-style the specialist and client properties should be a field level resolvers
To fetch this objects I need pass specialistId to the specialist field level resolver, as well as clientId to the client field level resolver.
At this point a problem arises.
The field level resolvers of client, specialist expects that root mutation returns fields like clientId and specialistId. But GraphQL syntax and types that were generated by that syntax doesn't include this props (make sense).
How to "extend" the return type of the resolver and its interface BookAppointmentPayload to make me and TypeScript happy?
This is my GraphQL schema
type Client {
id: ID!
name: String!
}
type Specialist {
id: ID!
name: String!
}
type Appointment {
id: ID!
date: Date!
client: Client!
specialist: Specialist!
}
input BookAppointmentInput {
date: Date!
userId: ID!
specialistId: ID!
}
type BookAppointmentPayload {
appointment: Appointment!
}
type Mutation {
bookAppointment(input: BookAppointmentInput!): BookAppointmentPayload!
}
This is TypeScript representation of GraphQL schema
interface Client {
id: string
name: string
}
interface Specialist {
id: string
name: string
}
interface Appointment {
id: string
date: Date
client: Client
specialist: Specialist
}
interface BookAppointmentPayload {
appointment: Appointment
}
Here I define my resolvers objects
const resolvers = {
...
Mutation: {
bookAppointment: (parent, args, context, info): BookAppointmentPayload => {
return {
appointment: {
id: '1',
date: new Date(),
clientId: '1', // This prop doesn't exist in the TypeScript interface of Appointment, but is required for the field-level resolver of a `client` prop
specialistId: '1' // This prop doesn't exist int he TypeScript interface of Appointment, but is required for the field-level resolver of a `specialist` prop
}
}
}
},
Appointment: {
client: (parent, args, context, info) => {
// I need a clientId (e.g. args.clientId) to fetch the client object from the database
return {
id: '1',
name: 'Jhon'
}
},
specialist: (parent, args, context, info) => {
// I need a specialistId (e.g. args.specialistId) to fetch the specialist object from the database
return {
id: '1',
name: 'Jane'
}
}
}
}
Solution that come to my mind:
Create an interface which represent "actual" return type of the resolver
...
interface Apppointment {
id: string
date: Date
clientId: string // instead of `client: Client`
specialistId: string // instead of `specialist: Specialist`
}
interface BookAppointmentPayload {
appointment: Appointment
}
...
But this doesn't reflect the GraphQL type. Also tools like graphql-generator generates the type with actual objects that should be included in the response, not the fields that are going to be used by field-level resolvers. (Am I wrong?)
I would like to know how you're solving such issue?
I've been investigating this problem quite a lot and have come to the following conclusion.
Create an interface which represent "actual" return type of the resolver
Most of the time the return type of the resolver function (in JavaScript) doesn't match the type that was declared in the GraphQL SDL
For instance,
# GraphQL SDL
type Appointment {
id: String!
client: User!
specialist: Specialist!
}
type BookAppointmentInput { ... }
type BookAppointmentPayload {
appointment: Appointment!
}
type Mutation {
bookAppointment: (input: BookAppointmentInput!): BookAppointmentPayload!
}
interface AppointmentDatabaseEntity {
id: string
clientId: string // In GraphQL-world this prop is an object, but not in JS. Use this prop in field-level resolver to fetch entire object
specialistId: string // In GraphQL-world this prop is an object, but not in JS. Use this prop in field-level resolver to fetch entire object
}
interface BookAppointmentPayload {
appointment: AppointmentDatabaseEntity // The return type SHOULDN'T be equal to the GraphQL type (Appointment)
}
const resolvers = {
Mutatiuon: {
bookAppointment: (parent, args, context, info) => {
const appointment = { id: '1', specialistId: '1', clientId: '1' }
return {
id: appointment.id,
specialistId: appointment.specialistId, // Pass this prop to the child resolvers to fetch entire object
clientId: appointment.clientId // Pass this prop to the child resolvers to fetch entire object
}
}
},
Appointment: {
client: (parent: AppointmentDatabaseEntity, args, context, info) => {
const client = database.getClient(parent.clientId) // Fetching entire object by the property from the parent object
return {
id: client.id,
name: client.name,
email: client.email
}
},
specialist: (parent: AppointmentDatabaseEntity, args, context, info) => {
const specialist = database.getSpecialist(parent.specialistId) // Fetching entire object by the property from the parent object
return {
id: specialist.id,
name: specialist.name,
email: specialist.email
}
}
}
}
But this doesn't reflect the GraphQL type
As far as I understand it is okay
Also tools like graphql-generator generates the type with actual objects that should be included in the response, not the fields that are going to be used by field-level resolvers. (Am I wrong?)
Yes. I was wrong. The graphql-generator has a configuration file that can be used to replace default generated types with the types that you expect your resolvers to return. This option is called mappers.
plugins
config:
mappers:
User: ./my-models#UserDbObject # User is GraphQL object, which will be replaced with UserDbObject
Book: ./my-modelsBook # Same rule goes here
I don't want to go into details of how to configure it and use, but you can check the links that helped me to understand this
Documentation (check the mappers chapter)
Great explanation by
Jamie Barton (YouTube)
If you disagree with my conclusions or you have a better understanding of how to handle it feel free to leave a comment
I have a GraphQL schema like this:
type User {
id: ID
name: String
email: String
addresses: [UserAddress]
}
type UserAddress {
id: ID
city: String
country: String
}
I always have doubts about how to make the best design for mutations. (I'm using apollo + prisma)
These are my options:
1) One single mutation
I need to create this mutation and input type:
input userAddressInput {
id: ID
city: String
country: String
}
mutation updateUser (
id: ID
name: String
email: String
addresses: UserAddressInput
): User
Then I execute mutations like this:
mutation updateUserData($id: ID, $name: String, $email: String) {
updateUser(id: $id, name: $name, email: $email) {
id
name
email
}
}
mutation updateUserAddress($id: ID, $userAddress: UserAddressInput) {
updateUser(id: $id, userAddress: $userAddress) {
id
addresses {
id
city
country
}
}
}
And resolvers like this:
Mutation: {
updateUser: (_, args) => {
if (args.name || args.email) {
// update model User by args.userData.id
}
if (args.userAddress) {
// update model UserAddress by args.userAddress.id
}
}
}
2) One mutation per type
I don't need to create any input type but I need two mutations:
mutation updateUser (
id: ID
name: String
email: String
): User
mutation updateUserAddress (
id: ID
city: String
country: String
): UserAddress
Then mutations like this:
mutation updateUser($id: ID, $name: String, $email: String) {
updateUser(id: $id, name: $name, email: $email) {
id
name
email
}
}
mutation updateUserAddress($id: ID, $city: String, $country: String) {
updateUserAddress(id: $id, city: $city, country: $country) {
id
city
country
}
}
And resolvers like this:
Mutation: {
updateUserAddress: (_, args) => {
// update model UserAddress by args.id
}
updateUser: (_, args) => {
// update model User by args.id
}
}
What is the best way to deal with such cases?
It depends what your use case is.
Does your GUI allow for update of a user's addresses without also updating the user info? If so you will likely need a separate mutation for updating only the addresses.
If you are allowing user and addresses to be edited and saved as one operation then individual mutations would require you to send multiple HTTP requests (one per mutation).
Do you need to update the user and addresses as an atomic transaction (i.e. all or nothing)? If so then you should use a single mutation.
I want to create a post with a list of tags attached to it. The models are connected many-to-many (one post can have several tags, and one tag can have several posts in it).
Here are my prisma models:
model Post {
id String #id #default(cuid())
slug String #unique
title String
body String
tags Tag[]
}
model Tag {
id String #id #default(cuid())
posts Post[]
name String
slug String #unique
}
And here's a mutation where I'm trying to create a post, and attach tags to it:
t.field('createPost', {
type: 'Post',
args: {
title: nonNull(stringArg()),
body: stringArg(),
tags: list(arg({ type: 'TagInput' }))
},
resolve: async (_, args, context: Context) => {
// Create tags if they don't exist
const tags = await Promise.all(
args.tags.map((tag) =>
context.prisma.tag.upsert({
create: omit(tag, "id"),
update: tag,
where: { id: tag.id || "" },
})
)
)
return context.prisma.post.create({
data: {
title: args.title,
body: args.body,
slug: `${slugify(args.title)}-${cuid()}`,
// Trying to connect a post to an already existing tag
// Without the "tags: {...} everything works
tags: {
set: [{id:"ckql6n0i40000of9yzi6d8bv5"}]
},
authorId: getUserId(context),
published: true, // make it false once Edit post works.
},
})
},
})
This doesn't seem to be working.
I'm getting an error:
Invalid `prisma.post.create()` invocation:
{
data: {
title: 'Post with tags',
body: 'Post with tags body',
slug: 'Post-with-tags-ckql7jy850003uz9y8xri51zf',
tags: {
connect: [
{
id: 'ckql6n0i40000of9yzi6d8bv5'
}
]
},
}
}
Unknown arg `tags` in data.tags for type PostUncheckedCreateInput. Available args:
type PostUncheckedCreateInput {
id?: String
title: String
body: String
slug: String
}
It seems like the tags field on the post is missing? But I did run prisma generate and prisma migrate. Also I can successfully query tags on a post if I add them manually using Prisma Studio. What could be causing this issue?
You need to use connect for the author as well. So the following will work fine:
return context.prisma.post.create({
data: {
title: args.title,
body: args.body,
slug: `${slugify(args.title)}-${cuid()}`,
// Trying to connect a post to an already existing tag
// Without the "tags: {...} everything works
tags: {
set: [{id:"ckql6n0i40000of9yzi6d8bv5"}]
},
author: { connect: { id: getUserId(context) } },
published: true, // make it false once Edit post works.
},
})
In my case, the issue arose when I created a new field on the prisma model called uid and tried to run the command prisma migrate dev
It brought the error
Error:
⚠️ We found changes that cannot be executed:
• Step 0 Added the required column `uid` to the `Transactions` table without a default value. There are 1 rows in this table, it is not possible to execute this step.
You can use prisma migrate dev --create-only to create the migration file, and manually modify it to address the underlying issue(s).
Then run prisma migrate dev to apply it and verify it works.
I solved it by adding the #default("") to it.
model Transactions {
id Int #id #default(autoincrement())
uid String #default("")
account String
description String
category String
reference String
currency String #default("GBP")
amount String
status String
transactionDate String
createdAt String
updatedAt String
}
I have used express-graphql and there i used to do something like this.
const SubCategoryType = new ObjectType({
name: 'SubCategory',
fields: () => ({
id: { type: IDType },
name: { type: StringType },
category: {
type: CategoryType,
resolve: parentValue => getCategoryBySubCategory(parentValue.id)
},
products: {
type: List(ProductType),
resolve: parentValue => getProductsBySubCategory(parentValue.id)
}
})
});
Here I have multiple resolvers, id and name are fetched directly from the result. and the category and products have there own database operation. and so on.
Now I am working on apollo-server and I can't find a way to replicate this.
for example I have a type
type Test {
something: String
yo: String
comment: Comment
}
type Comment {
text: String
createdAt: String
author: User
}
and in my resolver I want to split it up, for example something like this
text: {
something: 'value',
yo: 'value',
comment: getComments();
}
NOTE: this is just a representation of what I need.
You can add type-specific resolvers to handle specific fields. Let's say you have the following schema (based on your example):
type Query {
getTest: Test
}
type Test {
id: Int!
something: String
yo: String
comment: Comment
}
type Comment {
id: Int!
text: String
createdAt: String
author: User
}
type User {
id: Int!
name: String
email: String
}
Let's also assume you have the following DB methods:
getTest() returns an object with fields something, yo and
commentId
getComment(id) returns an object with fields id, text, createdAt and userId
getUser(id) returns an object with fields id, name and email
Your resolver will be something like the following:
const resolver = {
// root Query resolver
Query: {
getTest: (root, args, ctx, info) => getTest()
},
// Test resolver
Test: {
// resolves field 'comment' on Test
// the 'parent' arg contains the result from the parent resolver (here, getTest on root)
comment: (parent, args, ctx, info) => getComment(parent.commentId)
},
// Comment resolver
Comment: {
// resolves field 'author' on Comment
// the 'parent' arg contains the result from the parent resolver (here, comment on Test)
author: (parent, args, ctx, info) => getUser(parent.userId)
},
}
Hope this helps.