Resolve two GraphQL schema fields using one endpoint - javascript

There's a situation where there are two possible types to fill data property. I have made a union type for that (ComponentItem) to determine which field needs to be returned. The first schema (ComponentItem1) should just be a hardcoded list but the second one (ComponentItem2) is more dynamic where it gets a searchTerm from query and it actually calls an endpoint to fill the list and also has hasNextPage property.
Here are the schemas I made:
type Component {
id
title
data: ComponentItem
}
union ComponentItem = ComponentItem1 | ComponentItem2
type ComponentItem1 {
list: [List!]!
}
type ComponentItem2 {
hasNextPage: Boolean;
list: [List!]!
}
In the resolver I'm resolving the union type in order to generate proper __typename:
const resolver: {
ComponentItem: {
__resolveType(object) {
if(object.searchTerm){
return "ComponentItem2"
}
return "ComponentItem1"
},
},
}
What I'm currently doing is to resolve the list and hasNextPage individually but in this scenario I'm sending the request to the same endpoint twice.
const resolver = {
...resolver,
ComponentItem2: {
list: async (root, __, context) => {
const result = await fetch('search-endpoint')
return result?.items || []
}
hasNextPage: async (root, __, context) => {
const result = await fetch('search-endpoint')
return result?.hasNextPage || false
}
}
}
My question is how it is possible to share that result in another field resolver (Other than using "context"). Or if there's any better way to handle this situation let me know.

Related

Is deprecating all single record operations with multiple record operations a good idea?

TL;DR
Look at code examples and tell me which one would you choose.
The entire thing
I am in progress of making an GraphQL API. Amid doing that, I have realized, that I'd love to mass insert data, but I could not, because the only method available to me was the one used for single item insertion. This revealed a problem. If someone was to insert higher volumes of data, they would either have to send single item requests one by one, or I have to add another mutation that allows array of items as an input. As the first option is for apparent reasons not an option. This meant adding another method, but this got me thinking, why should I even be keeping single operations methods (get/update/add single item) in the first place, as they do exactly the same, just with the limitation of sending max 1 item, are harder to maintain, and pollute API documentation.
My question is, would you go ahead and replace all those single operation methods with a multiple operation method?
To me it's a no-brainer and the answer is yes. The reason why am I asking is, that vast majority of APIs does have the above described structure of using one endpoint for responding with singular item and one for responding with multiple items and I bet there is a reason for it.
Code example of what I mean:
Deprecating this:
/*=============================================
Types
=============================================*/
interface UpdateDog {
_id: string,
name: string
}
interface AddDog {
name: string
}
/*=============================================
Main
=============================================*/
export const DogModule = {
resolvers: {
Query: {
dog: async (parents: any, args: { _id: string }) => {
// Returns one of only one ID is passed in array
},
dogs: async (parents: any, args: { _ids: string[] }) => {
// Returns dogs
},
allDogs: async () => {
// Returns all dogs
}
},
Mutation: {
addDog: async (parents: any, args: { input: AddDog}, context: GraphqlContext) => {
// Add dog to the database
},
addDogs: async (parents: any, args: { input: AddDog[] }, context: GraphqlContext) => {
// Add dogs to the database
},
updateDog: async (parents: any, args: { input: UpdateDog }, context: GraphqlContext) => {
// Update dog
},
updateDogs: async (parents: any, args: { input: UpdateDog[] }, context: GraphqlContext) => {
// Update dogs
}
...
}
}
}
export default DogModule
In favor of this:
/*=============================================
Types
=============================================*/
interface UpdateDog {
_id: string,
name: string
}
interface AddDog {
name: string
}
/*=============================================
Main
=============================================*/
export const DogModule = {
resolvers: {
Query: {
dogs: async (parents: any, args: { _ids: string[] }) => {
// Returns one of only one ID is passed in array
// Multiple on multiple IDs
// All if not IDs are passed
}
},
Mutation: {
addDogs: async (parents: any, args: { input: AddDog[] }, context: GraphqlContext) => {
// Add them to the database
},
updateDogs: async (parents: any, args: { input: UpdateDog[] }, context: GraphqlContext) => {
// Update dogs
},
...
}
}
}
export default DogModule
Additional info:
Once done API will be a publicly accessible with API key. Meaning that I will be able to limit the amount of changes coming from one source.
I am aware that I cannot replace every single operation method multi operation method. (such as logins, or signup)

