Skip to content

Commit 0f44d9b

Browse files
committed
Introduce new hooks
1 parent 580cd79 commit 0f44d9b

File tree

17 files changed

+1210
-355
lines changed

17 files changed

+1210
-355
lines changed

docs/guides/apollo-server.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ const AppModule = new GraphQLModule({
2222
/*...*/
2323
});
2424

25+
const server = new ApolloServer({
26+
schema: AppModule.schema,
27+
context: session => session,
28+
})
29+
// or use `modules` in `ApolloServer`
2530
const server = new ApolloServer({
2631
modules: [
2732
AppModule

docs/guides/data-sources.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,10 @@ As described in Apollo Server docs, GraphQL-Modules also uses in-memory caching
115115
You can share GraphQL-Modules cache mechanism with your GraphQL Server;
116116

117117
```typescript
118-
const { schema, context, cache } = YourGraphQLModule;
118+
const { schema, cache } = YourGraphQLModule;
119119

120120
new ApolloServer({
121121
schema,
122-
context,
123122
cache
124123
});
125124
```

docs/introduction/context.md

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,21 +92,18 @@ export interface User {
9292
lastName: string;
9393
}
9494

95-
export interface IAuthModuleContext {
96-
currentUser: User;
97-
}
98-
99-
export interface IAuthModuleRequest {
95+
export interface ISession {
10096
req: express.Request;
97+
res: express.Response;
10198
}
10299

103-
export const AuthModule = new GraphQLModule<{}, IAuthModuleRequest, IAuthModuleContext>({
100+
export const AuthModule = new GraphQLModule({
104101
typeDefs,
105102
resolvers,
106103
providers: [
107104
AuthenticationProvider,
108105
],
109-
async context(session, currentContext, { injector }) {
106+
async context(session: ISession, currentContext, { injector }) {
110107
const authToken = session.req.headers.authentication;
111108
const currentUser = injector.get(AuthenticationProvider).authorizeUser(authToken);
112109
return {

docs/introduction/dependency-injection.md

Lines changed: 100 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -220,18 +220,44 @@ export const MyModule = new GraphQLModule({
220220

221221
## Hooks
222222

223-
### `OnRequest hook`
223+
### `OnInit` hook
224+
225+
This hook is called once when your application is started.
226+
227+
Example;
228+
229+
```typescript
230+
import { Injectable } from '@graphql-modules/di';
231+
import { OnRequest } from '@graphql-modules/core';
232+
@Injectable()
233+
export class DatabaseProvider implements OnInit {
234+
constructor(private dbClient: DbClient) {}
235+
onInit() {
236+
this.dbClient.connect();
237+
console.info('Database Client is connected!');
238+
}
239+
}
240+
```
241+
242+
### `OnRequest` hook
224243

225244
You can get access to useful information: the top `GraphQLModule` instance, GraphQL Context, and the network session by defining this hook as a method in your class provider.
226245

227246
```typescript
228247
import { Injectable, OnRequest } from '@graphql-modules/core';
229248

230-
@Injectable()
231-
export class MyProvider implements OnRequest {
249+
Example;
232250

251+
@Injectable({
252+
scope: ProviderScope.Session
253+
})
254+
export class AuthProvider implements OnRequest {
255+
userId: string;
233256
onRequest(moduleSessionInfo: ModuleSessionInfo) {
234257
// ...do your magic...
258+
// Let's assume you have your network request object under req property of network session
259+
const authToken = moduleSessionInfo.session.req.headers.authentication;
260+
this.userId = someFnForTokenExchange(authToken);
235261
}
236262
}
237263
```
@@ -240,26 +266,93 @@ export class MyProvider implements OnRequest {
240266
[API of `OnRequest` is available here](/docs/api/core/api-interfaces-onrequest)
241267
[API of `ModuleSessionInfo` is available here](/docs/api/core/api-classes-modulesessioninfo)
242268

269+
### `OnResponse` hook (experimental)
270+
271+
It takes same parameter like `OnRequest` hook but it gets called even before the server sends HTTP response to the client.
272+
273+
Example;
274+
275+
```typescript
276+
import { Injectable, OnResponse } from '@graphql-modules/core';
277+
278+
@Injectable()
279+
export class MyProvider implements OnResponse {
280+
281+
onResponse(moduleSessionInfo: ModuleSessionInfo) {
282+
// ...do your magic...
283+
clearDatabasePool(moduleSessionInfo.session);
284+
}
285+
}
286+
```
287+
288+
#### Note:
289+
> For now it only works with Apollo-Server, and you need to pass `formatResponse` from GraphQL-Modules.
290+
```typescript
291+
const { schema, formatResponse } = new GraphQLModule({
292+
providers: [MyProvider],
293+
...
294+
});
295+
296+
new ApolloServer({
297+
schema,
298+
context: session => session,
299+
formatResponse
300+
});
301+
```
302+
303+
304+
> `OnResponse` hook is called on each HTTP GraphQL request with a single `ModuleSessionInfo` parameter.
305+
[API of `OnResponse` is available here](/docs/api/core/api-interfaces-onresponse)
306+
[API of `ModuleSessionInfo` is available here](/docs/api/core/api-classes-modulesessioninfo)
307+
243308
### `OnConnect hook`
244309

245310
This hook is similar to `OnRequest` hook, but this is called on the initialization of WebSockets connection. It is exactly same with `OnConnect` hook that is passed to `subscriptions` in **Apollo Server**.
246311

247312
[You can learn more from Apollo docs.](https://www.apollographql.com/docs/graphql-subscriptions/authentication.html)
248313

314+
Example;
315+
249316
```typescript
250317
import { Injectable, OnConnect } from '@graphql-modules/core';
251318

319+
@Injectable({
320+
scope: ProviderScope.Session
321+
})
322+
export class AuthProvider implements OnConnect {
323+
userId: string;
324+
onConnect(connectionParams) {
325+
// ...do your magic...
326+
const authToken = connectionParams.authentication;
327+
this.userId = someFnForTokenExchange(authToken);
328+
}
329+
}
330+
```
331+
332+
> `OnConnect` hook is called once for each WebSocket GraphQL connection.
333+
[API of `OnConnect` is available here](/docs/api/core/api-interfaces-onconnct)
334+
335+
### `OnDisconnect hook`
336+
337+
This hook is similar to `OnResponse` hook, but this is called on the termination of WebSockets connection. It is exactly same with `OnDisconnect` hook that is passed to `subscriptions` in **Apollo Server**.
338+
339+
[You can learn more from Apollo docs.](https://www.apollographql.com/docs/graphql-subscriptions/authentication.html)
340+
341+
```typescript
342+
import { Injectable, OnDisconnect } from '@graphql-modules/core';
343+
252344
@Injectable()
253-
export class MyProvider implements OnConnect {
345+
export class MyProvider implements OnDisconnect {
254346

255-
onConnect(connectionParams, webSocket) {
347+
onDisconnect(connectionParams, webSocket) {
256348
// ...do your magic...
349+
clearSomeSubscriptions(moduleSessionInfo.session);
257350
}
258351
}
259352
```
260353

261-
> `OnConnect` hook is called once for each WebSocket GraphQL connection.
262-
[API of `OnConnect` is available here](/docs/api/core/api-interfaces-onrequest)
354+
> `OnDisconnect` hook is called once for each WebSocket GraphQL connection.
355+
[API of `OnDisconnect` is available here](/docs/api/core/api-interfaces-ondisconnect)
263356

264357
## Provider Scopes
265358

docs/introduction/subscriptions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ Subscriptions need to have defined `PubSub` implementation in your GraphQL-Modul
6565
You have to export `subscriptions` from your `AppModule`, and pass it to your GraphQL Server.
6666

6767
```typescript
68-
const { schema, context, subscriptions } = new GraphQLModule({
68+
const { schema, subscriptions } = new GraphQLModule({
6969
imports: [
7070
CommonModule,
7171
PostsModule
@@ -74,7 +74,7 @@ You have to export `subscriptions` from your `AppModule`, and pass it to your Gr
7474

7575
const server = new ApolloServer({
7676
schema,
77-
context,
77+
context: session => session,
7878
subscriptions
7979
});
8080

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
---
2+
id: understanding-session
3+
title: Understanding Session
4+
sidebar_label: Understanding Session
5+
---
6+
7+
When a GraphQL request arrives in GraphQL-Modules, GraphQL-Modules creates a scope only for that network request. GraphQL-Modules identifies this scope by a unique object that is given in the global application context. Global application context defined in your GraphQL server or library is not the same with module's context; because every resolvers, context builders, dependency injection and all other logics like these are encapsulated.
8+
9+
You can decide how you want to pass this session object like in your application context building phase.
10+
11+
GraphQL-Modules tries to get `session` property of your global application context first, but if there is no `session` property, it takes all application context object as your network session object.
12+
13+
### Using in `express-graphql`
14+
For example `express-graphql` passes `express.Request` by default as global application context;
15+
16+
```typescript
17+
const MyModule = new GraphQLModule({
18+
context(session: express.Request) {
19+
return {
20+
authToken: session.headers.authorization,
21+
};
22+
}
23+
});
24+
25+
// Some express code
26+
app.use('/graphql', graphqlHTTP({
27+
schema: MyModule.schema
28+
}));
29+
```
30+
31+
What if we need more stuff in network session;
32+
33+
```typescript
34+
interface MyModuleSession {
35+
req: express.Request,
36+
res: express.Response
37+
}
38+
const MyModule = new GraphQLModule({
39+
context(session: MyModuleSession) {
40+
res.on('finish', () => {
41+
// Some cleanup
42+
});
43+
return {
44+
authToken: session.req.headers.authorization,
45+
};
46+
}
47+
});
48+
// Some express code
49+
app.use('/graphql', graphqlHTTP((req, res) => ({
50+
schema: MyModule.schema,
51+
context: { session: { req, res }, otherThingsWillBeIgnored: ... }
52+
// or without session property
53+
context: { req, res }
54+
})));
55+
```
56+
57+
### Using in `apollo-server`
58+
59+
On the other hand, `apollo-server` needs to be passed it like below;
60+
61+
```typescript
62+
new ApolloServer({
63+
modules: [
64+
MyModule
65+
],
66+
context: ({ req, res }) => ({ req, res }),
67+
// or
68+
context: ({ req, res }) => ({ session: { req, res } }),
69+
// or
70+
context: session => ({ session }),
71+
// or
72+
context: session => session,
73+
})
74+
```
75+
76+
### Using in another application that doesn't use GraphQL Modules on the top
77+
78+
If you want to use a `GraphQLModule` in a non-GraphQLModules application, you can safely pass context builder of `GraphQLModule`.
79+
And you can use internal context of your `GraphQLModule` including **Dependency Injection**.
80+
GraphQL-Modules internally handles `session` without the need of passing `session` specifically.
81+
82+
#### Using `modules` of `ApolloServer`
83+
84+
```typescript
85+
const MyAccountsModule = AccountsModule.forRoot({ ... });
86+
new ApolloServer({
87+
modules: [ MyAccountsModule ]
88+
typeDefs: myTypeDefs,
89+
resolvers: myResolvers,
90+
context: ({ req, res }) => {
91+
// My Context Stuff
92+
return {
93+
myContextProp: {...},
94+
...MyAccountsModule.context({ req, res })
95+
}
96+
}
97+
})
98+
```
99+
100+
#### Using schema stitching
101+
102+
You can safely extract reusable `typeDefs`, `resolvers` and `context` from your `GraphQLModule`, and use it outside `GraphQLModule`.
103+
104+
```typescript
105+
const MyAccountsModule = AccountsModule.forRoot({ ... });
106+
107+
const typeDefs = mergeTypeDefs([
108+
MyAccountsModule.typeDefs,
109+
gql`
110+
type Query {
111+
someField: SomeType
112+
}
113+
`
114+
]);
115+
116+
const resolvers = mergeResolvers([
117+
MyAccountsModule.resolvers,
118+
{
119+
Query: {
120+
someField: ...
121+
}
122+
}
123+
]);
124+
125+
const schema = makeExecutableSchema({ typeDefs, resolvers });
126+
127+
const server = new ApolloServer({
128+
schema,
129+
introspection: true,
130+
playground: true,
131+
});
132+
```
133+
134+
This is what `Session` means in GraphQL-Modules. You can read more about **Provider Scopes** in **Dependency Injection** and **Provider Scopes** sections of our documentation.

package.json

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"test": "lerna run test",
1414
"build": "lerna run build",
1515
"build:api-docs": "./generate-api-docs.sh",
16+
"precommit": "lint-staged",
1617
"lint": "lerna run lint",
1718
"clean": "lerna clean --yes && rm -rf node_modules",
1819
"prerelease": "yarn build && yarn test",
@@ -32,9 +33,25 @@
3233
"rimraf": "2.6.3",
3334
"ts-jest": "24.0.0",
3435
"typedoc": "0.13.0",
35-
"typedoc-plugin-docusaurus": "1.0.14"
36+
"typedoc-plugin-docusaurus": "1.0.14",
37+
"husky": "1.3.1",
38+
"lint-staged": "8.1.5"
3639
},
3740
"publishConfig": {
3841
"access": "public"
42+
},
43+
"lint-staged": {
44+
"*.{ts,tsx}": [
45+
"tslint --fix",
46+
"git add"
47+
],
48+
"*.{js,json,css,md,ts,tsx}": [
49+
"prettier --write",
50+
"git add -f"
51+
]
52+
},
53+
"prettier": {
54+
"printWidth": 120,
55+
"singleQuote": true
3956
}
4057
}

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"tslint": "5.14.0",
4646
"typescript": "3.3.3333"
4747
},
48+
"sideEffects": false,
4849
"main": "dist/commonjs/index.js",
4950
"module": "dist/esnext/index.js",
5051
"typings": "dist/esnext/index.d.ts",

0 commit comments

Comments
 (0)