GraphQL
GraphQL is a query language for APIs. Unlike traditional REST APIs, GraphQL APIs have only one endpoint to which requests are sent. The content of the request describes all the operations to be performed and the data to be returned in the response. Many resources can be retrieved in a single request and the client gets exactly the properties it asks for.
Example of request
{
project(name: "GraphQL") {
tagline
}
}
Example of response
{
"data": {
"project": {
"tagline": "A query language for APIs"
}
}
}
The below document assumes that you have a basic knowledge of GraphQL.
To use GraphQL with FoalTS, you need to install the packages graphql
and @foal/graphql
. The first one is maintained by the GraphQL community and parses and resolves queries. The second is specific to FoalTS and allows you to configure a controller compatible with common GraphQL clients (graphql-request, Apollo Client, etc), load type definitions from separate files or handle errors thrown in resolvers.
npm install graphql@15 @foal/graphql
# OR
npm install graphql@14 @foal/graphql
Due to a specificity of the graphql
library, you must also modify your tsconfig.json
as follows:
{
"compilerOptions": {
...
"lib": [
...
"ESNext.AsyncIterable"
]
}
...
}
Basic Usage
The main component of the package is the abstract GraphQLController
. Inheriting this class allows you to create a controller that is compatible with common GraphQL clients (graphql-request, Apollo Client, etc) or any client that follows the HTTP specification defined here.
Here is an example on how to use it with a simple schema and resolver.
app.controller.ts
export class AppController {
subControllers = [
controller('/graphql', ApiController)
]
}
api.controller.ts
import { GraphQLController } from '@foal/graphql';
import { buildSchema } from 'graphql';
const schema = buildSchema(`
type Query {
hello: String
}
`);
const root = {
hello: () => {
return 'Hello world!';
},
};
export class ApiController extends GraphQLController {
schema = schema;
resolvers = root;
}
And here is an example of what your client code might look like:
import { request } from 'graphql-request';
const data = await request('/graphql', '{ hello }');
// data equals "{ hello: 'Hello world!' }"
Alternatively, if you have several strings that define your GraphQL types, you can use the schemaFromTypeDefs
function to build the schema.
import { GraphQLController, schemaFromTypeDefs } from '@foal/graphql';
const source1 = `
type Query {
me: User
}
`;
const source2 = `
type User {
id: ID
name: String
}
`;
// ...
export class ApiController extends GraphQLController {
schema = schemaFromTypeDefs(source1, source2);
// ...
}
Using Separate Files for Type Definitions
If you want to specify type definitions in separate files, you can use the schemaFromTypeGlob
function for this.
Example
src/
'- app/
'- controllers/
|- query.graphql
|- user.graphql
'- api.controller.ts
query.graphql
type Query {
me: User
}
user.graphql
type User {
id: ID
name: String
}
api.controller.ts
import { GraphQLController, schemaFromTypeGlob } from '@foal/graphql';
import { join } from 'path';
export class ApiController extends GraphQLController {
schema = schemaFromTypeGlob(join(__dirname, '**/*.graphql'));
// ...
}
Note that for this to work, you must copy the graphql files during the build. To do this, you need to install the copy
package and update some commands of your package.json
.
npm install cpx2 --save-dev
{
"scripts": {
"build": "foal rmdir build && cpx \"src/**/*.graphql\" build && tsc -p tsconfig.app.json",
"develop": "npm run build && concurrently \"cpx \\\"src/**/*.graphql\\\" build -w\" \"tsc -p tsconfig.app.json -w\" \"supervisor -w ./build,./config -e js,json,yml,graphql --no-restart-on error ./build/index.js\"",
"build:test": "foal rmdir build && cpx \"src/**/*.graphql\" build && tsc -p tsconfig.test.json",
"test": "npm run build:test && concurrently \"cpx \\\"src/**/*.graphql\\\" build -w\" \"tsc -p tsconfig.test.json -w\" \"mocha --file ./build/test.js -w --watch-files build \\\"./build/**/*.spec.js\\\"\"",
"build:e2e": "foal rmdir build && cpx \"src/**/*.graphql\" build && tsc -p tsconfig.e2e.json",
"e2e": "npm run build:e2e && concurrently \"cpx \\\"src/**/*.graphql\\\" build -w\" \"tsc -p tsconfig.e2e.json -w\" \"mocha --file ./build/e2e.js -w --watch-files build \\\"./build/e2e/**/*.js\\\"\"",
...
}
}
Alternatively, if you want to specify only specific files instead of using a glob pattern, you can call
schemaFromTypePaths
.import { GraphQLController, schemaFromTypePaths } from '@foal/graphql';
import { join } from 'path';
export class ApiController extends GraphQLController {
schema = schemaFromTypePaths(
join(__dirname, './query.graphql'),
join(__dirname, './user.graphql')
);
// ...
}
Using a Service for the Root Resolvers
Root resolvers can also be grouped into a service in order to benefit from all the advantages offered by services (dependency injection, etc.). All you have to do is add the @dependency
decorator as you would with any service.
api.controller.ts
import { dependency } from '@foal/core';
import { GraphQLController } from '@foal/graphql';
import { RootResolverService } from '../services';
// ...
export class ApiController extends GraphQLController {
schema = // ...
@dependency
resolvers: RootResolverService;
}
root-resolver.service.ts
export class RootResolverService {
hello() {
return 'Hello world!';
}
}
GraphiQL
This feature is available from version 2.3 onwards.
You can generate a GraphiQL
page with the GraphiQLController
class by installing the following package.
npm install @foal/graphiql
app.controller.ts
import { controller } from '@foal/core';
import { GraphiQLController } from '@foal/graphiql';
import { GraphqlApiController } from './services';
export class AppController {
subControllers = [
// ...
controller('/graphql', GraphqlApiController),
controller('/graphiql', GraphiQLController)
];
}