@ts-app/graphql
Create & Serve GraphQL Services with JavaScript Decorators. The goal of this package is to provide a simple mechanism to create, package and reuse GraphQL services from NPM packages.
@ts-app/graphql
provides API to create GraphQL services by decorating JavaScript functions as GraphQL resolvers with flexible query input mapping, error handling and authentication/authorization support. It also provides ts-app-graphql
CLI application to serve GraphQL services defined in compatible NPM packages.
Installation
Add these dependencies to a new NPM project.
$ npm add -S @ts-app/graphql @ts-app/server-bom
@ts-app/server-bom
are bundled peer dependencies and TypeScript @types to ease usage of this package. Typical users should just add it to your project. Advanced users may choose to resolve any unmet dependencies independently.
If installed correctly, you should be able to run the following command:
$ ts-app-graphql --help
Usage: ts-app-graphql [options]
Options:
-V, --version output the version number
-l --list
-p --package [packageName]
-s --schema [schemaDefinitionName]
-m --mongoUrl [mongoUrl] Pass an instance of MongoService with specified MongoURL to SchemaDefinition (e.g. mongodb://localhost:27017)
-r --rootType [rootType] Diagram root type (e.g. Query, Mutation) Default: "Query".
-d --develop Development mode
-h, --help output usage information
Usage
ts-app-graphql
...is an integrated GraphQL server.
It serves GraphQL services with Apollo Server and Express.
Serve Demo GraphQL Service
$ ts-app-graphql --package @ts-app/graphql --schema demoSchemaDefinition
DEBUG: Directory [/Users/xxx/my-todo/dist] does not exist. Watch for changes disabled
GraphQL Server
==============
Endpoint : http://localhost:3000/graphql (without SecurityService)
UI : http://localhost:3000/graphql-ui
Diagrams : http://localhost:3000/graphql-diagram/Query
Diagrams : http://localhost:3000/graphql-diagram/Mutation
Queries : echo,throwSomeError
Mutations: add,promiseToSubtract
INFO: Server started [33ms]
Start GraphQL server with:
- API endpoint at
http://localhost:3000/graphql
. The schema definitiondemoSchemaDefinition
is an exported module from@ts-app/graphql
. - The GraphiQL IDE for GraphQL is exposed at
/graphql-ui
.- You can execute queries against the GraphQL server via an integrated development environment that supports auto-completion.
-
GraphQL Voyager interactive graphs for the schema's Queries and Mutations are exposed at
graphql-diagram/Query
andgraphql-diagram/Mutation
respectively.
- List of queries and mutations supported by the current schema definition (Red color indicates that a resolver does not exist for the type)
If you are new to GraphiQL or GraphQL Voyager, take a few minutes to see how these tools work. Execute the demo mutations and queries. You may want to cross reference this GraphQL service's capabilities against DemoService.ts that provides the resolvers.
Options
-
--list
List schema definitions for specified package. These are basically named ECMAScript modules with a suffix ofSchemaDefinition
. It is expected that these modules are based on theSchemaDefinition
type.
Example:
$ ts-app-graphql --package @ts-app/graphql --list
@ts-app/graphql has 2 schema definition(s)
[demoSchemaDefinition,standardSchemaDefinition]
-
--package
Either an NPM package name or JavaScript filename that exports at least one schema definition. -
--schema
If package/JavaScript file contains multiple schema definitions, this option specifies the schema definition name to serve. Not required if only one schema definition exist. -
--mongoUrl
Pass an instance ofMongoService
with specified URL when creating the schema definition (e.g. mongodb://localhost:27017). This feels like a hack to inject the properties when creating schema definitions (need to rethink). -
--rootType
Render other root type via/graphql-diagram
. -
--develop
Development mode will watch for changes tosrc/*.graphqls
, copy it todist
. The server will automatically restart wheneverdist/*.graphqls
ordist/*.js
changes. Note that the server will not watch for changes tosrc/*.ts
and recompile. Usetsc --watch
to create a complete set up or refer to the tutorial.
Create GraphQL Service
There are various ways to create GraphQL services. @ts-app/graphql
takes an opinionated way where developers are expected to:
- Create GraphQL type definition for the service.
- Decorate functions with
@Resolver()
. - Export
SchemaDefinition
module.
Why this approach?
- Ability to split GraphQL schema (with its executable resolvers) into smaller NPM packages can be useful as GraphQL service grows.
- The
@Resolver
decorator is a simple and powerful approach to create resolver functions.- No need code against Resolver function's signature. Query input parameters are automatically mapped as an object or can be specified as named parameters.
- Built-in error handling. Resolvers errors are returned as HTTP status 200 along with an
error
string message. Detailed stack trace is logged at server side. - Extensible resolver processing via ResolverService "beforewares" & "afterwares". These can be used to implement global authentication & authorization, error handling and features that need to perform resolver pre/post processing.
- Built-in support for authentication & authorization when SecurityService is present. Currently, an implementation that uses JWT & MongoDB is available via
@ts-app/security
.
1. Create GraphQL Type Definitions
Describe GraphQL schema as a GraphQL type language string in a file with .graphqls
extension.
For schemas that do not depend on other schema definitions, there is nothing special here. Just describe the entire schema and things will work.
However, this package provides the following features to make sharing schemas easier:
-
Query
andMutation
root types can be repeated in all schema definitions. Its fields will be automatically merged. Just make sure all query/mutation field names are unique. - All other types should be unique when schema definitions are merged. For example, if you are relying on
standardSchemaDefinition
, you should not declarescalar Date
in your.graphqls
file as it will be provided bystandardSchemaDefinition
when merged.
2. Decorate Functions with @Resolver()
Decorating functions with @Resolver()
will mark it as a GraphQL resolver. When makeResolvers()
is called, an appropriately formed resolver will be generated.
It has the following default behavior:
- Function name will be used as resolver name.
- Resolver belongs to the "Query" root type
- GraphQL query's argument object is passed to the function as first parameter.
# given this query
fieldName(obj, args, context, info) { result }
# where args is the query's parameters
{ id: "123", title: "bob" }
# resolver function receives value of args as a single object
@Resolver()
fieldName(params) {
console.log(params.id, params.title)
}
- Function's return value is returned directly as resolver payload
- If the function throws an error, it will be caught and logged on the server with a timestamp. The resolver will then return the following payload (as HTTP 200):
{
error: `Error processing resolver function [${className}.${functionName}] [${now}]`
}
- Last parameter of resolver function is always populated with MiddlewareInputResolver object that represents resolver's input argumetns and metadata. Refer to
DemoService.resolverInfo()
for demo. For resolver functions that is not usingparamNames
, this will always be the second parameter.
Override default behavior with:
-
name
Map function to a different resolver name. -
type
Specify query type as either 'query' or 'mutation'. -
paramNames
Map GraphQL query's argument object to function parameters with a string array.
# given this query
fieldName(obj, args, context, info) { result }
# where args is the query's parameters
{ id: "123", title: "bob" }
# resolver function receives value of args as multiple function parameters
@Resolver({ paramNames: ['id', 'title'] })
fieldName(id, title) {
console.log(id, title)
}
-
auth
Specify roles allowed to access this resolver as a string array. It can also be specified asCustomAuth
. Note: This feature requires the use of securitySchemaDefinition.
3. Exporting SchemaDefinition Modules
To serve GraphQL service with ts-app-graphql
, we need to create a SchemaDefinition module that brings GraphQL type definitions, resolver functions and dependent SchemaDefinition modules together.
It is basically an ECMAScript module with a suffix of SchemaDefinition
that returns a SchemaDefinition type.
This module can declare dependencies to other schema definitions to combine functionality from multiple modules as a single GraphQL service. It solves a similar problem to what schema stitching does, except it is done before an executable schema is generated.
Next Step
Try the @ts-app/graphql - Tutorial.
- Create a Todo service in TypeScript.
- Expose it as a GraphQL service.