Building GraphQL services in isolation with apollo-federation - graphql

I'm currently trying to test 1 service's graphql endpoint that will eventually be apart of an apollo-federation/gateway graphql server. This service will extend a type in an existing service in the existing federated graph.
If I want to test my service in isolation with the apollo-federation & gateway, is there a way to do that while still using #extends and #external in my graphql schema? Currently the gateway throws: UnhandledPromiseRejectionWarning: Error: Unknown type: "SomeTypeInAnotherServer", which makes sense as there's no type to extend, but can I ignore this validation somehow?

as #xadm posted in a comment, you can achieve this with https://github.com/xolvio/federation-testing-tool which solves my problem.

Your question looks like you're trying to do development, but the answer you gave looks like you're specifically doing testing. I don't know if that's where you ended up because of tooling, or if that was your actual question, but this is the answer I have for people doing development:
If you're just running one of the services, you can still make queries against it, just do so in the way ApolloGateway would. Say for example, you have a person-service and a place-service, and People can visit many places:
Person Service
type Person #key(fields: "id") {
id: ID!
name: String
}
type Query {
person(id: ID): Person # Assuming this is the only entry-point
}
Place Service
type Place {
id: ID!
name: String
}
extend type Person #key(fields: "id") {
id: ID!
placesVisited: [Place]
}
Now you can make the following query to the place-service:
query ($_representations: [_Any!]!) {
_entities(representations:$_representations) {
... on Person {
id
placesVisited {
id
name
}
}
}
}
and here is your input:
{
"_representations": [{
"__typename": "Person",
"id": "some-id-of-some-person"
}]
}

Related

Graphql - How to include schema from other types

Let us say I have the following type:
type Foo {
id: ID!
field1: String
}
Now, I wish to define another type, which includes the earlier type. Something like this:
type Bar {
...Foo,
field2: String
}
How do I achieve the above in graphql? I want to basically first create a type, and then include that type in the definition of other types so that I don't have to type all the attributes multiple times.
I am using Amplify / AWS Appsync so if there's any special directive that I could use that would also be helpful
GraphQL has the concept interfaces for this. Appsync, AWS's GraphQL implementation, supports interfaces.
[Edit:] GraphQL does not support "...spread" syntax for interfaces. Fields are defined explicitly. Spread syntax does figure in GraphQL, but in the form of Fragments, resuable units of fields for reducing repetition in queries.
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}
Amplify, which automagically creates AppSync schemas, resolvers and data sources, is apparently a more difficult story. The amplify-cli repo has an open feature request, Does the GraphQL Transformer support interfaces?. I am no Amplify expert, but a quick look at the loooong feature request comment thread suggests the answer for Amplify is "not out-of-the-box", but "maybe works in narrow circumstances or with advanced customization".

Apollo Client 3: How to implement caching on client side for graphql interfaces?

I have a case where I have an interface, which has different type implementations defined in graphql. I may not be able to share the exact code. But the case looks something like:
interface Character {
name: String!
}
type Human implements Character {
name: String!
friends: [Character]
}
type Droid implements Character {
name: String!
material: String
}
There is query which returns either Human or Droid type in response.
Response may contain something like:
{
name: 'Human_01',
friends: []
__typename: 'Human'
}
or
{
name: 'Droid_01',
material: 'Aluminium'
__typename: 'Droid'
}
I am using Apollo Client 3 on client side for querying the data and have fragments for these like:
fragment Human on Human {
friends
}
fragment Droid on Droid {
material
}
fragment Character on Character {
name
...Human
...Droid
}
I am querying for the Character data as:
character {
...Character
}
Since, this is the case of interface, and as defined in the docs for Apollo client 3, we need to use possibleTypes in order to match the fragments in such cases. For caching purpose, I have defined InMemoryCache as:
new InMemoryCache({ possibleTypes: { Character: ['Human', 'Droid'] } })
The primary key field for a Character implementation is the name field, which I need to use in order to store its value in cache.
In Apollo client 3, it is mentioned to use typePolicies for defining keyFields for a type.
So, I need to ask as to whether I should define, type policy for both type implementations, specifying keyFields as name in both cases like:
new InMemoryCache({
possibleTypes: { Character: ['Human', 'Droid'] },
typePolicies: { Human: { keyFields: ['name'] }, Droid: { keyFields: ['name'] } }
});
In my example, I have provided only 2 such type implementations but there can be n number of type implementations corresponding to Character interface. So, in that case I will need to define keyFields as name in typePolicies for all the n type implementations.
So, does there exist any better way of implementing caching wrt these types of interface implementations ?
Any help would really be appreciated. Thanks!!!
Inheritance of type and field policies is coming in the next minor version of #apollo/client, v3.3!
You can try it out now by installing #apollo/client#3.3.0-beta.5.
To stay up to date on the progress of the v3.3 release, see this pull request.

