Skip to content

Commit c0c1b08

Browse files
author
hirsch88
committed
Add graphql lib and examples
1 parent 2207e77 commit c0c1b08

21 files changed

+613
-4
lines changed

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ DB_DATABASE="my_database"
3030
DB_SYNCHRONIZE=false
3131
DB_LOGGING=false
3232

33+
#
34+
# GraphQL
35+
#
36+
GRAPHQL_ENABLED=true
37+
GRAPHQL_ROUTE="/graphql"
38+
3339
#
3440
# Swagger
3541
#

.env.test

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ DB_DATABASE="my_database"
3030
DB_SYNCHRONIZE=false
3131
DB_LOGGING=false
3232

33+
#
34+
# GraphQL
35+
#
36+
GRAPHQL_ENABLED=true
37+
GRAPHQL_ROUTE="/graphql"
38+
3339
#
3440
# Swagger
3541
#

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,17 @@
5959
"compression": "^1.7.1",
6060
"copyfiles": "^1.2.0",
6161
"cors": "^2.8.4",
62+
"dataloader": "^1.3.0",
6263
"dotenv": "^4.0.0",
6364
"event-dispatch": "^0.4.1",
6465
"express": "^4.16.2",
6566
"express-basic-auth": "^1.1.3",
67+
"express-graphql": "^0.6.11",
6668
"express-status-monitor": "^1.0.1",
6769
"faker": "^4.1.0",
6870
"figlet": "^1.2.0",
6971
"glob": "^7.1.2",
72+
"graphql": "^0.11.7",
7073
"helmet": "^3.9.0",
7174
"jsonfile": "^4.0.0",
7275
"lodash": "^4.17.4",

src/api/queries/GetUsersQuery.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { GraphQLFieldConfig, GraphQLList } from 'graphql';
2+
import { Query, AbstractQuery, GraphQLContext } from './../../lib/graphql';
3+
import { UserService } from '../services/UserService';
4+
import { UserType } from './../types/UserType';
5+
import { User } from '../models/User';
6+
import { Logger } from '../../core/Logger';
7+
8+
@Query()
9+
export class GetUsersQuery extends AbstractQuery<GraphQLContext<any, any>, User[], any> implements GraphQLFieldConfig {
10+
public type = new GraphQLList(UserType);
11+
public allow = [];
12+
public args = {};
13+
14+
private log = new Logger(__filename);
15+
16+
public async run(root: any, args: any, context: GraphQLContext<any, any>): Promise<User[]> {
17+
const users = await context.container.get<UserService>(UserService).find();
18+
this.log.info(`Found ${users.length} users`);
19+
return users;
20+
}
21+
22+
}

src/api/types/UserType.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import {
2+
GraphQLID,
3+
GraphQLString,
4+
GraphQLObjectType,
5+
} from 'graphql';
6+
7+
export const UserType = new GraphQLObjectType({
8+
name: 'User',
9+
description: 'A single user.',
10+
fields: {
11+
id: {
12+
type: GraphQLID,
13+
description: 'The ID',
14+
},
15+
firstName: {
16+
type: GraphQLString,
17+
description: 'The first name of the user.',
18+
},
19+
lastName: {
20+
type: GraphQLString,
21+
description: 'The last name of the user.',
22+
},
23+
email: {
24+
type: GraphQLString,
25+
description: 'The email of this user.',
26+
},
27+
// pets: {
28+
// type: GraphQLString,
29+
// description: 'The personal number of this user.‚',
30+
// },
31+
},
32+
});

src/app.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { monitorLoader } from './loaders/monitorLoader';
2020
import { homeLoader } from './loaders/homeLoader';
2121
import { publicLoader } from './loaders/publicLoader';
2222
import { iocLoader } from './loaders/iocLoader';
23+
import { graphqlLoader } from './loaders/graphqlLoader';
2324
import { eventDispatchLoader } from './loaders/eventDispatchLoader';
2425

2526

@@ -38,6 +39,7 @@ bootstrapMicroframework({
3839
monitorLoader,
3940
homeLoader,
4041
publicLoader,
42+
graphqlLoader,
4143
],
4244
})
4345
.then(() => banner(log))