Passing query param to api in Angular

I have a child component, in which, after button press it pass a form field to a father component. I have to pass this fields as query Params to an API GET /valuation/ that require more or less 20 optional parameters, in which the name differs from Input fields.
I created a temporary object res inside the father function accept in which I iterate the field . Still I can't figure how to pass this parameter inside the object res, using multiple if is dirty code.
for example
Function in father component
accept(form:any){
const req: any = {};
if(form.name)
req.user = form.name
if(form.address)
req.clusterAddress = form.address
... and so on x 20 times
this.requestService.getRequest(req).subscribe(
(response) => {
this.getRequest(response);
}, (error) => {
console.error(error);
}
}
As you can see this is not a viable option, what I can use to take dynamically the query param names?
You can use a map to store information about which property from the form maps to which property on your res object and then iterate through the keys, check for the presence of the value and assign:
const formToResMap: { [key: string]: string } = {
name: 'name',
address: 'clusterAddress',
// ...
};
accept(form: any) {
const req = Object.keys(formToResMap).reduce((acc, key) => {
if (form[key]) {
acc[formToResMap[key]] = form[key];
}
return acc;
}, {});
console.log(req);
}

Typescript: handle data from service

I would like to display users' data, but I have a problem with displaying the correct profile pictures. If a user does not have a profile picture, I get "undefined" in the console. If one user has a profile picture, then the same picture will be displayed for all the users. I need help finding the error in my code.
export interface UserData {
id: number,
name: string
}
export interface UserWithImage extends UserData{
image?: string
}
export interface UserProfileImage {
id: number,
url: string
}
After I get the necessary data from the services, I try to push the profile image into the userData.
user-data.ts
userData: UserWithImage[];
userProfiles: UserProfileImage[];
userProfileImage: UserProfileImage[];
getUserData() {
this.userData = this.userService.getData();
this.userProfiles = await this.imagesService.getProfilePicture(this.userData?.map(u => u.id));
this.userProfileImage = this.userProfiles.filter(u => u.url);
this.userData?.forEach((data, i) => {
data.image = this.userProfileImage[i].url;
});
}
images.service.ts
public async getProfilePicture(ids: number[]): Promise<UserProfileImage[]> {
const toLoad = ids.filter(id => !this.userProfileImages.find(up => up.id === id)).map(u => u);
if (toLoad || toLoad.length) {
const loaded = (await firstValueFrom(this.httpClient.post<UserProfile[]>
(this.imgService.getServiceUrl(customersScope, `${basePath}settings/users/profil`), JSON.stringify(toLoad), {headers}))).map(sp => {
return {
id: sp.userId,
url: sp.profilepicId ? this.imgService.getServiceUrl(customersScope,
`${basePath}web/download/profilepic/${sp.profilepicId}/users/${sp.userId}`, true) : ''
} as UserProfileImage
});
this.userProfileImages = [...loaded, ...this.userProfileImages];
}
return this.userProfileImages;
}
user-data.html
<div ngFor="data of userData">
<etc-profil [name]="data.name" [image]="data.image"></etc-profil>
</div>
this.userData = this.userService.getData();
Is this an async function (i.e. are you missing an await)?
this.userProfiles = await this.imagesService.getProfilePicture(this.userData?.map(u => u.id));
This line would fail is this.userData is a promise. this.userProfiles would be undefined due to the use of optional chaining (?.)
this.userProfileImage = this.userProfiles.filter(u => u.url);
This line appears to do nothing, the filter predicate is saying that anything with a url property that is not null or undefined is included, but the interface says that url is non-optional and doesn't support null or undefined.
this.userData?.forEach((data, i) => {
data.image = this.userProfileImage[i].url;
});
Again, if this.userData is a promise, this will do nothing due to the optional chaining.
If it does run, its assumed that there is a one-to-one relationship between users and profile images (index count and order must be the same).
I didn't consider the implementation of getProfilePicture because I think these issues need resolving first.

Graphql mutation return value [duplicate]

Hi I am trying to learn GraphQL language. I have below snippet of code.
// Welcome to Launchpad!
// Log in to edit and save pads, run queries in GraphiQL on the right.
// Click "Download" above to get a zip with a standalone Node.js server.
// See docs and examples at https://github.com/apollographql/awesome-launchpad
// graphql-tools combines a schema string with resolvers.
import { makeExecutableSchema } from 'graphql-tools';
// Construct a schema, using GraphQL schema language
const typeDefs = `
type User {
name: String!
age: Int!
}
type Query {
me: User
}
`;
const user = { name: 'Williams', age: 26};
// Provide resolver functions for your schema fields
const resolvers = {
Query: {
me: (root, args, context) => {
return user;
},
},
};
// Required: Export the GraphQL.js schema object as "schema"
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
// Optional: Export a function to get context from the request. It accepts two
// parameters - headers (lowercased http headers) and secrets (secrets defined
// in secrets section). It must return an object (or a promise resolving to it).
export function context(headers, secrets) {
return {
headers,
secrets,
};
};
// Optional: Export a root value to be passed during execution
// export const rootValue = {};
// Optional: Export a root function, that returns root to be passed
// during execution, accepting headers and secrets. It can return a
// promise. rootFunction takes precedence over rootValue.
// export function rootFunction(headers, secrets) {
// return {
// headers,
// secrets,
// };
// };
Request:
{
me
}
Response:
{
"errors": [
{
"message": "Field \"me\" of type \"User\" must have a selection of subfields. Did you mean \"me { ... }\"?",
"locations": [
{
"line": 4,
"column": 3
}
]
}
]
}
Does anyone know what I am doing wrong ? How to fix it ?
From the docs:
A GraphQL object type has a name and fields, but at some point those
fields have to resolve to some concrete data. That's where the scalar
types come in: they represent the leaves of the query.
GraphQL requires that you construct your queries in a way that only returns concrete data. Each field has to ultimately resolve to one or more scalars (or enums). That means you cannot just request a field that resolves to a type without also indicating which fields of that type you want to get back.
That's what the error message you received is telling you -- you requested a User type, but you didn't tell GraphQL at least one field to get back from that type.
To fix it, just change your request to include name like this:
{
me {
name
}
}
... or age. Or both. You cannot, however, request a specific type and expect GraphQL to provide all the fields for it -- you will always have to provide a selection (one or more) of fields for that type.

TypeScript - Send optional field on POST only when there is value

I am using Formik, in my React application. I have a simple form with 3 fields. I am doing 2 operations with that form. Add/Edit Resources.
My Problem is that one field is optional. Meaning I should never send it, if its value is null. Currently, I send an empty string which is wrong.
I am using TS-React-Formik, and here is my code for the handleSubmit method:
interface IValues extends FormikValues {
name: string;
owner?: string;
groups: string[];
}
interface CreateAndEditProps {
doSubmit(service: object, values: object): AxiosResponse<string>;
onSave(values: IValues): void;
}
handleSubmit = (values: FormikValues, formikActions:FormikActions<IValues>) => {
const { doSubmit, onSave, isEditMode } = this.props;
const { setSubmitting } = formikActions;
const payload: IValues = {
name: values.name,
groups: values.groups,
owner: values.owner
};
const submitAction = isEditMode ? update : create;
return doSubmit(submitAction, payload)
.then(() => {
setSubmitting(false);
onSave(payload);
})
.catch(() => {
setSubmitting(false);
});
};
I thought a simple if statement would work, and while it does, I do not like it at all. Let me give you an example of why. If I add 2 more optional fields, as I am about to do, in a similar form, I do not want to do several if statements to achieve that.
If you could think of a more elegant and DRY way of doing it, It would be amazing. Thank you for your time.
Look at the removeEmptyKeys() below.
It takes in an Object and removes the keys that have empty string.It mutates the original Object, please change it accordingly if you expect a diff behaviour.
In your code after defining payload, I would simply call this method , removeEmptyKeys(payload)
Also it will resolve your if else problem.
removeEmptyKeys = (item)=>{
Object.keys(item).map((key)=>{
if(payload[key]===""){
delete payload[key]}
})}
var payload = {
one : "one",
two : "",
three : "three"
}
removeEmptyKeys(payload)
Please mark it as resolved if you find this useful.
For your code :
const removeEmptyKeys = (values: IValues): any => {
Object.keys(values).map((key) => {
if (payload && payload[key] === "")
{ delete payload[key] } })
return values;
}

Categories

Resources