I have an AuthGuard which reads the JWT Token in the request header; and based on that retrieves as well all the information about the user, injecting this information in the context.
I'm following the schema-first approach in NestJS, so I have defined my Mutation and three different arguments as follows:
type Mutation {
myMutation(
arg1: String!
arg2: String!
arg3: String!
): Boolean!
}
This, as I have the GraphQLDefinitionsFactory configured to .outputAs: 'interface', generates the following interface:
export interface IMutation {
myMutation(
arg1: string,
arg2: string,
arg3: string
): boolean | Promise<boolean>;
}
Until here, everything is as expected.
My problem is, that now I would like to perform an specific operation in the Resolver which requires some information from request context, and I don't know how to define that in the GraphQLDefinitionsFactory, or in the GraphQL schema, so I can inject it using the GraphQL argument decorators, but that it doesn't appear in the schema for the user as an argument to pass to the mutation.
Basically in the implementation of the Resolver I want to do this:
#Resolver('Default')
#UseGuards(MyJwtGuard)
export class DefaultResolver implements IMutation {
#Mutation()
async myMutation(
#Context() ctx: Context,
#Args('arg1') arg1: string,
#Args('arg2') arg2: string,
#Args('arg3') arg3: string,
): Promise<boolean> {
...
}
}
But I don't know how to get the ctx in the interface without modifying the GraphQL schema (or how to define it in the schema, but that it is "invisible" to the user, as it's going to be injected).
Related
I have 3 services, which are technically completely separate from each other:
User
Channel
UserChannelMembership
Each service defines its own subgraph. User does not refer Channel, Channel does not refer User. The only service which refers both is UserChannelMembership. User <-> Channel is M:M; UserChannelMembership is an actual service, which stores this relations data.
I use Apollo Federation to generate supergraph. Schema generated is good and I am able to obtain all I need except for the User/Channel fields I extend in the UserChannelMembership service [clarification: schema has the fields, the data is null].
Technically #ResolveField() functions are never called.
I tried to put a test #Query inside the same class, and it works. So the resolver file itself is fine. It's either something I don't understand or I miss or a bug (less likely).
Schema files and the resolver code are below.
The question itself is the following: I need UserChannelMembership service to extend the base User and Channel GraphQL types (defined initially in User and Channel services) and provide usersPerChannel and channelsPerUser fields, which will deliver the data provided by corresponding functions in UserChannelMembership service.
user.types.graphql (Defined in User service)
type User #key (fields: "id"){
id: ID!
username: String!
}
channel.types.graphql (Defined in Channel service)
type Channel #key (fields: "id"){
id: ID!
channelname: String!
}
user-channel-members.graphql (Defined in UserChannelMembership service)
extend type User #key(fields: "id") {
id: ID! #external
channelsPerUser: [Channel]
}
extend type Channel #key(fields: "id") {
id: ID! #external
usersPerChannel: [User]
}
user-channel-members.resolver.ts
#Resolver('User')
// also tried:
// #Resolver('UserChannelMembers')
export class UserChannelMembersResolver {
// this query works if I define type Query
// #Query('userChannelMembers')
// findAll() {...removed...}
//
// this never got called
#ResolveField()
// also tried:
// #ResolveField('channelsPerUser')
findChannelsPerUser(#Parent() user: any) {
...never called...
}
}
Final answer is the following.
Doesn't work:
export class UserChannelMembersResolver {
#Resolver('User')
#ResolveField('channelsPerUser')
async channelsPerUser(#Parent() user: any) {...}
#Resolver('Channel')
#ResolveField('usersPerChannel')
async usersPerChannel(#Parent() channel: any) {...}
}
Works:
(first file)
#Resolver('User')
export class UserResolver {
#ResolveField('channelsPerUser')
async channelsPerUser(#Parent() user: any) {...}
}
(second file)
#Resolver('Channel')
export class ChannelResolver {
#ResolveField('usersPerChannel')
async usersPerChannel(#Parent() channel: any) {...}
}
Then import both as a provider in a module and that's it.
Imagine the following (simplified) GraphQL schema:
type User {
username: String
email: String
}
type Query {
user(username: String, email: String): User
}
If I would only want to allow querying user by giving a username, I would of course change it to user(username: String!), making the username required using the exclamation mark. Same thing vice versa with the email.
Is it possible though to have a GraphQL native solution where I validate for the existence of only either one (logical XOR) or at least one (logical OR) of the two input parameters?
Of course I could do it in the query resolver manually, but a #constraint directive like it is being used in Apollo GraphQL spreading across variables would be nice.
Directly ... not possible:
check fields existence [within args] on resolver;
use #constraint to check each field shape;
Indirectly:
you can try to use union of input types:
.
type UserNameInput {
username: String!
}
type UserEmailInput {
email: String!
}
type UserInput = UserNameInput | UserEmailInput
type Query {
user(input: UserInput): User
}
If I have a mutation with 2 fields:
type Mutation {
createSimulation(
name: String
simulators: [AvailableSimulators!]!
timeToLiveInMS: Int
): Simulation!
create(
simulationID: ID!
simulator: AvailableSimulators!
type: String!
attributes: KeyValuePair
): CreateResult!
}
When I run the mutation in the graphql applo server playground, I need a value from the return of createSimulation in a call to create:
Can I somehow assign a variable that I can use in create?
Not part of the GraphQL standard. Should handle this in the resolver in the backend. It will depend on the technology used, but in most of the technologies you can use results from previous resolvers or call resolvers manually.
No, you should send 2 requests to handle it. The client-side should request createSimulation first to get a response then request another request to create mutation with that UUID.
I am trying to modify the GraphQL schema of the users-permissions plugin.
I want to change:
type UsersPermissionsLoginPayload {
jwt: String!
user: UsersPermissionsMe!
}
to:
type UsersPermissionsLoginPayload {
isAuthenticated: Boolean!
user: UsersPermissionsMe!
}
But I get many errors when I create ./extensions/users-permissions/config/schema.graphql.js:
...
There can be only one type named "UsersPermissionsLoginInput".
Field "UsersPermissionsLoginInput.identifier" can only be defined once.
Field "UsersPermissionsLoginInput.password" can only be defined once.
Field "UsersPermissionsLoginInput.provider" can only be defined once.
There can be only one type named "UsersPermissionsLoginPayload".
Field "UsersPermissionsLoginPayload.jwt" can only be defined once.
Field "UsersPermissionsLoginPayload.user" can only be defined once.
...
What is the correct way to customize/extend the schema?
Edit:
I see the types can simply be extended like so:
module.exports = {
definition: `
extend type UsersPermissionsLoginPayload {
isAuthenticated: Boolean!
}
`
}
that adds the isAuthenticated field to the UsersPermissionsLoginPayload type but does not remove the jwt field.
Is there no way to override the typedefs and resolvers? Do I need to implement my custom functionality using new types and new unique resolvers?
I am using Apollo GraphQL server and directives.
Here is my simple schema. Notice the directive on the token field, User type.
const typeDefs = `
directive #allow(service: String) on FIELD_DEFINITION
type User {
email: String!
pass: String!
... other fields here
token: String #allow(service: "login")
}
type Mutation {
login(email: String!, pass: String!): User
}`;
I would like to return the token field only if the login has been called. Otherwise, I would like to return the User object without the token field, all I could find is throwing an Exception or returning the null in the "token" field.
class SkipDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field, details) {
const { resolve = defaultFieldResolver } = field;
field.resolve = async function (...args) {
// If called in context different from "login"
// Here I would like to just "delete" the "token" field
else {
const result = await resolve.apply(this, args);
return result;
}
};
}
}
Ideas?
If a field is requested, it should be returned with either a value matching the field's type or else null. To do otherwise would break the spec.
There is no way you can modify this behavior through a schema directive. A field definition directive can only change runtime behavior by modifying the field's resolver. However, by the time the resolver is called, the selection set has already been determined so it's too late to modify it. Returning null or throwing an error are pretty much the only two options.
You might be able to implement some kind of workaround through either the formatResponse option or a custom plugin. However, because this behavior would break the spec, there's no telling if it wouldn't cause issues with client libraries or other tools.