This package is deprecated and will not receive further support. Use GraphQL Code Generator instead.
When using Apollo GrapQL you need to define the typeDefs for your schema. When using TypeScript you need to define interfaces to add proper typings. Because doing both of these things is rather a lot like code duplication, this package was created.
With this package you can simply define your model as a class, use the provided TypeScript decorators and call the generateTypeDefs
function, which will automatically generate your typeDefs for you.
npm i typescript-typedefs
Since we use decorators, tsconfig must be configured to work with them.
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true
}
}
As well as some babel plugins.
"plugins": [
"babel-plugin-transform-typescript-metadata",
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }]
]
This package exports
- Type - used as @Type() to decorate a class or @Type({implements: OtherClass})
- Input - used as @Input() to decorate a class
- Field - used as @Field(), @Field(String) or @Field({type: Int, nullable: true}) to decorate a class property
- Interface - used as @Interface() to decorate a class which should result in an interface in the typeDefs
- Int - used in an @Field() decorator
- Float - used in an @Field() decorator
- ID - used in an @Field() decorator
- generateTypeDefs - function used to create a typeDefs string from the array of decorated classes it recieves as argument.
import { Type, Field, ID, Int, generateTypeDefs } from 'typescript-typedefs';
@Type()
class Course {
@Field()
name: string;
}
@Type()
class Student {
@Field(ID)
id: string;
@Field()
name: string;
@Field(String)
friendNames: string[];
@Field({ type: Int, nullable: true })
room: number;
@Field()
gpa: number;
@Field(Course)
courses: Course[];
}
@Interface()
class Book {
@Field()
author: string;
}
// option 1 for implementing
@Type()
class CourseBook extends Book {
@Field()
author: string;
@Field()
course: string;
}
// option 2 for implementing
@Type({ implements: Book })
class ColoringBook implements Book {
@Field()
author: string;
@Field()
designer: string;
}
const generatedTypeDefs = generateTypeDefs([Course, Student, Book, CourseBook, ColoringBook]);
results in a string:
`
interface Book {
author: String!
}
type Course {
name: String!
}
type Student {
id: ID!
name: String!
friendNames: [String!]!
room: Int
gpa: Float!
courses: [Course!]!
}
type CourseBook implements Book {
author: String!
course: String!
}
type ColoringBook implements Book {
author: String!
designer: String!
}
`;
which can then be used in makeExecutableSchema()
from Apollo server.
Directives can be used, either with or without params. The field directive
must always be set, as many params as desired can be added afterwards.
Use something like eslint-plugin-graphql to display your directives in your frontend.
import { Type, Field, ID, Int, generateTypeDefs } from 'typescript-typedefs';
import { merge } from 'lodash';
import { SchemaDirectiveVisitor } from "graphql-tools";
import { GraphQLField, GraphQLEnumValue } from 'graphql';
@Type()
class Course {
@Field()
name: string;
@Field({ directives: [{ directive: 'deprecated', reason: 'Use name instead' }] })
courseName: string;
@Field({ directives: [{ directive: 'deprecated' }] })
longCourseName: string;
}
const generatedTypeDefs = generateTypeDefs([Course]);
const courseTypeDefs = `
extend type Query {
courses: [Course!]!
}
${generatedTypeDefs}
`;
const typeDefs = gql`
directive @deprecated(reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE
type Query {
_empty: string
}
type Mutation {
_empty: String
}
`;
class DeprecatedDirective extends SchemaDirectiveVisitor {
public visitFieldDefinition(field: GraphQLField<any, any>) {
field.isDeprecated = true;
field.deprecationReason = this.args.reason;
}
public visitEnumValue(value: GraphQLEnumValue) {
value.isDeprecated = true;
value.deprecationReason = this.args.reason;
}
}
const schema = makeExecutableSchema({
typeDefs: [typeDefs, courseTypeDefs],
resolver: merge(courseResolver),
schemaDirectives: {
deprecated: DeprecatedDirective,
},
});
This packages makes use of reflect-metadata which has difficulties with circular references. To work around this, there are 2 options:
// ./course/courseTypeDefs
@Type()
export class Course {
@Field(forwardRef(() => Student))
students: Student[];
}
// ./student/studentTypeDefs
@Type()
class Student {
@Field(Course)
courses: Course[];
}
export const studentCourseTypeDefs = generateTypeDefs([Student, Course]);
// ./course/courseTypeDefs
@Type()
export class Course {
@Field(Student)
students: Student[]
}
// .student//studentTypeDefs
@Type()
class Student {
courses: Course[]
}
// ./schema
const generatedTypeDefs = generateTypeDefs([Course, Student])
const extendTypeDefs = gql`
extend type Student {
courses: [Course!]!
}
`
const schema = makeExecutableSchema({
typeDefs: [generatedTypeDefs, extendedTypeDefs],
resolvers: merge(...)
})
Interfaces are not available at runtime, classes are.
- Add documentation to types/inputs
- Add enums
- Add nullable array elements
- Add nullables array elements:
Field({nullable: elementsAndArray})
- Add syntax for explicit arrays:
Field([String])
- investigate usage of 'string | undefined' instead of {nullable: true}