FaunaDB - How to bulk update list of entries within single graphQL mutation?

I want to bulk update list of entries with graphQL mutation in faunaDB.
The input data is list of coronavirus cases from external source. It will be updated frequently. The mutation should update existing entries if the entry name is present in collectio and create new ones if not present.
Current GRAPHQL MUTATION
mutation UpdateList($data: ListInput!) {
updateList(id: "260351229231628818", data: $data) {
title
cities {
data {
name
infected
}
}
}
}
GRAPHQL VARIABLES
{
"data": {
"title": "COVID-19",
"cities": {
"create": [
{
"id": 22,
"name": "Warsaw",
"location": {
"create": {
"lat": 52.229832,
"lng": 21.011689
}
},
"deaths": 0,
"cured": 0,
"infected": 37,
"type": "ACTIVE",
"created_timestamp": 1583671445,
"last_modified_timestamp": 1584389018
}
]
}
}
}
SCHEMA
type cityEntry {
id: Int!
name: String!
deaths: Int!
cured: Int!
infected: Int!
type: String!
created_timestamp: Int!
last_modified_timestamp: Int!
location: LatLng!
list: List
}
type LatLng {
lat: Float!
lng: Float!
}
type List {
title: String!
cities: [cityEntry] #relation
}
type Query {
items: [cityEntry!]
allCities: [cityEntry!]
cityEntriesByDeathFlag(deaths: Int!): [cityEntry!]
cityEntriesByCuredFlag(cured: Int!): [cityEntry!]
allLists: [List!]
}
Everytime the mutation runs it creates new duplicates.
What is the best way to update the list within single mutation?
my apologies for the delay, I wasn't sure exactly what the missing information was hence why I commented first :).
The Schema
An example of a part of a schema that has arguments:
type Mutation {
register(email: String!, password: String!): Account! #resolver
login(email: String!, password: String!): String! #resolver
}
When such a schema is imported in FaunaDB there will be placeholder functions provided.
The UDF parameters
As you can see all the function does is Abort with the message that the function still has to be implemented. The implementation starts with a Lambda that takes arguments and those arguments have to match what you defined in the resolver.
Query(Lambda(['email', 'password'],
... function body ...
))
Using the arguments is done with Var, that means Var('email') or Var('password') in this case. For example, in my specific case we would use the email that was passed in to get an account by email and use the password to pass on to the Login function which will return a secret (the reason I do the select here is that the return value for a GraphQL resolver has to be a valid GraphQL result (e.g. plain JSON
Query(Lambda(['email', 'password'],
Select(
['secret'],
Login(Match(Index('accountsByEmail'), Var('email')), {
password: Var('password')
})
)
))
Calling the UDF resolver via GraphQL
Finally, how to pass parameters when calling it? That should be clear from the GraphQL playground as it will provide you with the docs and autocompletion. For example, this is what the auto-generated GraphQL docs tell me after my schema import:
Which means we can call it as follows:
mutation CallLogin {
login (
email: "<some email>"
password: "<some pword>"
)
}
Bulk updates
For bulk updates, you can also pass a list of values to the User Defined Function (UDF). Let's say we would want to group a number of accounts together in a specific team via the UI and therefore want to update multiple accounts at the same time.
The mutation in our Schema could look as follows (ID's in GraphQL are similar to Strings)
type Mutation { updateAccounts(accountRefs: [ID]): [ID]! #resolver }
We could then call the mutation by providing in the id's that we receive from FaunaDB (the string, not the Ref in case you are mixing FQL and GraphQL, if you only use GraphQL, don't worry about it).
mutation {
updateAccounts(accountRefs: ["265317328423485952", "265317336075993600"] )
}
Just like before, we will have to fill in the User Defined Function that was generated by FaunaDB. A skeleton function that just takes in the array and returns it would look like:
Query(Lambda(['arr'],
Var('arr')
))
Some people might have seen an easier syntax and would be tempted to use this:
Query(Lambda(arr => arr))
However, this currently does not work with GraphQL when passing in arrays, it's a known issue that will be fixed.
The next step is to actually loop over the array. FQL is not declarative and draws inspiration from functional languages which means you would do that just by using a 'map' or a 'foreach'
Query(Lambda(["accountArray"],
Map(Var("accountArray"),
Lambda("account", Var("account")))
))
We now loop over the list but don't do anything with it yet since we just return the account in the map's body. We will now update the account and just set a value 'teamName' on there. For that we need the Update function which takes a FaunaDB Reference. GraphQL sends us strings and not references so we need to transform these ID strings to a reference with Ref as follows:
Ref(Collection('Account'), Var("account"))
If we put it all together we can add an extra attribute to a list of accounts ids as follows:
Query(Lambda(["accountArray"],
Map(Var("accountArray"),
Lambda("account",
Do(
Update(
Ref(Collection('Account'), Var("account")),
{ data: { teamName: "Awesome live-coders" } }
),
Var("account")
)
)
)
))
At the end of the Map, we just return the ID of the account again with Var("account") in order to return something that is just plain JSON, else we would be returning FaunaDB Refs which are more than just JSON and will not be accepted by the GraphQL call.
Passing in more complex types.
Sometimes you want to pass in more complex types. Let's say we have a simple todo schema.
type Todo {
title: String!
completed: Boolean!
}
And we want to set the completed value of a list of todos with specific titles to true. We can see in the extended schema generated by FaunaDB that there is a TodoInput.
If you see that extended schema you might think, "Hey that's exactly what I need!" but you can't access it when you write your mutations since you do not have that part of the schema at creation time and therefore can't just write:
type Mutation { updateTodos(todos: [TodoInput]): Boolean! #resolver }
As it will return the following error.
However, we can just add it to the schema ourselves. Fauna will just accept that you already wrote it and not override it (make sure that you keep the required fields, else your generated 'createTodo' mutation won't work anymore).
type Todo {
title: String!
completed: Boolean!
}
input TodoInput {
title: String!
completed: Boolean!
}
type Mutation { updateTodos(todos: [TodoInput]): Boolean! #resolver }
Which means that I can now write:
mutation {
updateTodos(todos: [{title: "test", completed: true}])
}
and dive into the FQL function to do things with this input.
Or if you want to include the ID along with data you can define a new type.
input TodoUpdateInput {
id: ID!
title: String!
completed: Boolean!
}
type Mutation { updateTodos(todos: [TodoUpdateInput]): Boolean! #resolver }
Once you get the hang of it and want to learn more about FQL (that's a whole different topic) we are currently writing a series of articles along with code for which the first one appeared here: https://css-tricks.com/rethinking-twitter-as-a-serverless-app/ which is probably a good gentle introduction.

AWS-Amplify API module: how to make GraphQL fields unique?

AWS-Amplify provides a couple of directives to build an GraphQL-API. But I haven't found out how to ensure uniqueness for fields.
I want to do something like in GraphCool:
type Tag #model #searchable {
id: ID!
label: String! #isUnique
}
This is an AWS-Amplify specific question. It's not about how to do this with generic GraphQL. It's very specifically about how to do this with AWS-Amplify's API module. (https://aws-amplify.github.io/docs/js/api)
Hey thanks for the question. This is not yet possible by default using the amplify-cli but you could do this yourself using pipeline resolvers and an extra index on your DynamoDB table. The steps to do this are as follows:
Create a GSI on the table where the label is the HASH KEY.
Create a pipeline resolver on the Mutation.createTag field in your schema. You can turn off the auto-generated Mutation.createTag mutation by changing your #model definition to #model(mutations: { update: "updateTag", delete: "deleteTag" }).
Create a function named LookupLabel that issues a Query against the new GSI where the label = $ctx.args.input.label. If this returns a value, throw an error with $util.error("Label is not unique"). If it returns no values then continue.
Create a function named CreateTag that issues a PutItem against the Tag table.
Add those two functions in order to your pipeline resolver.
You can read more about pipeline resolvers here https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html.
As of writing amplify does not yet support custom & pipeline resolvers but you can read more about the feature here https://github.com/aws-amplify/amplify-cli/issues/574 as it will be supported in the future. For now you can add the resolver manually in the AWS AppSync console or via your own CloudFormation template that targets the id of the API created by Amplify. It would also be helpful if you create an issue here (https://github.com/aws-amplify/amplify-cli/issues) and tag this as a feature request because it would be possible to automate this with an #unique directive but this would need to be planned.
Thanks
Update: now you can use #primarykey and #index annotations:
https://docs.amplify.aws/cli/migration/transformer-migration/#what-is-changing
basic:
profile #model {
name
email #primaryKey - has to be unique
other
}
so if you needed something like:
profile #model {
name
email: String! #hasOne
other
}
email #model {
email: String! #primaryKey
}
if you are on an older version see below
I will eventually be testing this out to see if this works but you might be able to do something like rename the id to a string!
so...
type Tag #model #key["id"] {
id: String!
}
or:
type Customer #model #key(fields: ["email"]) {
email: String!
username: String
}
this second one is taken directly from the docs: https://docs.amplify.aws/cli/graphql-transformer/key#designing-data-models-using-key
The docs were updated recently so hopefully they are easier for everyone to understand.
If you need a more advanced workflow with allot of keys, and stuff like that then you just have to separate things out and make more types for example:
type Customer #model {
id: String!
email: Email! #hasOne
username: String
}
type email #model #key(fields: ["email"]) {
email: String!
}

