How to make a GraphQL worker in Base
A code-first GraphQL server where your decorated classes are the schema, no separate SDL to sync.
How to make a GraphQL worker in Base
A GraphQL API in Base is a code-first affair. You do not write a schema file and then write resolvers that have to match it. You write decorated classes, and Base derives the schema from them. The class is the schema. This post walks the whole path: turn on GraphQL in settings, define a type, write a resolver, take arguments, and handle errors.
Turn it on
GraphQL is one key in your settings object. Point it at a server provider and list your resolvers:
type is the GraphQL server Base runs for you. resolvers is the list of classes that define your queries, mutations, and the types they return. That is the entire wiring step. Add the key, name the resolver, move on.
Define a type
A GraphQL type is a class with @GqlObjectType() on it and @GqlField() on each property you want in the schema:
@GqlField(() => String) does two jobs: it includes the property in the GraphQL type and it tells the schema builder what the field's type is. The arrow function is how you hand GraphQL the type, which keeps it working cleanly with TypeScript's own types. Mark a field optional with { nullable: true }:
The same class can also be an input type. Add @GqlInputType() and it serves both directions, so you describe the shape once whether it is going out in a response or coming in as an argument:
And because these are just decorated classes, your validation rules live on the same fields. A field can be a GraphQL field and a validated value at the same time:
One class, one set of fields, and it is your output type, your input type, and your validation rules together.
Write a resolver
A resolver is an injectable class marked with @GqlResolver(). Its methods become queries and mutations depending on which decorator you use:
@GqlQuery(() => User) makes the method a query that returns a User. @GqlMutation(() => User) makes it a mutation. The return type you pass in the decorator and the method's TypeScript return type line up, so the schema and your code describe the same shape.
Because the resolver is @Injectable(), it participates in dependency injection like any other Base class. Declare a repository or service in its constructor and Base provides it. Your resolver stays focused on shaping the response; the data access is injected.
Take arguments
Decorate a parameter with @GqlArgument(name, () => Type) to pull a named argument out of the query:
If the argument is one of your input-typed classes, the validation rules on that class run on the way in, so a resolver can trust that the argument it received already passed its own checks.
Reach the request context
When a resolver needs request-scoped information, inject it directly into the method with @InjectRequestContext():
The same request context is available here as on an HTTP route, which is the point: a query resolves against the same context machinery as the rest of your worker, not a GraphQL-only side channel.
Handle errors
Throw from a resolver and Base turns it into a proper GraphQL error response. Use the typed HTTP error helpers so the status and message are explicit:
You raise the error where it happens, in the resolver, and Base handles shaping it for the client.
The whole loop
GraphQL in Base is the same two moves as everything else. Add the graphql key to your settings and name your resolvers. Then write decorated classes: @GqlObjectType types, @GqlResolver classes, @GqlQuery and @GqlMutation methods, @GqlArgument parameters. The schema is not a separate artifact you maintain alongside your code. It is read out of your code, which means it cannot fall out of step with it.