From 7f520814fff83e5d2725c27bfb602001746687f8 Mon Sep 17 00:00:00 2001 From: Romain Lenzotti Date: Wed, 1 Nov 2023 10:24:21 +0100 Subject: [PATCH] docs: improve graphql documentation --- docs/.vuepress/config.base.js | 7 +- docs/.vuepress/public/graphql-nexus.png | Bin 0 -> 4287 bytes docs/.vuepress/public/graphql-ws-large.png | Bin 0 -> 44038 bytes docs/.vuepress/public/graphql-ws.png | Bin 0 -> 4338 bytes docs/readme.md | 9 +- docs/tutorials/graphql-apollo.md | 194 ++++++++ docs/tutorials/graphql-nexus.md | 125 +++++ docs/tutorials/graphql-typegraphql.md | 292 ++++++++++++ docs/tutorials/graphql-ws.md | 428 ++++++++++++++++++ docs/tutorials/graphql.md | 224 +-------- .../snippets/graphql/datasource-service.ts | 1 - packages/graphql/apollo/jest.config.js | 2 +- packages/graphql/typegraphql/jest.config.js | 8 +- packages/graphql/typegraphql/package.json | 1 - yarn.lock | 2 +- 15 files changed, 1074 insertions(+), 219 deletions(-) create mode 100644 docs/.vuepress/public/graphql-nexus.png create mode 100644 docs/.vuepress/public/graphql-ws-large.png create mode 100644 docs/.vuepress/public/graphql-ws.png create mode 100644 docs/tutorials/graphql-apollo.md create mode 100644 docs/tutorials/graphql-nexus.md create mode 100644 docs/tutorials/graphql-typegraphql.md create mode 100644 docs/tutorials/graphql-ws.md diff --git a/docs/.vuepress/config.base.js b/docs/.vuepress/config.base.js index a20d9548dcb..9a4bcd2df62 100644 --- a/docs/.vuepress/config.base.js +++ b/docs/.vuepress/config.base.js @@ -260,7 +260,8 @@ module.exports = ({title, description, base = "", url, apiRedirectUrl = "", them { text: "Temporal", link: `${base}/tutorials/temporal.html` - }, { + }, + { text: "BullMQ", link: `${base}/tutorials/bullmq.html` }, @@ -452,6 +453,10 @@ module.exports = ({title, description, base = "", url, apiRedirectUrl = "", them base + "/tutorials/mikroorm", base + "/tutorials/mongoose", base + "/tutorials/graphql", + base + "/tutorials/graphql-ws", + base + "/tutorials/graphql-apollo", + base + "/tutorials/graphql-typegraphql", + base + "/tutorials/graphql-nexus", base + "/tutorials/socket-io", base + "/tutorials/swagger", base + "/tutorials/ajv", diff --git a/docs/.vuepress/public/graphql-nexus.png b/docs/.vuepress/public/graphql-nexus.png new file mode 100644 index 0000000000000000000000000000000000000000..6f7fe4bffe0a5a4924a88a87f481bbb2ff479242 GIT binary patch literal 4287 zcmd5YCqskYW8BElzyd3bn4%uJ2!cz6z| z{_BGLTqN>7CX72A2(dG{!Bf(6>JQfd+%>Q=;NdAtKg4$DVWzo5gtOaJwD zy*MGxb>;tGi}$fL5Vux$MVxe~gOg(N&OIS&qU2ZAIe<*0{ScZip5yo8CTG@)M+3#nV@+_8Ccob^S)o~uD%mfx{@)v~92i>oq(vYKS{`!HjPs$7*0mUkP zD`Smq41&g7Kte7IKIRub=4)Gnbz$T50|~fPv-yc;UnTqZZxtsMvuaGV(E#;Vh+1^< z5hvu(j?6QRIk{Sp4BQeh{v6Qmj~w*4X7c32{OA*yUii_OH|_os+WY|Muj6ViyAJ(O z0_Xw$l1E@$+Nbihw?!I70LI)^^2dBX!=>FwY31jo#M6c3viF+xlk~Ps zOL2JW3E(dgzWp-KMbwseu+wCWyCu*D5>mnWc*h$fgqz7Yvir^p!QkA zLtq#^>u|z!Qb&IYeR1}iYSF1oFG)|`P)7k+$-e-P z+7PwSvp$SbH}f7=i|F^LRXe{OsdRML+;NNMtM8Vt?E_KOk}|(JyvPi34=J5*^&GUu zyyF|RJ_7%p5=~QF@0`WYh4=}j^d%ljqxF6DtXgW122^3+n0gDUTBdaT;N5?Kv=BtE zH3qBKzA@*5atDaWM4Mu2d^6Qi_K*?faNkMVK(LaD_fQ%)1u^e>t_DjOKxZ}62)>(3 zN8Og)gCfIR9Aor}@^R0*#Nfr$DPA`x%@zEqalDp;PMp#1>>PrI`fC}R!JSRLP$7WL zsOtbzwMMV!!H9%DI8q?GI|T`o0ZfJ z+Fc?$=H3s>Krw0pD6cbtx7-z4=o9BxR7jl+fb!obLMQKGdiXj&me#T3ZX`oi2O*tk zpdh*Bm6Wz5&}hdBpJx6+^ZuzYxeprcYZ1Qbjpmm2UWgdTQ&(I^eckXyp$9$hF97bA zeC~?j(g0Qgh}6e^kkTyM+pMd)xIK^+6axUzEPpMfvLkq)d!boaTpIa_c||xQyf{ zFuVsX>x-))ZWq~GI!U_C+Mc^i_hoysFa78>J^ah1$;fC(*m^4V$LjnC{9nv^#6avf z{^NECR(_MYkw4{Utbz4e)2F5As_FAOueX_v9cs^yLuC_i@lsBolFmV(;yV;H(qRz2 z@m136eDK0)s=Qd%UqT+7tnn)BK2PcQ|eJMJvVlBX!UN+gMQaDRQc)j zlUFZa*`|cvzvApw6&B5U-OSluDsiqy$(+j!(N%5)9XeW2M{L`$W*~1%cDWnRQRX}| zBC)ecvmlx2-le)S?ZH~I8zm24m8^V;7kr~(J&<){R>5!obq}gxYw67Ed6{2Z&qtuZ zoYn6KRb!ri1j`-cE;~I#VK*;_vi%{Bc^y%RBHzJokoS;E?~w$F7iu-{7G}={s8_zO zBy{Y2ZCa`NEXGu$xJdz^MreAiZ@5KAXC3I*P26P-YANB2X_}GY=a^|N&6aj`D)V;^o%vC|GQUVx};qnJEFuWS-&p~xOjU{-K--OKJAo(9A8B*V6RJ8wGG z4E&-M$p-35KPqIW(A4!V;l*Z{GYmw8DhB@)1LPhD0@lFGanidbz6*?_;ZZ+dZ4XW{ep27LG6PJvAWQSg~v3`8H8A-wu}oen&8%bIkUk9xr_vRSLLyqsiOKp6aFP9~mz3~0JnyF1u`4>fAzneE+!+mgT4W8Dxj zq2bLd!-p+g@`a-QH!T;QW9(`_%5A93A^0{|M4RDP(Ki#9t$si>dCmxs@40-z=arE?uWfB$SCk+BU4VDSXzt*#oa zO+v2Qan1PoD`=ym1X7MQll-+_hdxWZPd zU-m5R#T8cDGgp#|ov|1+cjr~vYp!1PwBWGjx)HG{-u7htp@KP97AKq(XZ!Va5HhEC zYN*h)=sS`RsU-7NxHS!uAw(zKO#c%}nKsg}n*=cmVnWsgPUa4YDdzGDz~7E8TCmPiCm*Q#vQ zv^aZR{*gZyT4RVS%hH9xFhdi3n z4Y=8^5k15YeTUk3=%lgx1?BW?L}bA)^|^EsLB3V)B7%P)pi^QxXOFK+i9~l}Tl1!_ zC6_5~YP8?NCqN*}OSe~15&O(~&kn1~aNX`Z!$swNnO<(it^3r7_rzLN7*O$vFvy%Y z^i`{#BHBw5&T%h$@#f(YLr*Rnwd31YCbJ*3YRZ%B4>u&U+eW9Q!4w zfbDpLf+af6V7e7!J?H=lTq;Japi-D*@jAI4@jVZGO>e1W%f zZ$(5w0S|3#VD#ps8-FXnv5hhw0G*mzhM#rG;uHEA-HaE%Z8HER+q@Y--b4DD!9emm z46w{m*21NlFB{9|?d%g)Wma+`$33lQ`gS*qS=S7XXgVuY3Sh;)>5xKRa0?q%zcveHJ5q4%BYBx2zJaMM^;EnO zQ|qi@n8Ym3rS#67ucI?bgPp4t4~HADfPNJ-c8Dx7I zx@slLw!m55Kw(<)wJ-!fv`brLDTrb^psf!ooeeOZqSQit9DZ%xs&=hQzdWX|G|@d4 zGvrM zv*9(zh6M0~n+Kbdd$r(_764!L&bmrc=^yny=np?kJBAiXrB!iMj*Jp`mW9A<~DI zwInT34Cwu_P^!vC-FEcAVbM0g0y}=VnnqqK)OQP14Cu$$qB7KI-o2M4z;@K%h z-DRzHKHlR)LA2f9aW6nZjC2DCjR;%69@#AQh$%WZS^i1t|KF$jpGSH1bRK6|{ITZB R_6PrLGh=I`k{fP`{{w6@>skN+ literal 0 HcmV?d00001 diff --git a/docs/.vuepress/public/graphql-ws-large.png b/docs/.vuepress/public/graphql-ws-large.png new file mode 100644 index 0000000000000000000000000000000000000000..1f70cefebac486bb2d9b82642b51e1043d5e3d0f GIT binary patch literal 44038 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 + +Unlock microservices potential with Apollo GraphQL. Seamlessly integrate APIs, manage data, and enhance performance. Explore Apollo's innovative solutions. +Ts.ED provides a module to create multiple Apollo server and bind it with Ts.ED server (Express or Koa). + +## Feature + +- Create [Apollo](https://www.apollographql.com/docs/apollo-server/api/apollo-server.html) Server and bind it with + Ts.ED, +- Create multiple servers, +- Support [TypeGraphQL](https://typegraphql.com/), [Nexus](https://nexusjs.org/) or standalone Apollo server (or whatever). +- Support subscription with [GraphQL WS](/tutorials/graphql-ws.md) + +## Installation + + + + +```bash +npm install --save @tsed/apollo graphql apollo-server-express +npm install --save-dev apollo-server-testing +``` + + + + +```bash +npm install --save @tsed/apollo graphql apollo-server-koa +npm install --save-dev apollo-server-testing +``` + + + + +```typescript +import {Configuration} from "@tsed/common"; +import "@tsed/platform-express"; +import "@tsed/apollo"; +import {join} from "path"; + +@Configuration({ + apollo: { + server1: { + // GraphQL server configuration + path: "/", + playground: true, // enable playground GraphQL IDE. Set false to use Apollo Studio + plugins: [] // Apollo plugins + // Give custom server instance + // server?: (config: Config) => ApolloServer; + + // ApolloServer options + // ... + // See options descriptions on https://www.apollographql.com/docs/apollo-server/api/apollo-server.html + } + } +}) +export class Server {} +``` + +## Register plugins + +You can register plugins with the `plugins` property. The plugins are executed in the order of declaration. + +```typescript +import {Configuration} from "@tsed/common"; +import "@tsed/platform-express"; +import "@tsed/apollo"; +import {join} from "path"; + +@Configuration({ + apollo: { + server1: { + plugins: [] // Apollo plugins + } + } +}) +export class Server {} +``` + +But if you need to register and access to the injector, you can use the `$alterApolloServerPlugins` hook. For example, +you can register the `graphql-ws` necessary to support the `subscription` feature of GraphQL like this: + +```typescript +import {Constant, Inject, InjectorService, Module} from "@tsed/di"; +import {useServer} from "graphql-ws/lib/use/ws"; +import Http from "http"; +import Https from "https"; +import {WebSocketServer} from "ws"; +import {GraphQLWSOptions} from "./GraphQLWSOptions"; + +@Module() +export class GraphQLWSModule { + @Constant("graphqlWs", {}) + private settings: GraphQLWSOptions; + + @Inject(Http.Server) + private httpServer: Http.Server | null; + + @Inject(Https.Server) + private httpsServer: Https.Server | null; + + @Inject() + private injector: InjectorService; + + async $alterApolloServerPlugins(plugins: any[], settings: GraphQLWSOptions) { + const wsServer = await this.createWSServer(settings); + + this.injector.logger.info(`Create GraphQL WS server on: ${settings.path}`); + + return plugins.concat({ + serverWillStart() { + return { + async drainServer() { + await wsServer.dispose(); + } + }; + } + } as any); + } + + protected createWSServer(settings: GraphQLWSOptions) { + const wsServer = new WebSocketServer({ + ...(this.settings.wsServerOptions || {}), + ...settings.wsServerOptions, + server: this.httpsServer || this.httpServer!, + path: settings.path + }); + + return useServer( + { + ...(this.settings.wsUseServerOptions || {}), + ...settings.wsUseServerOptions, + schema: settings.schema + }, + wsServer + ); + } +} +``` + +::: tip Note +Ts.ED provide a `@tsed/graphql-ws` package to support the `subscription` feature of GraphQL. See [here](https://tsed.io/api/graphql-ws.html) for more details. +::: + +## Get Server instance + +ApolloService (or TypeGraphQLService) lets you retrieve an instance of ApolloServer. + +```ts +import {AfterRoutesInit} from "@tsed/common"; +import {Inject, Injectable} from "@tsed/di"; +import {ApolloService} from "@tsed/apollo"; +import {ApolloServerBase} from "apollo-server-core"; + +@Injectable() +export class UsersService implements AfterRoutesInit { + @Inject() + private ApolloService: ApolloService; + // or private typeGraphQLService: TypeGraphQLService; + + private server: ApolloServerBase; + + $afterRoutesInit() { + this.server = this.apolloService.get("server1")!; + } +} +``` + +For more information about ApolloServer, look at its +documentation [here](https://www.apollographql.com/docs/apollo-server/api/apollo-server.html); + +## Author + + + +## Maintainers + + + +
+ +
diff --git a/docs/tutorials/graphql-nexus.md b/docs/tutorials/graphql-nexus.md new file mode 100644 index 00000000000..3c742cd1968 --- /dev/null +++ b/docs/tutorials/graphql-nexus.md @@ -0,0 +1,125 @@ +--- +meta: + - name: description + content: Nexus + - name: keywords + content: ts.ed express typescript graphql websocket node.js javascript decorators +--- + +# GraphQL Nexus + + + +GraphQL Nexus' APIs were designed with type-safety in mind. We auto-generate type-definitions as you develop, and infer +them in your code, giving you IDE completion and type error catching out of the box! + +## Installation + +This example need to be used with `@tsed/apollo` module. So, you must install it before (see [here](/tutorials/graphql-apollo.md)). + + + + +```bash +npm install --save @tsed/apollo +npm install --save nexus graphql apollo-server-express +npm install --save-dev apollo-server-testing +``` + + + + +```bash +npm install --save @tsed/apollo graphql +npm install --save nexus graphql apollo-server-koa +npm install --save-dev apollo-server-testing +``` + + + + +Now, we can configure the Ts.ED server by importing `@tsed/apollo` in your Server: + +```typescript +import {Configuration} from "@tsed/common"; +import "@tsed/platform-express"; +import "@tsed/apollo"; +import {schema} from "./schema"; +import {join} from "path"; + +@Configuration({ + apollo: { + server1: { + // GraphQL server configuration + path: "/", + playground: true, // enable playground GraphQL IDE. Set false to use Apollo Studio + schema, + plugins: [] // Apollo plugins + + // Give custom server instance + // server?: (config: Config) => ApolloServer; + + // ApolloServer options + // ... + // See options descriptions on https://www.apollographql.com/docs/apollo-server/api/apollo-server.html + } + } +}) +export class Server {} +``` + +Then create `schema/index.ts`: + +```typescript +import {makeSchema} from "nexus"; +import {join} from "path"; + +export const schema = makeSchema({ + types: [], // 1 + outputs: { + typegen: join(process.cwd(), "..", "..", "nexus-typegen.ts"), // 2 + schema: join(process.cwd(), "..", "..", "schema.graphql") // 3 + } +}); +``` + +### Data Source + +Data source is one of the Apollo server features which can be used as option for your Resolver or Query. Ts.ED provides +a @@DataSourceService@@ decorator to declare a DataSource which will be injected to the Apollo server context. + +```typescript +import {DataSource} from "@tsed/typegraphql"; +import {RESTDataSource} from "apollo-datasource-rest"; +import {User} from "../models/User"; +@DataSource() +export class UserDataSource extends RESTDataSource { + constructor() { + super(); + this.baseURL = "https://myapi.com/api/users"; + } + + getUserById(id: string): Promise { + return this.get(`/${id}`); + } +} +``` + +## Need help + +This documentation isn't complete. You can find more documentation on the [official website](https://nexusjs.org/). +But code example with Ts.ED + Nexus are welcome. + +## Author + + + +## Maintainers + + + +
+ +
diff --git a/docs/tutorials/graphql-typegraphql.md b/docs/tutorials/graphql-typegraphql.md new file mode 100644 index 00000000000..8976f013855 --- /dev/null +++ b/docs/tutorials/graphql-typegraphql.md @@ -0,0 +1,292 @@ +--- +meta: + - name: description + content: Use Type-graphql with Ts.ED framework. GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. + - name: keywords + content: ts.ed express typescript typegraphql node.js javascript decorators +--- + +# TypeGraphQL + + + +Although TypeGraphQL is data-layer library agnostic, it integrates well with other decorator-based libraries, like TypeORM, sequelize-typescript or Typegoose. + +## Installation + +To begin, install the `@tsed/typegraphql` package: + + + + +```bash +npm install --save @tsed/typegraphql graphql apollo-server-express +npm install --save type-graphql apollo-datasource apollo-datasource-rest +npm install --save-dev apollo-server-testing +``` + + + + +```bash +npm install --save @tsed/typegraphql graphql apollo-server-koa +npm install --save type-graphql apollo-datasource apollo-datasource-rest +npm install --save-dev apollo-server-testing +``` + + + + +Now, we can configure the Ts.ED server by importing `@tsed/typegraphql` in your Server: + + + + +```ts +import {Configuration} from "@tsed/di"; +import "@tsed/platform-express"; +import "@tsed/typegraphql"; +import "./resolvers/index"; // barrel file with all resolvers + +@Configuration({ + typegraphql: { + server1: { + // GraphQL server configuration + path: "/", + playground: true, // enable playground GraphQL IDE. Set false to use Apollo Studio + + // resolvers?: (Function | string)[]; + // dataSources?: Function; + // server?: (config: Config) => ApolloServer; + + // Apollo Server options + // See options descriptions on https://www.apollographql.com/docs/apollo-server/api/apollo-server.html + serverConfig: { + plugins: [] + } + + // middlewareOptions?: ServerRegistration; + + // type-graphql + // See options descriptions on https://19majkel94.github.io/type-graphql/ + // buildSchemaOptions?: Partial; + } + } +}) +export class Server {} +``` + + + + + + + + + +## Types + +We want to get the equivalent of this type described in SDL: + +```graphql +type Recipe { + id: ID! + title: String! + description: String + creationDate: Date! + ingredients: [String!]! +} +``` + +So we create the Recipe class with all properties and types: + +```typescript +class Recipe { + id: string; + title: string; + description?: string; + creationDate: Date; + ingredients: string[]; +} +``` + +Then we decorate the class and its properties with decorators: + +<<< @/tutorials/snippets/graphql/recipe-type.ts + +The detailed rules for when to use nullable, array and others are described +in [fields and types docs](https://typegraphql.com/docs/types-and-fields.html). + +## Resolvers + +After that we want to create typical crud queries and mutation. To do that we create the resolver (controller) class +that will have injected RecipeService in the constructor: + +```ts +import {Inject} from "@tsed/di"; +import {ResolverController} from "@tsed/typegraphql"; +import {Arg, Args, Query} from "type-graphql"; +import {RecipeNotFoundError} from "../errors/RecipeNotFoundError"; +import {RecipesService} from "../services/RecipesService"; +import {Recipe} from "../types/Recipe"; +import {RecipesArgs} from "../types/RecipesArgs"; + +@ResolverController(Recipe) +export class RecipeResolver { + @Inject() + private recipesService: RecipesService; + + @Query((returns) => Recipe) + async recipe(@Arg("id") id: string) { + const recipe = await this.recipesService.findById(id); + if (recipe === undefined) { + throw new RecipeNotFoundError(id); + } + + return recipe; + } + + @Query((returns) => [Recipe]) + recipes(@Args() {skip, take}: RecipesArgs) { + return this.recipesService.findAll({skip, take}); + } +} +``` + +### Inject Ts.ED Context + +There are two ways to inject the Ts.ED context in your resolver. +The first one is to use the `@InjectContext()` decorator: + +```ts +import {Inject, InjectContext} from "@tsed/di"; +import {PLatformContext} from "@tsed/common"; +import {ResolverController} from "@tsed/typegraphql"; +import {Arg, Args, Query} from "type-graphql"; +import {Recipe} from "../types/Recipe"; + +@ResolverController(Recipe) +export class RecipeResolver { + @InjectContext() + private $ctx: PLatformContext; + + @Query((returns) => Recipe) + async recipe(@Arg("id") id: string) { + this.$ctx.logger.info("Hello world"); + console.log(this.$ctx.request.headers); + } +} +``` + +The second one is to use the `@Ctx()` decorator provided by TypeGraphQL: + +```ts +import {ResolverController} from "@tsed/typegraphql"; +import {Arg, Ctx, Query} from "type-graphql"; +import {Recipe} from "../types/Recipe"; + +@ResolverController(Recipe) +export class RecipeResolver { + @InjectContext() + private $ctx: Context; + + @Query((returns) => Recipe) + async recipe(@Arg("id") id: string, @Ctx("req.$ctx") $ctx: PlatformContext) { + $ctx.logger.info("Hello world"); + console.log($ctx.request.headers); + } +} +``` + +## Data Source + +Data source is one of the Apollo server features which can be used as option for your Resolver or Query. Ts.ED provides +a @@DataSourceService@@ decorator to declare a DataSource which will be injected to the Apollo server context. + +```typescript +import {DataSource} from "@tsed/typegraphql"; +import {RESTDataSource} from "apollo-datasource-rest"; +import {User} from "../models/User"; + +@DataSource() +export class UserDataSource extends RESTDataSource { + constructor() { + super(); + this.baseURL = "https://myapi.com/api/users"; + } + + getUserById(id: string): Promise { + return this.get(`/${id}`); + } +} +``` + +Then you can retrieve your data source through the context in your resolver like that: + +```typescript +import {ResolverController} from "@tsed/typegraphql"; +import {Arg, Authorized, Ctx, Query} from "type-graphql"; +import {UserDataSource} from "../datasources/UserDataSource"; +import {User} from "../models/User"; + +@ResolverController(User) +export class UserResolver { + @Authorized() + @Query(() => User) + public async user(@Arg("userId") userId: string, @Ctx("dataSources") dataSources: any): Promise { + const userDataSource: UserDataSource = dataSources.userDataSource; + + return userDataSource.getUserById(userId); + } +} +``` + +## Multiple GraphQL server + +If you register multiple GraphQL servers, you must specify the server id in the `@ResolverController` decorator. + +```typescript +@ResolverController(Recipe, {id: "server1"}) +``` + +Another solution is to not use `@ResolverController` (use `@Resolver` from TypeGraphQL), and declare explicitly the resolver in the server configuration: + +```typescript +@Configuration({ + graphql: { + server1: { + resolvers: { + RecipeResolver + } + }, + server2: { + resolvers: { + OtherResolver + } + } + } +}) +``` + +## Subscriptions + +Ts.ED provides a `@tsed/graphql-ws` package to support the `subscription` feature of GraphQL. See [here](/tutorials/graphql-ws.md) for more details. + +## Author + + + +## Maintainers + + + +
+ +
diff --git a/docs/tutorials/graphql-ws.md b/docs/tutorials/graphql-ws.md new file mode 100644 index 00000000000..69731d5133b --- /dev/null +++ b/docs/tutorials/graphql-ws.md @@ -0,0 +1,428 @@ +--- +meta: + - name: description + content: GraphQL Websocket allows you to use the `subscription` feature of GraphQL using the Websocket transport protocol. + - name: keywords + content: ts.ed express typescript graphql websocket node.js javascript decorators +--- + +# GraphQL WS + + + +GraphQL Websocket allows you to use the `subscription` feature of GraphQL using the Websocket transport protocol. +This module is based on the [graphql-ws](https://the-guild.dev/graphql/ws) package. It pre-configures the socket server and GraphQL server to work together. + +## Feature + +- Support multiple GraphQL server +- Enable subscription feature of GraphQL + +### Installation + +This module need to be used with `@tsed/apollo` module. So, you must install it before (see [here](/tutorials/graphql-apollo.md)). + + + + +```bash +npm install --save @tsed/graphql-ws graphql-ws +``` + + + + +```bash +yarn add @tsed/graphql-ws graphql-ws +``` + + + + +```typescript +import {Configuration} from "@tsed/common"; +import "@tsed/platform-express"; +import "@tsed/apollo"; +import "@tsed/graphql-ws"; +import {join} from "path"; + +@Configuration({ + apollo: { + server1: { + // GraphQL server configuration + path: "/", + playground: true, // enable playground GraphQL IDE. Set false to use Apollo Studio + plugins: [], // Apollo plugins + + wsServerOptions: { + // See options descriptions on + }, + wsUseServerOptions: { + // See options descriptions on GraphQL WS + } + + // Give custom server instance + // server?: (config: Config) => ApolloServer; + + // ApolloServer options + // ... + // See options descriptions on https://www.apollographql.com/docs/apollo-server/api/apollo-server.html + } + }, + graphqlWs: { + // global options + wsServerOptions: { + // See options descriptions on + }, + wsUseServerOptions: { + // See options descriptions on + } + } +}) +export class Server {} +``` + +## Register plugins + +You can register plugins with the `plugins` property. The plugins are executed in the order of declaration. + +```typescript +import {Configuration} from "@tsed/common"; +import "@tsed/platform-express"; +import "@tsed/apollo"; +import {join} from "path"; + +@Configuration({ + apollo: { + server1: { + plugins: [] // Apollo plugins + } + } +}) +export class Server {} +``` + +But if you need to register and access to the injector, you can use the `$alterApolloServerPlugins` hook. For example, +you can register the `graphql-ws` necessary to support the `subscription` feature of GraphQL like this: + +```typescript +import {Constant, Inject, InjectorService, Module} from "@tsed/di"; +import {useServer} from "graphql-ws/lib/use/ws"; +import Http from "http"; +import Https from "https"; +import {WebSocketServer} from "ws"; +import {GraphQLWSOptions} from "./GraphQLWSOptions"; + +@Module() +export class GraphQLWSModule { + @Constant("graphqlWs", {}) + private settings: GraphQLWSOptions; + + @Inject(Http.Server) + private httpServer: Http.Server | null; + + @Inject(Https.Server) + private httpsServer: Https.Server | null; + + @Inject() + private injector: InjectorService; + + createWSServer(settings: GraphQLWSOptions) { + const wsServer = new WebSocketServer({ + ...(this.settings.wsServerOptions || {}), + ...settings.wsServerOptions, + server: this.httpsServer || this.httpServer!, + path: settings.path + }); + + return useServer( + { + ...(this.settings.wsUseServerOptions || {}), + ...settings.wsUseServerOptions, + schema: settings.schema + }, + wsServer + ); + } + + async $alterApolloServerPlugins(plugins: any[], settings: GraphQLWSOptions) { + const wsServer = await this.createWSServer(settings); + + this.injector.logger.info(`Create GraphQL WS server on: ${settings.path}`); + + return plugins.concat({ + serverWillStart() { + return { + async drainServer() { + await wsServer.dispose(); + } + }; + } + } as any); + } +} +``` + +::: tip Note +Ts.ED provide a `@tsed/graphql-ws` package to support the `subscription` feature of GraphQL. See [here](https://tsed.io/api/graphql-ws.html) for more details. +::: + +## Nexus + +### Installation + + + + +```bash +npm install --save @tsed/apollo +npm install --save nexus graphql apollo-server-express +npm install --save-dev apollo-server-testing +``` + + + + +```bash +npm install --save @tsed/apollo graphql +npm install --save nexus graphql apollo-server-koa +npm install --save-dev apollo-server-testing +``` + + + + +Now, we can configure the Ts.ED server by importing `@tsed/apollo` in your Server: + +```typescript +import {Configuration} from "@tsed/common"; +import "@tsed/platform-express"; +import "@tsed/apollo"; +import {schema} from "./schema"; +import {join} from "path"; + +@Configuration({ + apollo: { + server1: { + // GraphQL server configuration + path: "/", + playground: true, // enable playground GraphQL IDE. Set false to use Apollo Studio + schema, + plugins: [] // Apollo plugins + + // Give custom server instance + // server?: (config: Config) => ApolloServer; + + // ApolloServer options + // ... + // See options descriptions on https://www.apollographql.com/docs/apollo-server/api/apollo-server.html + } + } +}) +export class Server {} +``` + +Then create `schema/index.ts`: + +```typescript +import {makeSchema} from "nexus"; +import {join} from "path"; + +export const schema = makeSchema({ + types: [], // 1 + outputs: { + typegen: join(process.cwd(), "..", "..", "nexus-typegen.ts"), // 2 + schema: join(process.cwd(), "..", "..", "schema.graphql") // 3 + } +}); +``` + +## TypeGraphQL + +### Installation + +To begin, install the `@tsed/typegraphql` package: + + + + +```bash +npm install --save @tsed/typegraphql graphql apollo-server-express +npm install --save type-graphql apollo-datasource apollo-datasource-rest +npm install --save-dev apollo-server-testing +``` + + + + +```bash +npm install --save @tsed/typegraphql graphql apollo-server-koa +npm install --save type-graphql apollo-datasource apollo-datasource-rest +npm install --save-dev apollo-server-testing +``` + + + + +Now, we can configure the Ts.ED server by importing `@tsed/typegraphql` in your Server: + + + + +<<< @/tutorials/snippets/graphql/server-configuration.ts + + + + + + + + + +### Types + +We want to get the equivalent of this type described in SDL: + +``` +type Recipe { + id: ID! + title: String! + description: String + creationDate: Date! + ingredients: [String!]! +} +``` + +So we create the Recipe class with all properties and types: + +```typescript +class Recipe { + id: string; + title: string; + description?: string; + creationDate: Date; + ingredients: string[]; +} +``` + +Then we decorate the class and its properties with decorators: + +<<< @/tutorials/snippets/graphql/recipe-type.ts + +The detailed rules for when to use nullable, array and others are described +in [fields and types docs](https://typegraphql.com/docs/types-and-fields.html). + +### Resolvers + +After that we want to create typical crud queries and mutation. To do that we create the resolver (controller) class +that will have injected RecipeService in the constructor: + +<<< @/tutorials/snippets/graphql/resolver-service.ts + +#### Multiple GraphQL server + +If you register multiple GraphQL servers, you must specify the server id in the `@ResolverController` decorator. + +```typescript +@ResolverController(Recipe, {id: "server1"}) +``` + +Another solution is to not use `@ResolverController` (use `@Resolver` from TypeGraphQL), and declare explicitly the resolver in the server configuration: + +```typescript +@Configuration({ + graphql: { + server1: { + resolvers: { + RecipeResolver + } + }, + server2: { + resolvers: { + OtherResolver + } + } + } +}) +``` + +### Data Source + +Data source is one of the Apollo server features which can be used as option for your Resolver or Query. Ts.ED provides +a @@DataSourceService@@ decorator to declare a DataSource which will be injected to the Apollo server context. + +<<< @/tutorials/snippets/graphql/datasource-service.ts + +Then you can retrieve your data source through the context in your resolver like that: + +<<< @/tutorials/snippets/graphql/resolver-data-source.ts + +## Get Server instance + +ApolloService (or TypeGraphQLService) lets you to retrieve an instance of ApolloServer. + +<<< @/tutorials/snippets/graphql/get-server-instance.ts + +For more information about ApolloServer, look at its +documentation [here](https://www.apollographql.com/docs/apollo-server/api/apollo-server.html); + +## Testing + +Here is an example to create a test server based on TypeGraphQL and run a query: + +::: tip + +The unit example is also available to test any Apollo Server! +::: + + + + +<<< @/tutorials/snippets/graphql/testing.jest.ts + + + + +<<< @/tutorials/snippets/graphql/testing.mocha.ts + + + + +<<< @/tutorials/snippets/graphql/resolver-service.ts + + + + +<<< @/tutorials/snippets/graphql/recipes-service.ts + + + + +<<< @/tutorials/snippets/graphql/recipe-type.ts + + + + +<<< @/tutorials/snippets/graphql/recipe-args.ts + + + + +## Author + + + +## Maintainers + + + +
+ +
diff --git a/docs/tutorials/graphql.md b/docs/tutorials/graphql.md index 812f74845ed..f21555c2ae2 100644 --- a/docs/tutorials/graphql.md +++ b/docs/tutorials/graphql.md @@ -3,11 +3,13 @@ meta: - name: description content: Use Apollo, Nexus or Type-graphql with Ts.ED framework. GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. - name: keywords - content: ts.ed express typescript mongoose node.js javascript decorators + content: ts.ed express typescript nexus typegraphql apollo graphql-ws node.js javascript decorators --- # GraphQL + + GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools. @@ -21,228 +23,36 @@ what they need and nothing more, makes it easier to evolve APIs over time, and e ## Apollo -### Installation - - - - -```bash -npm install --save @tsed/apollo graphql apollo-server-express -npm install --save-dev apollo-server-testing -``` - - - - -```bash -npm install --save @tsed/apollo graphql apollo-server-koa -npm install --save-dev apollo-server-testing -``` + - - +Unlock microservices potential with Apollo GraphQL. Seamlessly integrate APIs, manage data, and enhance performance. Explore Apollo's innovative solutions. +Ts.ED provides a module to create multiple Apollo server and bind it with Ts.ED server (Express or Koa). -```typescript -import {Configuration} from "@tsed/common"; -import "@tsed/platform-express"; -import "@tsed/apollo"; -import {join} from "path"; - -@Configuration({ - apollo: { - server1: { - // GraphQL server configuration - path: "/", - playground: true, // enable playground GraphQL IDE. Set false to use Apollo Studio - plugins: [] // Apollo plugins - // Give custom server instance - // server?: (config: Config) => ApolloServer; - - // ApolloServer options - // ... - // See options descriptions on https://www.apollographql.com/docs/apollo-server/api/apollo-server.html - } - } -}) -export class Server {} -``` +See [here](/tutorials/graphql-apollo.md) for more details. ## Nexus -### Installation - - - - -```bash -npm install --save @tsed/apollo -npm install --save nexus graphql apollo-server-express -npm install --save-dev apollo-server-testing -``` - - - + -```bash -npm install --save @tsed/apollo graphql -npm install --save nexus graphql apollo-server-koa -npm install --save-dev apollo-server-testing -``` +GraphQL Nexus' APIs were designed with type-safety in mind. We auto-generate type-definitions as you develop, and infer them in your code, giving you IDE completion and type error catching out of the box! - - - -Now, we can configure the Ts.ED server by importing `@tsed/apollo` in your Server: - -```typescript -import {Configuration} from "@tsed/common"; -import "@tsed/platform-express"; -import "@tsed/apollo"; -import {schema} from "./schema"; -import {join} from "path"; - -@Configuration({ - apollo: { - server1: { - // GraphQL server configuration - path: "/", - playground: true, // enable playground GraphQL IDE. Set false to use Apollo Studio - schema, - plugins: [] // Apollo plugins - - // Give custom server instance - // server?: (config: Config) => ApolloServer; - - // ApolloServer options - // ... - // See options descriptions on https://www.apollographql.com/docs/apollo-server/api/apollo-server.html - } - } -}) -export class Server {} -``` - -Then create `schema/index.ts`: - -```typescript -import {makeSchema} from "nexus"; -import {join} from "path"; - -export const schema = makeSchema({ - types: [], // 1 - outputs: { - typegen: join(process.cwd(), "..", "..", "nexus-typegen.ts"), // 2 - schema: join(process.cwd(), "..", "..", "schema.graphql") // 3 - } -}); -``` +See [here](/tutorials/graphql-nexus.md) for more details. ## TypeGraphQL -### Installation - -To begin, install the `@tsed/typegraphql` package: - - - - -```bash -npm install --save @tsed/typegraphql graphql apollo-server-express -npm install --save type-graphql apollo-datasource apollo-datasource-rest -npm install --save-dev apollo-server-testing -``` - - - - -```bash -npm install --save @tsed/typegraphql graphql apollo-server-koa -npm install --save type-graphql apollo-datasource apollo-datasource-rest -npm install --save-dev apollo-server-testing -``` - - - - -Now, we can configure the Ts.ED server by importing `@tsed/typegraphql` in your Server: - - - - -<<< @/tutorials/snippets/graphql/server-configuration.ts - - - - - - - - - -### Types - -We want to get the equivalent of this type described in SDL: - -``` -type Recipe { - id: ID! - title: String! - description: String - creationDate: Date! - ingredients: [String!]! -} -``` - -So we create the Recipe class with all properties and types: - -```typescript -class Recipe { - id: string; - title: string; - description?: string; - creationDate: Date; - ingredients: string[]; -} -``` - -Then we decorate the class and its properties with decorators: - -<<< @/tutorials/snippets/graphql/recipe-type.ts - -The detailed rules for when to use nullable, array and others are described -in [fields and types docs](https://typegraphql.com/docs/types-and-fields.html). - -### Resolvers - -After that we want to create typical crud queries and mutation. To do that we create the resolver (controller) class -that will have injected RecipeService in the constructor: - -<<< @/tutorials/snippets/graphql/resolver-service.ts - -### Data Source - -Data source is one of the Apollo server features which can be used as option for your Resolver or Query. Ts.ED provides -a @@DataSourceService@@ decorator to declare a DataSource which will be injected to the Apollo server context. - -<<< @/tutorials/snippets/graphql/datasource-service.ts + -Then you can retrieve your data source through the context in your resolver like that: +Although TypeGraphQL is data-layer library agnostic, it integrates well with other decorator-based libraries, like TypeORM, sequelize-typescript or Typegoose. -<<< @/tutorials/snippets/graphql/resolver-data-source.ts +See [here](/tutorials/graphql-typegraphql.md) for more details. -## Get Server instance +## GraphQL WS (subscription) -ApolloService (or TypeGraphQLService) lets you to retrieve an instance of ApolloServer. + -<<< @/tutorials/snippets/graphql/get-server-instance.ts +GraphQL Websocket allows you to use the `subscription` feature of GraphQL using the Websocket transport protocol. -For more information about ApolloServer, look at its -documentation [here](https://www.apollographql.com/docs/apollo-server/api/apollo-server.html); +See [here](/tutorials/graphql-ws.md) for more details. ## Testing diff --git a/docs/tutorials/snippets/graphql/datasource-service.ts b/docs/tutorials/snippets/graphql/datasource-service.ts index d7040e02b38..bede39f8e02 100644 --- a/docs/tutorials/snippets/graphql/datasource-service.ts +++ b/docs/tutorials/snippets/graphql/datasource-service.ts @@ -1,7 +1,6 @@ import {DataSource} from "@tsed/typegraphql"; import {RESTDataSource} from "apollo-datasource-rest"; import {User} from "../models/User"; - @DataSource() export class UserDataSource extends RESTDataSource { constructor() { diff --git a/packages/graphql/apollo/jest.config.js b/packages/graphql/apollo/jest.config.js index 4d639298a95..25b2cbcea87 100644 --- a/packages/graphql/apollo/jest.config.js +++ b/packages/graphql/apollo/jest.config.js @@ -6,7 +6,7 @@ module.exports = { coverageThreshold: { global: { statements: 94.57, - branches: 82.14, + branches: 79.31, functions: 100, lines: 94.57 } diff --git a/packages/graphql/typegraphql/jest.config.js b/packages/graphql/typegraphql/jest.config.js index 5e4d10e38b6..0aa9384ca2a 100644 --- a/packages/graphql/typegraphql/jest.config.js +++ b/packages/graphql/typegraphql/jest.config.js @@ -9,10 +9,10 @@ module.exports = { }, coverageThreshold: { global: { - statements: 94.89, - branches: 65.85, - functions: 94.73, - lines: 94.89 + statements: 94.65, + branches: 64.1, + functions: 94.65, + lines: 94.65 } } }; diff --git a/packages/graphql/typegraphql/package.json b/packages/graphql/typegraphql/package.json index 93f487b1472..049d8eb84c1 100644 --- a/packages/graphql/typegraphql/package.json +++ b/packages/graphql/typegraphql/package.json @@ -35,7 +35,6 @@ "@types/graphql": "^14.5.0", "class-validator": "~0.14.0", "eslint": "^8.12.0", - "graphql-ws": "5.14.2", "graphql-passport": "0.6.3", "type-graphql": "^1.1.1" }, diff --git a/yarn.lock b/yarn.lock index 0b058a53c8b..79079e5d360 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12484,7 +12484,7 @@ graphql-tools@^4.0.8: iterall "^1.1.3" uuid "^3.1.0" -graphql-ws@5.14.2, graphql-ws@^5.14.2: +graphql-ws@^5.14.2: version "5.14.2" resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.14.2.tgz#7db6f6138717a544d9480f0213f65f2841ed1c52" integrity sha512-LycmCwhZ+Op2GlHz4BZDsUYHKRiiUz+3r9wbhBATMETNlORQJAaFlAgTFoeRh6xQoQegwYwIylVD1Qns9/DA3w==