GraphQL endpoint with generated API, introspection, and mutation behavior

Hi!

I’m enjoying playing with Dgraph and it’s definitely going to make up the core data component of an application I’m currently working on. Right now though I’m stuck on a couple core issues/concepts that I need to get a better understanding of in order to move forward.

Design

I can expand on this further but this is a brief overview of the app design for context.

  • The application will be using a GraphQL API generated using gqlgen (since the backend is being built entirely in Go).
  • The generated resolvers will be implemented to point at the GraphQL endpoint on the Dgraph instance I’ll have running.
  • Requests received by the gqlgen-generated API will be parsed and then translated into requests made to the Dgraph.

Schema

The initial/basic types that the Dgraph database will hold for additional context.

type User {
	id: ID!
	email: String! @id @search(by [hash])
	password: String!
	firstName: String!
	lastName: String!
	orgs: [Membership!]! @hasInverse(field: user)
}

type Org {
	id: ID!
	name: String!
	users: [Membership!]! @hasInverse(field: org)
	items: [Item!]! @hasInverse(field: org)
}

type Membership {
	id: ID!
	user: User! @hasInverse(field: orgs)
	org: Org! @hasInverse(field: users)
	role: Role!
}

enum Role {
	ADMIN_USER
	INTERNAL_USER
	EXTERNAL_USER
}

interface Item {
	id: ID!
	org: Org! @hasInverse(field: items)
}

type Specimen implements Item {
	id: ID!
	org: Org! @hasInverse(field: items)
	parent: Specimen! @hasInverse(field: children)
	children: [Specimen!]! @hasInverse(field: parent)
	description: String @search(by [fulltext])
}

This definitely still needs to be finalized (I’m still not entirely sure that the interface I currently have is necessary or should be used the way it is).

Questions

  1. Context: Inserting the schema.graphql file will complain if I include anything other than type, enum, or interface (which appears to be Dgraph’s design intent given the generated types). gqlgen allows for separate .graphql files that hold different resources and are combined when generating an API.
    a. Question: Given this, does it make sense to have one file holding types that gets used to insert the schema into Dgraph while the other files holding the mutation, query, input, etc definitions are combined with the type file to generate the user-facing API? The mutations/queries (defined in separate files) would follow the same pattern as the Dgraph-generated ones.
  2. Context: I’m currently testing out a scaled-down version of the schema but I’m having an incredible amount of difficult understanding/seeing the available Dgraph-generated types - for example DeleteUserPayload is opaque and I can’t figure out what fields to provide in my mutation definition.
    a. Question: What is the recommended method to observe the types and fields available on them as far as introspection in Dgraph? Other GraphQL examples I’ve tried haven’t worked.
  3. Context: These are all related specifically to my schema (see above).
    a. Question: Is it safe to use the User field email as the “primary key”?
    b. Question: When deleting a Membership (which defines the relationship between User and Org) will the reference to the Membership be removed from the fields on the respective User and Org?
    c. Question: Will deleting an Org remove the associated Memberships/User field values as well as delete the Items associated?
    d. Question: What is the best way to create the User, Membership, and Org types? Given that I want to require every Membership to have a User and an Org and I’d like to have Orgs to require at least one User (and Users won’t be of much use outside of the Org).

Sorry for all of the questions - I wish I was providing something more concrete!

Best,
John

P.S. I can also share the Postman tests I’ve been running on the scaled-down schema.
P.P.S. The core of the application I’m building will eventually be open source so pretty much everything can be shared from a technical perspective.

CC: @vardhanapoorv , @michaelcompton

Hi @forstmeier

You can use an interface but since its just being implemented by a single type so you can have it as type also. Also you don’t need to specify the hasInverse directive twice for same field. The schema can be modified to something like this -

type User {
  id: ID!
  email: String! @id @search(by: [hash])
  password: String!
  firstName: String!
  lastName: String!
  orgs: [Membership!]! @hasInverse(field: user)
}
type Org {
  id: ID!
  name: String!
  users: [Membership!]! @hasInverse(field: org)
  items: [Item!]! @hasInverse(field: org)
}
type Membership {
  id: ID!
  user: User!
  org: Org!
  role: Role!
}
enum Role {
  ADMIN_USER
  INTERNAL_USER
  EXTERNAL_USER
}
type Item {
  id: ID!
  org: Org!
  parent: Item @hasInverse(field: children)
  children: [Item!] @hasInverse(field: parent)
  description: String @search(by: [fulltext])
}
  1. Yes that sounds like a decent approach, have two files. Then while forming the user-facing API you would have to merge the two. Soon custom-logic feature will be released that will allow you to call a remote endpoint(http/graphql), which you can specific in the GraphQL schema file of Dgraph.

  2. You can use tools like GraphQL playground, Insomnia to introspect the schema. The support currently in Postman for GraphQL isn’t that good.

  3. Yeah you can use email if you know that it will be unique. Yes the relationship between the entities is removed along with its references. The best way to form the types depends on your specific use case and how you will query/mutate the data in your application.