src/core/banner.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ export function banner(log: Logger): void {
1313
log.info(`Version : ${env.app.version}`);
1414
log.info(``);
1515
log.info(`API Info : ${env.app.route}${env.app.routePrefix}`);
16+
if (env.graphql.enabled) {
17+
log.info(`GraphQL : ${env.app.route}${env.graphql.route}`);
18+
}
1619
if (env.swagger.enabled) {
1720
log.info(`Swagger : ${env.app.route}${env.swagger.route}`);
1821
}

src/core/env.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ dotenv.config({ path: path.join(process.cwd(), `.env${((process.env.NODE_ENV ===
1313
export const env = {
1414
node: process.env.NODE_ENV || 'development',
1515
isProduction: process.env.NODE_ENV === 'production',
16+
isTest: process.env.NODE_ENV === 'test',
17+
isDevelopment: process.env.NODE_ENV === 'development',
1618
app: {
1719
name: getOsEnv('APP_NAME'),
1820
version: (pkg as any).version,
@@ -29,6 +31,8 @@ export const env = {
2931
controllers: [path.join(__dirname, '..', 'api/**/*Controller{.js,.ts}')],
3032
middlewares: [path.join(__dirname, '..', 'api/**/*Middleware{.js,.ts}')],
3133
interceptors: [path.join(__dirname, '..', 'api/**/*Interceptor{.js,.ts}')],
34+
queries: [path.join(__dirname, '..', 'api/**/*Query{.js,.ts}')],
35+
mutations: [path.join(__dirname, '..', 'api/**/*Mutation{.js,.ts}')],
3236
},
3337
},
3438
log: {
@@ -49,6 +53,10 @@ export const env = {
4953
synchronize: toBool(getOsEnv('DB_SYNCHRONIZE')),
5054
logging: toBool(getOsEnv('DB_LOGGING')),
5155
},
56+
graphql: {
57+
enabled: toBool(getOsEnv('GRAPHQL_ENABLED')),
58+
route: getOsEnv('GRAPHQL_ROUTE'),
59+
},
5260
swagger: {
5361
enabled: toBool(getOsEnv('SWAGGER_ENABLED')),
5462
route: getOsEnv('SWAGGER_ROUTE'),
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { UserError } from './graphql-error-handling';
2+
3+
// export interface GraphQLHooks {
4+
// before<C, A, S>(context: C, args: A, source?: S): Promise<A> | A;
5+
// after<R, C, A, S>(result: R, context: C, args: A, source?: S): Promise<R> | R;
6+
// run<S, A, C, R>(rootOrSource: S, args: A, context: C): Promise<R> | Promise<undefined> | R | undefined;
7+
// }implements GraphQLHooks
8+
9+
export abstract class AbstractGraphQLHooks<TContext, TResult, TArgs> {
10+
11+
/**
12+
* This is our before hook. Here you are able
13+
* to alter the args object before the actual resolver(execute)
14+
* will be called.
15+
*/
16+
public before<S>(context: TContext, args: TArgs, source?: S): Promise<TArgs> | TArgs {
17+
return args;
18+
}
19+
20+
/**
21+
* This our after hook. It will be called ater the actual resolver(execute).
22+
* There you are able to alter the result before it is send to the client.
23+
*/
24+
public after<S>(result: TResult, context: TContext, args?: TArgs, source?: S): Promise<TResult> | TResult {
25+
return result;
26+
}
27+
28+
/**
29+
* This our resolver, which should gather the needed data;
30+
*/
31+
public run<S>(rootOrSource: S, args: TArgs, context: TContext): Promise<TResult> | TResult {
32+
throw new UserError('Query not implemented!');
33+
}
34+
35+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { AbstractGraphQLHooks } from './AbstractGraphQLHooks';
2+
3+
4+
export abstract class AbstractQuery<TContext, TResult, TArgs> extends AbstractGraphQLHooks<TContext, TResult, TArgs> {
5+
6+
/**
7+
* This will be called by graphQL and they need to have it not as a
8+
* member function of this class. We use this hook to add some more logic
9+
* to it, like permission checking and before and after hooks to alter some data.
10+
*/
11+
public resolve = async <S>(root: S, args: TArgs, context: TContext): Promise<TResult> => {
12+
// We need to store the query arguments in the context so they can be accessed by subsequent resolvers
13+
(context as any).resolveArgs = args;
14+
args = await this.before(context, args);
15+
let result = await this.run<S>(root, args, context);
16+
result = await this.after(result, context, args);
17+
return (result as TResult);
18+
}
19+
20+
}

0 commit comments

Comments
 (0)