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

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.

Related

Custom fields with a GraphQL query

Possibly exposing my ignorance of apollo-server but hoping someone can help: so ATM I have some schemas stitched together with #graphql-tools; all very simple, cool. I can make queries without problem.
There's a desire to add custom fields to given queries, so that we add extra data from other sources into the requested existing query template.
To explain by example: say the schema looks like this:
type User {
id
projectId
}
I'm trying to develop something so that the query getUserById($id...) can provide a template like so:
query userById($id: ID!) {
userById(id: $id) {
id
project {
id
name
# whatever other fields I want from Project type
}
}
}
And then apollo/graphql would then make a separate, asynchronous request to fetch the project for that given User.
As I understand graphql-tools, I can see resolvers allow the ability to make async requests for extra data ... but my problem is by defining project { within the query template, an error is thrown because - of course - project is not defined in the actual Schema itself.
Is there a way to filter and remove fields from a given query, somewhere in the chain of events? A custom apollo-server plugin perhaps? As I said I'm exposing my ignorance here but I've got a little lost in how apollo behaves in tandem with GraphQl.

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

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

GraphQL batching with logic

I have the following situation in GraphQL schema:
type User {
id: Float
name: String
cityId: Float
}
type City {
id: Float
country: String
}
On client I need information about User and City. But in order to load City I have to know its id, so I can't just batch these requests. Is it possible to make batch request with logic, so from client I make one request with two queries and maybe add some addition info that when the first request is done take id from it and then make another request.
After that both User and City go to client. It is so to say inner join, so I would like to have one request to load connected data.
I can't change the schema, but I can add libs and so on to client or server.
Thanks.
PS. sorry i have just noticed that you stated that you cannot change
the schema. I will leave it there for future reference, but it cannot
be applied on your problem probably.
i would suggest to rearrange your schema as follows. I do not have enough information if it would satisfy your needs. But i would suggest this.
type User {
id: Float
name: String
city: City
#for this city field there will be additional resolver binded to data loader
}
type City {
id: Float
country: String
}
This way the query will look like as follows
query getUsers {
users {
id
name
city {
id
country
}
}
}
There can be used UserConnection from Relay spec, but let's keep it simple for now.
On server side you will then need to implement two resolvers ... first is request for user list and then resolver for city field. Please note that resolvers have seperate context. In order to avoid N+1 requests problem and batch requests city requests to 1. It would be useful to implement data loader for cities to reduce the number of requests to database. The simple schema would be
User resolver, fetch users and return them in users resolver. CityId
is part of the payload for each use
Because city is second level of selection set you will receive each user in the first argument in the resolver function. You will use countryId to pass it to Countries data loader
Countries data loader will batch requests for each counry together. Data loader will transform the countryIds into country values and return them for each user.
GraphQL server will resolve the whole query and each city will be assigned to each user
This is the best approach that i know to deal with this and will also leads you to better architecture of your schema as you will leverage normalization of your appollo store and in my opinion it is easier to work with this format of the data on frontend as well. I hope that i did not miss something in your post and this information will be useful for you. The whole point is to just nest the country into the user, which leads to N+1 request problem and reduce the performance issue of N+1 problem with data loaders.

GraphQL pre-approved queries

I read that Facebook's internal servers accept any queries in dev
mode, and these are cached. In production, only a pre-approved/cached
query is permitted. This was mentioned as a model which other servers
should adopt.
Does someone know what tools do they use for that? Does this process is described more detailed somewhere?
I don't know how it's down in facebook but I can explain how I did it in GraphQL Guru. As graphql is language agnostic I'll explain without being language specific.
The way persisted queries work is a client sends a query with a unique id and variables to a graphql (persisted query ready) server.
{
"id": "1234",
"varibles": {
"firtName": "John",
"lstName": "Smith"
}
}
For the id don't use a hash of the query as the this results in long id names of varying sizes, which kind of defeats the purpose.
On your server, create a file with the same name as the persisted query id, which contains the actual graphql query. Or save it in a database.
To get the graphql query you will need to intercept it via middleware. The middleware retrieves the graphql query via its id and passes the query on to the graphql endpoint. Depending on how the query was defined the middleware may need to parse it. Also, it is in the middleware where you can whitelist if the persisted query id does not exist.
Then the graphql endpoint process the query as normal.
You can see a nodejs example here https://github.com/otissv/guru-express-server/blob/master/src/routes/graphql-route.js

Altering breeze query string in the controller before being processed

Scenario: my app runs queries where the user id is used in many cases to filter what data the user can see. The user id is an integer. I don't want the user to be able to simply alter the query string and change the user ID parm to another # (and essentially assume another user's identity). Through our authentication process, each request to the server includes a security token in the header, which was returned to the client when they logged in. During the auth process, this token is stored on the server and is mapped to the user's user id.
What I would like to do is pull the token out of the header, do a lookup and get the user ID that is mapped to the token value (got that working), and then alter the query string to add the user ID.
So a request may come in as
http://localhost/api/app/customerlist
And after I get the user id, it may look like this
http://localhost/api/app/customerlist?$filter=userid%20eq%1234
And then continue on.
This is a simple scenario but illustrates my goal. I am not able to add surrogate keys to the database and use a GUID or some other value as my filter column. Pretty much stuck with the structure.
Thanks
On the server side you can add any filters directly to whatever IQueryable is returned. If you use the Authorize attribute you can also access the user data via the "User" variable.
[BreezeController]
[Authorize]
public class NorthwindIBModelController : ApiController {
[HttpGet]
public IQueryable<Customer> CustomerList() {
var userName = User.Identity.Name;
var filter = filter on customers;
var custs = ContextProvider.Context.Customers.Where({ some filter using userName});
}
}
Also see:
Passing username to Web Api methods in ASP.NET MVC 4
Take a look at the EntityQuery.withParameters method. With it from the client you can take any query and add your user id filter to it. Something like
myQuery = myQuery.withParameters( { userId: 1234});
myEntityManager.executeQuery(myQuery).then(...);
Or am I missing something.

Resources