Apollo Graphql Client - How to attach new node to existing node - node.js

am trying to persist a new node in graphql using Apollo client. This new node should attach to an existing node. Not quite sure how to go about it. Suppose I have these type definitions:
type Person{
personId: ID!
name: String!
user: User #relationship(type: "LOGS_IN_AS", direction: OUT)
}
type User{
userId: ID!
username: String!
password: String!
person: Person!
}
Now assuming I already have a person in the database as follows:
{
personId: 1,
name: "John Doe"
}
How do I mutate a corresponding User node for this Person and ensure the necessary relationship is created using Apollo's auto generated mutations? Am using a neo4j backend by the way.
Thanks in advance

Don't know how you set up neo4j database but supposedly User should have a personId to link with Person.
You should probably defined a seperate schema type called UpsertUserInput or something like below for the mutation so neo4j can use it to link with the person
type UpsertUserInput{
userId: ID!
personId: ID!
username: String!
password: String!
}
the Appollo schema is not responsible for defining where data comes from or how it's stored. It is entirely implementation-agnostic.
https://www.apollographql.com/docs/apollo-server/schema/schema/#the-schema-definition-language

I see there is a solution, just needed to look a little deeper into the auto-generated mutations in the studio. Solution along the lines of...
useMutation(CREATE_USER, {
variables:{
input:{
userName: "johndoe#example.com",
password: "jdoespassword",
person:{
connect:{
where:{
node:{
personId: 1
}
}
}
}
}
}
})

Related

How can I add additional properties to an entity in NestJS?

I am using NestJS to build an api, and everything is working great so far!
I have a users table and a corresponding users.entity.ts file:
#Entity({ name: 'users' })
export class User extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({
unique: true,
})
email: string;
...
I am using AWS Cognito as an authentication provider - everything is working great there too. I am trying not to duplicate things, so I'd like to keep given_name and family_name values with Cognito and not have redundant fields on my postgres table.
I can get the user without problems, but I'm not sure how to "combine" them into my user entity.
For example,
// users.service.ts
const user = await this.usersRepository.findOne({
where: {
id: userId,
},
});
// id: dbc92...
// email: example#email.com
const cognitoUser = await this.cognitoService.adminGetUser(user.id);
// id: dbc92...
// email: example#email.com
// given_name: "Tony"
// family_name: "Stark"
return user;
I don't have a given_name property on my user entity—I'm not sure if I need one?
Ideally, I think what I'm trying to do is something like,
user.givenName = cognitoUser.given_name
user.familyName = cognitoUser.family_name
...
return user
But since my user.entity file doesn't have a givenName property, it's not to happy to do that.
I know I can "merge" the two objects using a spread operator, but I'd like to return the actual user entity if possible. How can I pull from two sources and return the proper entity?
As Mohammad said, if what you are trying to accomplish is:
user.givenName = cognitoUser.given_name
user.familyName = cognitoUser.family_name
then you can do this:
#Entity({ name: 'users' })
export class User extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({
unique: true,
})
email: string;
given_name: string;
family_name: string;
}
and be able to handle the data just from User but without replicating data in the database with unnecessary columns.
You can add your desired properties without adding #Column decorator. It will not make column in your table but still you have them in your entity.
From the official docs: https://github.com/typeorm/typeorm/blob/master/docs/listeners-and-subscribers.md#afterload
You can define a method with any name in entity and mark it with #AfterLoad and TypeORM will call it each time the entity is loaded using QueryBuilder or repository/manager find methods.
TypeORM Entity with custom properties / virtual fields:
// tested with `typeorm#0.3.7`
#Entity()
export class Post {
#AfterLoad()
updateCounters() {
if (this.likesCount === undefined) this.likesCount = 0
}
}
I don't think the chosen answer is quite correct because an exception for missing columns is thrown when an insert or update operation is attempted:
https://github.com/typeorm/typeorm/blob/cdabaa30287d357c0ae994209e573f97f92dad22/src/metadata/EntityMetadata.ts#L806-L814
If you are interested in reading more about the issue, is sounds like a feature selectAndMap is soon coming to typeorm to support this in a way that is more intuitive https://github.com/typeorm/typeorm/issues/1822#issuecomment-376069476

Prisma js return join fields

