TypeGraphQL
Build type-safe GraphQL APIs in TypeScript by defining schema and resolvers with classes and decorators instead of hand-written SDL.
Repository Health
Technical Analysis
TypeGraphQL removes the duplication that normally comes with building a GraphQL API in TypeScript: instead of writing a schema.graphql file, hand-rolled TypeScript interfaces for every type, and loosely-typed resolver functions that have to be kept in sync by hand, you define object types, inputs, queries, and mutations once as annotated classes. Decorators like @ObjectType(), @Field(), @Resolver(), @Query(), and @Mutation() describe both the runtime behavior and the GraphQL schema at the same time, so the schema is generated automatically from the classes at startup via buildSchema().
Beyond schema generation, TypeGraphQL bakes in the plumbing every non-trivial GraphQL API ends up building anyway: constructor-based dependency injection for resolver classes, an @Authorized() guard system for field- and resolver-level access control, automatic argument validation through class-validator, custom middleware pipelines, and subscription support built on graphql-yoga’s subscription helpers. Because everything is expressed as TypeScript classes, editor tooling, refactors, and the compiler catch mistakes that a hand-maintained SDL/resolver split would silently miss.
What You Get
- Decorator-driven schema definition (
@ObjectType,@InputType,@InterfaceType,@Field) that generates a GraphQL SDL schema directly from annotated TypeScript classes - Resolver classes (
@Resolver,@Query,@Mutation,@FieldResolver,@Subscription) that read like controllers, with constructor-based dependency injection into a configurable IoC container - Built-in
@Authorized()guards for role- and policy-based access control at the field or resolver level, with a pluggableauthCheckerfunction - Automatic argument validation via
class-validatordecorators on@ArgsType()classes, so invalid input is rejected before it reaches resolver code - Custom middleware support (
@UseMiddleware) for cross-cutting concerns like logging, error mapping, and query-complexity limits viagraphql-query-complexity - First-class subscriptions built on
@graphql-yoga/subscription’sRepeater/pipe/filterprimitives for pub/sub-based real-time fields
Common Use Cases
- Building a GraphQL API server (with Apollo Server, graphql-yoga, or any GraphQL-over-HTTP layer) where the schema is derived from TypeScript classes instead of maintained by hand in SDL
- Adding a typed GraphQL layer on top of an existing ORM (TypeORM, Prisma) so entity classes and GraphQL object types stay in sync as the data model evolves
- Enforcing authorization rules (role checks, ownership checks) declaratively at the field/query level via
@Authorized()rather than scattering checks through resolver bodies - Validating mutation/query arguments automatically using the same
class-validatordecorators already used elsewhere in a TypeScript codebase - Exposing GraphQL subscriptions for real-time features (chat, notifications, live updates) using the built-in pub/sub subscription resolvers
Under The Hood
Architecture
TypeGraphQL centers on a global MetadataStorage singleton (src/metadata/metadata-storage.ts) that decorators populate as classes are loaded: @ObjectType, @Resolver, @Query, @Field, and friends each register metadata entries (queries, mutations, subscriptions, field resolvers, object/input/interface type definitions) rather than doing any work themselves. At startup, SchemaGenerator.generateFromMetadata() (src/schema/schema-generator.ts) reads that accumulated metadata and walks it to construct actual graphql-js types (GraphQLObjectType, GraphQLInputObjectType, GraphQLInterfaceType, etc.), wiring each field to a generated resolver function built by src/resolvers/create.ts. This two-phase design — declarative registration via decorators, then a single synchronous build pass — means the core abstraction that would break the most under change is MetadataStorage’s shape: every decorator and the schema generator both depend on its exact structure.
Tech Stack
The library is TypeScript-only, targeting ES2021 with strict mode, experimentalDecorators, and path-mapped imports resolved via typescript-transform-paths. Runtime dependencies are deliberately minimal: graphql and class-validator are peer dependencies (so consumers control their exact versions), @graphql-yoga/subscription supplies the async-iterator primitives for subscriptions, and graphql-query-complexity is bundled for complexity-based rate limiting. It ships dual CJS/ESM builds (tsconfig.cjs.json / tsconfig.esm.json) plus a separate typings build, assembled by a custom scripts/package.json.ts post-build step.
Code Quality
The project has an extensive functional test suite under tests/functional/ (30+ spec files covering authorization, middlewares, interfaces, generics, directives, subscriptions, and more) run through ts-jest with a dedicated tests/tsconfig.json, plus a tests/helpers directory of shared test scaffolding. Static analysis is layered: ESLint with a large custom .eslintrc, Prettier formatting, cspell spell-checking, and markdownlint for docs, all wired into a pre-commit hook via Husky and lint-staged. GitHub Actions runs a dedicated check.yml workflow (type-checking across CJS/ESM/tests/examples/benchmarks configs, lint, format, spell-check) alongside a separate CodeQL security workflow, indicating a mature CI gate rather than a single generic test job.
What Makes It Unique
TypeGraphQL’s distinguishing choice is treating the TypeScript class itself as the sole schema authority — competing approaches either hand-write SDL and generate types from it (schema-first, e.g. GraphQL Code Generator) or hand-write resolvers alongside a separately maintained SDL file. TypeGraphQL instead derives the SDL from decorated classes at runtime, so a single edit to a class field automatically propagates to the schema, the resolver’s TypeScript types, and (via class-validator) argument validation — collapsing three normally-separate artifacts into one, at the cost of relying on experimental decorator metadata rather than pure standard TypeScript.