brunopuzoni / garph

Fullstack TypeScript experience for GraphQL-APIs

Home Page:https://garph.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

garph

Screen.Recording.2023-02-27.at.19.32.45.mov

Warning: Garph is currently in Alpha. We would love to hear your Feedback on our Discord

Note: tRPC-style client for Garph has arrived! See garph-gqty for more 🚀

Garph is a GraphQL schema-builder for TypeScript, that aims to deliver tRPC-like Developer-Experience. On top of that, Garph provides a GraphQL compability layer and type-safety primitives for any TypeScript project

Example of a GraphQL API built with Garph (served by Yoga):

import { g, InferResolvers, buildSchema } from 'garph'
import { createYoga } from 'graphql-yoga'
import { createServer } from 'http'

const queryType = g.type('Query', {
  greet: g.string()
    .args({
      name: g.string().optional().default('Max')
    })
    .description('Greets a person')
})

const resolvers: InferResolvers<{ Query: typeof queryType }, {}> = {
  Query: {
    greet: (parent, args, context, info) => `Hello, ${args.name}`
  }
}

const schema = buildSchema({ g, resolvers })
const yoga = createYoga({ schema })
const server = createServer(yoga)
server.listen(4000, () => {
  console.info('Server is running on http://localhost:4000/graphql')
})

Produces the following GraphQL schema:

type Query {
  greet(name: String = "Max"): String!
}

Get started

npm i garph
import { g } from 'garph'

Reference

Types

Object Type

g.type('Name', {
  greet: g.string()
})

String

g.string()

Int

g.int()

Float

g.float()

Boolean

g.boolean()

ID

g.id()

Enum

Plain:

g.enumType('Name', ['A', 'B', 'C'] as const)

(We need as const for proper type inference)

From TypeScript Enum:

enum Fruits {
  Apples,
  Oranges
}

g.enumType('Name', Fruits)

Union

g.unionType('Name', { a, b })

Ref

Reference another type by name or pass-by-reference

const name = g.type('Name', {
  greet: g.string()
})

g.ref(name)

Alternative:

const name = g.type('Name', {
  greet: g.string()
})

g.ref(() => name)

See Circular References for handling circular references

Interface

g.interface('Name', {
  greet: g.string()
})

Implementing an interface

const test = g.type('Test', {}).implements(interface)

Or a set of interfaces:

const test = g.type('Test', {}).implements([interface, interface])

Note: Inherited fields will be added to the schema automatically, you don't need to re-specify them

Input

g.inputType('Name', {
  greet: g.string()
})

Scalar

g.scalarType<Date, number>('Name', {
  serialize: (value) => value.getTime(),
  parseValue: (value) => new Date(value)
})

Directive

Currently not supported

Fragment

Currently not supported

Modifiers

Modifiers can be chained together to produce desired type

Implements

const test = g.type('Test', {}).implements(interface)

Or array of interfaces:

const test = g.type('Test', {}).implements([interface, interface])

List

g.string().list()

Optional (nullable)

g.string().optional()

Description

g.string().description("Description")

Args

g.string().args({
  name: g.string()
})

Required

g.string().required()

Default

g.string().default("Default string")

Deprecated

g.string().deprecated("Deprecation reason")

Extras

Inferring Types

Types can be inferred into TypeScript using the Infer utility

import { g, Infer } from 'garph'

const nameType = g.type('Name', {
  greet: g.string()
})

type NameType = Infer<typeof nameType>

Inferred type:

type NameType = {
  greet: string
}

Inferring Args

Argument Types can be inferred into TypeScript using the InferArgs utility

import { g, InferArgs } from 'garph'

const nameType = g.type('Name', {
  greet: g.string().args({ name: g.string().optional() })
})

type NameType = InferArgs<typeof nameType>

Inferred type:

type NameType = {
  greet: {
    name: string | null | undefined
  }
}

Inferring Resolvers

Resolvers can be inferred into TypeScript using the InferResolvers utility

const queryType = g.type('Query', {
  greet: g.string()
  .args({
    name: g.string().optional().default('Max'),
  })
  .description('Greets a person')
})

const resolvers: InferResolvers<{ Query: typeof queryType }, { context: any, info: any }> = {
  Query: {
    greet: (parent, args, context, info) => `Hello, ${args.name}`
  }
}

Inferred type:

{
  Query: {
    greet?: (parent: any, args: {
      name: string
    }, context: any, info: any) => string | Promise<string>
  }
}

Circular References

With Garph, circular references work just as you expect them to

Example GraphQL Schema:

type User {
  name: String!
  age: Int!
  friends(includeLastName: Boolean): [User!]!
}

Converted to Garph schema:

const userType = g.type('User', {
  name: g.string(),
  age: g.int(),
  friends: g.ref(() => userType).list()
    .args({
      includeLastName: g.boolean().optional()
    })
})

Inferred type:

type UserType = {
  name: string;
  age: number;
  friends: ...;
}

Converting to GraphQL schema

import { buildSchema } from 'garph'
const schema = buildSchema({ g, resolvers }, { defaultNullability: false })

Examples

Example projects can be found under examples/

Feedback

We would love to hear your Feedback on our Discord community

About

Fullstack TypeScript experience for GraphQL-APIs

https://garph.dev

License:MIT License


Languages

Language:TypeScript 78.8%Language:JavaScript 15.7%Language:CSS 5.5%