I'm trying to implement batching and caching with the Facebook DataLoader. Let's say I have the following schema (taken from here):
type Post {
id: ID! #id
title: String!
published: Boolean! #default(value: false)
author: User
comments: [Comment!]!
}
type User {
id: ID! #id
name: String
posts: [Post!]!
comments: [Comment!]!
}
type Comment {
id: ID! #id
text: String!
post: Post!
writtenBy: User!
}
I am working on a tricky resolver which lists all comments created by the same user under the same post for a given comment. To retrieve a single entry I would go like:
const fetchCommentsByUserForSamePost = async (commentId: string, userId: string): Promise<Comment[]> => {
const comments = await this.prisma.comment.findOne({ where: { id: commentId } })
.post()
.comments({
where: {
writtenBy: { id: userId }
}
})
return comments;
}
This works well for a single query, but I would like to batch the queries. In raw SQL I'd return commentId and userId in every row, such that I can group the results by these fields. But I can't find a way to return the original commentId with Prisma to generalize the query to work with a list of commentId - userId pairs.
Is there a way to accomplish this with Prisma, or I am missing something? I am aware that making two requests could solve this, but that would result in an involved logic, plus I'd rather avoid making two DB roundtrips.
Prisma 2.0 already has a Dataloader built in exactly for this purpose. This means your resolvers might do multiple calls to findOne but those will be batched into one big SQL query under the hood. So there should be no need for you to implement this optimization on your own.

NodeJS/GraphQL: Trying to use a custom DateTime scalar and I'm getting an error

I'm following a tutorial where the teacher is using a String type for his createdAt fields and suggested that if we wanted, to use a custom scalar type for a stronger typed DateTime field so I'm trying to do just that.
I'm getting the following error: Error: Unknown type "GraphQLDateTime".
Here is the offending code:
const { gql } = require('apollo-server')
const { GraphQLDateTime } = require('graphql-iso-date')
module.exports = gql`
type Post {
id: ID!
username: String!
body: String!
createdAt: GraphQLDateTime!
}
type User {
id: ID!
email: String!
token: String!
username: String!
createdAt: GraphQLDateTime!
}
input RegisterInput {
username: String!
password: String!
confirmPassword: String!
email: String!
}
type Query {
getPosts: [Post]
getPost(postId: ID!): Post
}
type Mutation {
register(registerInput: RegisterInput): User
login(username: String!, password: String!): User!
createPost(body: String!): Post!
deletePost(postId: ID!): String!
}
`
I have added the graphql-iso-date library and VSCode's intellisense is picking that up so I know that's not the issue. It's also indicating that GraphQLDateTime is not being used anywhere in the file even though I'm referencing it.
I know this is probably an easy fix but I'm still new to NodeJS and GraphQL in the context of NodeJS. Any idea what I'm doing wrong? Also is there another DateTime scalar that might be more preferable (Best practices are always a good idea.) Thanks!
There's two steps to adding a custom scalar using apollo-server or graphql-tools:
Add the scalar definition to your type definitions:
scalar DateTime
Add the actual GraphQLScalar to your resolver map:
const { GraphQLDateTime } = require('graphql-iso-date')
const resolvers = {
/* your other resolvers */
DateTime: GraphQLDateTime,
}
Note that the key in the resolver map (here I used DateTime) has to match whatever you used as the scalar name in Step 1. This will also be the name you use in your type definitions. The name itself is arbitrary, but it needs to match.
See the docs for more details.

Type as property resolves as null with graphql-yoga and prisma

