NO LONGER IN DEVELOPMENT: type-graphql (boring name) beat me to the punch.
A @decorator based GraphQL.js schema builder.
graphql-party makes it easy to create GraphQL schemas and resolvers from javascript classes using @decorators.
yarn add graphql-party
import { GraphQLServer } from 'graphql-yoga';
import { Query, Arg, Field, Types, buildSchema } from 'graphql-party';
class Hello {
@Query(Types.String)
hello(
@Arg('hello', Types.String)
hello: string
) {
return hello;
}
}
const server = new GraphQLServer({
schema: buildSchema(Hello),
});
server.start(() => console.log('Server is running on http://localhost:4000!'));
@ObjectType({ name?: string; description?: string })
@InputType({ name?: string; description?: string })
@Field(type, { name?: string; description?: string })
@Query(type, { name?: string; description?: string })
@Mutation(type, { name?: string; description?: string })
@FieldResolver(typeFor, outputType, { name?: string; description?: string })
@Arg(field: string, type: GraphQLPartyType)
@Context(field?: string)
: returns entire context without "field"
buildSchema(classesOrGlobs, config?: { cwd: string })
: can be a glob of matching files for auto inclusion, or an array of classes that containgraphql-party
metadata.setInstance(Class, constructedClass)
: By default, any decorated class other than@ObjectType()
or@InputType()
get instantiated without any constructor arguments. You can instantiate it before hand and that will be reused from then on.
import { ObjectType, Field, Types } from 'graphql-party';
@ObjectType()
class Author {
@Field(Types.NonNullable(Types.ID))
id: string;
@Field(Types.NonNullable(Types.String))
name: string;
}
@ObjectType()
class Message {
@Field(Types.NonNullable(Types.ID))
id: string;
@Field(Types.NonNullable(Types.String))
content: string;
@Field(Types.NonNullable(Types.String))
author: string;
}
Decorated @InputType()
classes are to be used where @Arg()
is used.
import { InputType, Field, Types } from 'graphql-party';
@InputType()
class MessageInput {
@Field(Types.NonNullable(Types.String))
content: string;
@Field(Types.NonNullable(Types.String))
author: string;
}
Class methods with @Query()
and @Mutation()
get combined into a Query
or Mutation
object type. The can live anywhere. Here is an example of one living inside of a service class.
import { Query, Mutation, Arg, Types, setInstance } from 'graphql-party';
import { Author } from '../models';
import { AuthorRepository } from '../repositories';
import { AuthorInput } from '../inputs';
class AuthorService {
constructor(private authorRepository: AuthorRepository) {}
@Query(Types.List(Author))
async authors(): Promise<Author> {
return await this.authorRepository.findAll();
}
@Mutation(Types.List(Author))
async createAuthor(
@Arg('input', AuthorInput) input: AuthorInput
): Promise<Author> {
return await this.authorRepository.create(input);
}
}
// Since AuthorService requires an author repository, it needs to be instantiated and set.
setInstance(AuthorService, new AuthorService(AuthorRepository.create());
@ObjectType()
class SomeType {
@Field(Types.ID) id?: string;
@Field(Types.Int) someInt?: number;
@Field(Types.Float) someFloat?: number;
@Field(Types.String) someString?: string;
@Field(Types.Boolean) someBoolean?: boolean;
@Field(SomeType) nestedType?: SomeType;
@Field(SomeOtherType) someOtherType?: SomeOtherType;
@Field(Types.NonNullable(Types.String))
nonNullable: string;
@Field(Types.NonNullable(Types.String))
nonNullable: string;
@Field(Types.List(Types.String))
list?: string[];
@Field(Types.NonNullable(Types.List(Types.String)))
list: string[];
// You can even provide a custom GraphQLScalarType
@Field(GraphQLDateTime) dateTime: Date;
}
Classes act as a simple class passed directly to GraphQL (see http://graphql.org/graphql-js/object-types/. So you can make a @Field()
a function:
@ObjectType()
class User {
@Field(Types.NonNullable(Types.String))
firstName: string;
@Field(Types.NonNullable(Types.String))
lastName: string;
@Field(Types.List(Types.ID))
friends?: string[];
@Field(Types.String)
fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
}
But if you have more complex logic, you can put it elsewhere, such as a service:
class UserService {
constructor(private userRepository: UserRepository) {}
@FieldResolver(User, Types.List(User))
async friends(user: User): Promise<User> {
return await this.userRepository.findFriendsForUser(user);
}
}
class SomeService {
@Query(Types.Int)
async rollDice(
@Arg('times', Types.Int)
times: number,
@Context('roller') roller: Function
): Promise<User> {
return roller(times);
}
}
Check out examples for more examples!