Let me know if you have any more questions.

1 Like

@vardhanapoorv Super helpful, thanks! Just a few quick follow ups:

  • I’ll circle back to dig into the introspection stuff in a bit because right now I’m drastically redesigning the schema - is there any sort of just “placeholder” field I could request on the Delete* mutations? Just for testing/validation purposes. I can also spin up the UIs you mentioned and point them at Dgraph with my updated schema (once it’s ready).
  • What is the behavior of Dgraph when one of these objects is deleted? For example, if the User object is deleted, what happens to the references to it that exist on other objects in the database?

Thanks again! This is really, really helpful.

Hi @forstmeier

Yes you can get the msg field which would have the value something as Deleted on success.
When an object is deleted, all its references on other objects are removed as well.

Let me know, if you have any more questions.

1 Like

Another quick question: any suggestions on handling multi-tenancy? I saw this discussion from a while ago and what I was thinking is on the front-end API (generated through gqlgen) I’ll have various directives for filtering query access by user permissions (so different objects can only be fetched by users with specific permission levels) but I’m curious as to the best route to prevent users from accessing each other’s Orgs or across Projects in the contrived example below.

type Org {
  Name: String!
  Projects: [Project!]!
}

type Project {
  Name: String!
  Items: [Item!]!
}

type Item {
  Name: String!
}

Where I could have multiple Orgs in the system and not want users belonging to one to be able to access Projects/Items in another Org. And also not have users who are invited to specific Projects within an org to not be able to see other Projects within the same org. Maybe this is a case for using directives again but I’m not sure if I want to attach Org/Projects to every other object within the schema? Just thinking out loud.

Hi @forstmeier
We have been working on providing this through Auth directive. This should be merged in master in the upcoming weeks. This will allow you to implement permissions easily that you can just define in the GraphQL schema.

Let me know if you have any more questions.

@vardhanapoorv Is there an open issue/pull request to track progress on that directive? I didn’t see one when I checked but I may have missed it. I’d also be interested in a list of whatever custom schema-side directives Dgraph provides (in addition say to the GraphQL-spec @skip/@include) if that’s available somewhere; I would also assume that this new potential @auth provided by Dgraph would be in that list.

Hi @forstmeier

Soon a post will be published around the progress of Auth directive. Yes all the custom schema-side directives will be included in the docs soon along with GraphQL-spec directives like @skip/@include.

Let me know if you have any more questions.

Sort of unrelated to the above questions but throwing it here since you’ve been super helpful:

  • delete: I’ve been able to use set correctly (updating scalar fields and list fields) but I’m running into issues with this query with the GraphQL endpoint
{
	"query": "mutation updateCustomer($input: UpdateCustomerInput!) { updateCustomer(input: $input) { customer { username reviews { comment } } } }",
	"variables": {
		"input": {
			"filter": {
				"username": {
					"eq": "Michael"
				}
			},
			"delete": {
				"reviews": ["0x8"]
			}
		}
	}
}

where the delete is receiving an unknown field response. Basically, I want to remove either a specific field or an element in a list for a field.

  • schema introspection: I’ve been running into a wall trying with running introspection against the GraphQL endpoint while running locally. I’m trying things like the root GraphQL documentation as well as queries like the one below
{
	"query": "query getGQLSchema { generatedSchema }"
}

but now luck. Where am I going wrong? Being able to see the queries (inputs and outputs) and types (like filters) that have been generated would really speed up my iterations on the schema.

P.S. There appears to be a tiny typo in the GraphQL docs:

PostFilter will contain less than le , less than or equal to le , equal eq , greater than or equal to ge and greater than gt search on numLikes .

^ I think the “less than” should be lt.

Hi @forstmeier

