Currently we have a single GraphQL API which is used by both public (storefront) and private (admin ui) clients.
Certain data is relevant to both clients, but its exact shape and properties may differ depending on whether the context is public or private.
Example: unpublished Products
Let's say we add a published flag to the Product entity. If a product is published, it should appear in the storefront but if not it should only appear in the admin ui.
When a client makes a products query, how do we know whether to include unpublished products in the results?
We could implicitly filter out all unpublished results if the client does not have the ReadCatalog permission.
We could add a flag, includeUnpublished which the admin ui app sets to true. If an unauthorized client tries to set this to true, an error could be thrown.
We could create a new query just for admins.
We could split the API into entirely separate endpoints, one for private clients and one for public clients.
In this issue I will explore the fourth option above.
Analysis
Here is a list of all the current queries & mutations, and whether they are used in a private or public context, or both.
Currently there are only 7 queries and 5 mutations which are shared between the public and private contexts.
Property resolvers
This is complicated somewhat by the fact that individual property resolvers also operate across the boundary between public and private.
For example, the productOptionGroup query is restricted to those with the ReadCatalog permission. The ProductOptionGroup type has a property, options which has a resolver also restricted to ReadCatalog.
However, it is possible to create a public query which attempts to list the options of a groups and fails due to lack of permissions for that resolver:
query {
product {
id
optionGroups {
id
options { # <- fails here
id
code
}
}
}
}
One major benefit of splitting the API is potentially better security. E.g. the private API could be kept inaccessible by setting up a reverse proxy server which only exposes the public API to the internet. A less strict mode could be where the private is available to the internet, but can only be read with a valid authentication token.
Currently we have a single GraphQL API which is used by both public (storefront) and private (admin ui) clients.
Certain data is relevant to both clients, but its exact shape and properties may differ depending on whether the context is public or private.
Example: unpublished Products
Let's say we add a
published
flag to theProduct
entity. If a product is published, it should appear in the storefront but if not it should only appear in the admin ui.When a client makes a
products
query, how do we know whether to include unpublished products in the results?ReadCatalog
permission.includeUnpublished
which the admin ui app sets totrue
. If an unauthorized client tries to set this to true, an error could be thrown.In this issue I will explore the fourth option above.
Analysis
Here is a list of all the current queries & mutations, and whether they are used in a private or public context, or both.
Queries public/private analysis
Query | Private | Public ------|---------|--------- administrators | ☑ | administrator | ☑ | assets | ☑ | asset | ☑ | me | ☑ | channels | ☑ | channel | ☑ | activeChannel | ☑ | config | ☑ | countries | ☑ | country | ☑ | availableCountries | | ☑ customerGroups | ☑ | customerGroup | ☑ | customers | ☑ | customer | ☑ | activeCustomer | | ☑ facets | ☑ | facet | ☑ | globalSettings | ☑ | order | ☑ | ☑ activeOrder | | ☑ orderByCode | | ☑ nextOrderStates | ☑ | ☑ orders | ☑ | eligibleShippingMethods | | ☑ paymentMethods | ☑ | paymentMethod | ☑ | productCategories | ☑ | ☑ productCategory | ☑ | ☑ productOptionGroups | ☑ | productOptionGroup | ☑ | products | ☑ | ☑ product | ☑ | ☑ promotion | ☑ | promotions | ☑ | adjustmentOperations | ☑ | roles | ☑ | role | ☑ | search | ☑ | ☑ shippingMethods | ☑ | shippingMethod | ☑ | shippingEligibilityCheckers | ☑ | shippingCalculators | ☑ | taxCategories | ☑ | taxCategory | ☑ | taxRates | ☑ | taxRate | ☑ | zones | ☑ | zone | ☑ |Mutations public/private analysis
Mutation | Private | Public ------|---------|--------- createAdministrator | ☑ | updateAdministrator | ☑ | assignRoleToAdministrator | ☑ | createAssets | ☑ | login | ☑ | ☑ logout | ☑ | ☑ registerCustomerAccount | | ☑ verifyCustomerAccount | | ☑ refreshCustomerVerification | | ☑ createChannel | ☑ | updateChannel | ☑ | createCountry | ☑ | updateCountry | ☑ | deleteCountry | ☑ | createCustomerGroup | ☑ | updateCustomerGroup | ☑ | addCustomersToGroup | ☑ | removeCustomersFromGroup | ☑ | createCustomer | ☑ | updateCustomer | ☑ | deleteCustomer | ☑ | createCustomerAddress | ☑ | ☑ updateCustomerAddress | ☑ | ☑ createFacet | ☑ | updateFacet | ☑ | deleteFacet | ☑ | createFacetValues | ☑ | updateFacetValues | ☑ | deleteFacetValues | ☑ | updateGlobalSettings | ☑ | importProducts | ☑ | addItemToOrder | | ☑ removeItemFromOrder | | ☑ adjustItemQuantity | | ☑ transitionOrderToState | ☑ | ☑ setOrderShippingAddress | | ☑ setOrderShippingMethod | | ☑ addPaymentToOrder | | ☑ setCustomerForOrder | | ☑ updatePaymentMethod | ☑ | createProductCategory | ☑ | updateProductCategory | ☑ | moveProductCategory | ☑ | createProductOptionGroup | ☑ | updateProductOptionGroup | ☑ | createProduct | ☑ | updateProduct | ☑ | deleteProduct | ☑ | addOptionGroupToProduct | ☑ | removeOptionGroupFromProduct | ☑ | generateVariantsForProduct | ☑ | updateProductVariants | ☑ | createPromotion | ☑ | updatePromotion | ☑ | deletePromotion | ☑ | createRole | ☑ | updateRole | ☑ | reindex | ☑ | createShippingMethod | ☑ | updateShippingMethod | ☑ | createTaxCategory | ☑ | updateTaxCategory | ☑ | createTaxRate | ☑ | updateTaxRate | ☑ | createZone | ☑ | updateZone | ☑ | deleteZone | ☑ | addMembersToZone | ☑ | removeMembersFromZone | ☑ |Currently there are only 7 queries and 5 mutations which are shared between the public and private contexts.
Property resolvers
This is complicated somewhat by the fact that individual property resolvers also operate across the boundary between public and private.
For example, the
productOptionGroup
query is restricted to those with theReadCatalog
permission. TheProductOptionGroup
type has a property,options
which has a resolver also restricted toReadCatalog
.However, it is possible to create a public query which attempts to list the options of a groups and fails due to lack of permissions for that resolver:
Feedback
I created a thread in the Spectrum GraphQL community asking for feedback on the idea. So far there are 2 responses who both say they split their API like this.
Security
One major benefit of splitting the API is potentially better security. E.g. the private API could be kept inaccessible by setting up a reverse proxy server which only exposes the public API to the internet. A less strict mode could be where the private is available to the internet, but can only be read with a valid authentication token.