Skip to content

Commit f8b85e3

Browse files
committed
release: 0.17.1
1 parent 8a31741 commit f8b85e3

26 files changed

+2723
-6
lines changed

CHANGELOG.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
# Changelog and release notes
22

3-
## Unreleased
3+
<!-- ## Unreleased -->
44
<!-- here goes all the unreleased changes descriptions -->
5+
6+
## v0.17.1
57
### Features
68
- add support for emitting schema file in not existing directory (#269)
9+
- drop support for Node.js v6 (end of LTS in April 2019)
710
### Fixes
811
- fix typings discovery support for WebStorm (#276)
9-
- allow for returning plain objects when using `ObjectType`s that implements `InterfaceType`s or extends others `ObjectType`s (#160)
10-
### Others
11-
- **Breaking Change**: drop support for Node.js v6 (end of LTS in April 2019)
12+
- allow for returning plain objects when using `ObjectType`s that implements `InterfaceType`s or extends other classes (#160)
1213

1314
## v0.17.0
1415
### Features

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "type-graphql",
3-
"version": "0.17.0",
3+
"version": "0.17.1",
44
"author": {
55
"name": "Michał Lytek",
66
"url": "https://github.com/19majkel94"
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
---
2+
title: Authorization
3+
id: version-0.17.1-authorization
4+
original_id: authorization
5+
---
6+
7+
Authorization is a core feature used in almost all APIs. Sometimes we want to restrict data access or actions for a specific group of users.
8+
9+
In express.js (and other Node.js frameworks) we use middleware for this, like `passport.js` or the custom ones. However, in GraphQL's resolver architecture we don't have middleware so we have to imperatively call the auth checking function and manually pass context data to each resolver, which might be a bit tedious.
10+
11+
That's why authorization is a first-class feature in `TypeGraphQL`!
12+
13+
## How to use
14+
15+
First, we need to use the `@Authorized` decorator as a guard on a field, query or mutation.
16+
Example object type field guards:
17+
18+
```typescript
19+
@ObjectType()
20+
class MyObject {
21+
@Field()
22+
publicField: string;
23+
24+
@Authorized()
25+
@Field()
26+
authorizedField: string;
27+
28+
@Authorized("ADMIN")
29+
@Field()
30+
adminField: string;
31+
32+
@Authorized(["ADMIN", "MODERATOR"])
33+
@Field({ nullable: true })
34+
hiddenField?: string;
35+
}
36+
```
37+
38+
We can leave the `@Authorized` decorator brackets empty or we can specify the role/roles that the user needs to possess in order to get access to the field, query or mutation.
39+
By default the roles are of type `string` but they can easily be changed as the decorator is generic - `@Authorized<number>(1, 7, 22)`.
40+
41+
Thus, authorized users (regardless of their roles) can only read the `publicField` or the `authorizedField` from the `MyObject` object. They will receive `null` when accessing the `hiddenField` field and will receive an error (that will propagate through the whole query tree looking for a nullable field) for the `adminField` when they don't satisfy the role constraints.
42+
43+
Sample query and mutation guards:
44+
45+
```typescript
46+
@Resolver()
47+
class MyResolver {
48+
@Query()
49+
publicQuery(): MyObject {
50+
return {
51+
publicField: "Some public data",
52+
authorizedField: "Data for logged users only",
53+
adminField: "Top secret info for admin",
54+
};
55+
}
56+
57+
@Authorized()
58+
@Query()
59+
authedQuery(): string {
60+
return "Authorized users only!";
61+
}
62+
63+
@Authorized("ADMIN", "MODERATOR")
64+
@Mutation()
65+
adminMutation(): string {
66+
return "You are an admin/moderator, you can safely drop the database ;)";
67+
}
68+
}
69+
```
70+
71+
Authorized users (regardless of their roles) will be able to read data from the `publicQuery` and the `authedQuery` queries, but will receive an error when trying to perform the `adminMutation` when their roles don't include `ADMIN` or `MODERATOR`.
72+
73+
Next, we need to create our auth checker function. Its implementation may depend on our business logic:
74+
75+
```typescript
76+
export const customAuthChecker: AuthChecker<ContextType> = (
77+
{ root, args, context, info },
78+
roles,
79+
) => {
80+
// here we can read the user from context
81+
// and check his permission in the db against the `roles` argument
82+
// that comes from the `@Authorized` decorator, eg. ["ADMIN", "MODERATOR"]
83+
84+
return true; // or false if access is denied
85+
};
86+
```
87+
88+
The second argument of the `AuthChecker` generic type is `RoleType` - used together with the `@Authorized` decorator generic type.
89+
90+
The last step is to register the function while building the schema:
91+
92+
```typescript
93+
import { customAuthChecker } from "../auth/custom-auth-checker.ts";
94+
95+
const schema = await buildSchema({
96+
resolvers: [MyResolver],
97+
// here we register the auth checking function
98+
// or defining it inline
99+
authChecker: customAuthChecker,
100+
});
101+
```
102+
103+
And it's done! 😉
104+
105+
If we need silent auth guards and don't want to return authorization errors to users, we can set the `authMode` property of the `buildSchema` config object to `"null"`:
106+
107+
```typescript
108+
const schema = await buildSchema({
109+
resolvers: ["./**/*.resolver.ts"],
110+
authChecker: customAuthChecker,
111+
authMode: "null",
112+
});
113+
```
114+
115+
It will then return `null` instead of throwing an authorization error.
116+
117+
## Recipes
118+
119+
We can also use `TypeGraphQL` with JWT authentication.
120+
Here's an example using `apollo-server-express`:
121+
122+
```typescript
123+
import express from "express";
124+
import { ApolloServer, gql } from "apollo-server-express";
125+
import * as jwt from "express-jwt";
126+
127+
import { schema } from "../example/above";
128+
129+
const app = express();
130+
const path = "/graphql";
131+
132+
// Create a GraphQL server
133+
const server = new ApolloServer({
134+
schema,
135+
context: ({ req }) => {
136+
const context = {
137+
req,
138+
user: req.user, // `req.user` comes from `express-jwt`
139+
};
140+
return context;
141+
},
142+
});
143+
144+
// Mount a jwt or other authentication middleware that is run before the GraphQL execution
145+
app.use(
146+
path,
147+
jwt({
148+
secret: "TypeGraphQL",
149+
credentialsRequired: false,
150+
}),
151+
);
152+
153+
// Apply the GraphQL server middleware
154+
server.applyMiddleware({ app, path });
155+
156+
// Launch the express server
157+
app.listen({ port: 4000 }, () =>
158+
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`),
159+
);
160+
```
161+
162+
Then we can use standard, token based authorization in the HTTP header like in classic REST APIs and take advantage of the `TypeGraphQL` authorization mechanism.
163+
164+
## Example
165+
166+
See how this works in the [simple real life example](https://github.com/19majkel94/type-graphql/tree/master/examples/authorization).
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
---
2+
title: Bootstrapping
3+
id: version-0.17.1-bootstrap
4+
original_id: bootstrap
5+
---
6+
7+
After creating our resolvers, type classes, and other business-related code, we need to make our app run. First we have to build the schema, then we can expose it with an HTTP server, WebSockets or even MQTT.
8+
9+
## Create Executable Schema
10+
11+
To create an executable schema from type and resolver definitions, we need to use the `buildSchema` function.
12+
It takes a configuration object as a parameter and returns a promise of a `GraphQLSchema` object.
13+
14+
In the configuration object you must provide a `resolvers` property, which can be an array of resolver classes:
15+
16+
```typescript
17+
import { FirstResolver, SecondResolver } from "../app/src/resolvers";
18+
// ...
19+
const schema = await buildSchema({
20+
resolvers: [FirstResolver, SampleResolver],
21+
});
22+
```
23+
24+
However, when there are several resolver classes, manual imports can be cumbersome.
25+
So we can also provide an array of paths to resolver module files instead, which can include globs:
26+
27+
```typescript
28+
const schema = await buildSchema({
29+
resolvers: [__dirname + "/modules/**/*.resolver.ts", __dirname + "/resolvers/**/*.ts"],
30+
});
31+
```
32+
33+
There are also other options related to advanced features like [authorization](authorization.md) or [validation](validation.md) - you can read about them in docs.
34+
35+
To make `await` work, we need to declare it as an async function. Example of `main.ts` file:
36+
37+
```typescript
38+
import { buildSchema } from "type-graphql";
39+
40+
async function bootstrap() {
41+
const schema = await buildSchema({
42+
resolvers: [__dirname + "/**/*.resolver.ts"],
43+
});
44+
45+
// other initialization code, like creating http server
46+
}
47+
48+
bootstrap(); // actually run the async function
49+
```
50+
51+
## Create an HTTP GraphQL endpoint
52+
53+
In most cases, the GraphQL app is served by an HTTP server. After building the schema we can create the GraphQL endpoint with a variety of tools such as [`graphql-yoga`](https://github.com/prisma/graphql-yoga) or [`apollo-server`](https://github.com/apollographql/apollo-server). Here is an example using [`apollo-server`](https://github.com/apollographql/apollo-server):
54+
55+
```typescript
56+
import { ApolloServer } from "apollo-server";
57+
58+
const PORT = process.env.PORT || 4000;
59+
60+
async function bootstrap() {
61+
// ... Building schema here
62+
63+
// Create the GraphQL server
64+
const server = new ApolloServer({
65+
schema,
66+
playground: true,
67+
});
68+
69+
// Start the server
70+
const { url } = await server.listen(PORT);
71+
console.log(`Server is running, GraphQL Playground available at ${url}`);
72+
}
73+
74+
bootstrap();
75+
```
76+
77+
Remember to install the `apollo-server` package from npm - it's not bundled with TypeGraphQL.
78+
79+
Of course you can use the `express-graphql` middleware, `graphql-yoga` or whatever you want 😉
80+
81+
## Create typeDefs and resolvers map
82+
83+
TypeGraphQL provides a second way to generate the GraphQL schema - the `buildTypeDefsAndResolvers` function.
84+
85+
It accepts the same `BuildSchemaOptions` as the `buildSchema` function but instead of an executable `GraphQLSchema`, it creates a typeDefs and resolversMap pair that you can use e.g. with [`graphql-tools`](https://github.com/apollographql/graphql-tools):
86+
87+
```typescript
88+
import { makeExecutableSchema } from "graphql-tools";
89+
90+
const { typeDefs, resolvers } = await buildTypeDefsAndResolvers({
91+
resolvers: [FirstResolver, SecondResolver],
92+
});
93+
94+
const schema = makeExecutableSchema({ typeDefs, resolvers });
95+
```
96+
97+
Or even with other libraries that expect the schema info in that shape, like [`apollo-link-state`](https://github.com/apollographql/apollo-link-state):
98+
99+
```typescript
100+
import { withClientState } from "apollo-link-state";
101+
102+
const { typeDefs, resolvers } = await buildTypeDefsAndResolvers({
103+
resolvers: [FirstResolver, SecondResolver],
104+
});
105+
106+
const stateLink = withClientState({
107+
// ...other options like `cache`
108+
typeDefs,
109+
resolvers,
110+
});
111+
112+
// ...the rest of `ApolloClient` initialization code
113+
```
114+
115+
Be aware that some of the TypeGraphQL features (i.a. [query complexity](complexity.md)) might not work with the `buildTypeDefsAndResolvers` approach because they use some low-level `graphql-js` features.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
title: Browser usage
3+
id: version-0.17.1-browser-usage
4+
original_id: browser-usage
5+
---
6+
7+
## Using classes in a client app
8+
9+
Sometimes we might want to use the classes we've created and annotated with TypeGraphQL decorators, in our client app that works in the browser. For example, reusing the args or input classes with `class-validator` decorators or the object type classes with some helpful custom methods.
10+
11+
Since TypeGraphQL is a Node.js framework, it doesn't work in a browser environment, so we may quickly get an error, e.g. `ERROR in ./node_modules/fs.realpath/index.js`, while trying to build our app with Webpack. To correct this, we have to configure Webpack to use the decorator shim instead of the normal module. We simply add this plugin code to our webpack config:
12+
13+
```js
14+
plugins: [
15+
// ...here are any other existing plugins that we already have
16+
new webpack.NormalModuleReplacementPlugin(/type-graphql$/, resource => {
17+
resource.request = resource.request.replace(/type-graphql/, "type-graphql/dist/browser-shim");
18+
}),
19+
];
20+
```
21+
22+
Thanks to this, our bundle will be much lighter as we don't need to embed the whole TypeGraphQL library code in our app.

0 commit comments

Comments
 (0)