If you want to remove a value from list you should use remove and use the delete mutation if you want to delete like the complete customer in this case. I think when you check the generated mutations and queries definitions that will make it easy for you. So I’m attaching the screenshots from Insomnia (you could be using a different tool) to try/view queries or mutations.

Screenshot 2020-05-03 at 11.56.54 PM Screenshot 2020-05-03 at 11.57.01 PM Screenshot 2020-05-04 at 12.03.35 AM

I hope these will help you to view queries/mutations available to you.

Thanks for the typo mention. Btw here are the two posts I told you that were coming out soon.


Let me know if you have anymore questions.

1 Like

@vardhanapoorv Again, absolute legendary support.

  • I’ve updated my mutations to include remove instead of delete so problem solved there.
  • I was using some other tools but Insomnia is much better at revealing the schema which is immensely helpful.
    • One quick follow up here: is it possible to fetch the entire generated schema out of Dgraph? I’d like to feed parts or all of the file into gqlgen and create an API using that.
  • Both of those posts are super valuable. I’ve started implementing my own solution for authorization (the example for authentication is very helpful and I’ve been adjusting it to suite my needs) - I’ll still definitely keep an eye out for @auth because I feel like that is a much cleaner solution.
  • I’d like to get actively involved in contributing to Dgraph - would you recommend any specific repo to start with? I’m particularly interested in contributing to the test suites or to documentation - whatever would be the most help for me to work on.

Best,
John

1 Like

The endpoint at /admin lets you query the SDL of the input and generated schemas

Input schema:

query {
  GQLSchema { schema }
}

Generated schema:

query {
  GQLSchema { generatedSchema }
}
1 Like

Hi @forstmeier

Here are few main repos https://github.com/dgraph-io/dgraph and https://github.com/dgraph-io/badger that you can contribute to. Soon the GraphQL docs site will be updated and then you can contribute to that as well.
Let me know if you have anymore questions.

1 Like

Hi, @michaelcompton I saw what looks like either complete, or nearly complete, work on the @auth directive get merged in today - wow, you guys move fast! I’m definitely most interested in this particular directive because I’m faced with a dilema.

  • I want to use Dgraph as my backend and expose a GraphQL frontend
  • I’d like to use gqlgen to save a lot of manual implementation work
  • Ideally, I’d just use Dgraph’s generated schema (as you helped me fetch in an earlier reply) but I’d still need to add in stuff like my own directives (e.g. @isAuthenticated and @isAuthorized) to specific queries/mutations to provide fine-grained control but finding a good tool to parse the output programmatically into Go code for me to manipulate is taking time

Alternatively, having something like a light authentication/etc middleware in a simple net/http server that is basically “passthrough” to the Dgraph API sounds pretty good so long as the @auth can be applied to use a fine-grained control/multi-tenancy resource. Does that make sense?

Hi, yeah @auth got merged. There will be docs and sample apps coming up over the next couple of weeks.

There’s two general approaches you can now take with Dgraph GraphQL

  1. have Dgraph as the exposed server - for that you’d use a combination of custom resolvers and auth (with @custom and @auth)
  2. implement some of the backend with Dgraph (maybe still using @auth and @custom) and then layer something else in front (gqlgen server in your case)

…actually there’s also a 3, which would be some sort of GraphQL Federation.

In all those scenarios, you would be free to use @auth as a fine grained auth - basically what you can say in that is these are the conditions under which you can see/edit this node; those conditions can be a combination of passed in values from a JWT, graph search or a combination of both.

Since there’s docs and samples landing in the next little while, I think the best way forward is for us to share those as soon as possible, or for us to jump on a quick call and have a chat.

Awesome! I’d probably go down the route of doing something more along the lines of #1 you mentioned above at least as an initial cut for the backend service which would basically just be a very light wrap on top of Dgraph. You guys moved fast on getting @auth done!

And yes, docs/examples would be awesome and I could always do a call depending on your availability. I’d also be happy to help write up documentation if needed.

1 Like

I’ve pivoted to definitely follow this @auth directive route given you have already implemented the changes and all that’s left is the documentation. Is there an ETA on those just for my planning purposes?

I’ll be doing the first cut over the next week.

Docs site should also change in that time so you can select between Dgraph v20.03.1 and what’s in master/beta. So we’ll try to get the in progress docs live as soon as we can.

1 Like

Awesome, I think how to use the new @auth directive in conjunction with something like JWTs issued by Auth0 would be super value-added.