From 78fd594d723c66f28ee9bfa48b029287e54aaed9 Mon Sep 17 00:00:00 2001 From: Taras Date: Tue, 9 Aug 2022 15:00:27 -0400 Subject: [PATCH 01/10] Improvig the GraphQL Plugin Documentation --- plugins/graphql/README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/graphql/README.md b/plugins/graphql/README.md index f24ce10ef3..c214e2c777 100644 --- a/plugins/graphql/README.md +++ b/plugins/graphql/README.md @@ -1,6 +1,13 @@ -# graphql +# @frontside/backstage-plugin-graphql -Welcome to the plugin for Backstage that exposes a GraphQL API for data stored in the Backstage catalog. +> **Status** +> Alpha - this plugin is in early stages but it already includes many design features that came from our experience implementing GraphQL API for Backstage with our clients. You should expect the schema provided by this plugin to change because we're missing a number of important features. + +Backstage GraphQL Plugin adds a GraphQL API to a Backstage developer portal. The GraphQL API behaves like a gateway to provide a single API for a growing number of features provided by Backstage. It includes the following features, + +1. Graph schema - easily query relationships between data in the catalog. +2. Schema-based resolvers - add field resolvers using directives without requiring JavaScript. +3. Supports Node ## Getting started From d36c7f2f6784229bff536fb9d88c36da31ce08ad Mon Sep 17 00:00:00 2001 From: Taras Date: Tue, 9 Aug 2022 15:41:01 -0400 Subject: [PATCH 02/10] Added getting started information to the GraphQL plugin --- plugins/graphql/README.md | 52 +++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/plugins/graphql/README.md b/plugins/graphql/README.md index c214e2c777..7999346ee0 100644 --- a/plugins/graphql/README.md +++ b/plugins/graphql/README.md @@ -3,14 +3,56 @@ > **Status** > Alpha - this plugin is in early stages but it already includes many design features that came from our experience implementing GraphQL API for Backstage with our clients. You should expect the schema provided by this plugin to change because we're missing a number of important features. -Backstage GraphQL Plugin adds a GraphQL API to a Backstage developer portal. The GraphQL API behaves like a gateway to provide a single API for a growing number of features provided by Backstage. It includes the following features, +Backstage GraphQL Plugin adds a GraphQL API to a Backstage developer portal. The GraphQL API behaves like a gateway to provide a single API for a growing number of features provided by Backstage. + +It includes the following features, 1. Graph schema - easily query relationships between data in the catalog. 2. Schema-based resolvers - add field resolvers using directives without requiring JavaScript. -3. Supports Node +3. Modular schema definition - allows organizing related schema into [graphql-modules](https://www.graphql-modules.com/docs) +4. Strives to support - [GraphQL Server Specification] + +Some key features are currently missing. These features may change the schema in backward-incompatible ways. + +1. [`Connection`](https://relay.dev/docs/guides/graphql-server-specification/#connections) based on [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm).(see [#68](https://github.com/thefrontside/backstage/issues/68)) +2. `viewer` query for retrieving data for the current user. (see [#67](https://github.com/thefrontside/backstage/issues/67)) + +We plan to add these over time. If you're interested in contributing to this plugin, feel free to message us in [`#graphql` channel in Backstage Discord](https://discord.gg/yXEYX2h7Ed). ## Getting started -``` -yarn add @frontside/backstage-plugin-graphql -``` +You can install the GraphQL Plugin using the same process that you would use to install other backend Backtstage plugins. + +1. Run `yarn add @frontside/backstage-plugin-graphql` in `packages/backend` +2. Create `packages/backend/src/plugins/graphql.ts` file with the following content + + ```ts + import { createRouter } from '@frontside/backstage-plugin-graphql'; + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + return await createRouter({ + logger: env.logger, + catalog: env.catalog, + }); + } + ``` + +3. Add plugin's router to your backend API router in `packages/backend/src/index.ts` + + ```ts + // import the graphql plugin + import graphql from './plugins/graphql'; + + // create the graphql plugin environment + const graphqlEnv = useHotMemoize(module, () => createEnv('graphql')); + + // add `/graphql` route to your apiRouter + apiRouter.use('/graphql', await graphql(graphqlEnv)); + ``` + + See [packages/backend/src/index.ts](https://github.com/thefrontside/backstage/blob/main/packages/backend/src/index.ts) for an example. + From dd8753a27ff97d4f6a17839ded5fa9a082868a54 Mon Sep 17 00:00:00 2001 From: Taras Date: Tue, 9 Aug 2022 15:47:19 -0400 Subject: [PATCH 03/10] Adding README --- .changeset/yellow-houses-exercise.md | 5 +++++ plugins/graphql/package.json | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .changeset/yellow-houses-exercise.md diff --git a/.changeset/yellow-houses-exercise.md b/.changeset/yellow-houses-exercise.md new file mode 100644 index 0000000000..ffbeadf5ae --- /dev/null +++ b/.changeset/yellow-houses-exercise.md @@ -0,0 +1,5 @@ +--- +'@frontside/backstage-plugin-graphql': patch +--- + +Adding README for `@frontside/backstage-plugin-graphql` diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index 0b5a4ddc57..c294946a48 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -68,7 +68,8 @@ }, "files": [ "dist", - "src/app/modules/**/*.graphql" + "src/app/modules/**/*.graphql", + "README.md" ], "jest": { "testTimeout": 15000 From ded40ec03a78f584924fdb16fd02f3ec2c870519 Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 10 Aug 2022 09:06:02 -0400 Subject: [PATCH 04/10] Updated README.md --- plugins/graphql/README.md | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/plugins/graphql/README.md b/plugins/graphql/README.md index 7999346ee0..78b4beda25 100644 --- a/plugins/graphql/README.md +++ b/plugins/graphql/README.md @@ -1,15 +1,14 @@ # @frontside/backstage-plugin-graphql -> **Status** -> Alpha - this plugin is in early stages but it already includes many design features that came from our experience implementing GraphQL API for Backstage with our clients. You should expect the schema provided by this plugin to change because we're missing a number of important features. +> **Status: Alpha** - this plugin is in early stage of code maturity. It includes features that were battle tested on client's projects, but require some time in open source to settle. You should expect the schema provided by this plugin to change because we're missing a number of important features. -Backstage GraphQL Plugin adds a GraphQL API to a Backstage developer portal. The GraphQL API behaves like a gateway to provide a single API for a growing number of features provided by Backstage. +Backstage GraphQL Plugin adds a GraphQL API to a Backstage developer portal. The GraphQL API behaves like a gateway to provide a single API for accessing data from the Catalog and other plugins. Currently, it only supports the Backstage catalog. It includes the following features, -1. Graph schema - easily query relationships between data in the catalog. -2. Schema-based resolvers - add field resolvers using directives without requiring JavaScript. -3. Modular schema definition - allows organizing related schema into [graphql-modules](https://www.graphql-modules.com/docs) +1. **Graph schema** - easily query relationships between data in the catalog. +2. **Schema-based resolvers** - add field resolvers using directives without requiring JavaScript. +3. **Modular schema definition** - allows organizing related schema into [graphql-modules](https://www.graphql-modules.com/docs) 4. Strives to support - [GraphQL Server Specification] Some key features are currently missing. These features may change the schema in backward-incompatible ways. @@ -26,7 +25,7 @@ You can install the GraphQL Plugin using the same process that you would use to 1. Run `yarn add @frontside/backstage-plugin-graphql` in `packages/backend` 2. Create `packages/backend/src/plugins/graphql.ts` file with the following content - ```ts + ```ts import { createRouter } from '@frontside/backstage-plugin-graphql'; import { Router } from 'express'; import { PluginEnvironment } from '../types'; @@ -39,20 +38,20 @@ You can install the GraphQL Plugin using the same process that you would use to catalog: env.catalog, }); } - ``` + ``` 3. Add plugin's router to your backend API router in `packages/backend/src/index.ts` - ```ts - // import the graphql plugin - import graphql from './plugins/graphql'; + ```ts + // import the graphql plugin + import graphql from './plugins/graphql'; - // create the graphql plugin environment - const graphqlEnv = useHotMemoize(module, () => createEnv('graphql')); + // create the graphql plugin environment + const graphqlEnv = useHotMemoize(module, () => createEnv('graphql')); - // add `/graphql` route to your apiRouter - apiRouter.use('/graphql', await graphql(graphqlEnv)); - ``` + // add `/graphql` route to your apiRouter + apiRouter.use('/graphql', await graphql(graphqlEnv)); + ``` See [packages/backend/src/index.ts](https://github.com/thefrontside/backstage/blob/main/packages/backend/src/index.ts) for an example. From 1e2691e79bdfb1c391271c31e90defc734ff166c Mon Sep 17 00:00:00 2001 From: Taras Date: Thu, 11 Aug 2022 11:42:31 -0400 Subject: [PATCH 05/10] How to integrate with GraphiQL --- plugins/graphql/README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/plugins/graphql/README.md b/plugins/graphql/README.md index 78b4beda25..cc235f9afd 100644 --- a/plugins/graphql/README.md +++ b/plugins/graphql/README.md @@ -18,6 +18,11 @@ Some key features are currently missing. These features may change the schema in We plan to add these over time. If you're interested in contributing to this plugin, feel free to message us in [`#graphql` channel in Backstage Discord](https://discord.gg/yXEYX2h7Ed). +- [@frontside/backstage-plugin-graphql](#frontsidebackstage-plugin-graphql) + - [Getting started](#getting-started) + - [Integrations](#integrations) + - [Backstage GraphiQL Plugin](#backstage-graphiql-plugin) + ## Getting started You can install the GraphQL Plugin using the same process that you would use to install other backend Backtstage plugins. @@ -55,3 +60,35 @@ You can install the GraphQL Plugin using the same process that you would use to See [packages/backend/src/index.ts](https://github.com/thefrontside/backstage/blob/main/packages/backend/src/index.ts) for an example. +## Integrations + +### Backstage GraphiQL Plugin + +It's convenient to be able to query the Backstage GraphQL API from inside of Backstage App. You can accomplish this by installing the [Backstage GraphiQL Plugin](https://roadie.io/backstage/plugins/graphiQL/) and adding the GraphQL API endpoint to the GraphiQL Plugin API factory. + +1. Once you installed `@backstage/plugin-graphiql` plugin [with these instructions](https://roadie.io/backstage/plugins/graphiQL/) +2. Modify `packages/app/src/apis.ts` to add your GraphQL API as an endpoint + + ```ts + factory: ({ errorApi, githubAuthApi, discovery }) => + GraphQLEndpoints.from([ + { + id: 'backstage-backend', + title: 'Backstage GraphQL API', + // we use the lower level object with a fetcher function + // as we need to `await` the backend url for the graphql plugin + fetcher: async (params: any) => { + const graphqlURL = await discovery.getBaseUrl('graphql'); + return fetch(graphqlURL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(params), + }).then(res => res.json()); + }, + }, + ]) + } + ``` + + Checkout this example [`packages/app/src/apis.ts`](https://github.com/thefrontside/backstage/blob/main/packages/app/src/apis.ts#L35). + From b5442b926250b52babd475619a7f42a65f782ba9 Mon Sep 17 00:00:00 2001 From: Taras Date: Thu, 11 Aug 2022 12:18:50 -0400 Subject: [PATCH 06/10] How to integrate with Backstage API plugin --- plugins/graphql/README.md | 70 ++++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/plugins/graphql/README.md b/plugins/graphql/README.md index cc235f9afd..de319f15fb 100644 --- a/plugins/graphql/README.md +++ b/plugins/graphql/README.md @@ -22,6 +22,7 @@ We plan to add these over time. If you're interested in contributing to this plu - [Getting started](#getting-started) - [Integrations](#integrations) - [Backstage GraphiQL Plugin](#backstage-graphiql-plugin) + - [Backstage API Docs](#backstage-api-docs) ## Getting started @@ -69,26 +70,57 @@ It's convenient to be able to query the Backstage GraphQL API from inside of Bac 1. Once you installed `@backstage/plugin-graphiql` plugin [with these instructions](https://roadie.io/backstage/plugins/graphiQL/) 2. Modify `packages/app/src/apis.ts` to add your GraphQL API as an endpoint - ```ts - factory: ({ errorApi, githubAuthApi, discovery }) => - GraphQLEndpoints.from([ - { - id: 'backstage-backend', - title: 'Backstage GraphQL API', - // we use the lower level object with a fetcher function - // as we need to `await` the backend url for the graphql plugin - fetcher: async (params: any) => { - const graphqlURL = await discovery.getBaseUrl('graphql'); - return fetch(graphqlURL, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(params), - }).then(res => res.json()); - }, + ```ts + factory: ({ errorApi, githubAuthApi, discovery }) => + GraphQLEndpoints.from([ + { + id: 'backstage-backend', + title: 'Backstage GraphQL API', + // we use the lower level object with a fetcher function + // as we need to `await` the backend url for the graphql plugin + fetcher: async (params: any) => { + const graphqlURL = await discovery.getBaseUrl('graphql'); + return fetch(graphqlURL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(params), + }).then(res => res.json()); }, - ]) - } - ``` + }, + ]) + } + ``` Checkout this example [`packages/app/src/apis.ts`](https://github.com/thefrontside/backstage/blob/main/packages/app/src/apis.ts#L35). +### Backstage API Docs + +You might want to show the schema from your GraphQL API in API definition section of an API entity in Backstage. You can use the `/api/graphql/schema` endpoint to read the schema provided by your GraphQL API. Here is how you can accomplish this. + +1. Create API entity and reference `definition.$text: http://localhost:7007/api/graphql/schema` + + ```yaml + apiVersion: backstage.io/v1alpha1 + kind: API + metadata: + name: backstage-graphql-api + description: GraphQL API provided by GraphQL Plugin + spec: + type: graphql + owner: engineering@frontside.com + lifecycle: production + definition: + $text: http://localhost:7007/api/graphql/schema + ``` +2. Modify `app-config.yaml` to allow reading urls from `localhost:7007` + + ```yaml + backend: + ... + + reading: + allow: + - host: localhost:7007 + ``` + + \ No newline at end of file From 1b6944df5c2871beea477bd37b98429868273f6e Mon Sep 17 00:00:00 2001 From: Taras Date: Thu, 11 Aug 2022 13:32:25 -0400 Subject: [PATCH 07/10] Allow passing modules into GraphQL plugin --- .changeset/gentle-shoes-know.md | 5 ++ plugins/graphql/README.md | 20 +++++++- plugins/graphql/src/app/app.ts | 31 ++++++------ plugins/graphql/src/app/index.ts | 15 ++++-- plugins/graphql/src/tests/graphql.test.ts | 2 - plugins/graphql/src/tests/setupTests.ts | 58 ++++++++++++----------- 6 files changed, 81 insertions(+), 50 deletions(-) create mode 100644 .changeset/gentle-shoes-know.md diff --git a/.changeset/gentle-shoes-know.md b/.changeset/gentle-shoes-know.md new file mode 100644 index 0000000000..fb42be759a --- /dev/null +++ b/.changeset/gentle-shoes-know.md @@ -0,0 +1,5 @@ +--- +'@frontside/backstage-plugin-graphql': patch +--- + +Allow importing GraphQL Modules into Backstage GraphQL Plugin \ No newline at end of file diff --git a/plugins/graphql/README.md b/plugins/graphql/README.md index de319f15fb..d72f3f42ad 100644 --- a/plugins/graphql/README.md +++ b/plugins/graphql/README.md @@ -20,6 +20,10 @@ We plan to add these over time. If you're interested in contributing to this plu - [@frontside/backstage-plugin-graphql](#frontsidebackstage-plugin-graphql) - [Getting started](#getting-started) + - [Extending Schema](#extending-schema) + - [In Backstage Backend](#in-backstage-backend) + - [](#) + - [In Backstage Backend](#in-backstage-backend-1) - [Integrations](#integrations) - [Backstage GraphiQL Plugin](#backstage-graphiql-plugin) - [Backstage API Docs](#backstage-api-docs) @@ -61,6 +65,21 @@ You can install the GraphQL Plugin using the same process that you would use to See [packages/backend/src/index.ts](https://github.com/thefrontside/backstage/blob/main/packages/backend/src/index.ts) for an example. +## Extending Schema + +Backstage GraphQL Plugin allows developers to extend the schema provided by the plugin. Extending the schema allows you to query additional information for existing types or add new types. GraphQL is often used as a gateway to many different APIs. It's reasonable and expected that you may want to add custom types and fields. This section will tell you what you need to know to extend the schema. + +### In Backstage Backend + +You can extend the schema from inside of Backstage Backend by creating a [GraphQL Module](https://www.graphql-modules.com) that you can pass to the GraphQL API plugin's router. Here are step-by-step instructions on how to set up your GraphQL API plugin to provide a custom GraphQL Module. + + + +#### + +### In Backstage Backend + + ## Integrations ### Backstage GraphiQL Plugin @@ -123,4 +142,3 @@ You might want to show the schema from your GraphQL API in API definition sectio - host: localhost:7007 ``` - \ No newline at end of file diff --git a/plugins/graphql/src/app/app.ts b/plugins/graphql/src/app/app.ts index 4015029820..ad8eb4fc80 100644 --- a/plugins/graphql/src/app/app.ts +++ b/plugins/graphql/src/app/app.ts @@ -1,39 +1,42 @@ -import type { CatalogApi, ResolverContext } from './types'; -import { GetEnvelopedFn, envelop, useExtendContext } from '@envelop/core'; +import type { CatalogApi } from './types'; +import { envelop, useExtendContext } from '@envelop/core'; import { useGraphQLModules } from '@envelop/graphql-modules'; -import { Application, createApplication } from 'graphql-modules'; +import { createApplication, Module } from 'graphql-modules'; import { makeExecutableSchema } from '@graphql-tools/schema'; import { createLoader } from './loaders'; import { Catalog } from './modules/catalog/catalog'; import { Core } from './modules/core/core'; import { transform } from './schema-mapper'; -export interface App { - (): ReturnType>; +export interface createGraphQLAppOptions { + catalog: CatalogApi; + modules: Module[] } -export const schema = create().schema; - -export function createApp(catalog: CatalogApi): App { - const application = create(); - const loader = createLoader({ catalog }); +export function createGraphQLApp(options: createGraphQLAppOptions) { + const application = create(options); + const loader = createLoader(options); const run = envelop({ plugins: [ - useExtendContext(() => ({ catalog, loader })), + useExtendContext(() => ({ catalog: options.catalog, loader })), useGraphQLModules(application), ], }); - return run; + return { run, application }; +} + +interface CreateOptions { + modules: Module[] } -function create(): Application { +function create(options: CreateOptions) { return createApplication({ schemaBuilder: ({ typeDefs, resolvers }) => transform(makeExecutableSchema({ typeDefs, resolvers })), - modules: [Core, Catalog], + modules: [Core, Catalog, ...options.modules], }); } diff --git a/plugins/graphql/src/app/index.ts b/plugins/graphql/src/app/index.ts index 8def5c64ba..5ff4acb495 100644 --- a/plugins/graphql/src/app/index.ts +++ b/plugins/graphql/src/app/index.ts @@ -5,13 +5,15 @@ import { Logger } from 'winston'; import { graphqlHTTP } from 'express-graphql'; import { CatalogClient } from '@backstage/catalog-client'; import { printSchema } from 'graphql'; +import type { Module } from 'graphql-modules'; export interface RouterOptions { logger: Logger; catalog: CatalogClient; + modules?: Module[] } -import { schema, createApp } from './app'; +import { createGraphQLApp } from './app'; export * from './app'; @@ -20,7 +22,10 @@ export async function createRouter( ): Promise { const { logger } = options; - const app = createApp(options.catalog); + const { run, application } = createGraphQLApp({ + catalog: options.catalog, + modules: options.modules ?? [] + }); const router = Router(); router.use(express.json()); @@ -30,13 +35,13 @@ export async function createRouter( }); router.get('/schema', (_, response) => { - response.send(printSchema(app().schema)) + response.send(printSchema(application.schema)) }); router.use('/', graphqlHTTP(async () => { - const { parse, validate, contextFactory, execute } = app(); + const { parse, validate, contextFactory, execute } = run(); return { - schema, + schema: application.schema, graphiql: true, customParseFn: parse, customValidateFn: validate, diff --git a/plugins/graphql/src/tests/graphql.test.ts b/plugins/graphql/src/tests/graphql.test.ts index a7a428d521..7d2faa3a84 100644 --- a/plugins/graphql/src/tests/graphql.test.ts +++ b/plugins/graphql/src/tests/graphql.test.ts @@ -40,8 +40,6 @@ describe('querying the graphql API', () => { harness.create("User", { displayName: "Janelle Dawe" }) - let users = [...harness.all('User')]; - console.dir({ users }, { depth: 5 }); }); diff --git a/plugins/graphql/src/tests/setupTests.ts b/plugins/graphql/src/tests/setupTests.ts index 053fe135b7..98fceb5dc1 100644 --- a/plugins/graphql/src/tests/setupTests.ts +++ b/plugins/graphql/src/tests/setupTests.ts @@ -5,7 +5,7 @@ import type { Operation } from 'effection'; import type { Node } from '@frontside/graphgen'; import { PromiseOrValue } from '@envelop/core'; -import { createApp } from '..'; +import { createGraphQLApp } from '..'; import { Factory, World, createFactory } from './factory'; @@ -19,13 +19,15 @@ export interface GraphQLHarness { export function createGraphQLAPI(): GraphQLHarness { let factory = createFactory(); - let app = createApp(createSimulatedCatalog(factory)); - + let { run, application } = createGraphQLApp({ + catalog: createSimulatedCatalog(factory), + modules: [] + }); return { query(query: string): Operation { return function* Query() { - const { parse, validate, contextFactory, execute, schema } = app(); + const { parse, validate, contextFactory, execute, schema } = run(); let document = parse(`{ ${query} }`); let errors = validate(schema, document); if (errors.length) { @@ -34,7 +36,7 @@ export function createGraphQLAPI(): GraphQLHarness { let contextValue = yield* unwrap(contextFactory()); let result = yield* unwrap(execute({ - schema, + schema: application.schema, document, contextValue, })); @@ -81,7 +83,7 @@ export function createSimulatedCatalog(factory: Factory): CatalogApi { } } -function *concat(...iterables: Iterable[]): Iterable { +function* concat(...iterables: Iterable[]): Iterable { for (let iterable of iterables) { yield* iterable; } @@ -100,30 +102,30 @@ export function nodeToEntity(node: Node & World[keyof World]): Entity { } as Entity; if (node.__typename === "Component") { let { type, lifecycle } = node; - return { - ...entity, - spec: { type, lifecycle }, - relations: relations({ - ownedBy: node.owner, - partOf: node.partOf, - hasPart: node.subComponents, - consumesApi: node.consumes, - providesApi: node.provides, - dependsOn: node.dependencies, - }), - } + return { + ...entity, + spec: { type, lifecycle }, + relations: relations({ + ownedBy: node.owner, + partOf: node.partOf, + hasPart: node.subComponents, + consumesApi: node.consumes, + providesApi: node.provides, + dependsOn: node.dependencies, + }), + } } else if (node.__typename === "Group" || node.__typename === "User") { - let { displayName, email, picture } = node; - return { - ...entity, - spec: { - profile: { - displayName, - email, - picture, - } + let { displayName, email, picture } = node; + return { + ...entity, + spec: { + profile: { + displayName, + email, + picture, } } + } } else if (node.__typename === "API") { return { ...entity, @@ -157,7 +159,7 @@ function isPromise(x: PromiseOrValue): x is Promise { return typeof (x as Promise).then === 'function'; } -function* unwrap(promiseOrValue: PromiseOrValue | Operation): {[Symbol.iterator](): Iterator, T, any> } { +function* unwrap(promiseOrValue: PromiseOrValue | Operation): { [Symbol.iterator](): Iterator, T, any> } { if (isPromise(promiseOrValue)) { return yield promiseOrValue; } else { From 060a44f70a062132f4d8ccdd0c333a9899a99cb1 Mon Sep 17 00:00:00 2001 From: Taras Date: Thu, 11 Aug 2022 13:33:09 -0400 Subject: [PATCH 08/10] Add my-module example to Backstage app --- packages/backend/package.json | 1 + packages/backend/src/graphql/my-module.ts | 18 ++++++++++++++++++ packages/backend/src/plugins/graphql.ts | 2 ++ 3 files changed, 21 insertions(+) create mode 100644 packages/backend/src/graphql/my-module.ts diff --git a/packages/backend/package.json b/packages/backend/package.json index e6c255347a..588bb45094 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -39,6 +39,7 @@ "@frontside/backstage-plugin-effection-inspector-backend": "0.1.1", "@frontside/backstage-plugin-humanitec-backend": "^0.3.0", "@frontside/backstage-plugin-graphql": "^0.2.0", + "graphql-modules": "^2.1.0", "@gitbeaker/node": "^34.6.0", "@internal/plugin-healthcheck": "0.1.0", "@octokit/rest": "^18.5.3", diff --git a/packages/backend/src/graphql/my-module.ts b/packages/backend/src/graphql/my-module.ts new file mode 100644 index 0000000000..a759a2bc00 --- /dev/null +++ b/packages/backend/src/graphql/my-module.ts @@ -0,0 +1,18 @@ +import { createModule, gql } from 'graphql-modules' + +export const myModule = createModule({ + id: 'my-module', + dirname: __dirname, + typeDefs: [ + gql` + type Query { + hello: String! + } + ` + ], + resolvers: { + Query: { + hello: () => 'world' + } + } +}) \ No newline at end of file diff --git a/packages/backend/src/plugins/graphql.ts b/packages/backend/src/plugins/graphql.ts index b6a16b60a2..1040104c39 100644 --- a/packages/backend/src/plugins/graphql.ts +++ b/packages/backend/src/plugins/graphql.ts @@ -1,11 +1,13 @@ import { createRouter } from '@frontside/backstage-plugin-graphql'; import { Router } from 'express'; import { PluginEnvironment } from '../types'; +import { myModule } from '../graphql/my-module'; export default async function createPlugin( env: PluginEnvironment, ): Promise { return await createRouter({ + modules: [myModule], logger: env.logger, catalog: env.catalog, }); From 6025f2671d3501f35d2cbc62ee57377584b7870a Mon Sep 17 00:00:00 2001 From: Taras Date: Thu, 11 Aug 2022 13:43:39 -0400 Subject: [PATCH 09/10] Added instructions for registering custom GraphQL modules --- plugins/graphql/README.md | 55 +++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/plugins/graphql/README.md b/plugins/graphql/README.md index d72f3f42ad..02cf8bdda8 100644 --- a/plugins/graphql/README.md +++ b/plugins/graphql/README.md @@ -22,8 +22,6 @@ We plan to add these over time. If you're interested in contributing to this plu - [Getting started](#getting-started) - [Extending Schema](#extending-schema) - [In Backstage Backend](#in-backstage-backend) - - [](#) - - [In Backstage Backend](#in-backstage-backend-1) - [Integrations](#integrations) - [Backstage GraphiQL Plugin](#backstage-graphiql-plugin) - [Backstage API Docs](#backstage-api-docs) @@ -71,14 +69,55 @@ Backstage GraphQL Plugin allows developers to extend the schema provided by the ### In Backstage Backend -You can extend the schema from inside of Backstage Backend by creating a [GraphQL Module](https://www.graphql-modules.com) that you can pass to the GraphQL API plugin's router. Here are step-by-step instructions on how to set up your GraphQL API plugin to provide a custom GraphQL Module. - - - -#### +You can extend the schema from inside of Backstage Backend by creating a [GraphQL Module](https://www.graphql-modules.com) that you can pass to the GraphQL API plugin's router. Here are step-by-step instructions on how to set up your GraphQL API plugin to provide a custom GraphQL Module. + +1. Add `graphql-modules` to your backend packages in `packages/backend` with `yarn add graphql-modules` +2. Create `packages/backend/src/graphql` directory that will contain your modules +3. Create a file for your first GraphQL module called `packages/backend/src/graphql/my-module.ts` with the following content + + ```ts + import { createModule, gql } from 'graphql-modules' + + export const myModule = createModule({ + id: 'my-module', + dirname: __dirname, + typeDefs: [ + gql` + type Query { + hello: String! + } + ` + ], + resolvers: { + Query: { + hello: () => 'world' + } + } + }) + ``` -### In Backstage Backend +4. Register your GraphQL module with the GraphQL API plugin by modifying `packages/backend/src/plugins/graphql.ts`. + You must import your new module and pass it to the router using `modules: [myModule]`. Here is what the result + should look like. + + ```ts + import { createRouter } from '@frontside/backstage-plugin-graphql'; + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + import { myModule } from '../graphql/my-module'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + return await createRouter({ + modules: [myModule], + logger: env.logger, + catalog: env.catalog, + }); + } + ``` +5. Start your backend and you should be able to query your API with `{ hello }` query to get `{ data: { hello: 'world' } }` ## Integrations From 9aa19816bb4325942c1d40585ee8b7b7d273040f Mon Sep 17 00:00:00 2001 From: Taras Date: Thu, 11 Aug 2022 17:54:21 -0400 Subject: [PATCH 10/10] Added documentation about directives --- plugins/graphql/README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/plugins/graphql/README.md b/plugins/graphql/README.md index 02cf8bdda8..025e6489e6 100644 --- a/plugins/graphql/README.md +++ b/plugins/graphql/README.md @@ -22,6 +22,10 @@ We plan to add these over time. If you're interested in contributing to this plu - [Getting started](#getting-started) - [Extending Schema](#extending-schema) - [In Backstage Backend](#in-backstage-backend) + - [Directives API](#directives-api) + - [`@field(at: String!)`](#fieldat-string) + - [`@relation(type: String!)`](#relationtype-string) + - [`@extend(type: String!)`](#extendtype-string) - [Integrations](#integrations) - [Backstage GraphiQL Plugin](#backstage-graphiql-plugin) - [Backstage API Docs](#backstage-api-docs) @@ -119,6 +123,30 @@ You can extend the schema from inside of Backstage Backend by creating a [GraphQ 5. Start your backend and you should be able to query your API with `{ hello }` query to get `{ data: { hello: 'world' } }` +### Directives API + +Every GraphQL API consists of two things - a schema and resolvers. The schema describes relationships and fields that you can retrieve from the API. The resolvers describe how you retrieve the data described in the schema. The Backstage GraphQL Plugin provides several directives to help write a GraphQL schema and resolvers for Backstage. These directives take into account some specificities for Backstage APIs to make it easier to write schema and implement resolvers. This section will explain each directive and the assumptions they make about the developer's intention. + +#### `@field(at: String!)` + +`@field` directive allows you to access properties on the object using a given path. It allows you to specify a resolver for a field from the schema without actually writing a real resolver. Under the hood, it's creating the resolver for you. It's used extensively in the [`catalog.graphql`](https://github.com/thefrontside/backstage/blob/main/plugins/graphql/src/app/modules/catalog/catalog.graphql) module to retrieve properties like `namespace`, `title` and others. For example, here is how we define the resolver for the `Entity#name` field `name: String! @field(at: "metadata.name")` + +#### `@relation(type: String!)` + +`@relation` directive allows you to resolve relationships between entities. Similar to `@field` directive, it provides the resolver from the schema so you do not have to write a resolver yourself. It assumes that relationships are defined as standard `Entity` relationships. The `type` argument allows you to specify the name of the relationship. It will automatically look up the entity in the catalog. For example, here is how we define `consumers` of an API - `consumers: [Component] @relation(type: "apiConsumedBy")`. + +#### `@extend(type: String!)` + +`@extend` directive allows you to inherit fields from another entity. We created this directive to make it easier to implement types that extend from `Entity` and other types. It makes GraphQL types similar to extending types in TypeScript. In TypeScript, when a class extends another class, the child class automatically inherits properties and methods of the parent class. This functionality doesn't have an equivalent in GraphQL. Without this directive, the `Component` type in GraphQL would need to reimplement many fields that are defined on Entity which leads to lots of duplication. Using this type, you can easily create a new type that includes all of the properties of the parent. For example, if you wanted to create a `Repository` type, you can do the following, + +```graphql +type Repository @extends(type: "Entity") { + languages: [String] @field('spec.languages') +} +``` + +Your `Repository` type will automatically get all of the properties from `Entity`. + ## Integrations ### Backstage GraphiQL Plugin