Given GraphQL query, creates db fields projection to fetch only fields that are required. <br/>Supports lists, nested queries, fragments and inline fragments.
npm install graphql-db-projectionGiven GraphQL query, creates db fields projection to fetch only fields that are required.
Supports lists, nested queries, fragments and inline fragments.
Install with yarn:
``bash`
$ yarn add graphql-db-projection
or npm:
`bash`
$ npm i -S graphql-db-projection
`js
import { ApolloProjector, IncludeAll, IgnoreField } from 'graphql-db-projection';
const typeDefs = gql
directive @proj(
projection: String,
projections: [String],
nameInDB: String
) on FIELD_DEFINITION
directive @all on FIELD_DEFINITION
directive @ignore on FIELD_DEFINITION
// ... your schemas;
// ...
// (you can also call the directives differently)
const server = new ApolloServer({
typeDefs,
resolvers,
schemaDirectives: {
proj: ApolloProjector,
all: IncludeAll,
ignore: IgnoreField
}
});
`
model:
`js
const typeDefs = gql type Address {
country: String
city: String
street: String
};`makeProjection
we can call on last ASTs param to get projections mapping:`js`
import makeProjection from 'graphql-db-projection';
// ...
const resolvers = {
Query: {
users: (obj, args, request, fieldASTs) => {
const projection = makeProjection(fieldASTs);
// ...
},
},
};`
then the following query:js`
query ($id: String){
user (id: $id){
firstName
address {
city
street
}
}
}`
will produce projection:js`
{
firstName: 1,
address: {
city: 1,
street: 1
}
}`
now you can use it to project fields for db, for example for mongoDB:js
import { toMongoProjection } from 'graphql-db-projection';
const resolvers = {
Query: {
users: (obj, args, request, fieldASTs) => {
const projection = makeProjection(fieldASTs);
const mongoProjection = toMongoProjection(projection)
return db.collection('users').findOne(args.id, mongoProjection);
}
}
}
`@allInclude automatically all nested fields
To automatically include all nested fields of object use directive:`js
const typeDefs = gql
type User {
username: String
address: Address @all
}
type Address {
city: String
postalCode: String
};`makeProjection
now result on query`js`
query ($id: String){
user (id: $id){
firstName
address {
city
street
}
}
}`
will be:js`
{
firstName: 1,
address: 1
}
directive:
`js
const typeDefs = gql;
`
the result when requesting all fields will be just { firstName: 1, username: 1 }Custom Projections
If resolve function of GraphQL field uses multiple DB fields to calculate the value, use @proj(projection: or @proj(projections: [ to specify absolute paths of fields you need:`js
const typeDefs = gql // will add username to projection
displayName: String @proj(projection: "username")
// will add gender, firstName and lastName to projection
fullName: String @proj(projections: ["gender", "firstName", "lastName"])
address: Address
}
type Address {
country: String
fullAddress: @proj(projections: ["city", "street"])
};
const resolvers = {
User: {
// displayName is calculated from username in DB
displayName: user => user.username,
// fullName is calculated from gender, firstName, lastName in DB
fullName: user => ${user.gender ? 'Mr.' : 'Mrs.'} ${user.firstName} ${user.lastName},${address.city} ${address.street}
},
Address: {
fullAddress: address => ,`
}
};`
requesting all these fields in GraphQL query will result in projection:js`
{
username: 1,
gender: 1,
firstName: 1,
lastName: 1,
addess: {
country: 1,
city: 1,
street: 1
}
}
:`js
const typeDefs = gql type User {
username: String
// stored as 'location' object in DB
address: Address @proj(nameInDB: "location")
}
type Address {
city: String
postalCode: String
};
const resolvers = {
Query: {
users: (obj, args, request, fieldASTs) => {
const projection = makeProjection(fieldASTs);
// ...
},
},
User: {
address: user => user.location,
},
};
``
requesting all these fields in GraphQL query will result in projection:js`
{
username: 1,
location: {
city: 1,
postalCode: 1
}
}
on fieldASTs argument inside subquery resolver function. Suppose we have array of Posts inside User:
`jsconst typeDefs = gql
;const resolvers = {
User: {
posts: (user, args, ctx, postsFieldASTs) => {
// you can make new isolated projection only for User's Posts fetch
// based on Post's GraphQL subquery
const projectionOfPost = makeProjection(postsFieldASTs);
const mongoProjection = toMongoProjection(projectionOfPost)
return db.collection('posts')
.find({ postedBy: user.id }, mongoProjection).toArray();
},
},
};
``