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

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!
}

Related

Restrict lambda resolver to owner for GraphQL API using Amplify

In my schema.graphql file I have the following:
type Floorplan #model #auth(rules: [
{ allow: private, operations: [read], provider: userPools },
{ allow: owner, provider: userPools }
]) #aws_cognito_user_pools {
id: ID! #primaryKey
name: String!
bedrooms: Int!
}
input FloorplanInput {
id: ID!
name: String!
bedrooms: Int!
}
type Mutation {
floorplanLambda(input: FloorplanInput): Floorplan
#function(name: "floorplanLambda-${env}")
#aws_cognito_user_pools
}
I created the lambda function to perform custom validation before updating.
The problem is that any authenticated user can update other users' floor plans. I thought adding #aws_cognito_user_pools would resolve this, but it doesn't.
Question: What do I need to add to lock down the floorplanLambda function so that it can only be successfully called by the owner of the Floorplan model?
When calling floorplanLambda I receive the error: "Not Authorized to access floorplanLambda on type Floorplan". I'm making the call with authMode AMAZON_COGNITO_USER_POOLS.
For some more context, I followed this tutorial to create the custom mutation lambda function: https://www.theclouddeveloper.io/use-lambda-resolvers-in-your-graph-ql-api-with-aws-amplify
So according to the response to my GitHub issue, this workflow is not currently supported. You can follow it here: https://github.com/aws-amplify/amplify-category-api/issues/528#issuecomment-1157894170
A workaround was provided by:
...setting the auth rule to private and then perform
validation in the lambda function...
I was thinking to create a custom auth function and then chain it in front of my custom mutation. Not sure if that will work but I'll report back once I've made some progress.

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".

How to update single data when using Fauna and GraphQL

I have some problems when using Fauna and GraphQL
When I use GraphQL to update data, all parameters are required
For example
// Schema
type User {
username: String!
password: String!
phone: String!
}
// Mutation
mutation {
updateUser(id: "xxxxx", {"phone": "+886 110220330"}){
username,
phone
}
}
// Error
But I only want to update the phone data this time, how can I skip other parameters?
Although it can solve the problem when I delete the Not null of all attributes in User type,
But it doesn't look like a good way and safe
// after
// Schema
type User {
username: String
password: String
phone: String
}
There is a preview feature that you can use by adding a header to your GraphQL query.
X-Schema-Preview: partial-update-mutation
Once you do this, you will have access to a new mutation called partialUpdateUser which will make all of the input values optional.
See the docs for more information: https://docs.fauna.com/fauna/current/api/graphql/previews/

Building GraphQL services in isolation with apollo-federation

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"
}]
}

How to "upsert" an array/nested field when issuing update mutation in Graphcool / GraphQL?

I have a Post type than has a tag field which can be associated with many Tag entries (man-to-many relationship). The issue I am running into is when updating a Post - I need to both create and associate new Tag's for tags that do not yet exist, while preserving the existing Post->Tag relationships. Basically, what I am looking for is something akin to an upsert when issuing a mutation update on a nested one-to-many field.
Here is my schema:
type Post #model {
createdAt: DateTime!
createdBy: User #relation(name: "PostsByUser")
description: String #defaultValue(value: "''")
id: ID! #isUnique
tags: [Tag!]! #relation(name: "TagsOfPost")
...
}
type Tag #model {
id: ID! #isUnique
tag: String!
createdBy: User #relation(name: "TagsByUser")
createdAt: DateTime!
posts: [Post!]! #relation(name: "TagsOfPost")
}
This mutation works to update a Post and add new tags, but overwrite all of the existing values in the Post's tag field:
mutation updatePost(
$id: ID!
$createdById: ID!
$timestamp: DateTime!
$description: String
$tags: [PosttagsTag!]!
) {
updatePost(
id: $id
createdById: $createdById
timestamp: $timestamp
description: $description
tags: $tags
) {
id
timestamp
description
tags {
id
tag
}
createdBy {
id
username
}
}
}
I came across this post by #marktani but it's not clear how to implement the combined method he outlines:
Combined
You can also use tags and tagsIds in the same mutation, this would connect the new Tutorial node to all the tags in tagsIds and connect it to the new tags in tags. This is what you want to do if you want to only allow tags with a unique text, so for a new Tutorial, there will likely be some tags that already exists, and some that need to be created.
Is it currently impossible to do this with one mutation? Would a second mutation be required after updating the post with new tags to re-establish the associations between the Post and existing Tag ids, i.e., having to repeatedly call addToTagsOfPost(tagsTagId: ID!
postsPostId: ID!)? Thanks!
Ok, so there is currently a Graphcool bug where passing in both tags and tagsIds to the mutation will create and associate newly created Tags but will not add the tagsIds associations to existing Tags. I posted an issue on Graphcool's GitHub repo and they have acknowledged it - https://github.com/graphcool/framework/issues/1288

Resources