GraphQL - Is it possible to set a variable with a result for a mutation

I want to do 2 creations in my GraphQL query. (I know my query structure is not correct, but it's to illustrate my question)
mutation {
affiliateCreate(company: "test mutation") {
$id: id,
affiliateUserCreate(affiliate_id: $id, name: "test name") {
id,
name
},
company
}
}
I want my first id result to be in variable who i pass to the second creation call? I'm very new to GraphQL and i was wondering if it's possible.
Is there any other way possible to do such thing? Or i must do 2 mutation call? The first with affiliateCreate and in it's fallback the second one?
Thank you
What you want to do is not supported by GraphQL. In the Graphcool APIs we approach this kind of situation with what we call nested mutations. I've also heard it being referred to as complex mutations.
A nested create mutation is characterized by a nested input object argument. If you add an input object author to the affiliateCreate mutation, you could use it like that:
mutation createAffiliateAndUser {
affiliateCreate(
company: "test company"
author: {
name: "test user"
}
) {
id
}
}
This would create an affiliate, a user and then link the two together. Similarily, if you add an input object affiliates to the userCreate mutation, it could look like this:
mutation createUserAndAffiliates {
userCreate(
name: "test user"
affiliates: [{
company: "first company"
}, {
company: "second company"
}]
) {
id
}
}

Resources