One Graph: Best practice to separate public & private fields/queries/mutations

Hello!,

I want to ask the Apollo community’s opinion about handling public & private fields/queries/mutations in a One Graph implementation.

Context
We currently have two separate Apollo servers under a different endpoint.
/public and /private and each one returns a different schema.

  • /public: used by our website/apps
  • /private: used by our admin panel

Requirements

  • I want to prevent selecting private fields by mistake in our clients (web, apps)
  • (optional) private fields shouldn’t be included in the schema

Possible Solutions

  1. separate private fields inside one field
type Query {
	userById(id: Int!): User
}

type User {
	
	# common fields, used by public (app, web) & private (admin)
	id: ID!
	firstName: String!
	lastName: String!

	# fields used only by private (admin)
	private: UserPrivate # add authorization only to this field
}

type UserPrivate {
	email: String!
	phone: String!
	bookings: [Booking!]
}
  1. separate Query & Types
type Query {
	userById(id: Int!): User
	userByIdPrivate(id: Int!): UserPrivate
}

type User {
	id: ID!
	firstName: String!
	lastName: String!
}

type UserPrivate {
  id: ID!
	firstName: String!
	lastName: String!
	email: String!
	phone: String!
	bookings: [Booking!]
}
  1. Unions
type Query {
	userById(id: Int!): User
}

union User = UserPublic | UserPrivate

type UserPublic {
	id: ID!
	firstName: String!
	lastName: String!
}

type UserPrivate {
	id: ID!
	firstName: String!
	lastName: String!
	email: String!
	phone: String!
	bookings: [Booking!]
}
  1. Interfaces
type Query {
	userById(id: Int!): User
}

# common fields
interface User {
	id: ID!
	firstName: String!
	lastName: String!
}

type UserPublic implements User {
	id: ID!
	firstName: String!
	lastName: String!
}

type UserPrivate implements User {
	id: ID!
	firstName: String!
	lastName: String!
	email: String!
	phone: String!
	bookings: [Booking!]
}
  1. Apollo Contracts
type Query {
	userById(id: Int!): User
}

type User {
	id: ID!
	firstName: String!
	lastName: String!
	email: String! @tag(name: "internal")
	phone: String! @tag(name: "internal")
	bookings: [Booking!] @tag(name: "internal")
}

My Thoughts:

  • Apollo Contracts looks good, but it’s only available in an enterprise plan, so It would be our last option.
  • (1) separate private fields in one field: looks easy to implement and to add the authorization rules to just the private field.

How are others doing? I would like to hear your opinion.

Thanks,

1 Like

I just really wish Contracts wasn’t an enterprise only feature. This is such a common problem. and it’s a big pain to switch clients from public / private and maintain two graphs.

I think making apollo contracts available is the most reasonable solution to this - but short of that happening I think sticking with 2 graphs is the only way to go. This solution works - but IMO it gets messy really quickly since you essentially 2x your schema in one graph as opposed to having two graph’s with the same amount of schema - but there is a clear separation of concerns