Skip to content

Latest commit

 

History

History
140 lines (111 loc) · 3.93 KB

README.md

File metadata and controls

140 lines (111 loc) · 3.93 KB

Note: 0.9.x contains breaking API changes. Go here for previous readme.

Getting Started

yarn add gqtx

Type-safety without manual work

gqtx is a thin layer on top of graphql-js for writing a type-safe GraphQL server in TypeScript. It provides you with a set of helper functions to create an intermediate representation of a GraphQL schema, and then converts that schema to a raw graphql-js schema. So you get to use everything from the reference implementation of GraphQL, but with way more type safety.

If a schema compiles, the following holds:

  • The type of a field agrees with the return type of the resolver.
  • The arguments of a field agrees with the accepted arguments of the resolver.
  • The source of a field agrees with the type of the object to which it belongs.
  • The return type of the resolver will not be input types (InputObject)
  • The arguments of a field will not be abstract types (Interface, Union)
  • The context argument for all resolver functions in a schema agree.

Most importantly, we achieve all this without having to:

  • Set up code generation tools
  • Write SDL and having your schema partially defined in code and in a DSL file
  • Require special compiler magic such as reflect-metadata and decorators

What does it look like?

import { Gql, buildGraphQLSchema } from 'gqtx';

enum Role {
  Admin,
  User,
}

type User = {
  id: string;
  role: Role;
  name: string;
};

const users: User[] = [
  { id: '1', role: Role.Admin, name: 'Sikan' },
  { id: '2', role: Role.User, name: 'Nicole' },
];

// We can declare the app context type once, and it will
// be automatically inferred for all our resolvers
declare module "gqtx" {
  interface GqlContext {
    viewerId: number;
    users: User[];
  }
}

const RoleEnum = Gql.Enum({
  name: 'Role',
  description: 'A user role',
  values: [
    { name: 'Admin', value: Role.Admin },
    { name: 'User', value: Role.User },
  ],
});

const UserType = Gql.Object<User>({
  name: 'User',
  description: 'A User',
  fields: () => [
    Gql.Field({ name: 'id', type: Gql.NonNull(Gql.ID) }),
    Gql.Field({ name: 'role', type: Gql.NonNull(RoleEnum) }),
    Gql.Field({ name: 'name', type: Gql.NonNull(Gql.String) }),
  ],
});

const Query = Gql.Query({
  fields: [
    Gql.Field({
      name: 'userById',
      type: UserType,
      args: {
        id: Gql.Arg({ type: Gql.NonNullInput(Gql.ID) }),
      },
      resolve: (_, args, ctx) => {
        // `args` is automatically inferred as { id: string }
        // `ctx` (context) is also automatically inferred as { viewerId: number, users: User[] }
        const user = ctx.users.find((u) => u.id === args.id);
        // Also ensures we return an `User | null | undefined` type
        return user
      },
    }),
  ],
});

const schema = buildGraphQLSchema({
  query: Query,
});

Use your favorite server option to serve the schema!

import express from 'express';
import graphqlHTTP from 'express-graphql';

const app = express();

app.use(
  '/graphql',
  graphqlHTTP({
    schema,
    graphiql: true,
  })
);

app.listen(4000);

gqtx works best with TypeScript strict mode

We recommend using TypeScript strict mode in order to have the best developer experience.

tsconfig.json

{
  "compilerOptions": {
+    "strict": true
  }
}

To Recap

  • We created an intermediate representation of a GraphQL schema via the helper functions exported by this library.
  • Then, we converted the schema to a real graphql-js schema by calling buildGraphQLSchema at server startup time.
  • Used existing express middleware express-graphql to server our schema with graphiql explorer
  • That's it! We get a fully type-safe server with almost zero type annotation needed