Conceptual Question: Shared GraphQL schema for multiple endpoints (client/admin) - graphql

Context
I am using a NX Workspace to organize two different angular frontends (client & admin). To separate client and admin logic, two different NestJS backend services including GraphQL are used for client and admin.
As both services fetch data from a single MongoDB a single database library is used for both frontends.
Both backend services currently use a single GraphQL Schema generated through schema-first approach and a single database layer. In most cases the types and fields definition matches between client and admin, but in some cases a single service requires additional query arguments or fields.
For example, the admin service depends on the fields confirmed or banned of type User while they shouldn't be available through the client service.
Furthermore, e.g. the getUsers query should not be exposed through the client service.
type User {
_id: ID
name: String
email: String
confirmed: Boolean
banned: Boolean
}
type Query {
getUserById(userId: String): User
getUsers(): [User]
}
Question
Are there any best practices how to proceed with the GraphQL Schema(s) in such a case as the types are almost similar.

You can use schema directives to define authorization rules in a declarative manner directly in your Graphql schema.
A common approach would be to assign roles to a user and then use these roles to allow/block access to certain mutations or queries.
So for your example, I would imagine any request coming from the client would be made by a user with a role of client and any request coming from admin would have a user role of admin
So to build on your example of limiting the getUsers query to just admins we could add this directive to our schema:
type User {
_id: ID
name: String
email: String
confirmed: Boolean
banned: Boolean
}
type Query {
getUserById(userId: String): User
getUsers(): [User] #hasRole(roles: [admin])
}
You can read more about how to actually implement the custom directive hasRoles in the nestJs docs https://docs.nestjs.com/graphql/directives

Related

Is there any way to apply field level authorization while writing/updating in mongodb?

I know there are various Role based authentication/authorisation available for mongodb on db as well as collection level. But Is there any way to implement authorisation on field level inside collection?
For example:
Their is a collection user:
User :{
id: String,
name: String,
roles: ArrayList,
....
}
So here I want to give access of writing/updating the roles to only DB admin and the rest fields can be updated by anyone.
Is there any way to implement this on DB layer only without going to application layer?
If yes, Then how to implement that using Spring boot ?

How to inject a value via middleware into a graphql query body (prisma-typegraphql)?

I am currently using prisma-typegraphql for generating a schema and it's props.
Problem:
Is there a way to write a middleware that accesses a value from an arbitrary source, then injects that into the graphql-query arguments? It should be passed to the resolver in a way that makes it seem like the data has been in the query from the start and no modification to the resolver is necessary.
More info
Let's say I have three models, one is Client, one is Project and one is User. The Project has a relationship to the client via its id, identifying the client the project belongs to, same for the user
model Client{
id
name
etc...
}
model User{
id
name
clientId / client
etc...
}
model Project{
id
title
clientId / client
etc...
}
I don't want the clientId from the frontend via query, instead I intend to verify the user making the request and then get the clientId from that user.
Currently my Auth Middleware does verify the user and then passes the user object along in the context. However, due to the generated nature of typegraphql-prisma resolvers, I cannot use the context to inject the data into the resolver without extending every single resolver.
I'd much rather inject the value I want into the graphql query before it reaches the resolver, so the resolver has no idea something changed and processes the query as intended.

How to query Apollo GraphQL server with a specific context?