I’m using a prisma server and graphql-yoga (graphql server on top of node.js) and I’m working for the first time with a type that has an other type as property. It’s one way so no relation I believe. In my case, a Product can be in a FavoriteList (so belong?) but a FavoriteList doesn’t have a relation the other way around.
type Product {
id: ID!
name: String!
releaseDate: DateTime!
brand: Brand!
createdAt: DateTime!
image: Image!
barcode: String
}
type FavoriteList {
id: ID!
products: [Product]
}
On my prisma admin I can add a record to FavoriteList with connecting [Product] and in the playground I can query for that by using the FavoriteLists query. But when I create a query (and resolver) for my graphql-yoga server, products are resolving as null. The query I’m using:
# prisma server & graphql server
query favoriteList {
favoriteLists {
id
products {
id
name
}
}
}
My setup for the graphql-server
type Query {
// ... other queries
favoriteLists: [FavoriteList]
}
// resovler in Query.js
async favoriteLists(root, args, context, info) {
return context.prisma.favoriteLists({}, info)
},
So having the same query, I am wondering if I have to do something else in my graphql-server? From what I can see the Product is not resolving on the FavoriteList type. Thanks in advance.
I would disagree and say you should have a relationship between Product and FavouriteList. When you break it down, one Product "belongs to" one or many FavouriteList and one FavouriteList "has" many Product. So I'd define your datamodel like this:
type Product {
id: ID!
name: String!
releaseDate: DateTime!
brand: Brand!
createdAt: DateTime!
image: Image!
barcode: String
favouriteLists: [FavouriteList!]! #relation(name: "ProductToFavouriteList")
}
type FavoriteList {
id: ID!
products: [Product!]! #relation(name: "ProductToFavouriteList")
}
Your query and resolvers look ok except maybe your resolver should return context.prisma.query.favoriteLists({}, info)?

GraphQL: How nested to make schema?

This past year I converted an application to use Graphql. Its been great so far, during the conversion I essentially ported all my services that backed my REST endpoints to back grapqhl queries and mutations. The app is working well but would like to continue to evolve my object graph.
Lets consider I have the following relationships.
User -> Team -> Boards -> Lists -> Cards -> Comments
I currently have two different nested schema: User -> team:
type User {
id: ID!
email: String!
role: String!
name: String!
resetPasswordToken: String
team: Team!
lastActiveAt: Date
}
type Team {
id: ID!
inviteToken: String!
owner: String!
name: String!
archived: Boolean!
members: [String]
}
Then I have Boards -> Lists -> Cards -> Comments
type Board {
id: ID!
name: String!
teamId: String!
lists: [List]
createdAt: Date
updatedAt: Date
}
type List {
id: ID!
name: String!
order: Int!
description: String
backgroundColor: String
cardColor: String
archived: Boolean
boardId: String!
ownerId: String!
teamId: String!
cards: [Card]
}
type Card {
id: ID!
text: String!
order: Int
groupCards: [Card]
type: String
backgroundColor: String
votes: [String]
boardId: String
listId: String
ownerId: String
teamId: String!
comments: [Comment]
createdAt: Date
updatedAt: Date
}
type Comment {
id: ID!
text: String!
archived: Boolean
boardId: String!
ownerId: String
teamId: String!
cardId: String!
createdAt: Date
updatedAt: Date
}
Which works great. But I'm curious how nested I can truly make my schema. If I added the rest to make the graph complete:
type Team {
id: ID!
inviteToken: String!
owner: String!
name: String!
archived: Boolean!
members: [String]
**boards: [Board]**
}
This would achieve a much much deeper graph. However I worried how much complicated mutations would be. Specifically for the board schema downwards I need to publish subscription updates for all actions. Which if I add a comment, publish the entire board update is incredibly inefficient. While built a subscription logic for each create/update of every nested schema seems like a ton of code to achieve something simple.
Any thoughts on what the right depth is in object graphs? With keeping in mind the every object beside a user needs to be broadcast to multiple users.
Thanks
GraphQL's purpose is to avoid a couple of queries, so I'm sure that making the nested structure is the right way. With security in mind, add some GraphQL depth limit libraries.
GraphQL style guides suggest you have all complex structures in separate Object Types ( as you have, Comment, Team, Board... ).
Then making a complex query/mutation is up to you.
I'd like you to expand this sentence
Which if I add a comment, publish the entire board update is
incredibly inefficient
I'm not sure about this as you have your id of the Card. So adding new comment will trigger mutation which will create new Comment record and update Card with the new comment.
So your structure of data on the backend will define the way you fetch it but not so much the way you mutate it.
Take a look at the GitHub GraphQL API for example:
each of the mutations is a small function for updating/creating piece of the complex tree even if they have nested structure of types on the backend.
In addition for general knowledge of what are approaches for designing the mutations, I'd suggest this article.
You can use nesting in GraphQL like
type NestedObject {
title: String
content: String
}
type MainObject {
id: ID!
myObject: [NestedObject]
}
In the above code, the type definition of NestObject gets injected into the myObject array. To understand better you can see it as:
type MainObject {
id: ID!
myobject: [
{
title: String
content: String
}
]
}
I Hope this solves your problem!

Resources