So far we've been trying to implement GraphQL for our microservices at hackgt/registration#159, but when implementing a resolver I have no type definitions for the resolver or for my GraphQL schema (typescript). I've tried solutions like graphql-typewriter but the types it generates has no root parameter in the query function signature (i.e. it is (args, context) and not (obj, args, context)).
Does this fit under this repo or is there another solution I'm not seeing?
My current workaround is manually typing everything.
Would also love this... Did you get any further @illegalprime?
@illegalprime Seems the topic of typing resolvers is still an open issue - graphql/graphql-js#574 - at least from a Flow perspective. There doesn't appear to be a related issue in the DefinitelyTyped repo, but something like GraphQLFieldResolver<TSource, TContext, TArgs> might be helpful in both Flow and TypeScript.
I think this is definitely something that would be super high value, if someone has a design please post it here!
This is indeed the weak link in the whole chain.
I can generate TS interfaces/types from my graphql schema (SDL) using graphql-code-generator / apollo codegen and from there strongly type my client side.
On the server side though, I have a disconnect between my schema and the resolvers implementation, which I can only check partially after the fact by running the server (which occasionally tells me I've done something wrong). Gaining compile-time safety here would be great.
I think this would still be great to have! Perhaps this is something we can work on after we finish cleaning up query code generation, as part of the Apollo CLI here: https://github.com/apollographql/apollo-cli
@prisma is working on something here; https://github.com/prisma/graphql-resolver-codegen
In terms of desired outcome, may I suggest the following:
schema.graphql
type Query {
companies: [Company!]!
company(id: ID!): Company
employee(id: ID!): Employee
}
type Mutation {
registerCompany(form: CompanyRegistrationForm!): Company!
registerEmployee(form: EmployeeRegistrationForm!, companyId: ID!): Employee!
}
input CompanyRegistrationForm {
name: String!
ticker: String
}
input EmployeeRegistrationForm {
name: String!
title: String
role: EmployeeRole
}
enum EmployeeRole {
CEO
Manager
Other
}
type Employee {
id: ID!
name: String!
title: String
role: EmployeeRole
company: Company
}
type Company {
id: ID!
name: String!
ticker: String
}
schema.ts
// Note how optional fields in the input types are marked as optional in
// the generated TypeScript interface
export interface CompanyRegistrationForm {
name: string;
ticker?: string | null;
}
export interface EmployeeRegistrationForm {
name: string;
title?: string | null;
role?: EmployeeRole | null;
}
export enum EmployeeRole {
CEO = "CEO",
Manager = "Manager",
Other = "Other"
}
export interface Employee {
id: string;
name: string;
title: string | null;
role: EmployeeRole | null;
company: Company | null;
}
export interface Company {
name: string
ticker: string | null
}
export interface Query_company_args {
id: string;
}
export interface Query_employee_args {
id: string;
}
export interface Mutation_registerCompany_args {
form: CompanyRegistrationForm;
}
export interface Mutation_registerEmployee_args {
form: EmployeeRegistrationForm;
companyId?: string | null; // Also note how it's generated as optional
}
export type Resolver<T, Root, Args, Context> = (
root: Root,
args: Args,
ctx: Context
) => T | Promise<T>;
export interface RootResolver<Context> {
Query: {
companies: Resolver<Company[], null, {}, Context>;
company: Resolver<Company | null, null, Query_company_args, Context>;
employee: Resolver<Employee | null, null, Query_employee_args, Context>;
};
Mutation: {
registerCompany: Resolver<
Company,
null,
Mutation_registerCompany_args,
Context
>;
registerEmployee: Resolver<
Employee,
null,
Mutation_registerEmployee_args,
Context
>;
};
}
export type QueryResolver<Context> = RootResolver<Context>['Query']
export type MutationResolver<Context> = RootResolver<Context>['Mutation']
Usage example:
import { Context } from "./graphql/context";
import { MutationResolver, QueryResolver, RootResolver } from "./graphql/schema";
const companies: QueryResolver<Context>['companies'] = (
root,
args,
ctx
) => { /* work it out */ }
const registerCompany: MutationResolver<Context>['registerCompany'] = (
root,
args,
ctx
) => { /* work it out */ }
const rootResolver: RootResolver<Context> = {
Query: {
companies,
// fill out with remaining resolvers
},
Mutation: {
registerCompany,
// fill out with remaining resolvers
},
}
export default rootResolver;
@disintegrator looks nice. But what's the purpose of the Query and Mutation typescript interfaces? Maybe chime in at @prisma's repo too, since they have a lot of generating already working.
@koenpunt good catch they aren't really used for anything. Edited and removed.
We have already it in https://github.com/dotansimha/graphql-code-generator
It generates interfaces for Mutation and Query as well as for whole GraphQL Types and single fields. You gain strongly typed arguments too and control of a context and related.
Closing in favor of above prior art as well as other alternatives (type-graphql, nexus). See also https://github.com/graphql/graphql-js/issues/2188.
Most helpful comment
We have already it in https://github.com/dotansimha/graphql-code-generator
It generates interfaces for
MutationandQueryas well as for whole GraphQL Types and single fields. You gain strongly typed arguments too and control of a context and related.