I am writing an Apollo GraphQL API that returns product information from various brands. A simplified version of the schema looks like this:
type Query {
products: [Product]!
}
type Product {
name: String!
brand: String!
}
I want to be able to query products from a specific brand. Normally this would be simple to achieve by adding a brand argument to the Product object:
type Query {
products(brand: String!): [Product]!
}
However, I have multiple GraphQL clients in different apps and each is associated with a specific brand so it seems redundant to always pass the same brand argument in every query. I also have many other objects in my schema (orders, transactions, etc.) that are specific to a brand and would require a brand argument.
Furthermore, my resolvers need to query a different API depending on the brand so even objects in my schema such as User, which are conceptually unrelated to a brand, would potentially need a brand argument so that the resolver knows which API to fetch from.
Is there a way to set the brand context for each client and have this context received by the server? Or maybe there is a better way to achieve this brand separation?
I would probably make Brand be a first-class type in your GraphQL query. That doesn't save you from having to qualify many of the queries you describe by a specific brand, but it at least gives you a common place to start from. Then you'd wind up with an API somewhat like:
type Query {
brand(name: String!): Brand
allProducts: [Product!]!
}
type Brand {
name: String!
products: [Product!]!
# users: [User!]!
}
type Product {
name: String!
brand: Brand! # typical, but not important to your question
}
If the differences between kinds of brands are visible at the API layer, you also could consider using a GraphQL interface to describe the set of fields that all brands have, but actually return a more specific type from the resolver.
The way you describe your application, it could also make sense to run one copy of the service for each brand, each with a different GraphQL endpoint. That would let you straightforwardly parameterize the per-brand internal object configuration and make the "current brand" be process-global context. The big constraints here are that, at a GraphQL level, one brand's objects can never refer to another, and if you have a lot of brands, you need some good way to run a lot of servers.

How to PUT / UPDATE nested data with GraphQL?

I'm attempting my first GraphQL backend using AWS AppSync. I'm simply trying to figure out how to use one-to-many associations. I expect to receive the many related objects as a list of children, and to be able to write some of these children when creating a new user.
type User {
id: ID!
name: String!
records: [Records!]!
}
type Records {
id: ID!
userId: ID!
title: String!
... etc ...
}
Using the AppSync interface, I click on Create Resources once to make a Records table and again to make a Users table, both in DynamoDB. This also automatically adds mutations, subscriptions, input types, and more types, to my schema, and creates resolvers for me.
What is the syntax for a mutation to create Record objects associated with my User objects? How can I PUT the Record data when I create the User?
If needed I can include more of the schema that AppSync is autogenerating.
Since you are using two DynamoDB tables (Users and Records), you will need to make two DynamoDB calls during the CreateUser mutation. One way to make two DynamoDB calls in a single mutation is to utilize DynamoDB's BatchPutItem operation.
To utilize BatchPutItem, you will need to modify the resolver which is attached to your CreateUser mutation. The resolver is responsible for taking your graphQL request, converting it into a DynamoDB operation, and then converting the results of the DynamoDB operation into a graphQL response. The resolvers have two components: a request mapping template, and a response mapping template.
The request mapping template will be responsible for taking mutation arguments and converting them into a DynamoDB BatchPutItem request.
The resolver's response mapping template will be responsible for converting the result of the DynamoDB BatchPutItem operation into your mutation's return type/structure.
Here is a tutorial on how to utilize multi-table BatchPutItem in a resolver: https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-dynamodb-batch.html
Here is a programming guide for using the Template language required for the resolvers: https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-programming-guide.html

Apollo: Extending type from remote schema

I currently have multiple GraphQL services running Apollo and have created a "Gateway" service that uses remote schema stitching in order to give me a single endpoint for access.
Within my Gateway service I am looking to extend the remote types to create references between the stitched schemas.
const linkTypeDefs = `
extend type User {
profile: Profile
}
extend type Profile {
user: User
}`;
const schema = mergeSchemas({
schemas: [userSchema, profileSchema, linkTypeDefs],
resolvers: /* Resolvers */
});
However I seem to be getting the following error:
GraphQLError: Cannot extend type "User" because it does not exist in the existing schema.
I have double checked and the type "User" and "Profile" exist and I can query them from the Gateway Graphiql.
Are there any particular steps I need to take in order to extend types merged from a remote schema?
I eventually resolved this by realising that userSchema and profileSchema were both returning a promise.
I awaited these return values and that resolved the issue for me.

Resources