menu
announcement

Spectrum is now read-only. Learn more about the decision in our official announcement.

GraphQL

GraphQL is a query language for APIs, with thousands of tools and libraries built by the community.

Channels
Team

Splitting a GraphQL API between public & private

February 19, 2019 at 3:52pm

Splitting a GraphQL API between public & private

February 19, 2019 at 3:52pm
Scenario: A GraphQL CMS backend which allows content creation and editing (for authenticated editors - private) and content delivery (for unauthenticated readers - public).
Consider the following schema:
type Query {
getArticles: [Article!]!
}
type Article {
id: ID!
title: String!
body: String!
published: Boolean!
}
When an editor lists the articles, they want to see both published and unpublished.
When a reader lists the articles, they must not be allowed to view unpublished articles.

Solution 1: input flag

We could add a flag that allows unpublished articles to be included in the list (defaults to false):
getArticles(includeUnpublished: Boolean): [Article!]!
Runtime logic could throw if a reader attempts to set this variable to true
con Ideally we should not expose such flags to readers. Imagine when we have a dozen "editor only" features to hide. This will get out of control. Our GraphQL API will be exposing a whole bunch of input variables and properties which are irrelevant to the reader clients.

Solution 2: separate queries

We could have 2 queries - getArticles for readers which only includes public data, and editorGetArticles for editors which includes all the private stuff.
con Now we will start to create a duplicate "editor version" of our API. We'll need to think of a naming scheme, our API may become confusing. If I am building a reader client, I always need to make sure I use the correct query.

Solution 3: separate APIs

We could have 2 entirely separate API endpoints, /reader and /editor, each of which expose only those types and queries which are relevant to the respective client.
The business logic of fetching the actual data can still be shared between the two APIs, of course.
What are your thoughts about the three approaches outlined above?

February 19, 2019 at 5:41pm
I guess it sort of depends on the overall setup and requirements of your app, but for what it's worth, solution 3 is what my team went with. We have /public for things like sign up, sign in, forgot password, etc... Then we have /admin for everything that can be done once authenticated. Everything under /admin requires a valid JWT, even viewing the schema. All the database models and other code are shared between the 2 endpoints. Then, with schema stitching, the appropriate queries are exposed to each endpoint.
Edited
like-fill
5
For each of user and quick solution, I personally created a different API for each use case. (admins / public). Definitely consider schema stitching and sharing as many type definitions as possible
like-fill
1

February 20, 2019 at 8:40am
Thanks and for the feedback. Another attractive aspect of splitting the API for me is the potential security benefit. The user could set it up so that the private endpoint is restricted e.g. only available via intranet if they need to. I'm gonna look into how such a reworking will impact my existing (fairly large) API implementation.
like-fill
5

February 20, 2019 at 7:14pm
I don't have a strong opinion about it, but the first GraphQL principle (suggested by Apollo) is One Graph. Based on this, I would suggest you nullify the sensitive data.
like-fill
2
hrmm, the idea of One Graph is nice. The main takeway to me is to define types definitions in one place and then build your schema for the use case via stitching. As a consumer of public GraphQL APIs, I would like a clean schema without a lot of noise that doesn't apply to me.
It is easy to create base interfaces and implement those given specifc contexts (user/admin)
Edited
Yes, I'm okay with both approaches. As I said before, I don't have a strong opinion about this suggested principle. It's easier to maintain only one graph, but it can confuse the users.
like-fill
1
After pondering the idea of two API paths, I'm wondering what happens when new roles are added that overlaps parts of /reader (public) and /editor (admin)?
Zero, One, or Infinity. If more than one is allowed, design should handle infinite roles, right?
Newbie to GraphQL here, so I've only got observations and questions. :)
like-fill
1
In my opinion, it really depends on the product. If you have a truely public api with no auth vs another with auth, that should be 2 different apis. If you have a SaaS with customers and an admin panel for your business needs and customer support stuff that's 2 api (customers / admins). The applications are different where 1 is an admin dashboard and the other is a customer application.
It's less about the role and more about the end application
like-fill
4
-- good points.
Real world: I wrote a script to work with the Vimeo API. Available fields vary depending on user role (and login token), but the API URLs are constant. It's not GraphQL, but it similarly lets the user specify what data to return.
I don't mean to hijack original Q about private/public, but got me wondering about idiomatic ways for GraphQL to return different data fields for N roles, that's all.
Edited
I've heard of schema stitching, but it's fuzzy. I'd appreciate pointers to tutorial/articles to get up to speed, and interested in hearing more on how schema stitching can help to make /public and /admin API. thx!

February 21, 2019 at 8:49am
In Saleor we have one GraphQL API that is used by two frontend applications - a public storefront and a private dashboard, that requires admin roles and permissions. I was also wondering how to approach the problem and having two different APIs seemed like the best idea.
It turned out that the GraphQL framework that we were using - Graphene (the only solution available for Python when we started building the API) - allows only single endpoint that serves the API. I don't know if this is by design or it is a flaw of the framework. I was trying to hack that somehow but then I stumbled upon a different issue - how to properly name my types. We have a Product model in the database and for the admin panel I needed to expose some additional fields that shouldn't be visible in public API. Should I then have PrivateProduct and PublicProduct types in my schema? Doesn't sound good and also Graphene only allowed to have one type per model.
As a result of all those difficulties we ended up with a single API, but we've built a permission system that guards our private fields and mutations at the resolver level. By using Python decorators, we wrap every resolver function that should be private with a proper permission check. If the user doesn't have the permission to access that field, we raise a GraphQL error and return null for that field.
One problem with that approach is exposing all fields and mutations (also the private ones) in the schema, but we'll have that in the documentation of our API anyway.
If you're interested you can test it in the demo of our API: https://demo.getsaleor.com/graphql/
This query returns public product data:
{
product(id: "UHJvZHVjdDo3NQ==") {
name
description
}
}
If you try to access a private field purchaseCost used by admins, you'll get an error:
{
product(id: "UHJvZHVjdDo3NQ==") {
name
purchaseCost {
start {
localized
}
}
}
}
like-fill
2
For me I would utilise contexts here. An authenticated user would have their role set or similar within the context allowing your resolvers to read this and output content relevant to them.
you describe my exact predicament! My CMS example was just hypothetical - in reality I am working on a headless ecommerce framework in the vein of Saleor. In fact, I've already studied your project quite a bit during my research phase :) I even give you guys a shout-out in my introductory blog post: https://www.vendure.io/blog/introducing-vendure/
Fortunately for me I'm using a node & apollo backend with Nest.js, so it's pretty easy to support multiple GraphQL endpoints.
Sure, you can restrict the output (e.g. return null for fields they are not permitted to read) but this still has the problem that the schema is full of irrelevant stuff for the public client. That is more my concern, since I already have a decent authorization system in place.
like-fill
1
Cool, I haven't heard of Vendure before but it looks really interesting! Node/Apollo Server is currently the best stack for building GraphQL servers IMHO. Python ecosystem is a few steps behind in that matter.