What I want is to utilize extremely convenient Postgraphile queries for front-end, and to write my back-end logic in my framework of choice with my ORM of choice with their own workflows (so makeExtendSchemaPlugin is not and option), but reuse Postgraphile for return types of mutations.
In general, I rely on Postgraphile as the graphql server for my app, and put mutations under it's schema by stitching Postgraphile schema and my mutations schemas, with mutations resolvers delegating to Postgraphile schema. additionalGraphQLContextFromRequest
provides DI container to my resolvers, so they have a way to call buisiness services. makeProcessSchemaPlugin
is the place where stiching happens. codegen generates types so typescript complains when schema+resolvers+services doesn't add up.
Here is my setup:
- postgraphile.ts
- postgraphile.graphql
- types.ts
- services
- base.graphql
- document
- service.ts
- schema.graphql
- resolver.ts
- user
- service.ts
- schema.graphql
- resolver.ts
- ... other domains
postgraphile.ts
set up as library:
export const setupPostgraphile = () => postgraphile(connString, schema, {
additionalGraphQLContextFromRequest: async (req) => {
// get DI cradle I previously set up in Fastify and put to resolvers context
return {
cradle: (req._fastifyRequest as FastifyRequest).server.cradle
} as AppResolverContext;
},
appendPlugins: [
MutationResolversSchemaPlugin
]
});
const MutationResolversSchemaPlugin = makeProcessSchemaPlugin((schema) => {
// save schema file ourselves instead of using postgraphile plugin because
// after stiching it will have duplicates of our types giving
// '...type tried to redefine...' errors
if (!isProd) {
writeFileSync('./postgraphile.graphql', printSchema(schema));
}
const mutationResolvers: MutationResolvers<AppResolverContext> = {
...documentResolvers(schema),
...userResolvers(schema),
// all other resolvers
};
return stitchSchemas({
subschemas: [schema],
// do NOT include postgraphile.schema, it will nullify Postgraphile resolvers
typeDefs: loadFilesSync<GraphQLSchema>('./services/**/*.graphql'),
resolvers: {
Mutation: mutationResolvers
}
});
});
document/schema.graphql
, note that Document is a Postgraphile type from postgraphile.graphql
:
input DocumentInput {
title: String
}
extend type Mutation {
createDocument(doc: DocumentInput!): Document!
}
document/resolver.ts
, types Query and QueryDocumentArgs are from codegen:
export const documentResolvers: AppMutationsSubresolver<
'createDocument'
> = (postgraphileSchema) => ({
createDocument: {
selectionSet: '{ id }',
resolve: async (parent, args, context, info) => {
const result = await context.cradle.documentService.createDocument(args.doc);
return delegateToSchema({
schema: postgraphileSchema,
operation: 'query',
fieldName: 'document' as keyof Query['document'],
args: {id: result.id} as QueryDocumentArgs,
context,
info
});
}
}
});
document/service.ts
:
export class DocumentService {
// DI setting up bla bla bla
async createDocument(dto: DocumentUpdateDTO) {
// my perfectly normal service code
...
await this.em.flush();
// return something which holds 'id' of created object, it will be used
// to query from Postgraphile
return serialize(doc);
}
}
postgraphile.graphql
is just a dump of Postgraphile schema (original, without my mutations) for IDE completions.
types.ts
to have IDe blame e.g. when I forgot some mutation or misspel some type, types are from codegen:
export type AppResolverContext = {
cradle: AppCradle; // my DI container type, I use awilix
}
// shorthand type for resolvers
export type AppMutationsSubresolver<
MutationsSubset extends keyof MutationResolvers<AppResolverContext>
> = (postgraphileSchema: GraphQLSchema) => Pick<MutationResolvers<AppResolverContext>, MutationsSubset>
base.graphql
:
type Mutation
Finally, doing mutation like this from front-end actually works, redirecting result to full-featured Postgraphile type:
mutation {
createDocument(doc: {name: "ololo"}) {
id, name
}
}
Graphql infra got really awesome recently.
There seems to be good alternative way: instead of stiching, make a type merging gateway, making your mutations schemas returning stub types names after Postgraphile types, and instructing schema stiching how to merge Postgraphile types by IDs. I wanted to keep things explicit and non-gateway, and didn't try it.