diff --git a/packages/web/docs/package.json b/packages/web/docs/package.json index 93947fe69d1..0c63b081f10 100644 --- a/packages/web/docs/package.json +++ b/packages/web/docs/package.json @@ -28,6 +28,7 @@ "tailwind-merge": "2.6.0" }, "devDependencies": { + "@mdx-js/typescript-plugin": "^0.0.8", "@tailwindcss/typography": "0.5.16", "@theguild/tailwind-config": "0.6.3", "@types/react": "18.3.18", diff --git a/packages/web/docs/public/blog-assets/add-reactivity-to-an-existing-source/PimpMyGraphQL.webm b/packages/web/docs/public/blog-assets/add-reactivity-to-an-existing-source/PimpMyGraphQL.webm new file mode 100644 index 00000000000..26fbcb2cda9 Binary files /dev/null and b/packages/web/docs/public/blog-assets/add-reactivity-to-an-existing-source/PimpMyGraphQL.webm differ diff --git a/packages/web/docs/public/blog-assets/graphql-as-a-best-practice-for-modern-angular-apps/graphiql.webm b/packages/web/docs/public/blog-assets/graphql-as-a-best-practice-for-modern-angular-apps/graphiql.webm new file mode 100644 index 00000000000..586e369f38c Binary files /dev/null and b/packages/web/docs/public/blog-assets/graphql-as-a-best-practice-for-modern-angular-apps/graphiql.webm differ diff --git a/packages/web/docs/public/blog-assets/graphql-deep-dive-5/2.webm b/packages/web/docs/public/blog-assets/graphql-deep-dive-5/2.webm new file mode 100644 index 00000000000..30dbff0276f Binary files /dev/null and b/packages/web/docs/public/blog-assets/graphql-deep-dive-5/2.webm differ diff --git a/packages/web/docs/public/blog-assets/graphql-deep-dive-5/8.webm b/packages/web/docs/public/blog-assets/graphql-deep-dive-5/8.webm new file mode 100644 index 00000000000..58e5b25241e Binary files /dev/null and b/packages/web/docs/public/blog-assets/graphql-deep-dive-5/8.webm differ diff --git a/packages/web/docs/public/blog-assets/graphql-deep-dive-5/9.webm b/packages/web/docs/public/blog-assets/graphql-deep-dive-5/9.webm new file mode 100644 index 00000000000..2cb0b3c5fb5 Binary files /dev/null and b/packages/web/docs/public/blog-assets/graphql-deep-dive-5/9.webm differ diff --git a/packages/web/docs/public/blog-assets/graphql-geo-strike/gameplay.mp4 b/packages/web/docs/public/blog-assets/graphql-geo-strike/gameplay.mp4 new file mode 100644 index 00000000000..5f9b0f9ab8d Binary files /dev/null and b/packages/web/docs/public/blog-assets/graphql-geo-strike/gameplay.mp4 differ diff --git a/packages/web/docs/public/blog-assets/graphql-geo-strike/gameplay.webm b/packages/web/docs/public/blog-assets/graphql-geo-strike/gameplay.webm new file mode 100644 index 00000000000..bf538f2850b Binary files /dev/null and b/packages/web/docs/public/blog-assets/graphql-geo-strike/gameplay.webm differ diff --git a/packages/web/docs/public/blog-assets/graphql-geo-strike/map.mp4 b/packages/web/docs/public/blog-assets/graphql-geo-strike/map.mp4 new file mode 100644 index 00000000000..66553b2b26d Binary files /dev/null and b/packages/web/docs/public/blog-assets/graphql-geo-strike/map.mp4 differ diff --git a/packages/web/docs/public/blog-assets/graphql-geo-strike/map.webm b/packages/web/docs/public/blog-assets/graphql-geo-strike/map.webm new file mode 100644 index 00000000000..abadbcff346 Binary files /dev/null and b/packages/web/docs/public/blog-assets/graphql-geo-strike/map.webm differ diff --git a/packages/web/docs/public/blog-assets/graphql-stencil-apollo/stencil.webm b/packages/web/docs/public/blog-assets/graphql-stencil-apollo/stencil.webm new file mode 100644 index 00000000000..dff1460e98e Binary files /dev/null and b/packages/web/docs/public/blog-assets/graphql-stencil-apollo/stencil.webm differ diff --git a/packages/web/docs/public/blog-assets/introducing-graphql-inspector/cli.webm b/packages/web/docs/public/blog-assets/introducing-graphql-inspector/cli.webm new file mode 100644 index 00000000000..380ce051f7a Binary files /dev/null and b/packages/web/docs/public/blog-assets/introducing-graphql-inspector/cli.webm differ diff --git a/packages/web/docs/public/blog-assets/manage-circular-imports-hell-in-graphql-modules/math.webm b/packages/web/docs/public/blog-assets/manage-circular-imports-hell-in-graphql-modules/math.webm new file mode 100644 index 00000000000..523a388e708 Binary files /dev/null and b/packages/web/docs/public/blog-assets/manage-circular-imports-hell-in-graphql-modules/math.webm differ diff --git a/packages/web/docs/public/blog-assets/whatsapp-clone/68ea32458cb9961d5132fe3c6ba8788c.webm b/packages/web/docs/public/blog-assets/whatsapp-clone/68ea32458cb9961d5132fe3c6ba8788c.webm new file mode 100644 index 00000000000..0b8d54f2880 Binary files /dev/null and b/packages/web/docs/public/blog-assets/whatsapp-clone/68ea32458cb9961d5132fe3c6ba8788c.webm differ diff --git a/packages/web/docs/public/blog-assets/whatsapp-clone/app.webm b/packages/web/docs/public/blog-assets/whatsapp-clone/app.webm new file mode 100644 index 00000000000..72bcf41454c Binary files /dev/null and b/packages/web/docs/public/blog-assets/whatsapp-clone/app.webm differ diff --git a/packages/web/docs/src/app/blog/(posts)/a-new-year-for-schema-stitching/page.mdx b/packages/web/docs/src/app/blog/(posts)/a-new-year-for-schema-stitching/page.mdx new file mode 100644 index 00000000000..bdf8cfdffd0 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/a-new-year-for-schema-stitching/page.mdx @@ -0,0 +1,94 @@ +--- +title: A New Year for GraphQL Schema Stitching +description: 2021 is the year to give Schema Stitching a fresh look after its recent renovation +date: 2021-01-14 +authors: gmac +tags: [graphql, graphql-federation, graphql-tools, schema-stitching] +--- + +> If you'd told me eight months ago that I'd end up +> [writing a book](https://github.com/gmac/schema-stitching-handbook) on Schema Stitching, I +> wouldn't have believed it. + +That's because eight months ago my team was trying to mature an old GraphQL prototype into a formal +API product, and our dependence on Schema Stitching v4 (a +[famously abandoned project](https://apollographql.com/docs/federation/migrating-from-stitching) of +Apollo) imposed numerous bugs, limitations, and performance bottlenecks on our work. I researched +transitioning to the newer [Apollo Federation](https://apollographql.com/docs/federation) as a +solution to these woes, but the involved migration path proved too daunting for our immediate time +constraints. + +## Not "The Old" Schema Stitching + +Something remarkable happened at this critical juncture for my team... + +[GraphQL Tools](https://graphql-tools.com/docs/introduction) (the repo of Schema Stitching +libraries) was brought back to life by **The Guild** and friends. The most pivotal new addition was +a small mention of "[type merging](https://graphql-tools.com/docs/stitch-type-merging)", a feature +that introduced automated query planning for types distributed across services (very similar to +Apollo Federation). The new v5 library was still pretty raw, but it started a feedback loop that +matured the library dramatically in v6, and then again in v7 (and will again in v8!). + +During these development cycles, numerous missing Stitching features were identified and added: +[batch execution](https://github.com/gmac/schema-stitching-handbook/wiki/Batching-Arrays-and-Queries#what-is-query-batching) +for consolidating sub-service requests, +[computed fields](https://graphql-tools.com/docs/stitch-type-merging#computed-fields) for +encapsulating service concerns, +[interface proxies](https://graphql-tools.com/docs/stitch-type-merging#computed-fields) for bridging +interfaces across services, and +[schema directives](https://graphql-tools.com/docs/stitch-directives-sdl) for configuring stitched +schemas via SDL annotations. Also, the +[library documentation](https://graphql-tools.com/docs/stitch-combining-schemas) was rewritten from +top-to-bottom for a new generation of users. + +Coming into 2021, Schema Stitching has been revitalized into an un-opinionated library capable of +building an opinionated framework like Apollo Federation. If you want out-of-the-box workflows, +Federation is the tool for you. Otherwise, Stitching is the comparable alternative that keeps you in +control of your full system architecture. + +## The Handbook + +The tricky thing about the self-service nature of Schema Stitching is that its usage extends beyond +just the scope of library documentation. To bridge this knowledge gap, the +[Schema Stitching Handbook](https://github.com/gmac/schema-stitching-handbook) was started as a +companion guide that walks through building stitching libraries into practical applications. The +content is split into a few major sections: + +1. **Foundation** chapters cover the setup and use of core stitching library features. These + chapters provide code examples and detailed explanations of subjects covered in the + [official documentation](https://graphql-tools.com/docs/stitch-combining-schemas). +2. **Architecture** chapters explore larger structural concerns around + [testing](https://github.com/gmac/schema-stitching-handbook/blob/master/continuous-integration-testing), + [versioning](https://github.com/gmac/schema-stitching-handbook/blob/master/versioning-schema-releases), + and + [releasing](https://github.com/gmac/schema-stitching-handbook/blob/master/hot-schema-reloading) + stitched schemas. Stitching has no CLI or + [managed services](https://apollographql.com/docs/federation/managed-federation/overview), but + you may find that these tools are not prohibitively difficult to self-service. +3. **Other Integration** chapters explore how + [other tools](https://github.com/gmac/schema-stitching-handbook/tree/master/federation-services) + and + [programming languages](https://github.com/gmac/schema-stitching-handbook/tree/master/subservice-languages/ruby) + fit into a stitched schema. Stitching is perfectly compatible with any valid GraphQL resource, + and we're excited to catalog that here. + +The Handbook is versioned as a living repo to provide a comprehensive guide that may evolve with the +library. We look to you, fellow developers, to contribute your own discoveries of how Schema +Stitching integrates with tools and languages that we haven't explored yet. + +## A New Year + +Whether you're planning a new API project, looking to migrate an existing one, or curious of +alternatives for your current distributed GraphQL architecture, 2021 is the year to give Schema +Stitching a fresh look. Read the +[new documentation](https://graphql-tools.com/docs/stitch-combining-schemas), +[review the handbook](https://github.com/gmac/schema-stitching-handbook), +[watch the video series](https://youtube.com/watch?v=8-mZqjgIdiI&list=PLTJ2vmU3jbWy6JntdRQZAmy0mcpYu2OD1), +or [join us for a conversation](https://github.com/ardatan/graphql-tools/discussions) to learn about +the exciting progress that was made last year. + +--- + +- [GraphQL Tools repository](https://github.com/ardatan/graphql-tools) +- [GraphQL Schema Stitching Handbook](https://github.com/gmac/schema-stitching-handbook) +- [Schema Stitching official documentation](https://graphql-tools.com/docs/stitch-combining-schemas) diff --git a/packages/web/docs/src/app/blog/(posts)/accounts.js-1.0-rc/page.mdx b/packages/web/docs/src/app/blog/(posts)/accounts.js-1.0-rc/page.mdx new file mode 100644 index 00000000000..4e5bb2cc1fe --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/accounts.js-1.0-rc/page.mdx @@ -0,0 +1,306 @@ +--- +title: Announcing Accounts.js 1.0 Release Candidate +tags: [accounts-js, graphql-modules, graphql] +authors: [niccolo] +date: 2024-01-08 +description: + Introducing Accounts.js 1.0 Release Candidate, an end to end authentication and accounts + management solution. +--- + +The first release candidate of [Accounts.js](https://www.accountsjs.com/) 1.0 is now officially +available! + +It's the culmination of a long process of rearchitecting the whole framework, which is finally a +first-class citizen of the [graphql-modules](https://the-guild.dev/graphql/modules) package. It +supports the latest GraphQL.js v16 and [graphql-tools](https://the-guild.dev/graphql/tools) v10 as +well as any modern GraphQL server including Apollo Server v4 and +[GraphQL Yoga](https://the-guild.dev/graphql/yoga-server) v5. + +## About Me + +My name is Niccolò Belli, [darkbasic](https://github.com/darkbasic) on GitHub. I'm a freelance +full-stack web developer passionate about open source who loves to work with Typescript and GraphQL, +along with managing Linux servers. I've been working on Open Source technologies since many years +and I've recently become an Accounts.js maintainer because I was tired of the existing alternatives +and their lack of GraphQL integration. While I don't like overly-opinionated frameworks that lock +you in into their ecosystems I love to work with libraries that allow you to quickly prototype your +application while being scalable and highly customizable. That's why I'm also a MikroORM +collaborator, which is the best Node.js ORM available and allows me to retain any amount of +flexibility if I decide to manually write big PostgreSQL queries and hydrate the results back into +the ORM for further processing. I've developed many tools around this workflow, including +automatically generating [slonik](https://github.com/gajus/slonik) types via the ORM metadata to +manually write composable SQL queries that are type safe at runtime and build time. This is material +for another blog post but I would love to make everything open source once my prerequisite +[zod PR](https://github.com/colinhacks/zod/pull/2234) gets merged. + +## What Is Accounts.JS + +The `@accounts` suite of packages aims to provide an end-to-end authentication and accounts +management solution with n user-friendly way to start while preserving options for configuration. +These packages offer OAuth support for popular providers such as Instagram or Twitter, two-factor +authentication, password-based accounts, recovery options, and customizable account creation and +validation. + +To integrate `accounts-js` into your application, you need to configure these three components: + +1. **Transports**: The flexibility of `accounts-js` allows it to be integrated with different types + of APIs. For now, we provide packages for both GraphQL and REST. + +2. **Databases**: Accounts.js provides a + [native Mongo integration](https://github.com/accounts-js/accounts/tree/master/packages/database-mongo). + Additionally, it offers + [MikroORM](https://github.com/accounts-js/accounts/tree/master/packages/database-mikro-orm) and + [Typeorm](https://github.com/accounts-js/accounts/tree/master/packages/database-typeorm) + integrations, which lets you use `accounts-js` with any database. Optionally, you can use Redis + to store the session data, or provide a custom database adapter that will work with existing + authentication strategies by implementing the `DatabaseInterface`. + +3. **Strategies**: You can use multiple strategies to let your users access your app. For now, it + supports password-based, magic link, and OAuth authentication methods. + +**Note**: Accounts.js is a **full-stack** solution, providing a full set of packages to seamlessly +implement your chosen authentication workflow on the **client** as well! + +## The New Architecture + +In Accounts.js 1.0, we use `graphql-modules` to compose the authentication framework, automatically +piecing together your preferred database adapter(s) with the authentication service(s) of your +choice (password-based, OAuth, etc). As mentioned before, `accounts-js` currently supports GraphQL +and REST. For the former, `graphql-modules` automatically provides the schema based on the modules +you're using, while for the latter, it provides dependency injection across the various modules to +piece them together. + +```ts +const app = createApplication({ + modules: [ + createAccountsCoreModule({ tokenSecret: 'secret' }), + createAccountsPasswordModule({ + requireEmailVerification: true, + sendVerificationEmailAfterSignup: true, + }), + createAccountsMongoModule({ dbConn }), + ], + schemaBuilder: buildSchema({ typeDefs, resolvers }), +``` + +If your application already uses `graphql-modules`, all you need to do is add the `accounts.js` +modules of your choice to your own application module. Otherwise, it's a matter of providing your +resolvers and type definitions to the `buildSchema` function. + +```ts +// GraphQL Yoga 5 +const yoga = createYoga({ + plugins: [useGraphQLModules(app)], + context: ctx => context(ctx, { createOperationController }) +}) +createServer(yoga).listen() + +// Apollo Server 4 +const apollo = new ApolloServer({ + gateway: app.createApolloGateway() +}) +startStandaloneServer(apollo, { + context: ctx => context(ctx, { createOperationController }) +}) +``` + +At this point, whatever your GraphQL server of choice, your authenticated application is just a few +lines of code away. + +> But what if all I care is REST? + +Use the `graphql-modules` injector to retrieve the `AccountsServer` instance and feed it to +`@accounts/rest-express`! + +```ts +const controller = app.createOperationController({ + context: {} +}) +const accountsServer = controller.injector.get(AccountsServer) +expressApp.use(accountsExpress(accountsServer)) +``` + +Alternatively, if you don't want to use `graphql-modules`, you can still manually instantiate the +providers. Here's +[an example](https://github.com/accounts-js/accounts/blob/master/examples/rest-express-typescript-without-modules/src/index.ts). + +## The New MikroORM Database Adapter + +The second big change in Accounts.js 1.0 is the release of the brand new +[MikroORM](https://mikro-orm.io/) database adapter. MikroORM is a TypeScript ORM for Node.js based +on Data Mapper, Unit of Work and Identity Map patterns. It is also very well-written and actively +developed. Today also marks the release of the v6 of MikroORM, which incorporates +[my recent work](https://github.com/mikro-orm/mikro-orm/pull/4321) to automatically batch references +and collections and retrieve them via dataloaders firing a single query. This is especially useful +with GraphQL transport since it automatically solves its notorious N+1 problem without you even +noticing it—more information [here](https://mikro-orm.io/docs/dataloaders). + +```ts +@Entity() +export class User extends AccountsUser { + @Property() + firstName: string + + @Property({ nullable: true }) + lastName?: string + + constructor({ firstName, lastName, ...otherProps }: CtorArgs) { + super(otherProps) + this.firstName = firstName + if (lastName) { + this.lastName = lastName + } + } +} +``` + +The Accounts.js MikroORM database adapter can be backed by your database of choice (PostgreSQL, +MySQL, MariaDB, SQLite, MongoDB). It won't force you into any existing entity schema: you can use +the existing entities or provide your own, but please make sure to extend the base ones so that +authentication can occur. + +```ts +entities: [ + User, + getUserSchema({ AccountsUser, abstract: true }), + getEmailSchema({ UserEntity: User }), + getServiceSchema({ UserEntity: User }), + getSessionSchema({ UserEntity: User }), +], +``` + +Under the hood, it uses MikroORM's [EntitySchema](https://mikro-orm.io/docs/entity-schema), so you +must also provide the schema for the base entities. + +## Breaking Changes + +`@accounts/boost` has been removed. It was no longer deemed necessary because of the new +`graphql-modules` architecture that already allows you to plug and play various modules. For +example, instead of providing an existing database connection, you can let `@accounts/module-mongo` +create a new one for you: + +```ts +const app = createApplication({ + modules: [ + [...], + // If you don't provide dbConn it will automatically + // create a new one, but the module needs to be awaited. + await createAccountsMongoModule(), +``` + +`@accounts/graphql-api` has been moved into the following packages: + +- `@accounts/module-core` +- `@accounts/module-magic-link` +- `@accounts/module-mikro-orm` +- `@accounts/module-mongo` +- `@accounts/module-password` +- `@accounts/module-typeorm` + +These packages can assemble your desired authentication workflow and your preferred database +adapter. Despite the significant changes under the hood, I strived to keep the public API mostly the +same, so migrating to 1.0 should be a manageable effort. + +## What's New + +- We switched from pnpm to yarn 4. +- We now return code 401 unmasked errors when unauthorized. +- Added the `requireEmailVerification` option to require the user to verify the email in order to be + able to authenticate. +- You can now enable both `ambiguousErrorMessages` and `enableAutologin` if + `requireEmailVerification` is disabled. +- `@accounts/password` provides express endpoints to verify the email or reset the password whenever + the user clicks on the links received via email. +- `@accounts/rest-express` now validates its inputs via express-validator. +- The examples now use graphql-yoga v5 instead of the old apollo-server 2. +- A new `@apollo/server` v4 example has been added. +- A basic graphql-http example has been added. +- A MikroORM example has been added. +- The accounts-microservice example has been rewritten from scratch to use modern graphql-tools. +- Docs have been refactored to use `docusaurus-plugin-typedoc-api` instead of + `scripts/generate-api-docs.ts`. +- New and much improved CI release workflow. +- Basic support for running tests from vscode. +- Upgraded `graphql-modules` to v3 alpha +- Upgraded `@graphql-tools/merge` to v9 +- Upgraded `@graphql-tools/schema` to v10 +- Upgraded `@graphql-tools/utils` to v10 +- Upgraded `graphql` to v16 +- Upgraded `typeorm` to 0.3.17 +- Upgraded `@apollo/client` to 3.8 +- Upgraded `@graphql-codegen` to v5 +- Upgraded `mongodb` to v6 +- Upgraded `ioredis` to v5 +- Upgraded `jsonwebtoken` to v9 +- Upgraded `lodash` to 4.17 +- Upgraded `pg` to 8.11 +- Upgraded `request-ip` to 3.3 +- Upgraded `oauth` to 0.10 +- Upgraded `node-fetch` to 2.7 +- Upgraded `express` to 4.18 +- Upgraded `emittery` to 0.13 +- Upgraded `@levminer/speakeasy` to 1.4 +- Upgraded `@graphql-tools/delegate` to v10 +- Upgraded `@graphql-tools/stitch` to v9 +- Upgraded `@graphql-tools/wrap` to v10 +- Upgraded `mongoose` to v8 +- Upgraded `react` to 18.2 +- Upgraded `jest` to 29 +- Upgraded `typescript` to 5.3 +- Upgraded `eslint` to v8 +- Upgraded `prettier` to v3 +- Upgraded `@apollo/server` to 4.9 +- Upgraded `docusaurus` to v3 + +## Remaining Work for the Stable Release + +OAuth authentication, while working, surely deserves some love. While REST endpoints for OAuth +should be functional, there are no mutations or resolvers yet, meaning you can't use them with the +GraphQL transport. I'd also like to review the OAuth code and write some examples. +`@accounts/oauth-instagram`, in particular, still relies upon the deprecated `request` package and +should be updated. Other popular OAuth providers like Facebook still need to be added (but PRs +exist). + +Before the 1.0 stable gets released, I plan to get the existing providers in a better shape, +together with examples and the relevant GraphQL schema. + +## Post 1.0 + +I want to create a new `@accounts/phone` authentication service which lets you authenticate via SMS +OTPs. That would be especially useful in react-native applications where you could automatically +read the SMS and automate the authentication process. + +Currently, Accounts.js bundles CommonJS code. While CommonJS can be imported in both CommonJS and +ESM applications, that would rule out Deno/Bun support. For the same reason, we cannot use a wrapper +either: while that would allow us to use ESM imports, it wouldn't be real ESM and thus won't be +compatible. The remaining alternatives are pure ESM and dual packages. While several library authors +opted for the former because of +[dual package hazard](https://nodejs.org/api/packages.html#packages_dual_package_hazard) concerns, I +weigh the benefits differently. While dual package hazards are real, the whole GraphQL ecosystem +relies on dual packages; thus, using `instance of` is already a hazard. These authors suggest using +`async imports` to import their pure ESM libraries in common projects, which exposes everyone to the +same hazard (not even considering that this is only viable in async contexts). That goes against why +they decided to bundle pure ESM in the first place. I think it's still too early to target pure ESM, +and dual package is the lesser evil, so I'm leaning towards that for future releases, but I'm ready +to change my mind if you provide enough arguments. + +I'd also like to implement some form of account linking, where users could link their existing +account with a different authentication service (for example, password-based and OAuth). + +I want to extend Multi-Factor Authentication outside of the password service, baking it into the +core of accounts.js so that any authentication service can take advantage of it. + +If future major versions of Accounts.js introduce breaking changes to the database structure, I +would like to provide migrations directly via the `@accounts/mikro-orm` package. + +Last but not least, I would like to bake in cookies authentication. Not only would that fare better +against XSS, but it would also allow server-side rendering and thus enable the usage of frameworks +like Next.js. Alternative storage methods would remain available for those using native +applications. + +At the end of the day, 1.0 is just a number, and I really want to provide stable APIs via semantic +versioning. + +Accounts.js is an open-source project, and we welcome your +[contributions](https://github.com/accounts-js/accounts)! diff --git a/packages/web/docs/src/app/blog/(posts)/accountsjs-graphql-modules/page.mdx b/packages/web/docs/src/app/blog/(posts)/accountsjs-graphql-modules/page.mdx new file mode 100644 index 00000000000..e471ace413b --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/accountsjs-graphql-modules/page.mdx @@ -0,0 +1,561 @@ +--- +title: Authentication with accounts-js & GraphQL Modules +description: How to implement server-side using accounts-js, GraphQL-Modules and Apollo-Server +date: 2018-11-16 +authors: arda +tags: [graphql, graphql-modules, authentication] +--- + +import { Callout } from '@theguild/components' + +When starting a backend project, two of the biggest concerns will usually be the right structure of +the project and authentication. If you could skip thinking and planning about these two, starting a +new backend project can be much easier. + + + If you haven't checked out our [blog post about authentication and authorization in GraphQL + Modules](/blog/graphql-modules-auth), please read that before! + + +Internally, we use [GraphQL-Modules](https://graphql-modules.com) and +[accounts-js](https://github.com/accounts-js/accounts) to help us with those two decisions, +GraphQL-Modules helps us solve our architectural problems in modular, schema-first approaches with +the power of GraphQL and accounts-js helps us create our authentication solutions by providing a +simple API together with client and server libraries that saves us a lot of the groundwork around +authentication. + +If you haven't heard about [accounts-js](https://github.com/accounts-js/accounts) before, it is a +set of libraries to provide a full-stack authentication and accounts-management solutions for +Javascript. + +It is really customizable; so you can write any plugins for your own authentication methods or use +the already existing email-password or the Facebook and Twitter OAuth integration packages. + +accounts-js has connector libraries for MongoDB and Redis, but you can write your own database +handler by implementing a simple interface. + +accounts-js provides a ready to use GraphQL API if you install their GraphQL library, and **we are +happy to announce that the GraphQL library is now internally built using GraphQL-Modules**! + +It doesn't affect people who are not using GraphQL Modules, but it helps the maintainers of +accounts-js and simplifies the integration for GraphQL-Modules-based projects. + +## How to Implement Server-Side Using accounts-js, GraphQL Modules and Apollo Server + +First install required dependencies from npm or yarn: + +```sh +yarn add mongodb @accounts/server @accounts/password @accounts/database-manager @accounts/mongo @accounts/graphql-api @graphql-modules/core apollo-server graphql-import-node +``` + +Let's assume that we're using MongoDB as our database, password-based authentication and +ApolloServer: + +```ts +import 'graphql-import-node' +import { ApolloServer } from 'apollo-server' +import { MongoClient } from 'mongodb' +import { DatabaseManager } from '@accounts/database-manager' +import { AccountsModule } from '@accounts/graphql-api' +import MongoDBInterface from '@accounts/mongo' +import { AccountsPassword } from '@accounts/password' +import { AccountsServer } from '@accounts/server' +import { resolvers } from './resolvers' +import * as typeDefs from './typeDefs.graphql' + +const PORT = process.env.MONGO_URI || 4000 +const MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost:27017/myDb' +const TOKEN_SECRET = process.env.TOKEN_SECRET || 'myTokenSecret' + +async function main() { + const mongoClient = await MongoClient.connect(MONGO_URI, { + useNewUrlParser: true, + native_parser: true + }) + const db = mongoClient.db() + const userStorage = new MongoDBInterface(db, { + convertUserIdToMongoObjectId: false + }) + // Create database manager (create user, find users, sessions etc) for accounts-js + const accountsDb = new DatabaseManager({ + sessionStorage: userStorage, + userStorage + }) + // Create accounts server that holds a lower level of all accounts operations + const accountsServer = new AccountsServer( + { + db: accountsDb, + tokenSecret: TOKEN_SECRET + }, + { + password: new AccountsPassword() + } + ) + const { schema } = new GraphQLModule({ + typeDefs, + resolvers, + imports: [AccountsModule.forRoot({ accountsServer })], + providers: [ + { + provide: Db, + useValue: db // Use MongoDB's instance inside DI + } + ] + }) + const apolloServer = new ApolloServer({ + schema, + context: session => session, + introspection: true + }) + const { url } = await apolloServer.listen(PORT) + console.log(`Server listening: ${url}`) +} + +main() +``` + +And we can extend User type with custom fields in our schema, and add a mutation which is restricted +to authenticated clients. + +```graphql +type Query { + allPosts: [Post] +} + +type Mutation { + addPost(title: String, content: String): ID @auth +} + +type User { + posts: [Post] +} + +type Post { + id: ID + title: String + content: String + author: User +} +``` + +Finally, let's define some resolvers for it: + +```ts +export const resolvers = { + User: { + posts({ _id }, args, { injector }) { + const db = injector.get(Db) + const Posts = db.collection('posts') + return Posts.find({ userId: _id }).toArray() + } + }, + Post: { + id: ({ _id }) => _id, + author({ userId }, args, { injector }) { + const accountsServer = injector.get(AccountsServer) + return accountsServer.findUserById(userId) + } + }, + Query: { + allPosts(root, args, { injector }) { + const db = injector.get(Db) + const Posts = db.collection('posts') + return Posts.find().toArray() + } + }, + Mutation: { + addPost(root, { title, content }, { injector, userId }: ModuleContext) { + const db = injector.get(Db) + const Posts = db.collection('posts') + const { insertedId } = Posts.insertOne({ title, content, userId }) + return insertedId + } + } +} +``` + +When you print the whole app's schema, you would see something like above: + +```graphql +type TwoFactorSecretKey { + ascii: String + base32: String + hex: String + qr_code_ascii: String + qr_code_hex: String + qr_code_base32: String + google_auth_qr: String + otpauth_url: String +} + +input TwoFactorSecretKeyInput { + ascii: String + base32: String + hex: String + qr_code_ascii: String + qr_code_hex: String + qr_code_base32: String + google_auth_qr: String + otpauth_url: String +} + +input CreateUserInput { + username: String + email: String + password: String +} + +type Query { + twoFactorSecret: TwoFactorSecretKey + getUser: User + allPosts: [Post] +} + +type Mutation { + createUser(user: CreateUserInput!): ID + verifyEmail(token: String!): Boolean + resetPassword(token: String!, newPassword: String!): Boolean + sendVerificationEmail(email: String!): Boolean + sendResetPasswordEmail(email: String!): Boolean + changePassword(oldPassword: String!, newPassword: String!): Boolean + twoFactorSet(secret: TwoFactorSecretKeyInput!, code: String!): Boolean + twoFactorUnset(code: String!): Boolean + impersonate(accessToken: String!, username: String!): ImpersonateReturn + refreshTokens(accessToken: String!, refreshToken: String!): LoginResult + logout: Boolean + authenticate(serviceName: String!, params: AuthenticateParamsInput!): LoginResult + addPost(title: String, content: String): Post +} + +type Tokens { + refreshToken: String + accessToken: String +} + +type LoginResult { + sessionId: String + tokens: Tokens +} + +type ImpersonateReturn { + authorized: Boolean + tokens: Tokens + user: User +} + +type EmailRecord { + address: String + verified: Boolean +} + +type User { + id: ID! + emails: [EmailRecord!] + username: String + posts: [Post] +} + +input UserInput { + id: ID + email: String + username: String +} + +input AuthenticateParamsInput { + access_token: String + access_token_secret: String + provider: String + password: String + user: UserInput + code: String +} +type Post { + id: ID + title: String + content: String + author: User +} +``` + +## How to Implement Client-Side Using accounts-js, React and Apollo-Client + +Now we can create a simple frontend app by using Apollo-Client and accounts-js client for this +backend app. The example below shows some example code that works on these two. + +```tsx +import React, { Component } from 'react' +import { render } from 'react-dom' +import ApolloClient from 'apollo-boost' +import gql from 'graphql-tag' +import { ApolloProvider, Mutation, Query } from 'react-apollo' +import { AccountsClient } from '@accounts/client' +import { AccountsClientPassword } from '@accounts/client-password' +import GraphQLClient from '@accounts/graphql-client' + +const apolloClient = new ApolloClient({ + async request(operation) { + const tokens = await accountsClient.getTokens() + if (tokens) { + operation.setContext({ + headers: { + 'accounts-access-token': tokens.accessToken + } + }) + } + }, + uri: 'http://localhost:4000/graphql' +}) + +const accountsGraphQL = new GraphQLClient({ graphQLClient: apolloClient }) +const accountsClient = new AccountsClient({}, accountsGraphQL) +const accountsPassword = new AccountsClientPassword(accountsClient) + +const ALL_POSTS_QUERY = gql` + query AllPosts { + allPosts { + id + title + content + author { + username + } + } + } +` + +const ADD_POST_MUTATION = gql` + mutation AddPost($title: String, $content: String) { + addPost(title: $title, content: $content) + } +` + +class App extends Component { + state = { + credentials: { + username: '', + password: '' + }, + newPost: { + title: '', + content: '' + }, + user: null + } + componentDidMount() { + return this.updateUserState() + } + async updateUserState() { + const tokens = await accountsClient.refreshSession() + if (tokens) { + const user = await accountsGraphQL.getUser() + await this.setState({ user }) + } + } + renderAllPosts() { + return ( + + {({ data, loading, error }) => { + if (loading) { + return

Loading...

+ } + if (error) { + return

Error: {error}

+ } + return data.allPosts.map((post: any) => ( +
  • +

    {post.title}

    +

    {post.content}

    +

    Author: {post.author.username}

    +
  • + )) + }} +
    + ) + } + renderLoginRegister() { + return ( +
    + Login - Register +
    +

    + +

    +

    + +

    +

    + +

    +

    + +

    +
    +
    + ) + } + renderAddPost() { + return ( + + {addPost => ( +
    + Add Post +
    +

    + +

    +

    + +

    +

    + { + e.preventDefault() + addPost({ + variables: { + title: this.state.newPost.title, + content: this.state.newPost.content + } + }) + }} + /> +

    +
    +
    + )} +
    + ) + } + render() { + return ( +
    +

    All Posts

    + {this.renderAllPosts()} + {this.state.user ? this.renderAddPost() : this.renderLoginRegister()} +
    + ) + } +} + +render( + + + , + document.getElementById('root') +) +``` + +As you can see from the example, it can be really easy to create an application that has +authentication in modular and future-proof approach. + +You can learn more about accounts-js from the +[docs](https://accounts-js.netlify.com/docs/getting-started.html) of this great library for more +features such as Two-Factor Authentication and Facebook and Twitter integration using OAuth. + +Also, you can learn more about GraphQL-Modules on the [website](https://graphql-modules.com) and see +how you can add GraphQL Modules features into your system in a gradual and selective way. + +If you want strict types based on GraphQL Schema, for each module, GraphQL Code Generator has +built-in support for GraphQL-Modules based projects. +[See the docs](https://github.com/Urigo/graphql-modules#graphql-code-generator-integration) for more +details. + +You can check out our example about this integration +[https://github.com/ardatan/graphql-modules-accountsjs-boilerplate](https://github.com/ardatan/graphql-modules-accountsjs-boilerplate). + +## All Posts about GraphQL Modules + +- [GraphQL Modules — Feature based GraphQL Modules at scale](/blog/graphql-modules) +- [Why is True Modular Encapsulation So Important in Large-Scale GraphQL Projects?](/blog/modular-encapsulation-graphql-modules) +- [Why did we implement our own Dependency Injection library for GraphQL-Modules?](/blog/dependency-injection-library-in-graphql-modules) +- [Scoped Providers in GraphQL-Modules Dependency Injection](/blog/graphql-modules-scoped-providers) +- [Writing a GraphQL TypeScript project w/ GraphQL-Modules and GraphQL-Code-Generator](/blog/graphql-typescript-modules-codegen) +- [Authentication and Authorization in GraphQL (and how GraphQL-Modules can help)](/blog/graphql-modules-auth) +- [Authentication with accounts-js & GraphQL Modules](/blog/accountsjs-graphql-modules) +- [Manage Circular Imports Hell with GraphQL-Modules](/blog/manage-circular-imports-hell-in-graphql-modules) diff --git a/packages/web/docs/src/app/blog/(posts)/add-reactivity-to-an-existing-source/page.mdx b/packages/web/docs/src/app/blog/(posts)/add-reactivity-to-an-existing-source/page.mdx new file mode 100644 index 00000000000..eccc6d3645f --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/add-reactivity-to-an-existing-source/page.mdx @@ -0,0 +1,178 @@ +--- +title: Add reactivity to an existing source using GraphQL Mesh +authors: jycouet +tags: [graphql, graphql-mesh, subscription] +date: 2020-12-15 +description: + How To extend an existing endpoint with subscriptions to bring reactivity to your application. +--- + +import { VideoEmbed } from '#components/video-embed' +import { Callout } from '@theguild/components' + +## Introduction + +Working on the +[code generator for svelte plugin](https://github.com/ticruz38/graphql-codegen-svelte-apollo), I was +looking for a public graphQL endpoint having queries, mutations and subscriptions for nice examples. +Unfortunately I didn't find anything out of the box, ready to use for my demos. So I decided to +extend an existing source! + +## Starting Point + +Across my researches I found one cool public endpoint regarding +[SpaceX](https://api.spacex.land/graphql). It has `Queries` and `Mutations` ready to use! So I +decided to pimp this endpoint, and bring a bit of reactivity with subscriptions. + +The idea is to publish the new user when it's inserted with this `mutation`: + +```graphql +mutation addUser { + insert_users(objects: { name: "jyc" }) { + affected_rows + returning { + name + } + } +} +``` + +How hard is to do this? Let's find out... **Let's Pimp this GraphQL!** + + + +## Implementation + +Multiple ways are possible! + +
    + Spoiler alert + Thanks to `graphql-mesh`, it's dead easy! 🚀🚀🚀 +
    + +### 1. Create a New Repo + +```sh +mkdir newRepo +cd newRepo +yarn init +``` + +### 2. Add `graphql-mesh` Packages + +```sh +yarn add @graphql-mesh/cli @graphql-mesh/graphql @graphql-mesh/transform-resolvers-composition graphql +``` + +### 3. Add `.meshrc.yaml` File in the Root Folder With + +```yaml filename=".meshrc.yaml" +sources: + - name: spacexGQL + handler: + graphql: + endpoint: https://api.spacex.land/graphql/ + +additionalTypeDefs: | + extend type Subscription { + usersAdded: [users] + } + +transforms: + - resolversComposition: + - resolver: 'Mutation.insert_users' + composer: ./src/composition/insert_user + +additionalResolvers: + - type: Subscription + field: usersAdded + pubsubTopic: usersAdded +``` + +Where I + +- declare the graphQL source +- extend `Subscription` to listen to user added users +- compose the existing `Mutation.insert_users` + +### 4. Add `./src/composition/insert_user.js` File with All the Logic for This New Mutation Composition + +```js filename="insert_user.js" +module.exports = next => async (root, args, context, info) => { + // add returning.id to the selection + info.operation.selectionSet.selections[0].selectionSet.selections.push({ + kind: 'Field', + name: { + kind: 'Name', + value: 'returning' + }, + arguments: [], + directives: [], + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { + kind: 'Name', + value: 'id' + }, + arguments: [], + directives: [] + } + ] + } + }) + + // send the mutation to the graphQL source + const result = await next(root, args, context, info) + + // select returned ids + const ids = result.returning.map(c => c.id) + + // Query users enforcing fields to return + const responseUser = await context.spacexGQL.apiQuery.users( + { + where: { id: { _in: ids } } + }, + { + fields: { + id: true, + name: true, + rocket: true, + timestamp: true, + twitter: true + } + } + ) + + // publish new users + context.pubsub.publish('usersAdded', responseUser) + + return result +} +``` + +That's it! + +## Wrap Up + +- Extending an endpoint with subscription is easy! _Thx to tooling._ + +- You can find the source on [GitHub](https://github.com/jycouet/space-x-land-with-sub) + +- You see all this in action directly in the + [playground](https://space-x-land-with-sub.herokuapp.com) + +I'm now ready to work on the demo for +[**Svelte Codegen**](https://github.com/ticruz38/graphql-codegen-svelte-apollo)! Stay tunned ;) + +[@jycouet](https://twitter.com/jycouet) + + + Special thanks to [n1ru4l](https://twitter.com/n1rual) who helped me find the nicest way to do it + ;) + diff --git a/packages/web/docs/src/app/blog/(posts)/announcing-free-single-sign-on-for-graphql-hive/page.mdx b/packages/web/docs/src/app/blog/(posts)/announcing-free-single-sign-on-for-graphql-hive/page.mdx new file mode 100644 index 00000000000..d739f8c8ea7 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/announcing-free-single-sign-on-for-graphql-hive/page.mdx @@ -0,0 +1,43 @@ +--- +title: Announcing free SSO (Single Sign On) for GraphQL Hive +tags: [graphql, graphql-federation, graphql-hive] +authors: laurin +date: 2022-11-07 +description: Link your GraphQL Hive Organization to your OAuth OIDC provider of choice. +--- + +Today we are excited to announce that due to popular demand we now support connecting a GraphQL Hive +Organization to an OAuth Open ID Connect Provider (OIDC) for Single Sign On (SSO). + +> [GraphQL Hive](https://the-guild.dev/graphql/hive) is our schema registry solution for +> continuously evolving your GraphQL schema backed by performance insights and usage statistics. +> Whether you are working in a small company with a single GraphQL service or a big multi-team +> company with multiple (federated or composed) GraphQL services, GraphQL Hive is the perfect tool +> to help you manage your GraphQL schema. + +GraphQL Hive gives you the flexibility of choosing your existing identity provider of choice for +authenticating users, such as Google Workspaces, Okta, Auth0, and many more. + +Users logging into GraphQL Hive via an OIDC provider will be automatically added and limited to +interacting within your organization. + +You can start using OIDC SSO on the GraphQL Hive Cloud version for any type of organization at no +additional cost starting today. + +Offering this feature for free is a big deal for us. We at The Guild believe that Single Sign On is +a core security requirement and not a luxury feature that you have to buy in. + +You can learn more about the broken relationship between SaaS vendors and SSO on the +[SSO Wall of Shame](https://sso.tax/). + +In addition, OAuth Open ID Connect login is not only limited to the GraphQL Hive Cloud version, but +is also available on self-hosted GraphQL Hive +[which we announced Last month](/blog/announcing-self-hosted-graphql-hive). + +Huge thanks go out to the incredible helpful team at [SuperTokens](https://supertokens.com/) that +helped us a lot with questions and feedback on shipping this feature. GraphQL Hive is +[fully open source](https://github.com/graphql-hive/console) and also uses the SuperTokens Open +Source project for user authentication. + +- [Get started with adding OAuth OIDC SSO to your GraphQL Hive organization](https://the-guild.dev/graphql/hive/docs/management/sso-oidc-provider) +- [Get started with with custom OIDC providers for self-hosted Hive](https://the-guild.dev/graphql/hive/docs/self-hosting/oidc-login) diff --git a/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/changes-history.png b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/changes-history.png new file mode 100644 index 00000000000..c16859a6667 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/changes-history.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/monitoring-preview.png b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/monitoring-preview.png new file mode 100644 index 00000000000..22d1f77b382 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/monitoring-preview.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/page.mdx b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/page.mdx new file mode 100644 index 00000000000..d1298f4dea7 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/page.mdx @@ -0,0 +1,185 @@ +--- +title: Announcing GraphQL Hive, the complete GraphQL API manager +tags: [graphql, graphql-federation, graphql-hive] +authors: [charlypoly, arda, laurin, saihaj, kamil, dotan, uri, dimitri] +date: 2022-05-24 +description: We are incredibly excited to share with you today the public launch of GraphQL Hive! +--- + +After months of development and collecting feedback from our customers using it in production, we +are incredibly excited to share with you today the public launch of +[Hive](https://the-guild.dev/graphql/hive)! + +## Built for the Community, for All GraphQL APIs + +[GraphQL Hive](https://the-guild.dev/graphql/hive) has been built with 3 main objectives in mind: + +- **Help GraphQL developers to get to know their GraphQL APIs** a little more with our schema + registry, performance monitoring, alerts, and integrations. +- **Support all kinds of GraphQL APIs**, from + [Federation](https://the-guild.dev/graphql/hive/federation), and Stitching, to standalone APIs. +- **Open Source at the heart**: GraphQL Hive is 100% open-source, and build in public with the + community. +- **A plug and play SaaS solution**: to give access to Hive to most people with a generous free + “Hobby plan” + +Without further ado, let's review together how GraphQL Hive will help you to get the best of your +GraphQL APIs. + +## Features Overview + +GraphQL Hive provides all the tools to get visibility of your GraphQL architecture at all stages, +from standalone APIs to composed schemas +([Federation](https://the-guild.dev/graphql/hive/federation), Stitching). + +From our experience working in the GraphQL field, **we believe that the following set of features +are essential for a good understanding and operating of GraphQL APIs**, at any stage. + +### Schema Registry + +Using Hive requires publishing changes to your GraphQL API schema. Once published on the Schema +Registry, Hive will offer 3 useful features to manage your GraphQL API: + +**Get warned about breaking changes**. For every new version of the schema either committed in a +Pull Request or publish, Hive will run a set of checks and notify your team via Slack, GitHub, or +within the Hive application. + +![Schema checks on CI](./schema-checks-gh.png) + +The definition of a “breaking change” is data-driven (based on "_live operations monitoring_") and +entirely configurable for each project: + +![Schema checks conditional breaking changes configuration](./schema-checks-conditional-configuration.png) + +The schema changes detection also applies in a Federation setup, when a given subgraph breaks the +composed supergraph. + +**Access the history of changes**. On top of detecting potential breaking changes, Hive gives your +team access to the full history of changes, even on a complex composed schema (Federation, +Stitching). + +Hive can also be configured to post the detected schema changes to Slack, fostering collaboration +between the front-end and back-end engineers. + +![Hive changes history page](./changes-history.png) + +**Supercharge your GraphQL development workflow**. Finally, Hive provides a high-availability and +multi-zone CDN service based on Cloudflare, allowing you to access the registry, through a secured +external service, that's always up regardless of Hive services. + +This secured URL, generated from the Hive UI, can be used to supercharge your GraphQL development +workflow, by: + +- **updating your GraphQL Code Generator config** to fetch the introspection from Hive CDN, instead + of production, staging, or local version of your GraphQL API +- **updating your GraphQL Gateway** to fetch the schema from Hive CDN instead of querying all + services +- **updating your GraphQL Mesh configuration** to fetch a GraphQL Source schema from Hive CDN + +Doing so will avoid deployment issues and development issues (ex: getting introspection for codegen +from the wrong development version of the schema). + +More examples are available in Hive documentation: +https://the-guild.dev/graphql/hive/docs/high-availability-cdn. + +The three aspects of Schema Registry covered above **will help you a safer and smoother deployment +of your GraphQL APIs**, even when working with standalone or monolithic GraphQL APIs. + +### Monitoring + +Once a Schema is deployed, **it is important to be aware of how it is used and what is the +experience of its final users**. + +GraphQL Hive helps you get a global overview of the usage of your GraphQL API with: + +- Error rates and repartition +- Global and queries performances (latency, RPM…) +- Operations count +- Active GraphQL clients + +![Hive monitoring dashboard](./monitoring-preview.png) + +At a glance, the Hive operations dashboard helps you to understand: + +- **Does your GraphQL API face unexpected traffic?** +- **Does a new version of Schema result in a raise of errors?** +- **Which queries are slow?** + +Being able to quickly answer these questions allows you to build better GraphQL APIs and better user +experiences. + +### Integrations + +**Hive is well integrated with Slack and most CI/CD systems** to get you up and running as smoothly +as possible! + +As covered earlier, **Hive can notify your team when schema changes occur, either via Slack or a +custom webhook:** + +![Schema checks Slack message](./slack-schema-change.png) + +Also, **the Hive CLI allows integration of the schema checks mechanism to all CI/CD systems** +(GitHub, BitBucket, Azure, and others). + +To compare new schema with the latest one and detect breaking changes, run the following command: + +```sh +hive schema:check schema.graphql +``` + +The same applies for schema publishing and operations checks. + +If you are using GitHub, you can directly benefit from **the Hive app that will automatically add +status checks to your PRs**! + +![Schema checks on CI](./schema-checks-gh.png) + +And we are just getting started, we are already planning more alerts/notifications types and more +integrations. + +## Get Started with GraphQL Hive + +By far the easiest way to use GraphQL Hive is to get started with one of our hosted plans below. + +**Our hobby plan is free and fits perfectly for most side projects**. When you're ready to upgrade, +we have you covered with our Pro plan. + +All plans come with **unlimited seats, high availability and all the features**. + +You can now give more insight into your GraphQL API's performance by inviting all of your team to +your project. + +### Pricing + +As you can see, the pricing is based on real usage and not the number of seats. + +You can start for free with the "Hobby" plan and upgrade to "Pro" once you hit the limits. + +| Feature / Plan | Hobby | Pro | Custom | +| -------------- | --------- | ----------- | ------------ | +| Base price | $ 0 | $ 10 | $ Talk to us | +| Users / Seats | Unlimited | Unlimited | Unlimited | +| Schema pushes | Unlimited | Unlimited | Unlimited | +| Monitoring | Up to 1M | $ 10 per 1M | Unlimited | +| Data retention | 7 days | 90 days | 12 months | + +If you need more than the Pro plan, let us know, and we +[can discuss your requirements](mailto:contact@the-guild.dev). + +### GraphQL Hive Can Be Self-Hosted + +As stated earlier, [GraphQL Hive](https://the-guild.dev/graphql/hive) is completely open-source +under the MIT license, meaning that you are free to host on your own infrastructure. + +You will find instructions in the [GitHub repository](https://github.com/graphql-hive) and feel free +to contact us for any questions or support. + +## Join Us in Building the Future of Hive + +Like all [The Guild](/) projects, GraphQL Hive is built with the community. + +We can't wait to [get you onboard](https://app.graphql-hive.com) and get your feedback, pull +requests, and feature requests on +[https://github.com/graphql-hive](https://github.com/graphql-hive). + +See you in Hive! 🐝 diff --git a/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/schema-checks-conditional-configuration.png b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/schema-checks-conditional-configuration.png new file mode 100644 index 00000000000..7be4217b9b9 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/schema-checks-conditional-configuration.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/schema-checks-gh.png b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/schema-checks-gh.png new file mode 100644 index 00000000000..a6f92420019 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/schema-checks-gh.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/slack-schema-change.png b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/slack-schema-change.png new file mode 100644 index 00000000000..6628567e3f2 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-hive-release/slack-schema-change.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/copy-header.png b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/copy-header.png new file mode 100644 index 00000000000..cbce181ff4d Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/copy-header.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/page.mdx b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/page.mdx new file mode 100644 index 00000000000..251f3e64b89 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/page.mdx @@ -0,0 +1,85 @@ +--- +title: Announcing GraphQL Network Inspector +tags: [graphql, graphql-network-inspector] +authors: [warrenjday] +date: 2023-02-22 +description: A better way to debug network traffic with GraphQL +--- + +import { YoutubeIframe } from '#components/youtube-iframe' + +GraphQL Network Inspector is a dedicated devtools extension which allows you to inspect and debug +GraphQL queries and mutations from network traffic. + +The extension has seen great adoption since its initial release and now I'm excited to announce it's +new home as part of The Guild. + +You can check out the open source extension and try it out today. +[GraphQL Network Inspector](https://github.com/warrenday/graphql-network-inspector) + + + +## Why GraphQL Network Inspector? + +If you've ever worked with GraphQL on the client, you'll know that it can be a real pain to debug +using the standard network tab. The standard devtools are designed for REST APIs and don't provide +clear visibility into the individual GraphQL queries and mutations being sent. + +With every request you'll see `POST /graphql`. This makes it difficult to distinguish requests +without opening the request details and inspecting the body. + +GraphQL Network Inspector solves this problem by providing a dedicated tab for GraphQL requests and +displaying each query in a readable format. + +![Compare](./request-compare.png) + +## Features Overview + +The great thing about a dedicated inspector is we can provide much more specific functionality +around GraphQL. Here are some of the features we've added to the extension: + +### Request Overview + +The request overview tab provides a simple overview of all queries and mutations. Here you can see +queries by name, distinguish between queries and mutations and even see total errors returned for +each request. + +![Request Overview](./request-overview.png) + +### Request Details + +Clicking into a request will show similar details to the standard inspector. But here we have a +better breakdown for each element of the request including; headers, request query with variables +and response. + +![Request Details](./request-details.png) + +### Copy Details to GraphQL Playground + +The extension also provides convenient buttons to copy the request query and variables into GraphQL +Playground, great if you want to re-run the query. + +Under the request details tab, you'll see a button to copy the query and variables to the clipboard. + +You can also click individual headers to copy them as well. + +![Copy to Playground](./copy-header.png) + +### Convenient Features + +Streamlining the debugging process is a key goal of the extension. On top of the features above +we've also introduces helpful extras such as full-text search and filtering. + +![Copy to Playground](./search.png) + +Check out the extension today and don't forget to give +[GraphQL Network Inspector](https://github.com/warrenday/graphql-network-inspector) a star ⭐️ on +GitHub. + +We'd love to hear your feedback and suggestions for the extension. How would you like to see it +integrate further with The Guild ecosystem? Let us know. + +Happy debugging! diff --git a/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/request-compare.png b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/request-compare.png new file mode 100644 index 00000000000..e694e819428 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/request-compare.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/request-details.png b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/request-details.png new file mode 100644 index 00000000000..e9c1ed9b620 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/request-details.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/request-overview.png b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/request-overview.png new file mode 100644 index 00000000000..b8503ec97f3 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/request-overview.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/response-details.png b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/response-details.png new file mode 100644 index 00000000000..6e558197235 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/response-details.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/search.png b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/search.png new file mode 100644 index 00000000000..9c4c5ea03e7 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-network-inspector/search.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/announcing-graphql-yoga-v2/page.mdx b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-yoga-v2/page.mdx new file mode 100644 index 00000000000..3de68ab7dc8 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/announcing-graphql-yoga-v2/page.mdx @@ -0,0 +1,313 @@ +--- +title: Announcing GraphQL Yoga 2.0! +tags: [server, graphql-federation, envelop, node, graphql] +authors: [charlypoly, arda, laurin, saihaj] +date: 2022-03-29 +description: + Fully-featured GraphQL Server with focus on easy setup, performance and great developer experience +--- + +Today we are incredibly excited to share with you the new GraphQL Yoga! This release was made +possible with your contributions, issues, and feedback. + +[The Guild](https://the-guild.dev/about-us) took over the development of +[GraphQL Yoga](https://graphql-yoga.com) from [Prisma](https://prisma.io) in early 2021, and with +the growing community of tools in the GraphQL space, most recently [Envelop](https://envelop.dev), +we were able to rewrite GraphQL Yoga 2.0 from scratch with easy setup, performance, and developer +experience at the core. + +GraphQL Yoga continues to boast a "convention over configuration" approach and the freedom to use +your favorite libraries, from HTTP to schema-building. + +You are no longer required to install dozens of dependencies to get features such as subscriptions, +file uploads, CORS, error masking, and more. + +Building a GraphQL Yoga server requires a single import and only a few lines of code to start +serving an API. And you also get [GraphiQL](https://github.com/graphql/graphiql) for making +development even easier. + +```js +// 1. Import GraphQL Yoga +import { createServer } from '@graphql-yoga/node' + +// 2. Create your server +const server = createServer({ + schema: { + typeDefs: /* GraphQL */ ` + type Query { + hello: String + } + `, + resolvers: { + Query: { + hello: () => 'Hello Hello Hello' + } + } + } +}) + +// 3. Serve the API and GraphiQL +server.start() +``` + +## The Yoga V2 Experience + +### Your Stack and Habits + +The main goal of Yoga v2 is to **allow you to leverage all the GraphQL ecosystem** by being +compatible with most of the existing schema-design, HTTP server libraries, and deployment +environments. + +Built on top of a **modular and extendable GraphQL Server**, Yoga v2 allows you to use your +preferred schema design approach and HTTP server library. + +For example, Yoga v2 is fully compatible with Express and Nexus, with no additional packages: + +```ts +import express from 'express' +import { makeSchema, queryType } from 'nexus' +import { createServer } from '@graphql-yoga/node' + +const Query = queryType({ + definition(t) { + t.string('hello', { resolve: () => 'hello world!' }) + } +}) +const schema = makeSchema({ + types: [Query] +}) + +const graphQLServer = createServer({ schema }) + +const app = express() + +// Bind GraphQL Yoga to the `/graphql` endpoint +// Here it takes the request and response objects and handles them internally +app.use('/graphql', graphQLServer) + +app.listen(4000, () => { + console.log('Running a GraphQL API server at http://localhost:4000/graphql') +}) +``` + +The same applies to **GraphQL Tools, Pothos, Nexus, TypeGraphQL, SDL first schema-design approaches, +graphql-js, Apollo Tools, Fastify, Koa, Next.js, SvelteKit, and Deno**. + +Beyond the compatibility of schema-design and HTTP server libraries, Yoga v2 makes **deploying a +GraphQL API to any environment seamless** (Vercel Functions, Cloudflare Workers, AWS Lambda and +more). + +Here, a GraphQL API built with GraphQL Modules, deployed on Cloudflare Workers: + +```tsx +import { createApplication } from 'graphql-modules' +import { createServer } from '@graphql-yoga/common' +import { helloWorldModule } from './helloWorld' + +const application = createApplication({ + modules: [helloWorldModule] +}) + +const server = createServer({ schema: application.schema }) + +server.start() +``` + +## Productivity at Your Fingertips + +### Batteries-Included + +Yoga v2 comes with **sensible defaults to make development faster**, all with **complete TypeScript +support**. + +**Features common to modern GraphQL APIs** such as file-uploads, subscription support, advanced +error handling, or CORS **come built-in with Yoga**: + +```ts +import { createServer, GraphQLYogaError } from '@graphql-yoga/node' + +// Provide your schema +const server = createServer({ + schema: { + typeDefs: /* GraphQL */ ` + # adding this custom scalar enables file upload support + scalar Upload + + type Query { + hello: String + } + + type Subscription { + countdown(from: Int!): Int! + } + + type Mutation { + readTextFile(file: Upload!): String + } + `, + resolvers: { + Query: { + hello: () => 'world' + }, + Subscription: { + countdown: { + // This will return the value on every 1 sec until it reaches 0 + subscribe: async function* (_, { from }) { + for (let i = from; i >= 0; i--) { + await new Promise(resolve => setTimeout(resolve, 1000)) + yield { countdown: i } + } + } + } + }, + Mutation: { + readTextFile: async (_, { file }: { file: File }) => { + let textContent = null + try { + textContent = await file.text() + } catch (e) { + // return an error visible by the client + throw new GraphQLYogaError(`Failed to parse file`) + } + return textContent + } + } + } + } +}) + +// We now serve a GraphQL API with Subscriptions (over SSE), CORS, +// and File uploads support! +server.start() +``` + +Yoga v2 also provides APIs to handle **logging, advanced Subscriptions** use-cases (over WS, +Pub/Sub), [**Apollo Federation**](https://the-guild.dev/graphql/hive/federation) Support, +[and more](https://graphql-yoga.com/docs/quick-start). + +### Easily Extend Your API with Envelop Plugins + +GraphQL Yoga supports [Envelop](https://envelop.dev) out of the box, which gives you greater +control, and the ability to hook into the GraphQL execution phases. + +Here, we are building a full-featured GraphQL API with security rules, a response cache and sentry +error reporting with only a few lines of code: + +```ts +import { useDepthLimit } from '@envelop/depth-limit' +import { useResponseCache } from '@envelop/response-cache' +import { useSentry } from '@envelop/sentry' +import { createServer } from '@graphql-yoga/node' + +const server = createServer({ + schema: { + typeDefs: /* GraphQL */ ` + type Query { + hello: String + } + `, + resolvers: { + Query: { + hello: () => 'Hello Hello Hello' + } + } + }, + plugins: [ + useDepthLimit({ + // set up some security rules + maxDepth: 10 + }), + useResponseCache(), // speed up our server with a response cache + useSentry() // report unexpected errors to sentry + ] +}) + +// Start the server and explore http://localhost:4000/graphql +server.start() +``` + +The Envelop Plugin currently [proposes more than 35+ plugins](https://envelop.dev/plugins) covering +most of the standard GraphQL APIs features you need in production. + +Ultimately, you can [develop custom Envelop plugins](https://envelop.dev/docs/plugins/custom-plugin) +to create reusable behaviors that hook on to the GraphQL lifecycle. + +## Production-Ready + +GraphQL Yoga v2 has been built in production for production usage. + +Built-in real-world conditions within our projects (e.g. [GraphQL Mesh](https://graphql-mesh.com)) +and with some of [our clients](/#services), performance was highly prioritised. The core of Yoga is +as performant as possible, and we continuously keep track and improve it. + +Also, the Yoga V2 repository runs performance checks on every commit and Pull Request, so we can +always capture any performance regression. + +Last but not least, every commit is ensured to run on all deployment targets such as AWS Lambda or +Cloudflare workers through an End-To-End testing suite! + +We continue our effort of pushing GraphQL Yoga to more production environments with the imminent +release of [Redwood 1.0](https://redwoodjs.com) that uses Yoga 2.0 as its default GraphQL server. + +## A Standards-Compliant GraphQL Server + +In the same way that TypeScript aims to stay aligned with ECMAScript, GraphQL Yoga is based on +several official and recognized specs: + +- [GraphQL-spec](https://spec.graphql.org/October2021), + [GraphQL-over-HTTP](https://github.com/graphql/graphql-over-http): guarantees your GraphQL API to + work with all existing GraphQL clients (Apollo, Relay, urql, and more) +- [GraphQL-Multipart-Request](https://github.com/jaydenseric/graphql-multipart-request-spec): + enables great file upload support +- [W3C Fetch API](https://fetch.spec.whatwg.org): we embrace the future of Node.js and provide the + same developer experience on all platforms + +## GraphQL Features from the Future + +Yoga v2 supports some experimental GraphQL features such as +[`@defer` and `@stream`](https://github.com/graphql/graphql-js/pull/2839), allowing you to get a +taste of the future of GraphQL (with compatible clients such as +[urql](https://formidable.com/open-source/urql/docs/comparison/#core-features)). + +Also, thanks to the Envelop plugin system, Yoga v2 can also act as "Babel for GraphQL", giving you +the option to use features that are not yet in the GraphQL spec but are very useful in production +today, like `@defer`, `@stream` and +[`@oneOf`](https://envelop.dev/docs/guides/using-graphql-features-from-the-future). + +## Get Started with Yoga V2 + +Yoga v2 provides the best GraphQL experience while giving you the freedom to use your preferred +stack and tools. + +### Get Started from Scratch with Our New Tutorial + +**Want to try it?** Give +[our brand-new tutorial](https://graphql-yoga.com/tutorial/basic/00-introduction) a try! It will +guide you in building a full-featured, modern API with GraphQL Yoga. + +[Episode #36 of `graphql.wtf`](https://graphql.wtf/episodes/36-graphql-yoga-2) is also great +introduction to GraphQL Yoga 2.0: + +import { YoutubeIframe } from '#components/youtube-iframe' + + + +### ... or Migrate Your Existing GraphQL Server to Yoga + +All Yoga v2's features +[are well documented on the website](https://graphql-yoga.com/docs/quick-start), and we also have +[some migration guides](https://graphql-yoga.com/docs/migration/migration-from-apollo-server) (from +v1, Apollo Server and Express GraphQL). + +## What's Next + +Yoga v2 is the biggest and most important project we released to date; still, it is just the +beginning of our GraphQL server vision. + +We can't wait to get your +[questions, user feedback, and feature requests/PRs](https://github.com/dotansimha/graphql-yoga), +and we already plan for new features such as an Enhanced Plugin System that will provide features +similar to Envelop but at the request level. + +Don't hesitate to reach out to us on Twitter and support us by +[sharing this article!]() diff --git a/packages/web/docs/src/app/blog/(posts)/announcing-self-hosted-graphql-hive/page.mdx b/packages/web/docs/src/app/blog/(posts)/announcing-self-hosted-graphql-hive/page.mdx new file mode 100644 index 00000000000..77e91d6d69c --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/announcing-self-hosted-graphql-hive/page.mdx @@ -0,0 +1,38 @@ +--- +title: Announcing self-hosted GraphQL Hive +tags: [graphql, graphql-federation, graphql-hive] +authors: [laurin] +date: 2022-10-05 +description: + Host your own GraphQL Hive instance. We are happy to announce that we have reached the milestone + of making it easy to host their own instance of GraphQL Hive. +--- + +It has been five months since [we launched GraphQL Hive](/blog/announcing-graphql-hive-release) and +one year since [we first announced GraphQL Hive](/blog/graphql-hive-preview), and it has been a wild +ride since then! + +> GraphQL Hive is our schema registry solution for continuously evolving your GraphQL schema backed +> by performance insights and usage statistics. Whether you are working in a small company with a +> single GraphQL service or a big multi-team company with multiple (federated or composed) GraphQL +> services, GraphQL Hive is the perfect tool to help you manage your GraphQL schema. + +We first were surprised but also excited about the amount of interest from companies of all sizes +and industries and individuals from all over the world in GraphQL Hive. We are so grateful for all +the support, contributions and feedback we received so far from external contributors and are +looking forward to a bright future! + +Aside from the diverse range of companies that are already successfully using our hosted GraphQL +Hive version, a lot of companies have been asking us whether it is possible to host your own GraphQL +Hive instance! + +Since the beginning of the project, we had a different vision for SaaS and community editions of +GraphQL Hive compared to traditional SaaS products. Instead of only open-sourcing a small portion of +the whole stack we always wanted to open-source the full stack and make it easy for anyone to host +their own instance of GraphQL Hive. + +Today, we are happy to announce that we have reached this milestone and spinning up your first +GraphQL Hive instance is as easy as running a single command! All you need is `docker` and +`docker-compose` installed on your machine. + +[Head over to our GraphQL Hive documentation for getting started!](https://the-guild.dev/graphql/hive/docs/self-hosting/get-started) diff --git a/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/063a3d092340209bd6e454e2fee4ac41.png b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/063a3d092340209bd6e454e2fee4ac41.png new file mode 100644 index 00000000000..b8f4853afca Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/063a3d092340209bd6e454e2fee4ac41.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/1600bc44b918bf784039061e9895496f.png b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/1600bc44b918bf784039061e9895496f.png new file mode 100644 index 00000000000..1ffe96842f6 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/1600bc44b918bf784039061e9895496f.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/250a232da3f4667a2feba74895729d19.png b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/250a232da3f4667a2feba74895729d19.png new file mode 100644 index 00000000000..7e08c911f8f Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/250a232da3f4667a2feba74895729d19.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/251fe78b9075dac4e8088a4e174427e1.png b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/251fe78b9075dac4e8088a4e174427e1.png new file mode 100644 index 00000000000..8ae00be82d6 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/251fe78b9075dac4e8088a4e174427e1.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/26d1e2f5199df372a39ca801d9ce936b.png b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/26d1e2f5199df372a39ca801d9ce936b.png new file mode 100644 index 00000000000..bb7447198cf Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/26d1e2f5199df372a39ca801d9ce936b.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/3b47588c7a195cbb887dad8db7fb8c7f.png b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/3b47588c7a195cbb887dad8db7fb8c7f.png new file mode 100644 index 00000000000..76d5be55f5a Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/3b47588c7a195cbb887dad8db7fb8c7f.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/8356f13d18d0ff7af35920e4bf73f854.png b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/8356f13d18d0ff7af35920e4bf73f854.png new file mode 100644 index 00000000000..fe095457047 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/8356f13d18d0ff7af35920e4bf73f854.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/9eb65f486aae61db008b4b6f5bea90e3.png b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/9eb65f486aae61db008b4b6f5bea90e3.png new file mode 100644 index 00000000000..41967bd05b5 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/9eb65f486aae61db008b4b6f5bea90e3.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/c6953c9d535f0c4ab8a02f8019954e99.png b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/c6953c9d535f0c4ab8a02f8019954e99.png new file mode 100644 index 00000000000..b144c0169c1 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/c6953c9d535f0c4ab8a02f8019954e99.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/page.mdx b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/page.mdx new file mode 100644 index 00000000000..472283532f9 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/apollo-angular-011/page.mdx @@ -0,0 +1,312 @@ +--- +title: Apollo Angular Version 0.11 +authors: kamil +tags: [graphql, apollo-angular] +date: 2017-03-01 +description: New name, AoT support, TypeScript improvements, and Angular 4 readiness +--- + +## New Name, AoT Support, TypeScript Improvements, and Angular 4 Readiness + +We recently released version `0.11.0` of +[apollo-angular](https://github.com/apollographql/apollo-angular) and a lot of things have changed +and improved since our last update blog! + +First, here is an overview list of the main changes: + +- New name +- Support for Apollo Client 0.8+ +- AoT support +- Multiple Apollo Client instances in a single app +- TypeScript improvements and TypeScript codegen +- Apollo Client Developer Tools +- ES6 Modules and Tree Shaking +- Support for Angular 4 + +So let's dive into it! + +--- + +## New Name + +![](./3b47588c7a195cbb887dad8db7fb8c7f.png 'After years of research we discovered a secret formula') + +As you all know, the term “Angular 2” is +[no longer a thing](https://angularjs.blogspot.com/2017/01/branding-guidelines-for-angular-and.html), +now it's just “Angular”, without the version suffix (#justAngular). So we renamed the package to be +**“apollo-angular”**. We really wanted “angular-apollo” to match up with `react-apollo`, but it was +already taken. That means that from now on the “angular2-apollo” package is deprecated. + +We've applied this rule to the service as well, so there is no “Angular2Apollo” anymore. It's just +**Apollo**. Simpler and more convenient. The migration process is very simple: + +```ts +import { Angular2Apollo } from 'angular2-apollo' + +class AppComponent { + constructor(apollo: Angular2Apollo) {} +} +``` + +```typescript +import { Apollo } from 'apollo-angular' + +class AppComponent { + constructor(apollo: Apollo) {} +} +``` + +--- + +## Apollo Client 0.8 + +We've updated our dependency to apollo-client 0.8 which includes a lot of improvements in size and +performance. check out the full list +[here](https://dev-blog.apollodata.com/apollo-client-0-8-e8db5efc75fb). + +--- + +## Ahead-Of-Time Compilation + +![](./9eb65f486aae61db008b4b6f5bea90e3.png 'Ahead-of-Time Compilation') + +One of the most interesting features of Angular is Ahead-of-Time compilation. Angular's compiler +converts the application, components, and templates to executable JavaScript code at build time. AoT +compilation improves the size of the app as well as the performance and stability thanks to +static-code analysis at build time. + +![](./063a3d092340209bd6e454e2fee4ac41.png 'Just-In-Time Compilation') + +To support this feature, we had to change the way of providing ApolloClient to ApolloModule. Instead +of using an instance of ApolloClient directly, it has to be wrapped with a function. Here's an +example: + +```ts +import { ApolloModule } from 'apollo-angular' +import { ApolloClient } from 'apollo-client' + +const client = new ApolloClient() + +function provideClient() { + return client +} + +ApolloModule.withClient(provideClient) +``` + +--- + +## Multiple Clients + +![](./251fe78b9075dac4e8088a4e174427e1.png 'Apollo service') + +We're happy to introduce a support for multiple clients. Yes, it's now possible to use many +instances of the ApolloClient inside of the ApolloModule, meaning you can call multiple GraphQL +endpoints from your single client app. + +The use case for this feature came from some of our enterprise users. Some common use cases are when +working with a server endpoint as well as a 3rd party API, or in case you are calling multiple +microservices GraphQL endpoints from on client app. While it's always better to have all of your +data in one GraphQL service to be able to get all of the data you need in one request, sometimes +it's unavoidable to have to call multiple APIs. + +We decided to make it as an optional feature and to implement it in a way that doesn't break your +existing app. Let me explain how it works. First thing, you need to define a function to return a +map of clients: + +```ts +function provideClients() { + return { + default: defaultClient, + extra: extraClient + } +} +``` + +Then, you can use a new method of ApolloModule called **forRoot** to provide clients, so you can use +it in your app: + +``` +ApolloModule.forRoot(provideClients) +``` + +The `Apollo` service has now two new methods: `use()` and `default()`. First one takes a key of a +client you want to use, second one returns the default client. + +```typescript + +class AppComponent { + apollo: Apollo; + + ngOnInit() { + // uses the defaultClient + this.apollo.watchQuery({...}).subscribe(() => {}); + + // works the same as the one above + this.apollo.default().watchQuery({...}).subscribe(() => {}); + // uses the extraClient + this.apollo.use('extra').watchQuery({...}).subscribe(() => {}); + } +} + +``` + +> It's important to know that if you want to have a default client, you need to use `default` as a +> key. + +--- + +## More Control + +![](./26d1e2f5199df372a39ca801d9ce936b.png 'We improved integration between frameworks') + +Apollo-Client and Apollo-Angular both are written in TypeScript, but we still had room for +improvements for our users, here are some of them. + +Thanks to [the recent change](https://github.com/apollographql/apollo-client/pull/914) we were able +to take advantage of TypeScript's feature called **Generic Types**. It's now possible to easily +define an interface of the “data” property in methods like _watchQuery_, _query_, _mutation_ and +many more. + +This gives you **more control** over the code, making it **more predictable** and easier to +**prevent bugs.** Take a look at an example. + +```ts +const query = gql` + query currentUser { + currentUser { + name + } + } +` + +interface User { + name: string +} + +interface Data { + currentUser: User +} + +class AppComponent { + apollo: Apollo + currentUser: User + + ngOnInit() { + this.apollo.watchQuery({ query }).subscribe(result => { + this.currentUser = result.data.currentUser + }) + } +} +``` + +It's very helpful and convenient, especially when used with RxJS operators. You gain more control +over the result modifications. But there are even more improvements! + +## Let's Talk about Observables + +In Angular world, we commonly use RxJS. Unfortunately, Apollo's standard observable shim is not +compatible with RxJS, so to have the best developer experience, we created the +ApolloQueryObservable. They both behave the same, containing the same methods (like refetch for +example), except the RxJS support. + +We recently changed the logic of the ApolloQueryObservable's generic type. Here's an example to see +how to migrate: + +```typescript +class AppComponent { + user: ApolloQueryObservable> + + getUser() { + this.user.subscribe(result => { + // result is of type ApolloQueryResult + }) + } +} +``` + +```ts +class AppComponent { + user: ApolloQueryObservable + + getUser() { + this.user.subscribe(result => { + // result is of type ApolloQueryResult + }) + } +} +``` + +## It Is Human Nature to Be Lazy + +![](./1600bc44b918bf784039061e9895496f.png) + +We love automation, just to avoid keep repeating the same things on and on again. I have great news +for you! + +As we know, GraphQL is strongly typed, so we have created a tool to **generate API code or type +annotations based on a GraphQL schema and query documents**. This tool is called +[“apollo-codegen”](https://github.com/apollographql/apollo-codegen). + +Thanks to Robin Ricard's work, Apollo Codegen now supports TypeScript, so Angular developers no +longer have to define types for their queries manually. + +## Better Developer Experience + +![](./c6953c9d535f0c4ab8a02f8019954e99.png) + +We are happy to announce that Angular integration works great with the +[**Apollo Client Developer Tools**](https://dev-blog.apollodata.com/apollo-client-developer-tools-ff89181ebcf). + +It's a Chrome DevTools extension for Apollo Client which has 3 main features: + +- A built-in GraphiQL console that allows you to make queries against your GraphQL server using your + app's network interface directly (no configuration necessary). +- A query watcher that shows you which queries are being watched by the current page, when those + queries are loading, and what variables those queries are using. +- A cache inspector that displays your client-side Redux store in an Apollo-Client-friendly way. You + can explore the state of the store through a tree-like interface, and search through the store for + specific field keys and values. + +Try the dev tools in your Angular Apollo app today! + +--- + +## ES6 Modules and Tree Shaking + +![](./8356f13d18d0ff7af35920e4bf73f854.png 'UMD bundle and ES6 Modules') + +App load time is an important part of the overall user experience. Earlier, I talked about AoT +compilation, which radically improves performance, but there is still room to speed things up. + +To make our app even smaller we can use a process called **Tree Shaking**. It basically follows the +trail of import and export statements by statically analyzing the code. This way we get rid of +unused parts of the application. + +As you know, every angular package has a **UMD bundle** (to support **CommonJS** and **AMD**) and a +separate space for **ES6 Modules**. Thanks to recent changes in the +[apollo-client](https://github.com/apollographql/apollo-client/pull/1069) and +[apollo-client-rxjs](https://github.com/kamilkisiela/apollo-client-rxjs/pull/16), we do the same, so +you can use tree shaking in your app! + +--- + +## Ready for the Future + +![](./250a232da3f4667a2feba74895729d19.png 'Coming out in March 2017') + +With the first stable version of Angular, the core team announced a predictable release schedule. It +means that every 6 months there's going to be a new major version of the framework. + +We have good news! + +**Angular 4.0.0** is now still in beta, but it's **fully compatible with Apollo**, so you don't have +to worry about any breaking changes. + +## Keep Improving + +We are working hard to give Angular developers the best developer experience we can. We want to hear +more from you — what should we do next, what can we improve? + +And if you are really interested in GraphQL, Did you know +[Apollo is hiring](https://meteor.com/careers#open-source-engineer)? diff --git a/packages/web/docs/src/app/blog/(posts)/apollo-angular-12/861767ce574bfae276f842a1c7046ec4.webm b/packages/web/docs/src/app/blog/(posts)/apollo-angular-12/861767ce574bfae276f842a1c7046ec4.webm new file mode 100644 index 00000000000..e6abc0b3291 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/apollo-angular-12/861767ce574bfae276f842a1c7046ec4.webm differ diff --git a/packages/web/docs/src/app/blog/(posts)/apollo-angular-12/page.mdx b/packages/web/docs/src/app/blog/(posts)/apollo-angular-12/page.mdx new file mode 100644 index 00000000000..7871acb9f55 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/apollo-angular-12/page.mdx @@ -0,0 +1,212 @@ +--- +title: Apollo-Angular 1.2 - using GraphQL in your apps just got a lot easier! +tags: [graphql, angular, apollo-angular] +authors: kamil +date: 2018-08-21 +description: + Check what's new in Apollo Angular and how to get the full potential benefits of using Angular + + GraphQL + TypeScript combined thanks to GraphQL-Code-Generator +--- + +import { StackBlitz } from '#components/stack-blitz' +import { VideoEmbed } from '#components/video-embed' +import { YoutubeIframe } from '#components/youtube-iframe' + +_Check what's new in Apollo Angular and how to get the full potential benefits of using Angular + +GraphQL + TypeScript combined thanks to GraphQL-Code-Generator._ + +We are very excited to announce a new version of Apollo Angular that dramatically improves and +simplifies the usage of GraphQL with Angular. + +This version also adds production and scale related features, that our large Enterprise and +production users had been asking for. + +## TL;DR + +- [Code generation for Apollo Angular](https://graphql-code-generator.com/docs/plugins/typescript-apollo-angular) +- Query, Mutation, Subscription as an Angular service +- [Apollo Angular Boost](https://npmjs.com/package/apollo-angular-boost) +- Testing tools + +--- + + + +--- + +## Introducing Query, Mutation and Subscription as an Angular Services + +Through almost two years of using GraphQL in Angular we gained a lot of experience, and learned how +people use the library. + +With the current API, having `query` and `watchQuery` methods sometimes confused a lot of +developers. For people who use Apollo for long time it's obvious but we often get asked about +differences between them and many newcomers are surprised. + +> We decided to add a new approach of working with GraphQL in Angular. + +```ts +import { Injectable } from '@angular/core'; +import { Query } from 'apollo-angular'; +import gql from 'graphql-tag'; + +@Injectable({...}) +export class FeedGQL extends Query { + document = gql` + query Feed { + posts { + id + title + } + } + ` +} +``` + +There are now 3 new simpler APIs: `Query`, `Mutation` and `Subscription`. Each of them allows to +define the shape of a result & variables. + +The only thing you need to do is to set the `document` property, That's it, and now you use it as a +regular Angular service: + +```ts +import { Component } from '@angular/core' +import { FeedGQL } from './feed-gql.ts' + +@Component({ + /* … */ +}) +export class FeedComponent { + constructor(feedGQL: FeedGQL) { + feedGQL.watch().valueChanges.subscribe(result => { + // ... + }) + } +} +``` + +In our opinion, the new API is more intuitive and documents feels now like a first class-citizens. + +> But it also opens up the doors for something wayyyyy cooler! + +## Taking It to the Next Level + +As an Angular developer, you already understand how much power Typescript adds to your development — +the Angular community took those capabilities to the next level with code generation, through things +like schematics. + +The GraphQL community also took the concept of static type capabilities into new places — over the +API and managing data automatically at runtime with the query language. + +While using GraphQL, Typescript and Angular and maintaining `apollo-angular` in the past 2 years we +always keep thinking how can we get all those technologies closer to create something that is more +powerful than the sum of its parts. + + + +## GraphQL Code Generator for Apollo Angular + +We are pleased to announce a new set of tools that takes the GraphQL schema from the server and the +query from the Angular component and generate everything in the middle for you! + +Just by consuming a static GraphQL schema and defining the data you need and its structure in a +GraphQL Query, there is no need for you to write any Typescript! You already defined it, why writing +it again? + +We will generate a strongly typed Angular service, for every defined query, mutation or +subscription, ready to use in your components! + +## How It Works + +You create a `.graphql` file with a document that you want to use in a component: + +```graphql +query Feed { + posts { + id + title + } +} +``` + +Next, you run the +[**GraphQL Code Generator — Angular Apollo Plugin**](https://graphql-code-generator.com/docs/plugins/typescript-apollo-angular) +to generate types and angular services. + +Then you simply import and use it as a regular, Angular service. + +```js +import { FeedGQL } from './generated' +``` + +> GraphQL Code Generator takes query's name, makes it PascalCased and adds GQL suffix to it. An +> example, “myFeed” becomes “MyFeedGQL”. + +See it here in action and play with it: + + + +To play with code generator try to clone this repository: + + + github.com/kamilkisiela/apollo-angular-services + + +Using Angular, Typescript and GraphQL in a coordinated way, gives us new level of simplicity and +power for our developer experience: + +- **Less code to write** — no need to create a network call, no need to create Typescript typings, + no need to create a dedicated Angular service +- **Strongly typed out of the box** — all types are being generated, no need to write any Typescript + definitions and struggle to keep them updated +- **Full developer experience of tools and IDEs** — development time autocomplete and error + checking, not only across your frontend app but also with your API teams! +- **Tree-shakable** thanks to Angular 6 + +## More Thanks to GraphQL + +We believe GraphQL is a game changer in how you plan and create your frontend apps. + +The vision that guides us is that you should be able to sketch a list of data types your backend can +provide, sketch components and their data dependencies — and all the rest of the plumbing can be +generated for you. + +Once you'll write an app like that, you will ask yourself why did you write all the other +boilerplate code by yourself before. + +But we've just talked about one new feature in apollo-angular. there is more: + +- **Testing utilities** There were a lot of questions about testing Apollo components, so we decided + to finally release something with a similar API to the one Angular's `HttpClient` uses. + [Sergey Fetiskin](https://medium.com/@sergeyfetiskin?source=post_header_lockup) wrote + [an article about it](https://medium.com/@sergeyfetiskin/testing-apollo-graphql-in-your-angular-application-595f0a04aad3). +- **Apollo Angular Boost** It's hard for newcomers to get started with Apollo Angular. Inspired by + Apollo Boost we decided to create an Angular version of it. Here's + [an interactive example](https://stackblitz.com/edit/simple-apollo-angular-boost-example). +- **Create Apollo on DI level** There is now an extra way to create Apollo Client. Instead of using + `Apollo.create` inside of a constructor, you can provide settings on Dependency Injection level. + Read the + [“Using Dependency Injection”](https://apollographql.com/docs/angular/basics/setup.html#using-dependency-injection) + chapter in docs. +- **GraphQL Subscription outside NgZone** Apollo.subscribe accepts now a second argument in which + you can enable running subscription's callback outside NgZone. +- **Automatic Persisted Queries for Angular** It's now possible to use APQ with Angular's + HttpClient, just install apollo-angular-link-persisted. + +--- + +- Query and Mutation as a service on + [StackBlitz](https://stackblitz.com/edit/apollo-angular-services) and + [GitHub](https://github.com/kamilkisiela/apollo-angular-services) +- Query and Mutation — + [Step by step tutorial](https://github.com/kamilkisiela/apollo-angular-introduction) +- Example: + [Apollo Angular Boost on StackBlitz](https://stackblitz.com/edit/basic-apollo-angular-app) +- [Apollo Angular repository](https://github.com/apollographql/apollo-angular) +- [Documentation](https://apollographql.com/docs/angular/basics/services.html) diff --git a/packages/web/docs/src/app/blog/(posts)/better-type-safety-for-resolvers-with-graphql-codegen/page.mdx b/packages/web/docs/src/app/blog/(posts)/better-type-safety-for-resolvers-with-graphql-codegen/page.mdx new file mode 100644 index 00000000000..ee979e0c09d --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/better-type-safety-for-resolvers-with-graphql-codegen/page.mdx @@ -0,0 +1,203 @@ +--- +title: Better Type Safety for your GraphQL resolvers with GraphQL Codegen +tags: [graphql, codegen, node, typescript] +authors: dotan +date: 2020-05-17 +description: + Type-check your resolvers' implementation in order to find issues in build-time. Simply integrate + with your existing TypeScript data models and context types. +--- + +import { CodeSandbox } from '#components/code-sandbox' + +export const embedOptions = { + theme: 'light', + view: 'editor', + previewwindow: 'console' +} + +If you use TypeScript to write your GraphQL schema implementation, you'll love the integration with +GraphQL Codegen and `typescript-resolvers` plugin. + +This plugin allows you to easily have typings for your resolvers, with super flexible configuration +that allow you to integrate it easily to your existing code, types and models. + +That means that you can type-seal your code and have complete type-safety: your GraphQL resolvers +will be typed (parent type, args, inputs, return value, context type), and you can use your own +TypeScript type models, so you can have type-safety all across your implementation, from API to +database. + +Having type check on your resolvers can help to improve your code quality, detect issues in build +time (instead of runtime), and improve developer experience. + +## Getting Started + +If you are already familiar with [GraphQL Code Generator](https://the-guild.dev/graphql/codegen), +you can skip this step. + +Start by installing GraphQL Codegen and the relevant plugins: + +```sh +yarn add @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers +``` + +Now, create `codegen.yml` file with the following, make sure to point to your schema location: + +```yaml +schema: YOUR_SCHEMA_LOCATION_HERE +generates: + ./resolvers-types.ts: + plugins: + - typescript + - typescript-resolvers +``` + +> GraphQL Code Generator uses [`graphql-tools`](https://the-guild.dev/graphql/tools) so you can +> point to your schema files, or `/graphql` endpoint. + +To run GraphQL Codegen, use: `yarn graphql-codegen` (or, create a script for it if you wish). This +should create a new file called `resolvers-types.ts` in your codebase. + +## Simple Resolvers Signature + +In this example, we'll use the following GraphQL schema and resolvers as reference. +[You can find a working live-demo of this part here](https://codesandbox.io/s/condescending-albattani-ixyp9?file=/resolvers.ts). + + + +This is a naive implementation of a GraphQL API, and we'll see how more advanced use cases could be +implemented in the upcoming steps. + +To get started with the generated files, import `Resolvers` identifier from your generated file, and +use it to type your resolvers object, for example: + + + +Now, TypeScript engine will verify that object you returned, and you'll be able to see that if +you'll change one of the fields, it will be type checked immediately. + +Also, if you'll change your schema types and re-run the codegen (or use +[Watch Mode](https://graphql-code-generator.com/docs/getting-started/development-workflow#watch-mode)), +it will re-generate the matching types and check your code again. + +As your probably understood, the default behavior of `typescript-resolvers` is using the base type +generated by `typescript`, that means, that your schema types and resolvers needs to match and have +the same signature and structure. + +But it's not always the case - because your GraphQL schema, in most cases, isn't the same as your +models types - this is why we have `mappers` configuration. + +## Use Your Model Types + +Models types are the way your data is being stored or represented behind the scenes. Think about +`User` object from example - in most cases, the representation of `User` in your database (or any +other downstream API) is different than the way your represent `User` in your API. Sometimes it's +because of security considerations, and sometimes because fields are internal and used only by you, +and not by the consumers. + +Those model types are the actual objects that you are usually using in your resolvers code. Those +can be created manually (with a simple TypeScript `type`, `interface` or `class`), or created +automatically from your downstream APIs, database or any other data-source that you use in your app. + +The way to tell codegen where are your models types are located is called `mappers`. + +To use `mappers` configuration, we need first to setup a real type safety, and have models types for +our actual objects. + +Let's assumes that your backend is implemented this way, with some models types: + + + +It means that now, your resolvers implementation needs to adjusted and handle the different data +structure: + + + +Noticed the errors? it caused by the fact that we don't have the appropriate mapping set yet. We +need to tell GraphQL codegen that our schema types are different than the model types. + +To do that, let's update codegen config with `mappers` and add a mapping between a GraphQL type to a +TypeScript type (and the file it's located in): + + + +This way, GraphQL Codegen will use your custom models types in the generated output, instead of the +default types, and your resolvers' implementation will look like that: + + + +Note that now you'll get autocomplete, type safety and a better connection between your GraphQL +schema and your GraphQL resolvers. + +## Typed Context + +`typescript-resolvers` also supports replacing the `context` type of your resolvers' implementation. +All you have to do, is to add this following to your codegen configuration: + +```yaml +schema: schema.graphql +generates: + ./resolvers-types.ts: + config: + contextType: models#MyContextType + mappers: + User: ./models#UserModel + Profile: ./models#UserProfile + plugins: + - typescript + - typescript-resolvers +``` + +This will make sure to replace `any` with `MyContextType`, and you'll be able to access a +fully-typed `context` object in your resolvers. + +## What's Next? + +A few notes that worth mentioning: + +- You can use `mappers` on every GraphQL type, interface or a union. +- Your resolvers' arguments (`args`) are also fully-typed, according to your schema definition. +- The `parent` value is also fully typed, based on your `mappers`. +- You can import your types from a node module package (`User: models-lib#UserType`). +- You can also map to built-in language types (`DateType: Date`) +- Aliasing the imports is also possible (`User: ./models#User as MyCustomUserType`) + +You can also modify the default mapper (`defaultMapper`) and allow partial resolution, this will +allow you to return partial objects in every resolver +([more info](https://github.com/dotansimha/graphql-code-generator/issues/1133#issuecomment-456812621)): + +```yaml +config: + useIndexSignature: true + defaultMapper: Partial<{T}> +``` + +For more advanced use-cases, you can find +[the complete plugin documentation here](https://graphql-code-generator.com/docs/plugins/typescript-resolvers/#configuration). diff --git a/packages/web/docs/src/app/blog/(posts)/build-realtime-graphql-backends-with-grafbase/page.mdx b/packages/web/docs/src/app/blog/(posts)/build-realtime-graphql-backends-with-grafbase/page.mdx new file mode 100644 index 00000000000..8f2ffe31e5c --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/build-realtime-graphql-backends-with-grafbase/page.mdx @@ -0,0 +1,118 @@ +--- +title: Build realtime GraphQL backends with Grafbase +description: + Build collaborative, multiplayer apps faster and easier than ever with GraphQL Live Queries using + Grafbase. +authors: notrab +tags: [graphql, real-time, live-query] +date: 2023-01-17 +--- + +**The Guild** has made working with realtime +[GraphQL Subscriptions and Live Queries](/blog/subscriptions-and-live-queries-real-time-with-graphql) +easy over the last few years thanks to plugins that work out of the box with +[GraphQL Yoga](https://the-guild.dev/graphql/yoga-server). + +However, there are developers who don't want the hassle of building a backend, managing deployments, +configuring continuous integration, juggling connection pooling, and more. Thankfully, there are +solutions designed to abstract all the hard work. + +[Grafbase](https://grafbase.com) was built to manage all of the above. You get a distributed GraphQL +API at the edge by creating a single `grafbase/schema.graphql` file in your project. + +Your schema contains models with special directives to relate data, set default values, validation, +and more. + +```graphql +# grafbase/schema.graphql + +type Post @model { + title: String! + slug: String! @unique +} +``` + +In 2021, [Laurin Quast](https://twitter.com/n1rual) introduced a collection of packages to support +the use of GraphQL Live Queries on the server and client. + +**Today, Grafbase is happy to announce it now supports GraphQL Live Queries!** + +This means that all you need to do to start using GraphQL Live Queries is add `@live` to your query! + +```graphql +query @live { + postCollection(first: 10) { + edges { + node { + id + title + } + } + } +} +``` + +The `@live` directive will observe any changes to the data and send a message using +[Server-Sent Events](https://grafbase.com/docs/realtime/live-queries#server-sent-events) with the +patch to update the current query state. + +The event is in the format of +[JSON Patch](https://grafbase.com/docs/realtime/live-queries#json-patch): + +```json +{ + "patch": [ + { + "op": "add", + "path": "/postCollection/edges/3", + "value": { + "node": { + "id": "post_01GJMDWJ2M6WWTM26S7C1KKYBE", + "title": "Instant serverless GraphQL backends" + } + } + } + ], + "revision": 1 +} +``` + +Together with The Guild, Grafbase is also releasing 2 new packages (with more on the way) to further +abstract the effort needed to implement Live Queries on the frontend. + +Starting with support for [Apollo Client](https://www.apollographql.com/docs/react/) and +[URQL](https://formidable.com/open-source/urql/), you can add Live Queries with the following +packages: + +- [`@grafbase/apollo-link`](https://www.npmjs.com/package/@grafbase/apollo-link) +- [`@grafbase/urql-exchange`](https://www.npmjs.com/package/@grafbase/urql-exchange) + +Developers not using either of those libraries can still use Live Queries with the native +[`EventSource`](https://grafbase.com/docs/realtime/live-queries#eventsource) API. + +## Try It Now! + +You can try Live Queries by building your own backend in one simple command: + +```bash +npx grafbase init +``` + +Once you've modelled your data using the [schema](https://grafbase.com/docs/schema/overview) you can +run your backend locally to build: + +```bash +npx grafbase dev +``` + +## About Grafbase + +Grafbase is the easiest way to build and deploy GraphQL backends. Go from idea to production in +seconds, without spending time on infrastructure. + +- Build backends with GraphQL SDL as configuration +- Spin up instant preview environments with every PR +- Build locally with the CLI — `npx grafbase dev` +- Deployed to the edge with no cold starts +- Add granular permissions for users (and groups) +- **Build collaborative multiplayer apps faster with Live Queries** diff --git a/packages/web/docs/src/app/blog/(posts)/building-graphql-servers-in-2022/page.mdx b/packages/web/docs/src/app/blog/(posts)/building-graphql-servers-in-2022/page.mdx new file mode 100644 index 00000000000..c27c5d4d9b5 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/building-graphql-servers-in-2022/page.mdx @@ -0,0 +1,67 @@ +--- +title: Building GraphQL Servers in 2022 +authors: notrab +tags: [graphql, conference, yoga] +date: 2022-06-28 +description: + In this talk we'll learn about the tools needed to build your own GraphQL server from scratch + using GraphQL Yoga. +--- + +Building GraphQL servers in 2022 couldn't be any easier thanks to GraphQL Yoga. This year at +[HasuraCon](https://hasura.io/events/hasura-con-2022/building-graphql-servers-in-2022-overview-of-the-bestnew-tools-you-should-add-to-your-toolbelt) +I discussed how Yoga started, how it evolved, and what we can expect from the future. + +For many years of building GraphQL servers in the Node ecosystem, our choices have been limited. +Apollo Server library has dominated the ecosystem. With its pairing client library, it was often a +good fit for a very long time. However, I believe there are more mature and battle-tested solutions +available today, thanks to the evolution of Yoga. + +[GraphQL Yoga](https://graphql-yoga.com) takes a different approach by providing enough bells and +whistles that make you production-ready, but builds itself on top of the core primitives of HTTP. +Yoga is runtime agnostic too, so if you're currently working with Cloudflare Workers, Vercel +Functions, AWS Lambda or something else, Yoga will fit right in. + +GraphQL Yoga comes with [Envelop](https://envelop.dev), and is the recommended way you should be +using Envelop today to extend your GraphQL server. + +import { YoutubeIframe } from '#components/youtube-iframe' + + + +If you haven't used Yoga before, this is how easy it is to create a production-ready GraphQL server: + +```ts +import { createServer } from '@graphql-yoga/node' + +const server = createServer({ + schema: { + typeDefs: /* GraphQL */ ` + type Query { + hello: String + } + `, + resolvers: { + Query: { + hello: () => 'Hello from Yoga!' + } + } + } +}) + +server.start() +``` + +If you are interested in following along with me to build a server, add plugins and more, you can +watch the recording of the workshop: + + + +Hopefully you find this talk interesting, and you learn more about building GraphQL severs with +Yoga! diff --git a/packages/web/docs/src/app/blog/(posts)/building-random-gif-generator-with-fets-and-giphy/auto.png b/packages/web/docs/src/app/blog/(posts)/building-random-gif-generator-with-fets-and-giphy/auto.png new file mode 100644 index 00000000000..98c08700193 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/building-random-gif-generator-with-fets-and-giphy/auto.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/building-random-gif-generator-with-fets-and-giphy/banner.png b/packages/web/docs/src/app/blog/(posts)/building-random-gif-generator-with-fets-and-giphy/banner.png new file mode 100644 index 00000000000..73852ed065e Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/building-random-gif-generator-with-fets-and-giphy/banner.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/building-random-gif-generator-with-fets-and-giphy/gif.gif b/packages/web/docs/src/app/blog/(posts)/building-random-gif-generator-with-fets-and-giphy/gif.gif new file mode 100644 index 00000000000..bd6bce978c5 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/building-random-gif-generator-with-fets-and-giphy/gif.gif differ diff --git a/packages/web/docs/src/app/blog/(posts)/caching-data-with-dataloader/page.mdx b/packages/web/docs/src/app/blog/(posts)/caching-data-with-dataloader/page.mdx new file mode 100644 index 00000000000..c26e3a77434 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/caching-data-with-dataloader/page.mdx @@ -0,0 +1,227 @@ +--- +title: Caching data with DataLoader +authors: giladtidhar +tags: [cache, data, server, graphql] +date: 2022-01-26 +description: How to use DataLoader to cache data in your GraphQL server. +--- + +import { Callout } from '@theguild/components' + +## Caching + +Caching is an essential part of designing scalable and performant GraphQL API. + +But, what is caching? Caching is the technical design of trying to avoid compute-intensive tasks by +keeping their results temporarily in a cache. A cache is a place in memory for important data, which +can be delivered to the client faster than the regular procedure. + +For example, for a GraphQL API, we might want to cache a field value that comes from a slow external +API. The goal of caching is to improve performance where it's needed the most. + +### How Is Caching Faster? + +Using a cache for compute-intensive data is faster for a few reasons: + +- A cache is usually not stored in storage, but in memory, which is a much faster. +- A cache is smaller than a database, so there are a lot less values to filter through. +- While some cached data might be queried a lot of times, it will be fetched from the database only + one time, so there's less time lost for fetching multiple times. + +## How Do We Use Caching? + +### Caching with `dataloader` + +My preferred platform for caching is dataloader, and in this tutorial I will explain how to use +dataloader with Node.js and Typescript, to have caching in your server. + + +**Tip**: `dataloader` outside of GraphQL + +The `dataloader` package was created specifically for GraphQL, but it can work with other Node.js +programs. + + + +## Using DataLoader + +### 1. Install DataLoader in Your Project + +Install `dataloader` by running the following: + +`npm install --save dataloader` or `yarn add dataloader` + +### 2. Import `dataloader` + +To start, import `dataloader` and LRU (LRU is a cache algorithm that will put only the most _X_ +requested items in the cache). + +```ts +import DataLoader from 'dataloader' +import { LRUMap } from 'lru_map' +``` + +### 3. Define the Types That Would Be Saved to Our Cache + +The first type should be a way to represent the data, like a string with a name, and the other is +the data itself. For example, let's say we're trying to cache books. Our package name will be the +books' id, so we can tell which book is which. Our package data will be the books' length, writer, +price etc... + +so in this case, our schema would look like this: + +```graphql +type book { + id: ID! + name: String! + author: author! + price: Int! + description: String! + length: Int! +} +type author { + id: ID! + name: String! +} +``` + +and the corresponding data type would look like this: + +```ts +type packageId = string + +type PackageData = /* note that this is the type of the data we represent. */ { + id: string + name: string + author: { + id: string + name: string + } + price: number + description: string + length: number +} +``` + +The reason we write types is for type safety, let's say we have books and toys which have different +values, we wouldn't want that somehow toys will be inserted into our books cache + +### 4. Create the DataLoader Instance + +```ts +const dataLoader = new DataLoader( + async (/* (1) */ keys: readonly packageId[]) => + /* (2) */ Promise.all( + keys.map(async packageId => { + await getData(packageId).catch(e => e) + }) + ), + { + cacheMap: new LRUMap(100) /* (3) */ + } +) +``` + +As you can see, we give the keys that the user asks for to the dataloader (1). + +I recommend setting the keys to be some kind of id for the following reason: Lets say that in your +api, the user asks for data using its id, so the user will fetch with `id:"something"`, you could +just pass the id as the key, instead of changing it. + +The dataloader will first check if it has the keys on cache, and if so, it would return the data, +without going through the database, saving you a lot of valuable time. + +After that (2), we give the dataloader a function for fetching the data, in case it doesn't have it +in the cache. In this case, I have the function `getData`, and I'm using the keys to "get data" from +my database. + +In the end (3) we give it cacheMap and some value, the value represents how many queries dataloader +will cache, in this case, after a 100 values, it will delete the least used value (the one that +wasn't queried for the longest time) to make space for the 101st value. + +From now, to query data, you just run + +```ts +dataLoader.load(keys) +``` + +## DataLoader with GraphQL + +Dataloader was designed to work with GraphQL, to solve the +[n+1 problem](https://medium.com/the-marcy-lab-school/what-is-the-n-1-problem-in-graphql-dd4921cb3c1a) + +The N+1 problem is a common problem when designing GraphQL API. Looking at the Query below, we can +see that the GraphQL API will call 20 times the `Book.author` resolver: + +```graphql +query BooksWithAuthors { + books(first: 20) { + id + title + author { + id + name + } + } +} +``` + +Depending on the resolver implementation, this query might trigger 20 SQL queries or API calls to +resolve authors that might've wrote multiple books. Dataloader helps to solve this problem by +caching, deferring and grouping similar resolver calls. + +### How to Use the DataLoader Package with GraphQL? + +To use the dataloader with GraphQL just pass it in the context! + +Now, your code should look somewhat like this: + +```ts +import DataLoader from "dataloader"; +import { LRUMap } from "lru_map"; + +type packageId = string; +type PackageData = Package; + +export type GraphQLContext = { + dataLoader: DataLoader; + // ... +}; +const dataLoader = new DataLoader( + async (keys: readonly PackageId[]) => { + return await Promise.all( + keys.map(async (packageId) => { + await getData(packageId).catch((e) => e); + }) + ); + }, + { + cacheMap: new LRUMap(100), + } +); +// ... +export async function contextFactory() { + return { dataLoader, ... }; +} +``` + +Now, in your resolvers, just call `context.dataLoader.load(keys)` and that's it! You now have +caching in your server! + +An example of a resolver implementation: + +```ts +export const resolvers = { + Query: { + getBook(parent, input, context) { + return context.dataLoader.load(input.bookId) + } + } +} +``` + +for further learning, check these out: + +[The n+1 problem](https://medium.com/the-marcy-lab-school/what-is-the-n-1-problem-in-graphql-dd4921cb3c1a) +[The dataloader github page](https://github.com/graphql/dataloader) +[A great video about dataloader](https://youtu.be/_FQ1ZEWIn2s) diff --git a/packages/web/docs/src/app/blog/(posts)/ci-ci-graphql-inspector/circleci.jpeg b/packages/web/docs/src/app/blog/(posts)/ci-ci-graphql-inspector/circleci.jpeg new file mode 100644 index 00000000000..be201163f83 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/ci-ci-graphql-inspector/circleci.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/ci-ci-graphql-inspector/page.mdx b/packages/web/docs/src/app/blog/(posts)/ci-ci-graphql-inspector/page.mdx new file mode 100644 index 00000000000..5e44767b6dc --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/ci-ci-graphql-inspector/page.mdx @@ -0,0 +1,92 @@ +--- +title: Validate GraphQL Schema - Continuous Integration & Delivery Pipeline +authors: kamil +tags: [graphql, graphql-inspector] +date: 2020-05-03 +description: + Detect breaking changes and identify possible issues in GraphQL API with GraphQL Inspector +--- + +GraphQL Inspector since day one supported any kind of Continuous Integration and Delivery Pipeline. +Nothing new here, except now it's more flexible and super lightweight. + +The new version of GraphQL Inspector comes with a **CLI crafter specifically for CI/CD**. + +We recently released a lot more interesting features. You can read about them in +["New GraphQL Inspector and upcoming features"](/blog/new-graphql-inspector) article. + +## CLI for CI? + +The regular CLI comes with everything built-in. Loads Schema, Operations and Fragments from any +source. It can extract GraphQL pieces from GraphQL files, code files (Vue, React, Angular and even +Gatsby). The CLI supports GraphQL Endpoints, Git repositories and also files on GitHub. + +It's heavy! + +Usually, in the CI/CD pipeline you want to compare a current schema with the one in production and +detect breaking changes. Which means, you don't need all the other commands and features of +Inspector CLI. + +## CLI Designed for CI/CD + +That's why in new GraphQL Inspector, everything is a plugin. + +You want to compare schemas and detect breaking changes? + +```bash +yarn global add @graphql-inspector/diff-command +``` + +Your production schema is accessible through an endpoint but newly proposed schema is defined +in `.graphql` files? + +```bash +yarn global add @graphql-inspector/url-loader +yarn global add @graphql-inspector/graphql-loader +``` + +Ok, everything is ready! + +## How to Compare Schemas? + +You can use the command below in your CI/CD pipeline: + +```bash +graphql-inspector diff "https://api.com/graphql" "schema.graphql" +``` + +Here's how the result looks like. It shows a list of all changes and groups them by criticality +level (Breaking, Dangerous or Safe). + +![](./circleci.jpeg 'Example result') + +To learn more about CLI for CI, please read +["Continuous Integration"](https://graphql-inspector.com/docs/products/ci) chapter in our +documentation. + +--- + +## Future Plans + +We're working on **Azure and Bitbucket integrations** plus a **monitoring feature**. Talk to us to +try it out. + +We want to make **Azure** and **Bitbucket** a first-class citizens in GraphQL Inspector and give you +the same experience as you get right now with **GitHub**. + +**Monitoring** will enable you to analyze the traffic of your GraphQL APIs and provide details +needed to improve performance. Collecting informations about the usage will let you safely remove +deprecated pieces of GraphQL Schema. + +> If you're interested, please reach out to us! + +--- + +### Enjoy GraphQL Inspector! + +We have big plans for Inspector, and you're very welcome to join us in that journey. + +GraphQL Inspector is a tool created by developers, for developers and that's why we'd love to get +your feedback and shape GraphQL Inspector together! + +Oh... and it's [Open Sourced](https://github.com/kamilkisiela/graphql-inspector)! diff --git a/packages/web/docs/src/app/blog/(posts)/codegen-typescript-react-apollo/b58035f95547416aab98b54d3ce812f6.png b/packages/web/docs/src/app/blog/(posts)/codegen-typescript-react-apollo/b58035f95547416aab98b54d3ce812f6.png new file mode 100644 index 00000000000..1d99d3bf396 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/codegen-typescript-react-apollo/b58035f95547416aab98b54d3ce812f6.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/codegen-typescript-react-apollo/page.mdx b/packages/web/docs/src/app/blog/(posts)/codegen-typescript-react-apollo/page.mdx new file mode 100644 index 00000000000..bffad9d880a --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/codegen-typescript-react-apollo/page.mdx @@ -0,0 +1,153 @@ +--- +title: GraphQL Code Generator for Typescript React Apollo +authors: arda +tags: [graphql, codegen, react, react-apollo, hooks] +date: 2018-08-21 +updateDate: 2020-05-20 +description: + Generate React Apollo Query, Mutation and Subscription components and hook. All completely typed + by TypeScript. +--- + +import { CodeSandbox } from '#components/code-sandbox' +import { Callout } from '@theguild/components' + +export const embedOptions = { + theme: 'light', + view: 'editor', + previewwindow: 'console' +} + +[GraphQL Code Generator](https://github.com/dotansimha/graphql-code-generator) is a template based +generator that lets you generate anything out of your GraphQL schemas and queries. + +So we've created a new template that generates **React Apollo**'s **_Query_**, **_Mutation_** and +**_Subscription_** components, as well as **_HOC_** components, all completely typed by +**_TypeScript_**, so you won't have to do that work manually! + +## Introducing a Code Generator for React Apollo + +Whether you use the new **React Apollo API** or you prefer to use **HOC**, there is really no need +to write those wrapper components again and again! + +Based on a GraphQL static schema and a GraphQL query, the +[GraphQL Codegen - Typescript React Apollo Template](https://github.com/dotansimha/graphql-code-generator/tree/master/packages/templates/typescript-react-apollo) +will generate a ready to use, fully typed components. All you need to do is to write your query, +mutation or subscription and just use those components in your application. + +![](./b58035f95547416aab98b54d3ce812f6.png) + +Using **_React_**, **_TypeScript_** and **_GraphQL_** in a coordinated way, gives us new level of +simplicity and power for our developer experience: + +- **Less code to write** — no need to create a network call, no need to create Typescript typings, + no need to create a dedicated React Component +- **Strongly typed out of the box** — all types are being generated, no need to write any Typescript + definitions and struggle to keep them updated +- **Full developer experience of tools and IDEs** — development time autocomplete and error + checking, not only across your frontend app but also with your API teams! + +## Play with It + +We prepared an example of how to use those auto generated components, +[it's available on CodeSandbox](https://codesandbox.io/s/github/dotansimha/graphql-codegen-react-apollo-example). + + + +## Start Using It + +All you need to do to use React Apollo template is to install two packages: + +``` +yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo +``` + +> Make sure you have `graphql` installed as well. + +Now let's create `codegen.yml` configuration file, pointing to our schema and documents files: + + + +Now let's set up a npm script in `package.json` to run `graphql-codegen` command: + +```json +{ + "scripts": { + "generate": "graphql-codegen" + } +} +``` + +It might seem like a lot but lets split it into smaller pieces and explain each one of them it will +make things easier. + +- **schema:** — path of a file with schema or an URL +- **documents** - list of code files that contains your GraphQL queries +- **generates:** — list of output files, with a nested list of plugins we would like to use. + +Then, define a `.graphql` file with a document that you'd like to use in a component: + + + +Next, you need to run GraphQL Code Generator to generate Typescript types and React components: + +``` +yarn generate +``` + +> You can also run `yarn graphql-codegen` directly. + +Then, you simply import the auto-generated React Hook, named after your GraphQL operation name and +use it in your component: + + + +You can learn more about [React Apollo API here](https://apollographql.com/docs/react). + + +If you are not using React Hooks, and you prefer to use React HOC or React Components for your data fetching, you can change the codegen configuration flags according to your needs: + +`withHooks: true` - will generate type-safe hooks. + +`withComponent: true` - will generate type-safe data components. + +`withHOC: true` - will generate type-safe High-Order-Components. + + + +--- + +We believe **_GraphQL_** is a game changer in how you plan and create your frontend apps. + +The vision that guides us is that you should be able to sketch a list of data types your backend can +provide, sketch components and their data dependencies — and all the rest of the plumbing can be +generated for you. + +Once you'll write an app like that, you will ask yourself why did you write all the other +boilerplate code by yourself before. + +This is just one template out of many, check out more things you can generate with the +[GraphQL Code Generator](https://graphql-code-generator.com) and give us ideas about other templates +you would like to see implemented. diff --git a/packages/web/docs/src/app/blog/(posts)/collecting-graphql-live-query-resource-identifier-with-graphql-tools/cover.jpg b/packages/web/docs/src/app/blog/(posts)/collecting-graphql-live-query-resource-identifier-with-graphql-tools/cover.jpg new file mode 100644 index 00000000000..0766eea382d Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/collecting-graphql-live-query-resource-identifier-with-graphql-tools/cover.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/collecting-graphql-live-query-resource-identifier-with-graphql-tools/page.mdx b/packages/web/docs/src/app/blog/(posts)/collecting-graphql-live-query-resource-identifier-with-graphql-tools/page.mdx new file mode 100644 index 00000000000..b6df63775df --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/collecting-graphql-live-query-resource-identifier-with-graphql-tools/page.mdx @@ -0,0 +1,364 @@ +--- +title: Collecting GraphQL Live Query Resource Identifier with GraphQL Tools +tags: [graphql, graphql-tools, real-time, live-query] +authors: laurin +date: 2021-07-01 +description: + GraphQL Tools can help to solve a variety of problems. Lean how it can be used for live queries. +--- + +import { Callout } from '@theguild/components' + +Photo by [Łukasz Nieścioruk](https://unsplash.com/@luki90pl) on +[Unsplash](https://unsplash.com/photos/BMVzvCYTlvU") + + + **Note:** This article showcases technical details for implementing a Live Query engine with + graphql-tools. If you are new to Live Queries you might want to check out [Subscriptions and Live + Queries - Real Time with GraphQL](/blog/subscriptions-and-live-queries-real-time-with-graphql) + first. + + +GraphQL live queries can solve real-time updates in a more elegant way than GraphQL subscriptions. + +Instead of subscribing to events live queries primarily subscribe to data changes. + +Instead of updating the client store manually a live query updates the client store _magically_ +without redundant cache update logic on the client. + +[You can learn more about the differences here](https://medium.com/open-graphql/graphql-subscriptions-vs-live-queries-e38302c7ab8e). + +All those benefits, however, come with the drawback of the server having to become stateful, in +particular, being aware of all the data the client operation consumes and re-executing those query +operations for a specific client once the underlying data changes. + +As I first started experimenting with GraphQL live queries the easiest solution was to simply +trigger live query re-executions based on the `Query` object type root fields. E.g. a query with a +selection set selection on the `Query.viewer` field could be re-executed by emitting the +`Query.viewer` event through the live query store event emitter. However, the viewer could be a +completely different record/resource for each client that consumes the given query operation. + +To be more clear here is the corresponding schema: + +```graphql +type User { + id: ID! + login: String! +} + +type Query { + """ + Returns the authenticated user. Returns null in case the user is not authenticated. + """ + viewer: User + """ + List of the users that are currently online. + """ + onlineUsers: [User!]! +} + +type Mutation { + updateLogin(newLogin: String!): Boolean! +} +``` + +```graphql +query viewer @live { + viewer { + id + login + } +} +``` + +Let's see how the implementation for this could look like: + +```tsx +const Query = { + viewer: (source, args, context) => { + return context.viewer + } +} + +const Mutation = { + updateLogin: async (source, args, context) => { + await context.db.updateUser(context.viewer.id, args.newLogin) + + context.liveQueryStore.triggerUpdate(`Query.viewer`) + return true + } +} +``` + +If a specific user updates his login we shouldn't invalidate and re-execute any live query operation +that has a viewer selection set for any connected user who might not even be affected by that +change! + +![All queries are invalidated](https://dev-to-uploads.s3.amazonaws.com/i/f5s5de59uvkbjwbuwrou.png) + +At the same time, the user could also be referenced in another operation e.g. a list of all +available users (`Query.onlineUsers`). The `Query.viewer` event would not cover and schedule a +re-execution for operations that select the user via that field. + +## There Must Be a Better Solution for Uniquely Identifying the Selection Set Data + +As you probably noticed the user has an `id` field of the `ID!` (nonnull id) type. This is a +commonly used field for uniquely identifying a resource on the client-side. Apollo-client uses the +`__typename` field in combination with the `id` field as the default resource cache key (`User:1`), +Relay goes a step further and already assumes that the resource type is already encoded (e.g. +`base64("User:1")` Note: +[You are not forced to use base64 🤔](https://dev.to/daniel15/comment/np0o)) inside the id and +therefore only uses the id field. + +What if we could also use such an identifier on the server-side in our live query store +implementation? + +My current implementation just traversed the AST of the query operation and extracted the +[schema coordinates](https://github.com/graphql/graphql-spec/pull/746) on the root query type. E.g. +`Query.viewer` for the `viewer` live query operation from above. + +![Live Query Store with schema coordinates on operation context](https://dev-to-uploads.s3.amazonaws.com/i/ft60prjse6pcqnlin4qv.png) + +However, in case we would want to identify the user via the id we must also add something like +`User:1` to the set of resources the live query operation selects. This requires schema knowledge as +the live query store needs to know which type has an id field and if included in the selection set, +gather the corresponding resource identifier. + +![Live Query Store with resource identifiers and schema coordinates on operation context](https://dev-to-uploads.s3.amazonaws.com/i/cdicogp30k34ml9o3ccg.png) + +As mentioned above this allows more granular query invalidations. + +![More granular updates are now possible](https://dev-to-uploads.s3.amazonaws.com/i/sde19ha6upubo0ofyg8a.png) + +The first drawback I had in mind is that if an operation does not specify the `id` field on the +selection set, the resource cannot be tracked by the live query store. + +However, most operations will probably select the `id` field as it is most likely used on the client +for the cache keys. + +Furthermore, it could be possible to simply transform the query in such a way that the `id` field is +added to the selection set (similar to how apollo-client is by default adding a `__typename` +selection to each object type). + +For keeping things simple I decided to push the responsibility for selecting the id field to the +client that sends the live query operation. I also could not find a use-case in my existing +application where there was no `id` selection for a resource 👍. + +## Implementing the Resource Identifier Collector + +The next obstacle is to decide how the ids are extracted and I had two options in mind. + +### 1. Traversing the GraphQL Execution Result Tree + +This simply seemed complicated to me as I would need to traverse the whole result while somehow +guessing/checking the type of each leaf based on the operation AST and the schema. I quickly dropped +that idea. + +### 2. Manually Register the Resource Identifier by Calling a Function That Is Injected via the Context + +The goal of my live query store implementation is to add live query support to any schema with +minimal effort. Passing something along-side the context that a library user must call inside a +query resolver seemed wrong and all this should be an implementation detail the library user should +not care about. + +Imagine if we had to register a resource manually in each resolver that returns an object type. + +```tsx +const Query = { + viewer: (source, args, context) => { + const viewer = context.viewer + context.registerResource(`User:${viewer.id}`) + return viewer + } +} +``` + +It might seem quite simple for a single resolver, however, it can quickly clutter and lead to bugs +if we have to manually do that for any resource in any resolver. + +Ideally a library user will just have to add a `context.liveQueryStore.triggerUpdate("User:1")` line +to the `updateLogin` mutation field resolver in order to _magically_ schedule an operation +re-execution, without the overhead of adding an additional function call to each resolver. + +```tsx +const Query = { + viewer: (source, args, context) => { + // No tracking registration code here. + return context.viewer + } +} + +const Mutation = { + updateLogin: async (source, args, context) => { + await context.db.updateUser(context.viewer.id, args.newLogin) + + context.liveQueryStore.triggerUpdate(`User:${context.viewer.id}`) + return true + } +} +``` + +So, I thought more about how this could be implemented in a less verbose way. + +As any other field, the `id` field has a resolver (either the default resolver provided by GraphQL +or a user-defined resolver), so if there was a way to wrap each `id` field resolver with a function +that could solve the issue. The wrapper could call the actual resolver, register the resource, and +then return the value. The user won't have to care about anything (besides adding the `id` field to +the selection set of the query). + +The best library for transforming and modifying GraphQL Schemas is +[`graphql-tools`](https://github.com/ardatan/graphql-tools). Fortunately, it is now maintained by +The Guild, as apollo abandoned it and was maintained pretty poorly. + +So I dug a bit into the fancy documentation and found `@graphql-tools/wrap`. + +A quick excerpt from the documentation: + +> Schema wrapping is a method of making modified copies of GraphQLSchema objects, without changing +> the original schema implementation. + +Looks perfect, but since in this specific case I could potentially modify/map an existing schema, I +digged a bit further, chatted with [@yaacovCR](https://github.com/yaacovCR) and learned about +`mapSchema` from `@graphql-tools/utils`. It maps an existing schema into a new schema while applying +some rules (kind of similar how `Array.map()` works 🤔). + +Using `mapSchema` might be slightly faster as there will be no delegation to a second schema. + +So the final plan is the following: We map the existing schema and warp the resolver fields append +the resource identifiers to a Set. + +```ts +import { + defaultFieldResolver, + execute, + GraphQLOutputType, + GraphQLScalarType, + GraphQLSchema, + isNonNullType, + isScalarType +} from 'graphql' +import { MapperKind, mapSchema } from '@graphql-tools/utils' + +const ORIGINAL_CONTEXT_SYMBOL = Symbol('ORIGINAL_CONTEXT_SYMBOL') + +const isNonNullIDScalarType = (type: GraphQLOutputType): type is GraphQLScalarType => { + if (isNonNullType(type)) { + return isScalarType(type.ofType) && type.ofType.name === 'ID' + } + return false +} + +// invokes the callback with the resolved or sync input. Handy when you don't know whether the input is a Promise or the actual value you want. +export const runWith = (input: T | Promise, callback: (value: T) => void) => { + if (input instanceof Promise) { + input.then(callback, () => undefined) + } else { + callback(input) + } +} + +const addResourceIdentifierCollectorToSchema = ( + schema: GraphQLSchema, + idFieldName: string +): GraphQLSchema => + mapSchema(schema, { + [MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typename) => { + const newFieldConfig = { ...fieldConfig } + + let isIDField = fieldName === idFieldName && isNonNullIDScalarType(fieldConfig.type) + let resolve = fieldConfig.resolve ?? defaultFieldResolver + + newFieldConfig.resolve = (src, args, context, info) => { + if (!context || ORIGINAL_CONTEXT_SYMBOL in context === false) { + return resolve(src, args, context, info) + } + + const collectResourceIdentifier: ResourceIdentifierCollectorFunction = + context.collectResourceIdentifier + context = context[ORIGINAL_CONTEXT_SYMBOL] + const result = resolve(src, args, context, info) + + if (isIDField) { + runWith(result, (id: string) => collectResourceIdentifier({ typename, id })) + } + return result + } + + return newFieldConfig + } + }) +``` + +The implementation for executing the operation is similar to the following: + +```ts +const newIdentifier = new Set(rootFieldIdentifier) +const collectResourceIdentifier: ResourceGatherFunction = ({ typename, id }) => + // for a relay spec conform server the typename could even be omitted :) + newIdentifier.add(`${typename}:${id}`) + +// You definitely wanna cache the wrapped schema as you don't want to re-create it for each operation :) +const wrappedSchema = addResourceIdentifierCollectorToSchema(schema) + +const result = execute({ + schema: wrappedSchema, + document: operationDocument, + operationName, + rootValue, + contextValue: { + [ORIGINAL_CONTEXT_SYMBOL]: contextValue, + collectResourceIdentifier + }, + variableValues: operationVariables +}) +``` + +I had to wrap the "user" context in a context (context-ception 🤯) on which I also attached the +function for adding the resource identifier to the resource identifier set. I got inspired for this +by the apollo-server source code, as I knew it has a way for measuring resolver execution time, +which must be done on a request/operation basis similar to the resource identifier collection. This +method allows using a new function/context for each execution. Inside the field resolver, the +correct user context is then passed into the actual (user) field resolver. + +Now after the operation has been executed against the schema the `newIdentifier` Set should contain +the identifiers of all the resources that were resolved during the operation execution. + +The live query store can now use that information for re-executing queries once a resource +identifier event is emitted 👌. + +## Conclusion + +Identifying resources and invalidating queries based on a resource basis rather than a query root +field basis allows more efficient query re-executions and can avoid pushing unnecessary updates to +clients. + +GraphQL Tools is a super handy library that can be used for solving a huge variety of problems. I am +glad it got such a huge update and good documentation! + +The implementation probably won't cover all use-cases. What if a client is not authenticated and the +`Query.viewer` resolver returns `null`. There is no `User:ID` string available on the live query +store operation context once the user has authenticated. Either a `Query.viewer` update must be +emitted through the live query store emitter (which will affect ANY client operation that selects +the `viewer`), the client must re-execute the operation after login or the live query store must +somehow be notified to re-execute all operations of the user that just authenticated. + +In case you are interested in the source code for the implementation +[check it out on GitHub](https://github.com/n1ru4l/graphql-live-query/blob/7674745b441201448feb7043290bd2ac7dbdd11c/packages/in-memory-live-query-store/src/InMemoryLiveQueryStore.ts#L44-L87) + +**There is still more to discover and build in live query land!** + +We still need to manually notify the live query store that a resource must be invalidated. An +abstraction for doing this behind the scenes could vastly differ for different stacks. + +Maybe the ORM/database store layer could emit the events or a proxy could emit those events based on +database operations such as `INSERT`, `DELETE`, and `UPDATE`. + +Re-executing a query operation is nice and smart, but not the most efficient solution. What if we +could only re-execute certain resolvers? I already have some ideas in mind, and I will probably +write about that as well! + +[Check out this super cool talk about live queries @ Facebook!](https://youtube.com/watch?v=BSw05rJaCpA) + +[Check out this super cool talk about live queries @ Samsara!](https://youtube.com/watch?v=g-asVW9JFPw) + +[I also wrote an article about my Socket.io GraphQL Server Engine implementation!](https://dev.to/n1ru4l/graphql-live-queries-with-socket-io-4mh6) diff --git a/packages/web/docs/src/app/blog/(posts)/coolest-underrated-design-pattern-in-react/8da637649866c3f3ebcc4e5c90437d89.png b/packages/web/docs/src/app/blog/(posts)/coolest-underrated-design-pattern-in-react/8da637649866c3f3ebcc4e5c90437d89.png new file mode 100644 index 00000000000..361ea5584df Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/coolest-underrated-design-pattern-in-react/8da637649866c3f3ebcc4e5c90437d89.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/coolest-underrated-design-pattern-in-react/f8270eba98b7fce153579c2607ca2eb8.png b/packages/web/docs/src/app/blog/(posts)/coolest-underrated-design-pattern-in-react/f8270eba98b7fce153579c2607ca2eb8.png new file mode 100644 index 00000000000..907d3fe42f7 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/coolest-underrated-design-pattern-in-react/f8270eba98b7fce153579c2607ca2eb8.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/create-native-addon-using-c/6989c5ee48f37336c330100619adbe13.png b/packages/web/docs/src/app/blog/(posts)/create-native-addon-using-c/6989c5ee48f37336c330100619adbe13.png new file mode 100644 index 00000000000..3e674f42d7c Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/create-native-addon-using-c/6989c5ee48f37336c330100619adbe13.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/create-native-addon-using-c/e2b532f4bd546a9c1bdb1cb2022fbd80.jpg b/packages/web/docs/src/app/blog/(posts)/create-native-addon-using-c/e2b532f4bd546a9c1bdb1cb2022fbd80.jpg new file mode 100644 index 00000000000..146a194daf0 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/create-native-addon-using-c/e2b532f4bd546a9c1bdb1cb2022fbd80.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/2b3e4b1d4204649da2b520229d0a3b5d.png b/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/2b3e4b1d4204649da2b520229d0a3b5d.png new file mode 100644 index 00000000000..dcefd9e133f Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/2b3e4b1d4204649da2b520229d0a3b5d.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/33a00b9051bfb0894f2d6964afed0de9.png b/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/33a00b9051bfb0894f2d6964afed0de9.png new file mode 100644 index 00000000000..0e552e8c195 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/33a00b9051bfb0894f2d6964afed0de9.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/4d50875c67ddc3d9139635c8597a1ea1.png b/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/4d50875c67ddc3d9139635c8597a1ea1.png new file mode 100644 index 00000000000..963f72ee6a8 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/4d50875c67ddc3d9139635c8597a1ea1.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/7e4dac18ecbfa0f679d7d2c41134b75d.png b/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/7e4dac18ecbfa0f679d7d2c41134b75d.png new file mode 100644 index 00000000000..5b00aa25533 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/7e4dac18ecbfa0f679d7d2c41134b75d.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/9ecd4800956fc386f893c8730a0beb63.jpeg b/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/9ecd4800956fc386f893c8730a0beb63.jpeg new file mode 100644 index 00000000000..ef0fc783c6a Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/9ecd4800956fc386f893c8730a0beb63.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/b8088e43c6db651c5daa8ed96b65125a.png b/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/b8088e43c6db651c5daa8ed96b65125a.png new file mode 100644 index 00000000000..c1528f47c15 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/b8088e43c6db651c5daa8ed96b65125a.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/dee039857e0e507016a5fb742f50c814.png b/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/dee039857e0e507016a5fb742f50c814.png new file mode 100644 index 00000000000..ba7f7b6b0c9 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/customizable-css-engine-in-javascript/dee039857e0e507016a5fb742f50c814.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/defer-support-codegen/monitoring-view.png b/packages/web/docs/src/app/blog/(posts)/defer-support-codegen/monitoring-view.png new file mode 100644 index 00000000000..c055d72f3df Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/defer-support-codegen/monitoring-view.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/defer-support-codegen/page.mdx b/packages/web/docs/src/app/blog/(posts)/defer-support-codegen/page.mdx new file mode 100644 index 00000000000..9b3aae69177 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/defer-support-codegen/page.mdx @@ -0,0 +1,260 @@ +--- +title: Using @defer Directive with GraphQL Code Generator +authors: aleksandra +tags: [graphql, graphql-federation, codegen] +date: 2023-05-24 +description: + Learn how to boost GraphQL performance using the @defer directive and GraphQL Code Generator for + deferred fragment field resolution. +--- + +Optimization is crucial for the good performance of GraphQL applications, which can grow in +complexity and size. The `@defer` directive in GraphQL is a feature that allows fragment fields to +be resolved separately from the rest of the query. This can be useful when dealing with large +queries or slow-to-resolve fields. + +And now, we have some exciting news to share: support for `@defer` is available in GraphQL Code +Generator! 🥳 + +You can start using GraphQL Code Generator to generate code for queries that include deferred +fragments, and the generated code will automatically include support for the `@defer` directive. + +Let's take a closer look at how the directive works and how you can use it in your GraphQL +applications. + +## How Does `@defer` Work? + +When a query includes a deferred fragment field, the server will return a partial response with the +non-deferred fields first, followed by the remaining fields once they have been resolved. + +For example, consider the following query: + +```graphql +query GetUser($id: ID!) { + user(id: $id) { + id + name + ...OrdersFragment @defer + } +} + +fragment OrdersFragment on User { + orders { + id + total + } +} +``` + +Let's say you have a query that fetches information about a user, but the `orders` field takes more +time to resolve. You can use the `@defer` directive to first return a partial response with the `id` +and `name` and then later return the `orders` field. This can improve the performance of your +application by reducing the amount of time it takes to load initial data. + +## Using `@defer` with GraphQL Codegen + +Once you update the GraphQL Code Generator to the latest version and start using the `@defer` +directive in your queries, the generated code will automatically include support for the directive. +Below, we're going to see an example of usage. + +As the `@defer` directive +[is now supported in Apollo](https://www.apollographql.com/docs/react/data/defer/), we're going +explore how it works with Apollo Client. You need to use Apollo Client version 3.7.0 or higher for +the defer support to work. + +_Note: you could also use the `@defer` directive with other GraphQL clients, such as +[URQL](https://formidable.com/open-source/urql/docs/) — the example usage in Hive with URQL is +described below._ + +Here's an example `codegen.ts` config: + +```jsx +// condegen.ts +import { type CodegenConfig } from '@graphql-codegen/cli' + +const config: CodegenConfig = { + schema: '', + documents: ['src/**/*.tsx'], + generates: { + './gql/': { + preset: 'client' + } + }, + hooks: { afterAllFileWrite: ['prettier --write'] } +} + +export default config +``` + +Let's consider the same example with `user` and `orders`. Once you declare a fragment and a query in +your application, you can run the codegen, and it will have the information about deferred fields. + +```jsx +// src/index.tsx +import { graphql } from './gql' + +const OrdersFragment = graphql(` + fragment OrdersFragment on User { + orders { + id + total + } + } +`) + +const GetUserQuery = graphql(` + query GetUser($id: ID!) { + user(id: $id) { + id + name + ...OrdersFragment @defer + } + } +`) +``` + +The generated type for `GetUserQuery` will have information that the fragment is _incremental,_ +meaning it may not be available right away. + +```tsx +// gql/graphql.ts + +export type GetUserQuery = { __typename?: 'Query'; id: string; name: string } & ({ + __typename?: 'Query' +} & { + ' $fragmentRefs'?: { OrdersFragment: Incremental } +}) +``` + +Apart from generating code that includes support for the `@defer` directive, the Codegen also +exports a utility function called `isFragmentReady`. This function can be used to determine whether +the data for a deferred fragment field has been resolved or not. The `isFragmentReady` function +takes three arguments: the query document, the fragment definition, and the data returned by the +query. You can use it to conditionally render components based on whether the data for a deferred +fragment is available, as shown in the example below: + +```jsx +// index.tsx +import { useQuery } from '@apollo/client'; + +import { useFragment, graphql, FragmentType, isFragmentReady } from './gql'; + +const OrdersFragment = graphql(`...`) + +const GetUserQuery = graphql(`...`); + +const OrdersList = (props: { data: FragmentType }) => { + const data = useFragment(OrdersFragment, props.data); + return ( + // render orders list + ) +}; + +function App() { + const { data } = useQuery(GetUserQuery); + + return ( +
    + {data && ( + <> + Name: {data.name} + Id: {data.name} + {isFragmentReady(GetUserQuery, OrdersFragment, data) + && } + + )} +
    + ); +} + +export default App; +``` + +With the above code, the `orders` field is rendered only if it's available in the data object. + +### Example with GraphQL Yoga and Apollo Client + +If you want to see the `@defer` directive in action, you can check out +[the example in the GraphQL Code Generator repository](https://github.com/dotansimha/graphql-code-generator/tree/master/examples/react/apollo-client-defer). + +### How Does It Compare with Relay? + +Relay allows for incremental delivery of data from the server to the client without an extra logic +on the client because it uses Suspense. It enables the client to render parts of the UI as soon as +the corresponding data is available. + +The same can be achieved in other GraphQL clients with the `@defer` directive and the +`isFragmentReady` utility function. + +As the Suspense usage requires deep integration with a GraphQL client code, the GraphQL Code +Generator does not support it out-of-the-box. We are looking forward to collaborate with the +maintainers of clients like Apollo Client and Urql, and go for a more generic suspense-backed +implementation in the future. + +## Example Usage in GraphQL Hive + +To evaluate the codegen support for the `@defer` directive, we integrated it into the GraphQL Hive +application. GraphQL Hive is a schema registry for GraphQL. + +> With Hive you manage and collaborate on all your GraphQL schemas and GraphQL workflows, regardless +> of the underlying strategy, engine or framework you're using: this includes Schema Stitching +> (opens in a new tab), [Apollo Federation](https://the-guild.dev/graphql/hive/federation), or just +> a traditional monolith approach. + +The Hive application uses [URQL](https://formidable.com/open-source/urql/docs/) as its GraphQL +client, which supports the `@defer` directive starting from version 4.0.0. + +In Hive, the operations page displays all server-executed operations, providing valuable insights +into operation performance, end-user consumption, and operation success rates. + +![](./monitoring-view.png) + +Given the substantial amount of data to be loaded, we decided to apply the `@defer` directive to the +operation stats query fragments: + +```graphql +query generalOperationsStats($selector: OperationsStatsSelectorInput!, $resolution: Int!) { + operationsStats(selector: $selector) { + ... on OperationsStats { + # ... + } + ... on OperationsStats @defer { + failuresOverTime(resolution: $resolution) { + # ... + } + requestsOverTime(resolution: $resolution) { + # ... + } + durationOverTime(resolution: $resolution) { + # ... + } + } + ... on OperationsStats @defer { + clients { + # ... + } + } + } +} +``` + +You can check out [a PR to Hive](https://github.com/kamilkisiela/graphql-hive/pull/2082) that adds +`@defer` in the operations page. + +Throughout the process, we collaborated a bit with the URQL team to enhance URQL's `graphcache` +exchange, resulting in improved support for the `@defer` directive in the new version. You can check +out the relevant PRs: + +- [Mark deferred, uncached results as partial](https://github.com/urql-graphql/urql/pull/3163) +- [Fix defer field state becoming sticky and affecting future fields](https://github.com/urql-graphql/urql/pull/3167) +- [Handle both variations of multipart/mixed preamble](https://github.com/urql-graphql/urql/pull/3172) + +--- + +To summarize, the `@defer` directive in GraphQL is a feature that allows fragment fields in a query +to be resolved separately from the rest of the query. With support for `@defer` now available in +GraphQL Code Generator, you can easily add deferred resolution to your GraphQL applications and +optimize their performance. We hope that this new feature will help you build even more powerful and +performant GraphQL apps! + +Let us know your thoughts. We'd love your feedback! diff --git a/packages/web/docs/src/app/blog/(posts)/dependency-injection-library-in-graphql-modules/page.mdx b/packages/web/docs/src/app/blog/(posts)/dependency-injection-library-in-graphql-modules/page.mdx new file mode 100644 index 00000000000..7757f2215e6 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/dependency-injection-library-in-graphql-modules/page.mdx @@ -0,0 +1,279 @@ +--- +title: Dependency Injection in GraphQL-Modules +authors: arda +tags: [DI, graphql, graphql-modules] +date: 2019-01-11 +description: Why did we implement our own Dependency Injection library for GraphQL-Modules? +--- + +## Why Not Inversify / Angular / NestJS? + +When designing GraphQL Modules, we tried different approaches to encapsulate modules safely. We +created a solution where you won't need to use Dependency Injection when you start, but after you +are getting to a certain scale, you can make use of our separate DI library to ease the separation +and make the boundaries stricter, only at the point when it really makes sense and helps you. When +you get to that point, DI helps you mostly for ease of mocking providers for testing and +encapsulation for less error-prone implementation. + +In the early stages of GraphQL-Modules, it used to have +[Inversify](https://github.com/inversify/InversifyJS) internally for Dependency Injection in +GraphQL-Modules. Inversify is a platform-agnostic Dependency Injection library written in +JavaScript. Unfortunately, **_Inversify_** didn't fit our needs for modular DI (Dependency +Injection) due to some critical reasons that we'll expand on this article. + +That's why, we implemented our own platform-agnostic Dependency Injection library called +`@graphql-modules/di` which is independent from `@graphql-modules/core`, and it can be used by +itself. + +It supports factory, class and value providers that can have `Symbol`, `string`, `number`, +`function` and `object` tokens in a `provide` definition, and it can address constructable classes, +factory functions and constant values as injected values. It has some features that are similar to +[Angular's Dependency Injection](https://angular.io/guide/dependency-injection) that is mentioned in +their documentation. In this article we'll explain the similarities and differences between those +solutions. + +**Let's start with the principles we wanted to have in our DI logic, and we will finish with the +comparisons with Angular and NestJS.** + +## Encapsulation + +While we were still using **_Inversify_**, we used to have a single `GraphQLApp` top module. Regular +GraphQL Modules cannot import each other, but can be imported by that `GraphQLApp` object. Also +these modules didn't have an **_encapsulation_**, because all the **_providers_**, **_schemas_** and +**_resolvers_** were getting concatenated without any **_encapsulation_**. After that, we decided to +provide complete Modular approach, and make everything module. Now Each module has its own injector, +valid schema and context which is independent from its parent modules. Also, each module is +constructed by using the imported modules' DI containers, schema contents and context builders. + +{/* prettier-ignore */} +If you want to know more about **_encapsulation_** see the + +blog post about it. + +For example, if you have a **_DatabaseModule_** in your application that has to be shared across the +whole application, and it needs to take a custom provider that will be defined by the user. What you +will do in those DI implementations is to create a provider by decorating it with a global scoped +provider; then put it in **_ApplicationModule_**. However, this would **violate** the +**encapsulation principle** of the modular approach. + +To summarize; while your **_DatabaseModule_** is imported by other modules in the lower level and +that module will use a provider in its parent. It mustn't know what imports it. + +We handle this in a different way in GraphQL Modules by passing configuration by obeying the +encapsulation rule; + +```typescript +const AppModule = new GraphQLModule({ + imports: ({ config: { connectionStuff }}) => { // getting the configuration in schema and DI container generation phase + DatabaseModule.forRoot({ // Define this configured module as the default instance inside AppModule + connectionStuff + }), + OtherModuleThatUsesDB, + }, + configRequired: true, // makes schema and DI container prevent to be requested without a valid configuration +}) + +const DatabaseModule = new GraphQLModule({ + providers: [SomeDbProvider], + configRequired: true, // makes this module prevent to be used without a valid configuration in any part of the application +}); + +@Injectable() +export class SomeDbProvider { + constructor(@ModuleConfig() config) { // get configuration into the provider + // some logic with the configuration + } +} + +const OtherModuleThatUsesDB = new GraphQLModule({ + imports: [ + DatabaseModule.forChild() // Use the configured DatabaseModule in the higher level, prevent this module to be imported without unconfigured DatabaseModule + ] +}) +``` + +The three benefits we have with this type of DI and modular system; + +- _AppModule_ is protected from an unsafe call without a valid configuration that is needed in the + internal process (_DatabaseModule_); thanks to `configRequired` . +- _DatabaseModule_ is protected from an unsafe import without a valid configuration that is needed + in the internal process (_SomeDbProvider_); thanks to `configRequired` again! +- _OtherModuleThatUsesDb_ is protected from an unsafe call or import without a definition of a + well-configured _DatabaseModule._ + +## Hierarchy + +We also have another problem about the hierarchical approach of existing DI implementations. Let me +give an example; + +- **_A DI Container_** has **_FooProvider_** +- **_B DI Container_** has **_BarProvider_** +- **_C DI Container_** has **_BazProvider_** +- **_D DI Container_** has **_QuxProvider_** +- **A** imports **B** +- **B** imports **D** +- **B** imports **C** + +For example the following case will happen in our DI; + +- **_FooProvider_** cannot be injected inside **_B_** while **_BarProvider_** can be injected by + **A**; because the injector of **_B_** only have its providers and **_D_**'s and **C**'s + (**_BazProvider_** and **_QuxProvider_**). + +As you can see in the comparison with **_Inversify_**, **_Inversify_** can attach only one child DI +container in a parent DI container; and this wouldn't fit our hierarchy-based modular approach. For +example, you cannot make **B extend** both **D** and **C** by keeping **D** and **C** encapsulated. +If you merge all of them into the one injector like Angular does, **D** can access **C**'s providers +without importing **C**. + +## Scoped Providers + +In contrast with client side application, your DI system wouldn't need to have different scopes like +Application Scope and Session Scope. In our implementation, we can define providers that have a +lifetime during a single GraphQL Network Request and not the whole the application runtime; because +some providers needs to belong in a single client. Unfortunately, you need to find tricky ways to +manage scoped providers if you use existing DI libraries like **_Inversify_**. + +> In server-side applications, every client request has its own scope that we call \`**session +> scope\`**. We think this session scope should be able to have its own providers and DI logic +> outside the application scope. + +You can read more about the different scopes that we have in GraphQL Modules in the blog post +{/* prettier-ignore */} about Scoped Providers. + +## Comparisons with Other DI Implementations of Frameworks + +### Comparison with Inversify + +To provide _true encapsulation_; we should have an **_injector/container_** with multiple children, +and these children must not know each other and even its parent. In **_Inversify_**, they have +something like that which is called **_Hierarchical_** **_DI_**. In this feature of **_Inversify_**, +the`parent` term can be misunderstood. because an injector seeks for its own providers then looks +for the other injector that is being defined as `parent` in that feature. But, we need is more than +that. We want to have multiple `parent` according to their logic. + +> Every module has its own injector together with its children's injectors. So, every module can +> interact with its children's providers with its own injector. + +You can read more about _Hierarchy_ and _Encapsulation_ in the beginning of this article. + +### Comparison with Angular's DI + +In Angular, there is one injector that is shared across all the application while there are +different encapsulated injectors belonging to each module in different scope. + +> Our Dependency Injection implementation is more strict in terms of encapsulation than Angular's. + +```typescript +import { NgModule } from '@angular/core' +import { BarModule } from '@bar' +import { FooModule, FooProvider } from '@foo' + +@NgModule({ + imports: [FooModule, BarModule], + providers: [ + { + provide: FooProvider, + useClass: MyFooProvider + } + ] +}) +export class AppModule {} +``` + +This is an example top module written in Angular. Let's assume `FooProvider` originally implemented +and defined inside `FooModule` and used by `BarModule` . When `FooProvider` token is replaced by +`MyFooProvider` in `AppModule` scope, `BarModule` also uses our `MyFooProvider` inside its logic. +This explicitly violates encapsulation in the modular approach, because Angular is supposed to send +to `BarModule` `FooModule` 's `FooProvider` implementation instead of the one defined outside of +`BarModule` ; because it shouldn't know what is defined in the higher level. + +```typescript +import { BarModule } from '@bar' +import { FooModule, FooProvider } from '@foo' +import { GraphQLModule } from '@graphql-modules/core' + +export const AppModule = new GraphQLModule({ + imports: [FooModule, BarModule], + providers: [ + { + provide: FooProvider, + useClass: MyFooProvider + } + ] +}) +``` + +However, GraphQL-Modules will send to `BarModule` the correct `FooProvider` , because `BarModule` +imports `FooModule` not `AppModule` . So, `MyFooProvider` will be sent if the current injector is +inside `AppModule` scope. + +In GraphQL-Modules, if A imports B and B imports C, A can access C's scope. But, C cannot access the +scopes of B and A. This allows us to create a safely built GraphQL Application and reduces the error +prone. You can read more about _Hierarchy_ and _Encapsulation_ in the beginning of this article. + +Angular doesn't care about that. it has a single injector for the all application. This may cause +some problems for debugging on a large scale backend application. + +> That ability is not so important for Angular applications; because they are running on the +> browser, not on the server. But still you can notice the similar behavior to GraphQL-Modules DI's +> if you load an Angular module lazily using router's `loadChildren` property. + +And Angular's DI library doesn't differentiate _Application and Session scopes_ because it doesn't +need that. An Angular application has a lifetime until `window` is terminated by closing it or +refreshing page; the session and application runtime can be considered same in the client +application. That's why, our DI implementation is more complex than Angular's as it need to handle +multiple clients by running a single application. You can read more about +[_Scoped Providers_](/blog/graphql-modules-scoped-providers) in the beginning of this article + +### Comparison with NestJS' DI Implementation + +NestJS is a server-side model-view-controller full-fledged framework that has all the principles of +MVC Backend framework. exposing a GraphQL API is only one of its various features. Our goal with +GraphQL Modules was to create a set of tools and libraries that answer needs for the GraphQL +ecosystem, and can be (and should be) used by NestJS users as well. + +> The principles and approaches we're trying to apply in GraphQL Modules is not only for Dependency +> Injection but also for GraphQL Schemas and Context. + +In comparison to Nest's goals, GraphQL-Modules is a platform-agnostic library that can be used even +with the plain `graphqljs` package **without a server**; because it was designed to work exactly +same in all GraphQL Server platforms such as Apollo, Yoga, graphql-express etc. For example, you can +**use _Apollo_'s data sources with graphql-express**; because GraphQL Modules **passes its own cache +mechanism into the dataloaders thanks to our independent DI logic**. + +> The result of a GraphQLModule is a context builder and a schema while NestJS' GraphQLModule is a +> NestJS module that exposes an API using Apollo. + +NestJS has its own dependency injection system which is similar to Angular's but **more strict**; +you can define both global (not by default like Angular) and encapsulated providers at the same +time. We preferred not to have global providers like Angular (you can see how it works above in the +Angular comparison); because we consider it can be more **error-prone**, and it has to be done by +**passing configuration**. You can read more with an example about passing configuration and keeping +safe boundaries between your modules in the +[_Encapsulation_](/blog/modular-encapsulation-graphql-modules) article. + +Also, NestJS DI system doesn't share the important feature of defining providers that have the +life-time of the client request (we call it **session scope**). We think a backend system would need +to differentiate DI containers according to these different scopes. You can read more about it in +[_Scoped Providers_](/blog/graphql-modules-scoped-providers) part of this article above. + +## Working Together + +We wanted to share the principles behind our implementation in order to get more background to our +community and get feedback from other library authors. + +That is also one of the reasons we created our DI library as a separate library, not only so it +would be optional to our users, but also to potentially share that implementation with other +libraries like NestJS and Inversify for example. + +## All Posts about GraphQL Modules + +- [GraphQL Modules — Feature based GraphQL Modules at scale](/blog/graphql-modules) +- [Why is True Modular Encapsulation So Important in Large-Scale GraphQL Projects?](/blog/modular-encapsulation-graphql-modules) +- [Why did we implement our own Dependency Injection library for GraphQL-Modules?](/blog/dependency-injection-library-in-graphql-modules) +- [Scoped Providers in GraphQL-Modules Dependency Injection](/blog/graphql-modules-scoped-providers) +- [Writing a GraphQL TypeScript project w/ GraphQL-Modules and GraphQL-Code-Generator](/blog/graphql-typescript-modules-codegen) +- [Authentication and Authorization in GraphQL (and how GraphQL-Modules can help)](/blog/graphql-modules-auth) +- [Authentication with AccountsJS & GraphQL Modules](/blog/accountsjs-graphql-modules) +- [Manage Circular Imports Hell with GraphQL-Modules](/blog/manage-circular-imports-hell-in-graphql-modules) diff --git a/packages/web/docs/src/app/blog/(posts)/extending-your-graphql-service/distributed-graph.png b/packages/web/docs/src/app/blog/(posts)/extending-your-graphql-service/distributed-graph.png new file mode 100644 index 00000000000..56563b0053a Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/extending-your-graphql-service/distributed-graph.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/extending-your-graphql-service/page.mdx b/packages/web/docs/src/app/blog/(posts)/extending-your-graphql-service/page.mdx new file mode 100644 index 00000000000..be13664b43b --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/extending-your-graphql-service/page.mdx @@ -0,0 +1,193 @@ +--- +title: 'Extending your GraphQL service: Federation or Schema Stitching' +description: + A summary of information regarding extending your GraphQL service to use Federation or Schema + Stitching. +date: 2024-11-18 +authors: egoodwinx +tags: [graphql, graphql-tools, schema-stitching, graphql-federation] +featured: true +--- + +If you're looking to extend your GraphQL service into separated services, you've likely come across +two key terms: schema stitching and federation. Even if these concepts are new to you, don't +worry—this post will introduce you to both approaches and help you understand their role in +enhancing your GraphQL infrastructure. + +## What Is GraphQL? + +Let's start with a brief overview of GraphQL: it's a query language and runtime for APIs that work +with your existing data. GraphQL delivers precisely the data you need—no more, no less—and allows +APIs to evolve over time. + +However as GraphQL gained widespread adoption, many companies faced growing pains with their +implementations. As their graphs grew, they began to face the challenges typical of monolithic +systems. These large graphs blurred the lines between distinct business domains, which is where +separating GraphQL services becomes beneficial. + +There are several reasons why you might want to split your GraphQL service into smaller, separate +services: + +**Separate Business Domains** + +In most applications, data is already organized by business domains. For instance, the data and +logic behind a `Users` service are distinct from that of an `Orders` service. Separating your +GraphQL services allows you to maintain this natural separation, making it easier to manage, +develop, and scale each domain individually. + +**Scaling Requirements** + +As your GraphQL service grows, maintaining a single, monolithic system can lead to complexity and +performance issues. Different parts of the system may have varying scaling needs—for example, +user-related queries might need to handle more traffic than order-related ones. Splitting services +by domain allows each one to scale independently based on demand, improving both performance and +resource efficiency. + +**Simpler Ownership and Maintenance** + +Having distinct GraphQL schemas and resolvers for different services enables parallel development by +different teams. It also simplifies ownership and accountability, as each service can be managed and +maintained independently. Smaller, focused services are easier to understand, test, and deploy, +making long-term maintenance less burdensome compared to a single, complex monolith. + +## What Is the Solution? + +The solution may seem straightforward: separate the different domains and information into different +schemas. But what happens when domains or information are related? + +This is where schema stitching or federation comes into play. These approaches allow definitions to +relate data between different schemas. + +## [Schema Stitching](https://the-guild.dev/graphql/stitching/docs) + +Schema stitching takes many smaller services’ schemas and stitches them together to make a larger +schema. This larger schema is used by a proxy service, the gateway, to delegate different parts of +the query to different subschema services. + +![image.png](./distributed-graph.png) + +There are three main approaches to schema stitching: + +#### [Schema Extension](https://the-guild.dev/graphql/stitching/docs/approaches/schema-extensions) (Gateway-Level Configuration) + +Schema extension uses schema delegation to take different subschemas and extend them at the gateway +level. + +Say you had a type, `book` and a type `author` in different schemas and services. At the gateway +level you can extend the type `book` with a new field `author` that delegates to the `author` +service. + +#### [Type Merging](https://the-guild.dev/graphql/stitching/docs/approaches/type-merging) (Gateway-Level Configuration) + +Type merging allows for partial definitions of a type to exist in any subschema which are all merged +into a unified type at the gateway. + +This is useful if you have different standalone GraphQL APIs and you need to configure those at the +gateway level. + +Lets consider a situation where you needed a type `author` but had two different services. In the +first service you would have the fields `id` and `name` . In the second service you would have the +fields `id` and `description`. In the gateway it would merge the `author` type to contain all the +fields `id`, `description`, and `name`. When you built a query it would go to the service that +declared the type. + +#### [Stitching Directives](https://the-guild.dev/graphql/stitching/docs/approaches/stitching-directives) (Service Level Configuration) + +Similar to Federation, schema stitching allows for directives-based approach of schema stitching. +This allows you to configure your merging at the service level versus the gateway level. + +In this example you would use the `@merge` directive and `@key` directive with a selectionSet to +explain how to merge the schemas. + +## Federation + +Federation lets you use directives to combine multiple APIs into a single federated graph. + +When a user makes a request, it goes through the router. Based on the directives, the router +orchestrates and distributes the request across the APIs and returns a unified response. + +This router needs a supergraph as a map to route to the different subgraphs, this supergraph can be +created by paid applications like GraphOS or free services such as The Guild’s own +[Hive](https://the-guild.dev/graphql/hive). + +Federation introduces the concept of Entities which are federated types that exist across multiple +subgraphs. + +Here is a comparison of Federation-Compatible Gateway Implementations: + +https://the-guild.dev/graphql/hive/federation-gateway-audit + +For more information on Federation and The Guild's support, please see here: + +https://the-guild.dev/graphql/hive/federation + +## What Is the Overall Difference? + +Schema stitching merges multiple schemas into a single unified schema by directly combining them. In +this setup, the gateway executes the requests by resolving data from multiple services, often +relying on custom logic to determine how to fetch and combine the data from different schemas. + +Federation allows each subgraph to manage part of the unified schema independently. The central +gateway composes these subgraphs into a unified graph, and relationships between services are +handled through federation directives like `@key`, `@provides`, and `@extends`. The gateway uses +these directives to predetermine how data is fetched and merged across services. + +This structure enables static checks, ensuring predictable behavior of the gateway in production. By +using tools like Hive CLI or Apollo Studio to create the supergraph, you can validate the schema +ahead of time, preventing conflicts before the supergraph is deployed. Additionally, these tools +provide insight into how the gateway will execute queries, giving you confidence in its behavior +after deployment. + +The Guild’s products are compatible with both Federation and Schema Stitching. + +## Which One Is Better for Your Use Case? + +This is a question only you can answer, but let’s look at common questions you should ask to +determine which approach is better for your use case. + +| | Schema Stitching | Federation | +| ------------------------------------------------------- | ---------------- | ---------- | +| Customizability | More | Less | +| Scalability | Less | More | +| Collaboration | Less | More | +| GraphQL Knowledge Required for subschema/graphlet teams | Less | More | +| GraphQL Knowledge Required for Gateway team | More | Less | + +**What is the overall GraphQL knowledge for the teams involved?** + +Federation requires more over knowledge of GraphQL from all teams involved as they need to work +together to make the federated schema work as expected. Schema Stitching requires less knowledge +from subschema teams but more knowledge from the team owning the gateway as the stitching logic can +become complex as the number of services grow. + +**What is your organizational structure?** + +If you have teams across organizations who need to collaborate, it may be better to use Apollo +Federation as it supports more collaboration on the schema so teams need to work together and +understand what each other is doing with their schema. + +If you want to minimize the need for teams to collaborate closely, schema stitching might be a good +choice. With schema stitching, the gateway "magically" combines the services for the teams managing +subschemas. The gateway handles most of the stitching process, so subschema teams don't need to +worry about how their schemas are integrated. + +**How fast do you need to build your solution?** + +Schema Stitching is usually a faster approach for smaller or simpler projects since it is straight +forward. You can combine schemas quickly without much additional infrastructure but it takes longer +to develop as the overall service grows. + +Federation takes a bit longer to set up as it requires multiple subgraphs, a gateway, and a +registry. As the services grow, Federation can speed up development for teams since each team +independently manages their service. + +**How customizable does your implementation of the gateway need to be?** + +Schema stitching allows for a deeper customization of schemas, resolvers, and transformations since +you have full control over how each service is combined. + +Federation is more suited for scalability and team collaboration. Customization is more structured +through federation directives. + +If you would like to talk to an expert in regards to your GraphQL implementation, feel free to get +in touch with The Guild [here](https://the-guild.dev/#get-in-touch). diff --git a/packages/web/docs/src/app/blog/(posts)/federation-gateway-audit/cli-compatible.png b/packages/web/docs/src/app/blog/(posts)/federation-gateway-audit/cli-compatible.png new file mode 100644 index 00000000000..ad96927403e Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/federation-gateway-audit/cli-compatible.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/federation-gateway-audit/cli-not-compatible.png b/packages/web/docs/src/app/blog/(posts)/federation-gateway-audit/cli-not-compatible.png new file mode 100644 index 00000000000..5d127513b34 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/federation-gateway-audit/cli-not-compatible.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/federation-gateway-audit/page.mdx b/packages/web/docs/src/app/blog/(posts)/federation-gateway-audit/page.mdx new file mode 100644 index 00000000000..856c21260cb --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/federation-gateway-audit/page.mdx @@ -0,0 +1,134 @@ +--- +title: Audit of GraphQL Gateways Supporting Apollo Federation +tags: [graphql, graphql-federation] +authors: kamil +date: 2024-09-10 +description: + Set of tests to evaluate and compare the compatibility of different GraphQL gateways with Apollo + Federation. +--- + +import NextImage from 'next/image' + +The Apollo Federation spec is a hot topic, we must admit. The adoption of it keeps increasing, +especially over past two years. There's a reason! It's a fantastic piece of technology, but also a +complex piece of tech. + +> If you're curious about the results, +> [visit the audit page](https://the-guild.dev/graphql/hive/federation-gateway-audit) + +## Complexity Is Not Obvious{/* eslint-disable-line mdx/remark */} + +**As a user** of Apollo Federation, you might not see the full spectrum of +[Apollo Federation](https://the-guild.dev/graphql/hive/federation). + +When building subgraphs, you might occasionally encounter composition errors, but ultimately, +everything works and you query data. The real complexity lies in tooling, starting with the +composition library, and ending on the gateway and it's query planning. + +**As maintainers** of [Hive Gateway](https://the-guild.dev/graphql/hive/docs/gateway) +([previously part of GraphQL Mesh](/blog/graphql-mesh-v1-hive-gateway-v1)) and creators of GraphQL +Hive (our Schema Registry for GraphQL Federation), we've seen a wide range of supergraphs. Over the +past few years, we've been working with companies adopting or already using Apollo Federation in +production. + +We learned something, actually quite a lot! + +Today, we're even collaborating with creators of Apollo Federation and the +[ChilliCream](https://chillicream.com/) teams (who built +[GraphQL Fusion](https://chillicream.com/blog/2023/08/15/graphql-fusion/)) on +[the new specification for federated GraphQL APIs](https://graphql.org/blog/2024-05-16-composite-schemas-announcement/), +as proud members of GraphQL Foundation. + +We've seen Apollo Federation's complexity, we understood the rules of it very well, and we have +something incredibly interesting to share! + +Before we get to that, let's first discuss what does it mean to "support Apollo Federation" on a +gateway. + +## What Does "Apollo Federation Support" Really Mean?{/* eslint-disable-line mdx/remark */} + +As you know, the spec contains a lot of directives, and these can be used in various ways, resulting +in **many** combinations. Sometimes weird at first glance, but when you really think about them, +they make sense. + +In a lot of cases, the spec is abused, but hey… what's allowed, has to work! + +Most of the time you will see “Apollo Federation support” phrase or a list of supported directives, +and this is fine, but it tells only a part of the story. + +If a gateway supports the `@key` directive, it may or may not support it on an Interface type, for +example. You simply don't know until you try it! + +Did you know that in Apollo Federation v1, a field that is annotated with `@external`, that is also +part of key fields, and is not defined anywhere else, is still resolvable? + +```graphql +extend type Product @key(fields: "id name") @key(fields: "upc") { + id: ID @external + name: String @external + upc: String @external + price: Float! +} +``` + +Not everyone knows that key fields of extension types in Federation v1, must be annotated with +@external , and these are still resolvable by the subgraph. That's why we built the audit! + +![CLI output](./cli-not-compatible.png) + +> Here's how the auditing tool looks like. It's a simple CLI and we're going to release it on NPM +> and as a binary soon. As of today, you need to clone the repository. + +## The Apollo Federation Audit!{/* eslint-disable-line mdx/remark */} + +As you know, The Guild has open source nature in its core. What we do we share, we open source, we +give away for free. It was always like that, it won't ever change. + +Over the years we encountered a lot of aspects of Apollo Federation when working on query planning +for Hive Gateway (previously GraphQL Mesh, just a reminder that it's not a new project, but rather +well established). + +We spent quite a few months, writing down our knowledge about Apollo Federation, in form of 40 +supergraphs, covering wide range, if not all, of the spec. + +Each of those supergraphs, have a set of subgraphs running as real GraphQL APIs, with real runtime +and real data. + +We have created 170 tests in total so far… + +All that effort to measure how compatible our gateway is with Apollo Federation... and now we give +it away to everyone, even our “competitors”, for free, no strings attached! + +> We never had a tool to measure gateway's compatibility with Apollo Federation, but now we have one +> — and we've put it to the test! + +![Screenshot of results](./results.png) + +Visit the audit page and see the results: +[https://the-guild.dev/graphql/hive/federation-gateway-audit](https://the-guild.dev/graphql/hive/federation-gateway-audit) + +The main reason behind building such tool, was to improve our Hive Gateway, have a set of tests we +can use for various of ways. We were also curious how well the rest of the GraphQL gateways perform, +how compatible they are, how good their query planning understands Apollo Federation. The results +are interesting. + +## About the Audit{/* eslint-disable-line mdx/remark */} + +The existing GraphQL Gateway benchmarks focus on performance, pure speed, query planning overhead +and related factors. You only care about performance, if the gateway can query data correctly. Who +wants a fast, but partially broken gateway? All the gateway implementors knows that, and they work +hard to improve their gateway every day. + +We wanted to push the community forward, by giving them a chance to make a giant jump. + +That's why the auditing tool is fully open-source (MIT license), and available for everyone. + +Visit the repository: +[https://github.com/the-guild-org/graphql-federation-gateway-audit](https://github.com/the-guild-org/graphql-federation-gateway-audit) + +We want to turn our effort into helping potential users of GraphQL gateways to better understand +what Apollo Federation support means, but also the maintainers of those gateways to improve their +score and learn from our experience. + +We aim to make it a shared effort, an "official" auditing tool for Apollo Federation gateways. diff --git a/packages/web/docs/src/app/blog/(posts)/federation-gateway-audit/results.png b/packages/web/docs/src/app/blog/(posts)/federation-gateway-audit/results.png new file mode 100644 index 00000000000..f1266406efe Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/federation-gateway-audit/results.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/fetch-for-servers/page.mdx b/packages/web/docs/src/app/blog/(posts)/fetch-for-servers/page.mdx new file mode 100644 index 00000000000..d59d6fe1680 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/fetch-for-servers/page.mdx @@ -0,0 +1,285 @@ +--- +title: JavaScript runs everywhere, so should your servers - here is how +tags: [fetch, whatwg, node, graphql, graphql-yoga] +authors: arda +date: 2022-08-22 +description: A new way to make any Javascript server platform-agnostic. +--- + +## TL;DR + +- We made GraphQL Yoga 2.0 **platform-agnostic**, so it is able to run anywhere JavaScript can run + (Cloudflare Workers, Deno, Next.js. AWS Lambdas etc.) thanks to the new **Fetch API standard** +- We've created Ponyfills so it will work the same on older versions of Node that don't + have Fetch API elements globally available +- We've made a [**new general library**](https://github.com/ardatan/whatwg-node) out of it so that + any other framework or app could achieve the same +- Let's help other frameworks in the ecosystem to migrate to this new library and standard + +At the beginning of the year, [we launched GraphQL Yoga 2.0](/blog/announcing-graphql-yoga-v2) - a +server framework for GraphQL APIs. + +While planning version 2.0 of Yoga, we were thinking about all the things that changed in the +ecosystem and what developers using JavaScript **expect from their server** frameworks now and in +the **future**. + +One of the most powerful trend in the JS ecosystem was the **proliferation of new environments and +platforms** that can run JS (Lambdas, Cloudflare Workers, Deno, Bun etc.). So we set up to build a +single GraphQL server framework that could run on any of these platforms. + +What we've found in the process was fascinating, and we believe would change how any JS HTTP +frameworks are being built, without any relation to GraphQL + +While Node.js is the most popular environment, multiple platforms can run JavaScript, usually, +having their own way of creating servers, and using different APIs. + +On the other hand, the JavaScript client-side has mostly migrated over the years to a common set of +standards (`fetch`), independently of the underlying platform. + +This is where we realized that WHATWG Fetch API that uses +[`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request), +[`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), and `ReadableStream` could +also be used on the server side to provide a unified API to run JavaScript HTTP servers everywhere. + +## Why Fetch API Standard? + +When you send a request from the client, `fetch(...requestArgs)` uses +[`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object under the hood that +contains all the details (headers, method, etc.) and the data stream you need for that +communication. Then you take the +[`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) object that contains the +connection stream, and process it as you want with `.json()`, `.arrayBuffer()`, `.formData()` or +just access the stream itself by `.body()` as `ReadableStream` + +On the server side, you can take +[`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object and process it with the +same methods without dealing with the internals of your platform. + +Streaming responses like SSE uses `ReadableStream`, and for Multipart requests (e.g. file uploads) +uses `FormData`, which is exactly the same forwarded from the browser or any other client using +Fetch API. + +You can see how easy to handle file uploads; + +```ts +const formData = await request.formData() +const myFile = await formData.get('myFile') +const fileContents = await myFile.text() +``` + +See more in our code; +[https://github.com/dotansimha/graphql-yoga/blob/master/packages/common/src/plugins/requestParser/POSTMultipart.ts#L18](https://github.com/dotansimha/graphql-yoga/blob/master/packages/common/src/plugins/requestParser/POSTMultipart.ts#L18) + +Streaming responses like +[Server Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) +example: + +```ts +let interval +new Response( + new ReadableStream({ + start() { + interval = setInterval(() => { + this.enqueue(`data: ${Date.now()}\n\n`) + }, 1000) + }, + cancel() { + clearInterval(interval) + } + }), + { + headers: { + 'Content-Type': 'text/event-stream' + } + } +) +``` + +See more in our code; +[https://github.com/dotansimha/graphql-yoga/blob/master/packages/common/src/plugins/resultProcessor/push.ts#L42](https://github.com/dotansimha/graphql-yoga/blob/master/packages/common/src/plugins/resultProcessor/push.ts#L42) + +## What about Node.js? + +Even though many new platforms support the Fetch API Standard, which means we can have a single +solution for all, currently, in the older LTS versions of Node.js, we don't have an implementation +of the Fetch API built in. + +Furthermore, Node.js doesn't use Web standard streams and the Fetch API in its `http` and `https` +modules. + +That's why we created the [`@whatwg-node/fetch` package](https://github.com/ardatan/whatwg-node) +(previously known as `cross-undici-fetch` ) that fills in the gaps of different `fetch` +implementations in all the LTS Node.js versions. Under the hood, `@whatwg-node/fetch` utilizes +`undici` if available or otherwise falls back to using `node-fetch`, which you are probably already +familiar with. + +> In case [`@whatwg-node/fetch`](https://github.com/ardatan/whatwg-node) is imported in an +> environment that already has Fetch API built in like Cloudflare Workers, no ponyfills are added to +> your built application bundle. + +#### Ponyfill vs Polyfill + +Polyfill patches the native parts of an environment while ponyfill just exports the “patched” stuff +without touching the environment's internals. We prefer pony filling because it prevents us from +breaking other libraries and environmental functionalities. + +## Is It Possible to Have a Library That Creates a Cross-Platform Server? + +When rebuilding GraphQL Yoga from scratch, cross-platform support was one of the most important +features we wanted to implement. We wanted to create a GraphQL server library that can be integrated +with different Node.js server frameworks and other JS environments like CF Workers and Deno with a +few additional lines of code. + +After a few iterations, it was clear to us that this is certainly possible and finally shipped this +as part of GraphQL Yoga v2. + +GraphQL Yoga instance itself can be used directly as a request listener that you pass to Express's +`app.use`, Node's native `http.createServer`, Next.js functions and other non-Node.js environments; +we just pass GraphQL Yoga as an event listener for CF Workers' +`self.addEventListener('fetch', yoga)`. + +As we already mentioned in the “Why Fetch API?” part, the server library itself doesn't need to care +about the platform's connection-specific details like Node's `IncomingMessage` and `ServerResponse` +or Next.js's `NextApi.Request` objects. You are now able to focus on your server implementation +details by consuming a “universal standard” +[`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and returning “another +standard” [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) + +As we realized how well this works out for our users, we decided that we need to bring this to the +next level and extract that logic into a standalone library called +[`@whatwg-node/server` ](https://github.com/ardatan/whatwg-node). + +You simply provide your request handler that has a single +[`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) parameter and expects you to +return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) instance. The +generated request handler instance can be integrated with regular Node HTTP servers, Fastify, Koa, +Deno, CF Workers, Next.js, etc. with a few lines of code. + +{/* prettier-ignore */} +```ts +import { createServerAdapter } from '@whatwg-node/server' +import { Request, Response } from '@whatwg-node/fetch' + +const myServer = createServerAdapter({ + handle(request: Request) { + return new Response('Hello world', { + status: 200 + }) + } +}) + +// Node.js +import { createServer } from 'http' + +const nodeServer = createServer(myServer) +nodeServer.listen(4000) + +// CF Workers +self.addEventListener('fetch', myServer) + +// Next.js +export default myServer + +// Deno +serve(myServer, { addr: ':4000' }) +``` + +### How Does It Look in Real-Life Usage Today? + +You can check the GraphQL Yoga repository to see how we use this library; + +[https://github.com/dotansimha/graphql-yoga/blob/no-more-node/packages/graphql-yoga/src/server.ts#L628](https://github.com/dotansimha/graphql-yoga/blob/no-more-node/packages/graphql-yoga/src/server.ts#L628) + +And the simplicity of the integrations in our examples; + +[https://github.com/dotansimha/graphql-yoga/tree/no-more-node/examples](https://github.com/dotansimha/graphql-yoga/tree/no-more-node/examples) + +Finally, how small the code is when we want to process the request and the response objects; + +[https://github.com/dotansimha/graphql-yoga/tree/no-more-node/packages/graphql-yoga/src/plugins](https://github.com/dotansimha/graphql-yoga/tree/no-more-node/packages/graphql-yoga/src/plugins) + +> There is literally nothing platform-specific in GraphQL Yoga, and this allows us to focus on +> creating a good GraphQL Server implementation for the entire GraphQL JS ecosystem. + +## Node.js Server Frameworks, Routers & Middlewares + +Node.js solved issues years before the web standards could. However, we think that many server-side +ideas inspired by Node.js can now easily be managed with JavaScript with Fetch, Web Streams and +other web standards in the JavaScript ecosystem. + +There are many mature libraries such as Fastify, Koa, Express, and Hapi that are implemented only +for Node.js without using the Fetch API Standard. The experience with those libraries in the current +era of Node.js taught us a lot about how the server can be designed but maybe it is time to reduce +the environment-specific APIs in the JS ecosystem. + +The major reason for using a server framework is usually “Routing” then “Middlewares” so the +question is “Why cannot we just have that with Fetch API?” + +You can basically achieve routing like below, however it looks a bit unsafe. + +```tsx +createServerAdapter({ + handle(request: Request) { + if (request.url.endsWith('/hello')) { + return new Response('{ "message": "hello" }', { + status: 200, + headers: { + 'Content-Type': 'application/json' + } + }) + } + if (request.url.endsWith('/secret')) { + return new Response('No way!', { + status: 401 + }) + } + return new Response('Nothing here!', { + status: 404 + }) + } +}) +``` + +There is another library called `itty-router` which can be used for Routing with Fetch API. You can +see how simple it is to achieve “Routing” in a platform-independent way. + +{/* prettier-ignore */} +```tsx +import { Router } from 'itty-router' +import { createServerAdapter } from '@whatwg-node/server' + +// now let's create a router (note the lack of "new") +const router = Router() + +// GET collection index +router.get('/todos', () => new Response('Todos Index!')) + +// GET item +router.get('/todos/:id', ({ params }) => new Response(`Todo #${params.id}`)) + +// POST to the collection (we'll use async here) +router.post('/todos', async request => { + const content = await request.json() + + return new Response('Creating Todo: ' + JSON.stringify(content)) +}) + +// 404 for everything else +router.all('*', () => new Response('Not Found.', { status: 404 })) + +// attach the router "handle" to our server adapter +const myServer = createServerAdapter(router) + +// Then use it in any environment +import { createServer } from 'http' + +const httpServer = createServer(myServer) +httpServer.listen(4000) +``` + +## What Node.js Frameworks Are You Using Today? + +Maybe it is worthwhile to open a new issue on their repo and see if we could all help them to become +platform-agnostic, while still supporting older versions of Node, thanks to this new library. + +Please try it out and give us feedback [on the repo](https://github.com/ardatan/whatwg-node)! diff --git a/packages/web/docs/src/app/blog/(posts)/git-rebase-not-interactive/aaf7b6a36c30ab802f51d8ecb40478e9.png b/packages/web/docs/src/app/blog/(posts)/git-rebase-not-interactive/aaf7b6a36c30ab802f51d8ecb40478e9.png new file mode 100644 index 00000000000..b130586322f Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/git-rebase-not-interactive/aaf7b6a36c30ab802f51d8ecb40478e9.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-as-a-best-practice-for-modern-angular-apps/discourse-api.png b/packages/web/docs/src/app/blog/(posts)/graphql-as-a-best-practice-for-modern-angular-apps/discourse-api.png new file mode 100644 index 00000000000..dce1bdbd7f5 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-as-a-best-practice-for-modern-angular-apps/discourse-api.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-as-a-best-practice-for-modern-angular-apps/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-as-a-best-practice-for-modern-angular-apps/page.mdx new file mode 100644 index 00000000000..f099e2158d6 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-as-a-best-practice-for-modern-angular-apps/page.mdx @@ -0,0 +1,118 @@ +--- +title: GraphQL as a best practice for modern Angular apps? +authors: uri +tags: [graphql, angular] +date: 2016-07-28 +description: + In this post, I'll make the case for why Angular needs a best practice for communicating with the + server, and why GraphQL should be that best practice. +--- + +In this post, I'll make the case for why Angular needs a best practice for communicating with the +server, and why GraphQL should be that best practice. + +## Best Practices + +The Angular community is establishing best practices so that we all can benefit from making our apps +more performant, easier to maintain, and more modern. + +Some current best practices include composing everything into +[Components](https://docs.angularjs.org/guide/component), using one-way data binding, lazy loading, +having an immutable global state management (Redux & ng-rx), and more… + +That is all great, and means that if we will follow those best practices our apps will behave better +and will look more modern… + +…until we get to data fetching from the server. + +## Data Fetching + +Today, whether we are developing in a large company, consulting, or writing our own app, when we +fetch data from the server we are often left with old practices that don't address the needs of a +modern app. + +Also, we are kind of powerless and unable to decide how the data will be supplied to our apps by the +server, even though the way we fetch the data to our app is at least as meaningful to the way our +app behaves as how we present it. + +**We should come up with best practices for data fetching that is more in line with the modern way +we write our apps. These should take into consideration the following needs: data documentation, +network latency, server side rendering and faster initial loading, real time communication patterns, +latency compensation, and optimistic UI.** + +## REST + +REST is the current protocol we go around when we're talking about app data fetching. REST has its +benefits, but it was evolved in a time when the web was very different from today, when everything +was about static HTML and forms and not about apps. + +Here are the areas where REST is currently lacking: + +- **self documentation** — when you send a request to a REST endpoint, there is nothing in the + protocol that tells you what you are going to get (and not everyone has the resources to create a + nice, updated documentation like Twitter) + +- REST doesn't support **real time data** fetching + +- tough choices when designing your REST endpoint, which I'll elaborate on below + +**Over-fetching** — When one endpoint serves all the data, each Component calls it again and again. +This means it serves more fields than the component needed _and_ we call it many times, creating +more load on the server + +**Under-fetching** — When many endpoints serve multiple resources and fields. This creates many +round trips for one Component as well as complex joins on the client. + +![](./discourse-api.png 'The Discourse REST API — look on the links at the bottom on the GraphQL version of this') + +## Rethinking Data Fetching + +So it looks like we need to rethink data fetching, just like we rethought web apps. + +Luckily, Facebook ran into the same problem in 2012 when they needed to rethink the way they fetch +data as they wrote their mobile apps on top of their existing stack. + +They developed a solution, and open sourced it as [GraphQL](https://graphql.org). + +## GraphQL + +GraphQL is the new data communication protocol for modern apps. + +The server communicates what data it can provide and the client specifies what data it needs in a +**simple, graph-like structure**, in **one round trip**, no matter how deep, how complex, or how +many resources the data contains. + +import { VideoEmbed } from '#components/video-embed' + + + +This means: one request to get exactly the information the app needs, when it needs it. **No +over-fetching and under-fetching.** + +With that, each component needs to specify its data dependencies and a client library will merge +them into one request. There's no need for a shared service with prepared fetching functions. + +GraphQL is also not a storage engine! You can connect it to an existing REST endpoint or SQL and +NoSql databases. + +## Shared Best Practices between Frameworks + +For all the reasons above, GraphQL is already the best practice for fetching data with React. Also, +all the Facebook apps and clients use GraphQL. + +If the Angular community embraces GraphQL as a best practice, it would open the door to sharing more +tools and knowledge with the React community. + +--- + +To start learning about GraphQL, take a look at these sources: + +- [Why GraphQL is the future](https://medium.com/apollo-stack/why-graphql-is-the-future-3bec28193807#.3wpy7r34b) +- [The basics of GraphQL in 5 minutes](https://medium.com/apollo-stack/the-basics-of-graphql-in-5-links-9e1dc4cac055#.uvh5c43kt) +- [Replacing Discourse REST API with GraphQL](https://medium.com/apollo-stack/discourse-in-graphql-part-1-ee1ffd8a22df#.ehvb5vgl1) +- [Angular-Apollo Docs](https://docs.apollostack.com/apollo-client/angular2.html) and + [GitHub Repo](https://github.com/apollostack/angular2-apollo) +- [Building faster modern apps with Angular and GraphQL](https://graphql.com/articles/angular-graphql-faster-modern-apps) diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/01-dashboard.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/01-dashboard.jpg new file mode 100644 index 00000000000..1e9ce06a7a4 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/01-dashboard.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/02-create-api.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/02-create-api.jpg new file mode 100644 index 00000000000..2b22f1f897c Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/02-create-api.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/03-copy-audience.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/03-copy-audience.jpg new file mode 100644 index 00000000000..6d2079c16b6 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/03-copy-audience.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/04-domain.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/04-domain.jpg new file mode 100644 index 00000000000..fb2c22f077f Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/04-domain.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/05-graphiql-unauthenticated.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/05-graphiql-unauthenticated.jpg new file mode 100644 index 00000000000..d48e62cb2ae Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/05-graphiql-unauthenticated.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/06-auth0-applications.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/06-auth0-applications.jpg new file mode 100644 index 00000000000..4e2a9e60f87 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/06-auth0-applications.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/07-auth-create-application.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/07-auth-create-application.jpg new file mode 100644 index 00000000000..7d8b7c746f0 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/07-auth-create-application.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/08-login.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/08-login.jpg new file mode 100644 index 00000000000..cf4a37591a3 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/08-login.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/09-authentication-token.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/09-authentication-token.jpg new file mode 100644 index 00000000000..ea04dc33900 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/09-authentication-token.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/10-authenticated-request.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/10-authenticated-request.jpg new file mode 100644 index 00000000000..f97200bd6a0 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/10-authenticated-request.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/cover.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/cover.jpg new file mode 100644 index 00000000000..fb4e75ee525 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/cover.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/page.mdx new file mode 100644 index 00000000000..46ed5e0da54 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-authentication-with-envelop-and-auth0/page.mdx @@ -0,0 +1,349 @@ +--- +title: GraphQL Authentication with Envelop and Auth0 +authors: laurin +tags: [graphql, authentication, envelop] +date: 2021-12-19 +description: Learn how to quickly add authentication to you GraphQL Envelop setup with Auth0. +--- + +Authentication in the process of identifying who is trying to access our API. Building our own +solution can be hard and cause severe security issue if done wrong. In recent years third-party +authentication providers became quite popular. One of those is Auth0, which comes with an +exceptional free plan allowing up to 7.000 active users and unlimited logins, making it one of the +best available solutions for getting started. + +In this guide we will go through all the steps required for integrating authentication into an +existing envelop setup using the [`@envelop/auth0`](https://envelop.dev/plugins/use-auth0) package. + +## Prerequisites + +Ideally, you already have your basic envelop setup with your http framework of choice. This guide we +will be based on the +[`graphql-helix` fastify example](https://github.com/dotansimha/envelop/tree/main/examples/graphql-helix), +but the code can be easily transferred to any other example as listed on our +[Integrations and Examples documentation](https://envelop.dev/docs/integrations). In case you are +hitting any roadblocks feel free to reach out to us via the chat box on this page! The full code of +the end-result is also available in our examples +[`graphql-helix-auth0` fastify example](https://github.com/dotansimha/envelop/tree/main/examples/graphql-helix-auth0). + +## Installing Dependencies + +We start by installing the package into our envelop setup with your favorite Package manager. + +```bash +yarn install -E @envelop/auth0 +``` + +## Adding the Auth0 Plugin to the Envelop Setup + +```ts +import { useAuth0 } from '@envelop/auth0' + +// ... other imports and code + +const getEnveloped = envelop({ + plugins: [ + useSchema(schema), + useAuth0({ + domain: 'TODO', + audience: 'TODO', + extendContextField: 'auth0' + }) + ] +}) + +// ... server code +``` + +Let's break down the code. There are several configuration options we need to pass to the plugin. + +**`domain`** The domain of the Auth0 server we need to communicate with for authenticating a user. +We will fill this out in the next step. + +**`audience`** The audience is the identifier of the API and is forwarded to Auth0 in order to +specify for which API we are trying to authenticate our user for. E.g. if our API is hosted on +`http://localhost:3000/graphql`, we would pass that value. We will fill this out in the next step. + +**`extendContextField`** Once a user got successfully authenticated the authentication information +is added on the context object under this field. In our resolvers we can then access the +authentication information via `context.auth?.sub`. + +## Setting up the Auth0 API + +In order to properly configure the `useAuth0` plugin we need the `domain` and `audience` values. We +will retrieve them by setting and configuring Auth0 from scratch! + +If didn't already sign up for Auth0, you should do it now on +[Auth0 Sign Up](https://auth0.com/sign-up). Since you can sign up with your GitHub or Google Account +it should be super fast! + +After logging in navigate to the [Auth0 dashboard](https://manage.auth0.com/dashboard) and from +there to the APIs page, where we will click the **Create API** button. + +![Auth0 Dashboard](./01-dashboard.jpg) + +Choose any name for the API, we are going with `Envelop Demo` for this example. The `Identifier` +field should be set to the URL of our GraphQL API. We are hosting our API on localhost and set it to +the host and port on which our fastify helix server is served, which is +`http://localhost:3000/graphql`. For production, you should instead set it to the URL of the +production server. + +![Auth0 API](./02-create-api.jpg) + +We can ignore the Signing Algorithm option and go with the pre-set value. Once everything is filled +out properly we can click the `Create` button. + +Now we already have one of the missing config options we needed `audience` , which is equal to the +URL we just entered `http://localhost:3000/graphql`. + +![Auth0 Copy Audience](./03-copy-audience.jpg) + +The `domain` value is a bit hidden, but we can find it on the detail page of the API we just +created, on the `Test` tab. + +![Auth0 domain](./04-domain.jpg) + +It will vary depending on your account name and region, but in general it follows this pattern + +``` +{account_name}.{region}.auth0.com +``` + +This is our `domain` configuration value. + +Let's quickly add this information to our envelop setup. + +```ts +import { useAuth0 } from '@envelop/auth0' + +// ... other imports and code + +const getEnveloped = envelop({ + plugins: [ + useSchema(schema), + useAuth0({ + domain: '{account_name}.{region}.auth0.com', + audience: 'http://localhost:3000/graphql', + extendContextField: 'auth0' + }) + ] +}) +``` + +We now have all the information needed for configuring the envelop plugin. However, we did not yet +setup an application that is required for users to authenticate in the browser. + +But before doing so, let's verify that the plugin is doing what it should do. + +## Expose Authentication Information via GraphQL Schema + +Before we start our server we should add some types and fields to our schema in order to query for +the authentication information. The complete code should look like this: + +```ts +// The quickest way of building a schema :) +import { makeExecutableSchema } from '@graphql-tools/schema' + +const schema = makeExecutableSchema({ + typeDefs: /* GraphQL */ ` + """ + Describes the authentication object as provided by Auth0. + """ + type AuthenticationInfo { + """ + String that uniquely identifies an authenticated user. + """ + sub: String! + } + + type Query { + """ + The authentication information of the request. + """ + authInfo: AuthenticationInfo + } + `, + resolvers: { + Query: { + authInfo(_source, _args, context) { + return context.auth0 + } + } + } +}) +``` + +Then we can start our server. The helix fastify server can be started via `yarn start`. + +```sh +C:\Users\laurin\Projects\envelop\examples\graphql-helix > yarn start +yarn run v1.22.10 +$ ts-node index.ts +GraphQL server is running. +``` + +Next, we are going to execute a query on the GraphiQL instance exposed on +`http:localhost:3000/graphql`. + +```graphql +query { + authInfo { + sub + } +} +``` + +![GraphiQL Unauthenticated](./05-graphiql-unauthenticated.jpg) + +As expected the value of the `authInfo` field is `null`, as we are not passing any authentication +headers along with our request. + +## Generating an Auth0 Access Token + +In order to retrieve an access token, we first need to set up an Auth0 application and an +authentication route. For the sake of this guide and in order to reduce complexity we will simply +add an route to our fastify http server that renders some HTML with a ` + + + + + + `) + } +}) +``` + +As mentioned before it is not that fancy. After restarting the server and opening +`http://localhost:3000/` URL we should see a blank page and an Auth0 LogIn pop-up. + +![Auth0 Create Application](./08-login.jpg) + +After a successful login the authentication token is added to the blank page. + +![Auth0 Create Application](./09-authentication-token.jpg) + +Let's copy that one and move back to our GraphiQL instance. + +## Sending an Authenticated Request + +In the **Request Headers** tab we can specify our Authorization header in the following format: + +```json +{ + "Authorization": "Bearer " +} +``` + +Then after re-executing the operation we see that the result now contains our authentication +information! + +```json +{ + "data": { + "authInfo": { + "sub": "google-oauth2|101177380012777232372" + } + } +} +``` + +![Auth0 Create Application](./10-authenticated-request.jpg) + +## Next Steps + +Congratulations on successfully implementing authentication for your GraphQL API with Envelop and +Auth0! + +The full code of this guide can be found in our +[Envelop examples](https://github.com/dotansimha/envelop/tree/main/examples/graphql-helix). + +More information about advanced configuration options can be found on the +[`useAuth0` PluginHub page](https://envelop.dev/plugins/use-auth0). + +In the GraphQL schema of this guide we only re-expose the auth0 authentication information. For a +true registration flow the user information should be persisted via a `register` mutation or +similar, so additional information such as first and last name is stored within a database. + +A full user object could be loaded when building the context via the +[`useExtendContext` plugin](https://envelop.dev/plugins/use-extend-context). + +```ts +const getEnveloped = envelop({ + plugins: [ + useSchema(schema), + useAuth0(auth0Config), + useExtendContext(async context => { + if (context.auth0) { + return { + user: await context.db.loadUserBySub(context.auth0.sub) + } + } + return {} + }) + ] +}) +``` + +Learn more about all the other features you can easily add to your GraphQL setup over on the +[Envelop Docs](https://envelop.dev/docs). + +We are continuously adding new plugins that allow solving hard problems with ease! + +Another new plugin that is being cooked up in the lab is the operation complexity plugin, which +allows (rate-)limiting of operations sent to your server based on a score calculated from the +selection set. If you are building a public GraphQL API you don't want to miss out on this! Help us +to shape the plugin by dropping feedback over the +[Draft PR for the Operation Complexity Plugin](https://github.com/dotansimha/envelop/pull/671) or +contact us via the Chat below. We are curious about your feedback und use-cases! diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-authz/cover.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-authz/cover.jpg new file mode 100644 index 00000000000..f998bf1dd35 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-authz/cover.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-authz/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-authz/page.mdx new file mode 100644 index 00000000000..78f8433c4a7 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-authz/page.mdx @@ -0,0 +1,318 @@ +--- +title: GraphQL AuthZ - GraphQL Authorization layer +authors: [dimatill, laurin] +tags: [graphql, authorization, graphql-federation, graphql-authz] +date: 2021-12-19 +description: + Flexible modern way of adding an authorization layer on top of your existing GraphQL backend + systems. +--- + +Today we are excited to introduce GraphQL AuthZ - a new open-source library for adding authorization +layers in different GraphQL architectures. + +## Intro + +GraphQL AuthZ is a flexible modern way of adding an authorization layer on top of your existing +GraphQL microservices or monolith backend systems. + +It plays well with both code-first and schema-first (SDL) development, supports different ways of +attaching authorization rules, has zero dependencies in the core package (aside from a peer +dependency on graphql-js), and keeps the schema clean from any authorization logic! + +Also, GraphQL AuthZ has integration examples for all major GraphQL frameworks and libraries. Let's +dig a little deeper and break down the core features and how they can help you improve your GraphQL +developer experience. + +## How Does It Work? + +GraphQL AuthZ wraps the graphql.js execution phase and runs logic for enforcing defined +authorization rules before and after this phase. The key function from `graphql-js` that is +responsible for running the execution logic is named `execute`. + +This simplified pseudo-code describes how the GraphQL AuthZ hooks into the process by wrapping the +`execute` function. + +```typescript +import { execute as originalExecute } from 'graphql' + +function execute(args) { + const preExecutionErrors = runPreExecutionRules(args) + if (preExecutionErrors) { + return preExecutionErrors + } + const result = originalExecute(args) + const postExecutionErrors = runPostExecutionRules(args, result) + if (postExecutionErrors) { + return postExecutionErrors + } + return result +} +``` + +By wrapping existing functionality we gain the following key benefits. + +- Compatibility with modern GraphQL technologies providing ways to wrap the graphql.js `execute` + function. Here are a few working examples for + [Envelop](https://github.com/AstrumU/graphql-authz/tree/main/examples/packages/envelop), + [GraphQL Helix](https://github.com/AstrumU/graphql-authz/tree/main/examples/packages/graphql-helix), + [Apollo Server](https://github.com/AstrumU/graphql-authz/tree/main/examples/packages/apollo-server-code-first), + and + [express-graphql](https://github.com/AstrumU/graphql-authz/tree/main/examples/packages/express-graphql). +- The executable GraphQL schema does not contain any authorization logic allowing more flexible + re-usage for other use-cases +- Authorization rules can be added on top of an existing remote GraphQL schema. GraphQL AuthZ can be + added as a layer on your GraphQL gateway that composes smaller subgraphs into one big graph. +- Separation of the authorization logic into two phases + - The pre-execution phase for static authorization rules based on the context and incoming + operation + - The post-execution phase for flexible authorization rules based on the execution result + +## Failing Early in the Pre-Execution Phase + +With GraphQL AuthZ it's possible to execute authorization logic before any of the resolvers have +been executed. This empowers you to fail the request in the early stage, send back an error and +reduce server workload. + +This technique works the best for authorization logic that is not dependent on remote data sources. +For example, checking if the user is authenticated or has some certain role or permission. + +```typescript +// creating pre-execution rules + +const IsAuthenticated = preExecRule()(context => !!context.user) + +const IsEditor = preExecRule()(context => context.roles.has('editor')) +``` + +However, if you need the flexibility for fetching data from a remote source such as a database +before you can determine whether an operation should be executed, you still have the power to +leverage those data sources with async code. + +This technique is a perfect fit for mutation fields, as you want to avoid executing the mutation +operation if the user has insufficient permissions e.g. he does not own a specific resource. + +```typescript +// user can only publish a post if he owns it + +const CanPublishPost = preExecRule()(async (context, fieldArgs) => { + const post = await db.posts.get(fieldArgs.postId) + return post.authorId === context.user?.id +}) +``` + +## Using the GraphQL Schema as a Data Source + +By pursuing the GraphQL AuthZ approach your executable schema does not contain any authorization +logic. This simplifies using the executable schema as a data source. For example, instead of calling +a remote database using an interface attached to our context object directly, the `graphql` function +from `graphql.js` package could be called, with the executable schema as an argument along with +graphql operation. By doing this, the authorization layer is not dependent on the underlying +database(s), its architecture, and ORM(s). It is dependent only on the GraphQL schema which is a +dependency of the GraphQL authorization layer by design. + +```typescript +// using schema as a data source inside pre-execution rule + +const CanPublishPost = preExecRule()(async (context, fieldArgs) => { + const graphQLResult = await graphql({ + schema: context.schema, + source: `query post($postId: ID!) { post(id: $postId) { author { id } } }`, + variableValues: { postId: fieldArgs.postId } + }) + + const post = graphQLResult.data?.post + + return post && post.author.id === context.user?.id +}) +``` + +Authorization logic could require any kind of data fetched from different databases or +(micro)services. Some data points could even be resolved by third-party microservices or APIs that +are not part of the composed graph. + +By using the GraphQL schema as the data source, authorization rules don't need to be aware of +complex implementation details or directly connect to different databases or microservices. + +This makes GraphQL AuthZ extremely powerful, especially for subgraphs in a microservice architecture +with centralized gateway-level authorization. + +All the subschemas could live without any authorization logic and a federated or stitched gateway +can leverage GraphQL AuthZ for actually applying global authorization logic for the whole graph +while leveraging the graph for fetching the data required for doing so, without having to be aware +of GraphQL resolver implementation details. + +## Reduce Remote Procedure Calls with Post-Execution Rules + +In addition to the pre-execution rules, GraphQL AuthZ also allows you to write post-execution rules. + +Fetching remote data from within authorization rules is powerful but it adds overhead and requires +additional network roundtrips. In most cases, the data required for performing authorization logic +is closely related to entities fetched via the GraphQL operation selection set. You can avoid this +by performing authorization logic based on the execution result in the post-execution phase. + +In your post-execution rules, you can specify a selection set for fetching additional data, related +to the rule target, that is required for running the rule. + +For example, if your rule is attached to some object field it could require additional information +about sibling fields with their relations. + +```typescript +// creating post-execution rule + +const CanReadPost = postExecRule({ + selectionSet: '{ status author { id } }' +})( + (context, fieldArgs, post, parent) => + post.status === 'public' || post.author.id === context.user?.id +) +``` + +By using this technique we reduce remote procedure calls to individual data sources by executing +authorization logic on top of GraphQL execution result that is enriched with the additional data +specified by the authorization rules. Since related data is often stored in the same place it can be +fetched from a data source in one roundtrip (via the GraphQL schema) instead of performing one +remote procedure call for the authorization and one call for the actual data populating. + +## Microservices + +With GraphQL AuthZ it is possible to implement a centralized gateway authorization layer as well as +microservice level authorization. You can choose between storing the whole authorization schema in a +holistic way on a stitched or federated gateway or having dedicated authorization schemas for each +subgraph/schema specified by your services. It is even possible to mix both approaches! + +> “Shifting this configuration out of the gateway makes subschemas autonomous, and allows them to +> push their own configuration up to the gateway—enabling more sophisticated schema releases.” — +> [schema stitching handbook](https://github.com/gmac/schema-stitching-handbook/tree/master/stitching-directives-sdl) + +The `@graphql-authz/directive` package provides a GraphQL directive that can be used to annotate +types and fields within your subschemas SDL and a configuration transformer that can be used on the +gateway to convert the subschema directives into explicit authorization settings. + +```graphql +# using @authz directive + +type User { + id: ID! + email: String! @authz(rules: [IsAdmin]) + posts: [Post!]! +} + +type Post @authz(rules: [CanReadPost]) { + id: ID! + title: String! + body: String! + status: Status! + author: User! +} + +type Query { + users: [User!]! @authz(rules: [IsAuthenticated]) + post(id: ID!): Post +} + +type Mutation { + publishPost(postId: ID!): Post! @authz(rules: [CanPublishPost]) +} +``` + +On top of that directives can as well be used in monolith architecture. + +If you are not pursuing a schema-first (SDL) development flow and are more a fan of the code-first +approach (which does not let you specify directives without framework-specific voodoo magic), the +authorization schema can also be described as a plain JSON object. Allowing you to specify the rules +that should be executed on your object types, interfaces, and fields. + +```typescript +// defining auth schema + +const authSchema = { + Post: { __authz: { rules: ['CanReadPost'] } }, + User: { + email: { __authz: { rules: ['IsAdmin'] } } + }, + Mutation: { + publishPost: { __authz: { rules: ['CanPublishPost'] } } + }, + Query: { + users: { __authz: { rules: ['IsAuthenticated'] } } + }, + // wildcards are supported. read more: https://github.com/AstrumU/graphql-authz#wildcard-rules + '*': { + __authz: { rules: ['Reject'] }, + '*': { + __authz: { rules: ['IsAuthenticated'] } + } + } +} +``` + +## Comparing GraphQL AuthZ with GraphQL Shield + +[GraphQL Shield](https://github.com/maticzav/graphql-shield) is a great tool for creating +authorization layers that has vast adoption from the community. In fact, GraphQL AuthZ is highly +inspired by GraphQL Shield! However, GraphQL Shield uses a different approach compared to GraphQL +AuthZ for applying authorization rules. + +The main difference is GraphQL Shield uses field middleware by wrapping all the `resolver` functions +within your GraphQL schema for executing authorization logic during the data-resolving phase, while +GraphQL AuthZ wraps the entire `execute` logic. + +The benefits of the wrapping approach are described in previous paragraphs, however, there are also +some drawbacks. For example, with GraphQL AuthZ post-execution rules there is no ability to fail the +request early because post-execution rules are executed after all resolvers are executed. GraphQL +Shield, on the other hand, can fail the request during the execution phase which happens before the +post-execution phase but later than the pre-execution phase. On the other hand, post-execution rules +have the benefit of accessing the resolved value of the field they are specified on and, +furthermore, even the sibling fields that are specified via the rules selection set. + +The middleware approach of GraphQL shield is missing these possibilities because authorization logic +is executed in the context of field resolvers and has access only to the parent object which is the +value returned from the parent field resolver. At that stage, the wrapped field resolvers have not +been executed yet. + +Another feature in GraphQL Shield is the built-in contextual caching mechanism for rules. At the +moment, GraphQL AuthZ has no built-in caching, but you can implement it yourself within the GraphQL +AuthZ rules, or in the future caching could even become a built-in feature. (pull requests are more +than welcome) + +## Which Approach to Choose? + +Let's wrap up all the use-cases mentioned above and also give a library recommendation. + +- If you have very few places that should be covered by authorization, and you don't plan to + increase such places, you can just add authorization logic right inside resolvers to not + overcomplicate things. +- If all of your authorization logic doesn't depend on the data and shouldn't perform complex + calculations, you can use + [operation-field-permissions](https://envelop.dev/plugins/use-operation-field-permissions) + [Envelop](https://envelop.dev) plugin. +- If your authorization logic contains some calculations which are the same for many fields of your + schema, you can use [GraphQL Shield](https://github.com/maticzav/graphql-shield) to leverage the + built-in contextual caching mechanism. +- If your authorization logic heavily depends on the data, or you want to use schema directives to + attach auth rules, you can use [GraphQL AuthZ](https://github.com/AstrumU/graphql-authz) to + leverage the 'GraphQL schema as a data source' pattern and post-execution rules. + +## Conclusion + +GraphQL AuthZ is a new approach for applying GraphQL native authorization. We are happy that we can +finally share this library with the community and keen to learn about the ways it might be used +within your next project! Don't hesitate to contact us for questions or to evaluate whether this +library might be a fit for your architecture. + +Please check out the [GraphQL AuthZ repository on GitHub](https://github.com/AstrumU/graphql-authz) +for learning more. You can find a tutorial on how to set it up in different ways and with different +technologies. + +The repository also contains the following, ready to run, examples: + +- [Apollo Server (schema-first, directives)](https://github.com/AstrumU/graphql-authz/tree/main/examples/packages/apollo-server-schema-first) +- [Apollo Server (code-first, extensions)](https://github.com/AstrumU/graphql-authz/tree/main/examples/packages/apollo-server-code-first) +- [express-graphql (schema-first, directives)](https://github.com/AstrumU/graphql-authz/tree/main/examples/packages/express-graphql) +- [GraphQL Helix (schema-first, authSchema)](https://github.com/AstrumU/graphql-authz/tree/main/examples/packages/graphql-helix) +- [Envelop (schema-first, directives)](https://github.com/AstrumU/graphql-authz/tree/main/examples/packages/envelop) +- [TypeGraphQL (code-first, extensions)](https://github.com/AstrumU/graphql-authz/tree/main/examples/packages/type-graphql) +- [NestJS (code-first, directives)](https://github.com/AstrumU/graphql-authz/tree/main/examples/packages/nestjs) +- [Schema Stitching (gateway, directives)](https://github.com/AstrumU/graphql-authz/tree/main/examples/packages/schema-stitching) +- [Apollo Federation (gateway, authSchema)](https://github.com/AstrumU/graphql-authz/tree/main/examples/packages/apollo-federation) diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-cli-is-back/23b46179aaf5f6f3422b0ab82db7ed80.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-cli-is-back/23b46179aaf5f6f3422b0ab82db7ed80.jpg new file mode 100644 index 00000000000..27abdf44330 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-cli-is-back/23b46179aaf5f6f3422b0ab82db7ed80.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-cli-is-back/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-cli-is-back/page.mdx new file mode 100644 index 00000000000..b439f2c2514 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-cli-is-back/page.mdx @@ -0,0 +1,258 @@ +--- +title: GraphQL CLI is back! +authors: uri +tags: [graphql, graphql-cli] +date: 2019-10-31 +description: + Your Swiss Army Knife for the GraphQL ecosystem. Production-ready GraphQL app in seconds. +--- + +## TL;DR; + +- [GraphQL CLI](https://github.com/urigo/graphql-cli) is a **popular command-line tool** providing + various tools for creating and maintaining GraphQL based applications +- [Prisma recently transferred the project to The Guild](https://prisma.io/blog/the-guild-takes-over-oss-libraries-vvluy2i4uevs) + — and we **completely rewrote** it and closed over 100 issues +- We've already fixed and added many commands, but we are looking to **learn and integrate** with + tools and companies **across the ecosystem!** +- You can now generate a **full stack working app**, from a GraphQL schema **model**, in **2 + minutes** using the init+generate commands! +- This is an alpha phase — **we want your feedback**, as a user and as a tool creator — Please + [create an issue](https://github.com/Urigo/graphql-cli/issues/new/choose) and + [join our Discord channel](https://discord.gg/xud7bH9) + +import { YoutubeIframe } from '#components/youtube-iframe' + + + +## Overview + +The GraphQL CLI provides: + +- Helpful commands to improve your daily workflows, from starting a project to maintaining it for + the long run +- Rich ecosystem and compatibility with libraries, editors and IDEs based on a unified + [graphql-config](/blog/graphql-config) +- A powerful plugin system to extend GraphQL CLI with custom commands — supported by the community + and The Guild + +> The main target of the GraphQL CLI is to provide a default entry point for the community to use +> proven techniques for building and deploying GraphQL enabled applications while being vendor +> agnostic. + +Advanced developers and tool creators can **extend** +[graphql-cli](https://github.com/urigo/graphql-cli) to provide additional capabilities while still +benefiting from a robust set of default commands for daily use — We want to use the CLI to encourage +open collaborations between different tool creators. + +## History + +Over the years the GraphQL ecosystem flourished and evolved towards more production-ready use cases +with a large number of active community packages available. + +GraphQL evolved thanks to the large community and the many supporting libraries it has created. + +The GraphQL CLI has become a place for the community to share ideas and best practices across +different solutions and libraries thanks +[to the push from Prisma](https://prisma.io/blog/new-tooling-to-improve-your-graphql-workflows-7240c81e1ba3). + +[The Guild took over GraphQL CLI](https://prisma.io/blog/the-guild-takes-over-oss-libraries-vvluy2i4uevs) +to continue on that promise: + +- Making it as easy as possible create and deploy GraphQL based applications +- Making it easier to maintain production-grade, scalable GraphQL applications + +All of that while: + +- Keeping the CLI updated with the latest solutions and practices +- Making it extensible and configurable without any solution bias — any approach and architecture + could easily integrate and benefit from the CLI +- Keeping the industry leading, long term open source library maintenance standard that The Guild is + known for + +## Try It Out Today + +We've already refactored most of the code, created a new structure, closed and fixed all the known +issues and released a new alpha version. + +Install new version (follow the latest alpha in the +[releases page](https://github.com/Urigo/graphql-cli/releases)): + +```sh +npm install graphql-cli@canary +``` + +Create a new project with GraphQL CLI by running: + +```sh +graphql init +``` + +GraphQL CLI will guide you and after only few seconds, your project will be ready to use! + +## End-To-End Type Safety + +Code generation + end-to-end type safety is a hot topic nowadays. Thanks to tools like +[**GraphQL Code Generator**](https://graphql-code-generator.com) we're able to produce flexible code +for both backend and frontend, just from GraphQL Schema and Operations with Fragments. + +In GraphQL CLI, you get it out of the box by running: + +```sh +graphql codegen +``` + +> _Discover what can be generator on [GraphQL Codegen website](https://graphql-code-generator.com)._ + +## Production Ready GraphQL Backend + +Thanks to integration with [**GraphBack**](https://graphback.dev), you're able to produce an entire +Data Base, GraphQL schema with operations and strongly typed resolvers. + +```sh +graphql generate +``` + +> Take a look at [GraphBack website](https://graphback.dev) to learn more. + +## Bulletproof Your GraphQL API + +GraphQL CLI comes with most of the features of +[**GraphQL Inspector**](https://graphql-inspector.com)**.** + +With just few simple commands you're able to: + +- detect breaking or dangerous changes +- validate Operations and Fragments at build time +- analyze the usage of GraphQL Schema (unused types and fields) +- find duplicates and similar GraphQL Types +- serve faked GraphQL schema + +```sh +graphql diff +graphql similar +graphql validate +graphql coverage +graphql serve +``` + +> Visit [GraphQL Inspector docs](https://graphql-inspector.com). + +## This Is Just the Start! + +The GraphQL CLI has been rewritten in order to make it extremely customizable and extensible. + +Our goal is to make sure that any tool can work and benefit from this setup. + +The CLI offers freedom for anyone to create any command that will extend their workflows by creating +separate library. Alternatively you can open a conversation about new command that can be included +into our supported set of commands. + +If you prefer to use all the Apollo toolings and products, AppSync's solutions, Prisma, OneGraph, +Hasura, Postgraphile or any other tool — we want to make the GraphQL CLI the best supporting tool +for your stack. + +> We won't impose any choices on the users. We want the community to lead and have template +> generators for any technology. + +This project is completely open and free from any bias, and we are open to any feedback and +collaboration with anyone from the community. Please reach out! + +## Example Use Cases of GraphQL CLI + +The CLI gives you the ability to build a base template with your favorite stack and tools. + +Templates can be based on graphql.js, Apollo, Nexus, TypeGraphQL or anything other framework. +Creating a custom template may help to enforce a specific structure that fits your product and +company. + +The CLI comes with two default templates that provide a seamless starting point for both backend and +frontend, both could be pushed to production in a short time period. + +Additionally, for existing applications, the CLI will support migrating existing databases or REST +API to GraphQL. + +## Production-Ready GraphQL App in Seconds + +> Or "Making GraphQL easy — From nothing to a full production-ready app in 2 minutes — with any +> stack!"" + +There are many great GraphQL boilerplate repositories on available Github. + +But when using those, it is often hard to adjust those to real business cases. + +As an alternative to sample apps, developers can rely on frameworks that provide a high level of +abstraction. + +But technologies that offer rapid application development might often come at the cost of the +maintenance and flexibility that can seriously limit the extensibility of your application server. + +```sh +graphql init +``` + +We believe that making it easy to start with GraphQL is extremely important, but without sacrificing +other factors like extensibility, scalability and wider control. + +> Simple shouldn't equal bad architecture + +GraphQL CLI addresses this very important problem in the core by utilizing two main concepts: code +generation and rich ecosystem of base templates. + +The `graphql init` command is trying to address three simple questions: + +- _Can we build an application template that can offer production-ready capabilities and yet is + simple enough to work without extensive learning?_ +- _Can we provide our data model as input to the GraphQL engine and benefit from autogenerated data + access methods?_ +- _Can we use the same techniques for an existing application and generate partial models?_ + +> You can think of it as a smarter `create-react-app`, that works on a full-stack and understands +> your data model. + +We are calling leading boilerplate creators and frameworks to collaborate with us. We can help you +expose your boilerplates also as templates for the init command. + +We would also love feedback from internal infrastructure teams from companies who wish to create +their own best practices and guidelines. + +For more information please refer to +[https://github.com/aerogear/graphback](https://github.com/aerogear/graphback) + +## One Config to Rule Them All — GraphQL Config + +_At the heart of a project created using GraphQL CLI is the GraphQL Config file. It lets the CLI +know where all the GraphQL parts are._ + +Config is essential for CLI templates and for the command creators that can utilize its +extensibility to save additional metadata. Thanks to [`graphql-config`](https://graphql-config.com), +the CLI can provide seamless support for every extension and streamline development experience on +top of the GraphQL CLI generated projects and corresponding templates. + +_For more information about GraphQL Config, you can +[click here to learn more.](https://graphql-config.com)_ + +## Migration from 3.x.x to 4.x.x + +We have provided a complete migration document for existing users who wish to update to the latest +version of the CLI. Please keep in mind that CLI is still in the alpha phase and we are looking for +the feedback before officially releasing a final version of the CLI. + +Please follow +[https://github.com/Urigo/graphql-cli/blob/master/MIGRATION.md](https://github.com/Urigo/graphql-cli/blob/master/MIGRATION.md) +migration guide. + +## Help Us to Shape the GraphQL Ecosystem + +Start using the GraphQL CLI today! + +Even though we are in an alpha phase, the CLI is fully usable and ready for the community to adopt +it. + +Our team is open to any suggestions and ideas for new commands. + +> We will support and answer all your questions on [GitHub](https://github.com/urigo/graphql-cli) +> and on our [Discord channel](https://discord.gg/xud7bH9). diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-code-generator-011/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-code-generator-011/page.mdx new file mode 100644 index 00000000000..3b59619118a --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-code-generator-011/page.mdx @@ -0,0 +1,379 @@ +--- +title: GraphQL Code Generator v0.11 +authors: dotan +tags: [graphql, codegen] +date: 2018-08-21 +description: Generate React and Angular Apollo Components, Resolver signatures and much more! +--- + +import { Callout } from '@theguild/components' + + + This blog post refers to an outdated version, please check https://the-guild.dev/graphql/codegen + for the latest docs! + + +## Generate React and Angular Apollo Components, Resolver Signatures and Much More! + +We are very excited to announce a new release for graphql-code-generator! + +If you haven't checkout [graphql-code-generator](https://graphql-code-generator.com) before, please +check out the following introductory blog posts: + +- [GraphQL Code-Generator](/blog/graphql-code-generator) +- [What's new in GraphQL Code Generator 0.9.0](/blog/graphql-code-generator-090) + +**v0.11** holds a lot of improvements, new features and a couple of new templates, mostly in +Typescript — for frontend and backend! + +Here is the gist of it: + +- **New React and Angular templates** to greatly simplify the usage of Apollo Client and Typescript + and auto-generate a lot of the current boilerplate +- **Generate types for resolvers** +- Major overall to support generation of **custom scalar types** +- Support for **GraphQL-Yoga** with **graphql-import** +- Watch mode +- Major refactoring and improvements to the **Typescript Mongo template** + +--- + +But the best part of all — on this release we saw a **major increase in contributions and +contributors**! This is a strong sign for the growth of the ecosystem around graphql-code-generator. + +> Thank you so much everyone, we are humbled by your help. keep it coming! + +Even though we haven't decided to declare 1.0 yet, we use this library on each one of our projects +and our clients projects. + +Each PR merged creates an alpha release to npm which we then test out and upgrade all of those +applications. + +Only then we release a full npm version, so you can be assured that by time of the release, it has +already been tested by major Enterprise applications who deploy daily into production, on top of all +the unit tests on the project! + +So all the new features that are being mention on this post, are already being used by us +extensively. + +OK, let's start with the new features for the **client side** Typescript users: + +## React Templates + +[react-apollo](https://github.com/apollographql/react-apollo) recently introduced +[a new default API](https://blog.apollographql.com/introducing-react-apollo-2-1-c837cc23d926) with +Query, Mutation and Subscription components. + +Whether you use the new API or prefer HOC and you use Typescript, there is no need to write those +wrapper components again and again! + +With the new +[TypeScript React Apollo Template](https://github.com/dotansimha/graphql-code-generator/tree/master/packages/templates/typescript-react-apollo), +you don't need to anymore! + +All you need to do is to write your GraphQL Query, Mutation or Subscription, and the codegen will +generate **fully typed** react-apollo components and HOC for each one. + +That makes using React Apollo with Typescript so much simpler! + +You can read more about it in the blog post +{/* prettier-ignore */} about GraphQL Code Generator +for Typescript React Apollo. + +Thank you [Arda TANRIKULU](https://twitter.com/ardatanrikulu) for that amazing contribution! + +## Angular Templates + +But why just React? Angular should get the same benefits! + +[Apollo-angular](https://github.com/kamilkisiela/apollo-angular) also recently added added the +Query, Mutation and Subscription services. + +But why writing them by yourself? + +With the new +[TypeScript Angular Apollo Template](https://github.com/dotansimha/graphql-code-generator/tree/master/packages/templates/typescript-apollo-angular), +all you need to do is to write your GraphQL Query, Mutation or Subscription, and the codegen will +generate a fully functioning, fully typed Angular service for each of those! + +As this was a contribution by [Kamil Kisiela](https://twitter.com/kamilkisiela), the creator and +maintainer of the apollo-angular library, we believe we can endorse it from now on as the best way +of using apollo-angular! + +You can read more about it in the blog post about Apollo Angular +1.2. + +Thank you very much [Kamil Kisiela](https://twitter.com/kamilkisiela), the creator and maintainer of +[`apollo-angular`](https://github.com/kamilkisiela/apollo-angular) for that amazing contribution, +from the `apollo-angular` and the `graphql-code-generator` side! + +Moving on to the new **backend features**. + +## Generate Types for Resolvers + +The TypeScript template is now able to generate Typescript types not only for a schema and documents +but also for GraphQL Resolvers. And It's enabled by default! + +We think this was another missing piece of making your code even more strongly typed, from end to +end. + +The template now generates a Typescript type for each GraphQL type with resolvers for all of its +fields but it is also possible to extract a type of a single field. + +```typescript +import { QueryResolvers } from './generated/graphql' +import { getPosts } from './posts' + +const Query: QueryResolvers.Resolvers = { + posts() { + return getPosts() + } +} +``` + +As you can see the usage is straightforward, but we recommend to explore the source code of a +generated file. + +## Custom Scalars Improvements + +Prior to this version, scalars were declared with `any` which means we didn't take enough advantage +of TypeScript here. This was an another opportunity to strengthen up your code. We're happy to tell +you it's now possible to **customize a scalar** and provide a type instead of having it as `any`. + +We also introduced a new option called **prepend** to add a code at the beginning of a generated +file, it completes the custom scalars feature. + +## Unified Naming Strategy + +camelCase, PascalCase, snake_case, lowercase, UPPERCASE, CONSTANT_CASE, there's a lot of ways for +people to name their types in GraphQL schema. It was a wild west, until now. + +We decided to unify the naming strategy in codegen and pick just one. We chose PascalCase. And now, +everything generated by codegen follows that rule. + +Maybe at some point in the future, graphql-code-generator will allow to set your own naming +strategy. Contributions are welcome so don't feel afraid, we will help you! + +_Keep on mind that it's a breaking change, TypeScript might warn about non-existing type, if so then +change it to be PascalCased._ + +## Watch-Mode + +Now the GraphQL Code Generator detects changes in your schema and type definitions, and generates +typings automatically after you edit these files. + +The only thing you need to do is add `-w` parameter to the command line. + +Thank you [Arda TANRIKULU](https://twitter.com/ardatanrikulu) and +[FredyC](https://github.com/FredyC) for that great contribution! + +## Support for GraphQL-Yoga with `graphql-import` + +Thanks to +[David Yahalomi](https://medium.com/u/c544f341ba49?source=post_page-----15bb9b02899e----------------------)'s +great contribution, GraphQL Code Generator now understands the graphql-import syntax being used by +GraphQL Yoga. + +```graphql +# import B from "b.graphql" + +type A { + # test 1 + first: String + second: Float + b: B +} +``` + +## MongoDB Schema Template + +We are also [working](https://github.com/dotansimha/graphql-code-generator/pull/490) on improving +the **MongoDB Typescript** schema template. + +We are using this template in many of our apps in production, it saves us a lot of boilerplate and +potential bugs, and it's also easier to maintain and keep track of the MongoDB schema objects. + +Basically, this template lets you generate TypeScript typings for the shape of your MongoDB objects +based on your GraphQL schema. + +For example, using the following schema: + +```graphql +type User @entity { + id: String @id + username: String! @column + email: @column +} +``` + +You will get a generated TypeScript interface: + +```typescript +import { ObjectID } from 'mongodb' + +export interface UserDbObject { + _id: ObjectID + username: string + email?: string | null +} +``` + +Then, you can use it with MongoDB driver for NodeJS: + +```typescript +import { Collection } from 'mongodb' +import { db } from './my-db-instance' + +const MyCollection: Collection = db.collection('users') +``` + +Now your MongoDB collection is typed, and it's easier to find bugs during development. + +We understand that a lot of edge cases might occur because of the differences between GraphQL schema +and the lack of schema in MongoDB, so we added `@map` directive that lets you do custom mapping of +fields: + +```graphql +type User @entity { + id: String @id + username: String! @column @map(path: "profile.credentials.username") + email: @column +} +``` + +Will output: + +```typescript +import { ObjectID } from 'mongodb'; + +export interface UserDbObject { + _id: ObjectID; + profile { + credentials: { + username: string; + }; + }; + email?: string | null; +} +``` + +There is also the need to handle the difference between embedded types and linked types, so you can +use `@entity(embbeded: true)` and `@embbeded` to declare link and embedded entities: + +```graphql +type User @entity { + id: String @id + profile: Profile! @embedded + email: @column + photos: [Photo] @link +} + +type Profile @embedded { + fullName: String +} + +type Photo @entity { + id: String! @id + url: String! @column +} +``` + +Will output: + +```typescript +import { ObjectID } from 'mongodb' + +export interface UserDbObject { + _id: ObjectID + profile: ProfileDbObject + photos: [ObjectID] + email?: string | null +} + +export interface ProfileDbObject { + fullName: string +} + +export interface PhotoDbObject { + _id: ObjectID + url: string +} +``` + +Another issue is to add fields that are relevant only for the MongoDB schema, and not relevant to +your GraphQL schema (such as security token and login hashes), so you can define it in your GraphQL +Schema: + +```graphql +type User @entity(additionalFields: [ + { path: "services.login.token", type: "string" } +]) { + id: String @id + email: @column +} +``` + +We also addressed GraphQL Interface, and now you can use `@abstractEntity` to override your +interfaces, and if you are using GraphQL Unions, you can add a custom `@discriminatorField` to your +union, and it will add the field to your MongoDB schema. + +We are also thinking about adding more templates for entities schemas in the future, such as +[JSON schema](https://json-schema.org). + + + We are planning to release the new version of that template next week and write a dedicated blog + post on it with more details. + + +Also, that template can be a great start for others to add the same generated features to other data +sources like **SQL** and **NoSQL** databases, **DB ORMs** and more! + +Other honorable mentions on this release: + +- [Support for AWS AppSync](https://github.com/dotansimha/graphql-code-generator/pull/354) +- [Generate introspection file](https://github.com/dotansimha/graphql-code-generator/pull/371) +- [Huge performance improvements](https://github.com/dotansimha/graphql-code-generator/pull/434) + thanks to [prevostc](https://github.com/prevostc) +- [Added implementingTypes for interfaces](https://github.com/dotansimha/graphql-code-generator/pull/339) + +_Watch the complete breakdown of the releases and features here: +(https://github.com/dotansimha/graphql-code-generator/releases)[https://github.com/dotansimha/graphql-code-generator/releases]_ + +As usual, we keep all of our dependencies up to date with each release thanks to **renovate**. This +release updates everything to latest as usual, including **TypeScript 3.0**. + +We believe it is a good open source practice and helps not just us and our users but it also helps +our open source dependencies authors to get early feedback on any issue that might happen. + +> Friends help friends test early! ;) + +We encourage that practice on for any open source library and any application! + +--- + +## What Other Ideas Do You Have for Generating Things? + +If you have any ideas or custom needs, that's exactly where the graphql-code-generator shines, let +us know about your needs, and we would be happy to support you! + +This version wouldn't be possible without the great help of [sgoll](https://github.com/sgoll), +[David Yahalomi](https://medium.com/u/c544f341ba49?source=post_page-----15bb9b02899e----------------------), +[Arda TANRIKULU](https://twitter.com/ardatanrikulu), +[Kamil Kisiela](https://twitter.com/kamilkisiela), [degroote22](https://github.com/degroote22), +[jycouet](https://github.com/jycouet), [FredyC](https://github.com/FredyC), +[prevostc](https://github.com/prevostc), arvind-chandrashekar-ck, +[Cactucs](https://github.com/Cactucs). + +Also want to contribute? here are some ideas for future contributions (_but any ideas would be +welcomed!_), we would love to support and help any new contributor who want to give it a try! + +- [Support for graphql-config](https://github.com/dotansimha/graphql-code-generator/issues/382) +- [Additional examples of generator templates written in JS](https://github.com/dotansimha/graphql-code-generator/issues/311) +- [Flow template](https://github.com/dotansimha/graphql-code-generator/issues/160) + +Don't forget — if you created your own template and think it could benefit others, +[link it on our readme here](https://github.com/dotansimha/graphql-code-generator#available-templates). + +--- + +Star and follow us on **GitHub** and **Twitter**, we have a lot more coming soon! diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-code-generator-090/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-code-generator-090/page.mdx new file mode 100644 index 00000000000..ee867a6d5ef --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-code-generator-090/page.mdx @@ -0,0 +1,189 @@ +--- +title: What's new in GraphQL Codegen 0.9.0 +authors: dotan +tags: [graphql, codegen] +date: 2018-05-07 +description: + The GraphQL Codegen library can generate any code for any language — including type definitions, + data models, query builder, resolvers, etc... +--- + +import { Callout } from '@theguild/components' + + + This blog post refers to an outdated version, please check https://the-guild.dev/graphql/codegen + for the latest docs! + + +The [GraphQL codegen library](https://github.com/dotansimha/graphql-code-generator) can generate +**any code for any language** — including type definitions, data models, query builder, resolvers, +ORM code, complete full stack platforms!! and any specific code for your needs. + +_Not sure what GraphQL Code Generator is? [read this](/blog/graphql-code-generator)!_ + +## I'm Excited to Announce a New Version of the GraphQL Code Generator! 🎉🎉🎉 + +Here are some of the new feature and changes in the new release: + +- **New CLI util that helps you to write custom templates**: now it's much easier to write your own + generator template! +- **TypeScript & MongoDB template**: a new template was added — you can now generate TypeScript + models for MongoDB +- **Programmatic usage**: our can now use it from your code and not just as CLI +- **TypeScript template** now supports output customization using environment variables. + +And also a lot of bugs were fixed. +[You can find the full changelog here](https://github.com/dotansimha/graphql-code-generator/blob/master/CHANGELOG.md#090-2018-05-07). + +[_I would like to thank all the developers that took part in the development of the codegen — thank you for all of your help, code, and wonderful ideas!_](https://github.com/dotansimha/graphql-code-generator/graphs/contributors) + +--- + +## New CLI Util That Helps You to Write Custom Templates + +As part of the development, we understood the value of templates sharing between developers. We also +understood that it's difficult to get started with writing your own codegen templates. That's why we +created a new CLI util called `codegen-handlebars-templates-scripts`. + +With these tools, you can easily scaffold, build, test and publish your new template, and share it +with other developers. + +To start with the new tool, install it globally: + +- **Custom output processors**: we know that Handlebars isn't for everyone, so now you can write + custom processors that does whatever you need + +```sh +yarn global add codegen-handlebars-templates-scripts +``` + +Then, create a directory for your template, and run the init command: + +```bash +mkdir my-template +cd my-template +codegen-handlebars-templates-scripts init +``` + +Now you got a new Codegen template project, so all you have to do it to start writing your template. + +If you wish to build it and test it, you can use the predefined commands: + +```bash +yarn build +yarn test +``` + +To get some inspiration, ideas and examples, you can take a look at +[the implementation of the TypeScript templates](https://github.com/dotansimha/graphql-code-generator/tree/master/packages/templates/typescript). + +### TypeScript & MongoDB Template + +We also implemented a +[new template](https://github.com/dotansimha/graphql-code-generator/tree/master/packages/templates/typescript-mongodb), +to make it easier for MongoDB developers to integrate the GraphQL Code Generator. + +The new template is called `graphql-codegen-typescript-mongodb-template` and to use it, run the +following: + +```bash +yarn add -D graphql-codegen-typescript-mongodb-template +gql-gen --template graphql-codegen-typescript-mongodb-template --schema ... +``` + +The idea behind the new template is to help MongoDB developer to write better code, and to make sure +their data is type safe. + +With this template, you can defined you GraphQL schema in the following format: (don't worry, we +also included the GraphQL @directives for you as part of the package) + +```graphql +type User @entity { + id: String @id + username: String! @column + email: @column +} +``` + +And your generated output will be: + +```typescript +import { ObjectID } from 'mongodb' + +export interface UserDbObject { + _id: ObjectID + username: string + email?: string | null +} +``` + +To read more about the usage and for more examples, +[go ahead and read the template's README](https://github.com/dotansimha/graphql-code-generator/blob/master/packages/templates/typescript-mongodb/README.md). + +### Custom Output Processors + +We know that writing templates isn't easy, and we know that not everyone likes to use Handlebars to +write templates, so we made it easier to write your own output processor. + +Implementing a custom output processor is easy. All you have to do is to create a JavaScript file +(or any other, and compile it to JS), and use it for your `--template` flag. Your JS file should use +`default export` a function that will build the entire output. + +The code generator core will make sure to pass everything you need regarding your GraphQL schema and +GraphQL documents. + +[You can read more about custom output processors here.](https://github.com/dotansimha/graphql-code-generator/tree/master/packages/templates#write-your-own-output-processor) + +### Programmatic Usage + +Part of the new release is an easier way to use the GraphQL Code Generator programmatically. So if +you want to integrate the codegen into another util — now you can do it! + +Just import `generate` from `graphql-code-generator` and run it with your options object (you can +also choose whether to write the files to the FS or not): + +```typescript +import { generate } from 'graphql-code-generator' + +async function doSomething() { + const generatedFiles = await generate({ + template: 'typescript', + url: 'http://127.0.0.1:3000/graphql', + out: process.cwd() + '/models/' + }) +} +``` + +### New Features in TypeScript Template + +In this release we did some bug fixes and changes in the TypeScript template: we now generate +nullables and nullable arrays in a better way, and we also fixes some bugs in the generated results. + +The TypeScript template now also supports multiple configuration options, so you can customize the +output according to you needs: + +`printTime` - Setting this to `true` will cause the generator to add the time of the generated +output on top of the file. + +`avoidOptionals` - This will cause the generator to avoid using TypeScript optionals (`?`), so the +following definition: `type A { myField: String }` will output `myField: string | null` instead of +`myField?: string | null`. + +`enumsAsTypes` - Will generate the declared enums as TypeScript `type` instead of `enums`. This is +useful if you can't use `.ts` extension. + +`immutableTypes` - This will cause the codegen to create immutable types as output, by adding +`readonly` to the properties and `ReadonlyArray`. + +--- + +## What's Next? + +Our next steps for the GraphQL code generator is to expand the templates collection and to create a +community for sharing templates. + +If you wish to help us by writing a template a sharing, feel free to contact us by creating a +[new issue in the package repository](https://github.com/dotansimha/graphql-code-generator/issues/new). + +If you already created a template, you can +[edit the README file and add it there](https://github.com/dotansimha/graphql-code-generator/edit/master/README.md#L61). diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-code-generator-and-prisma/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-code-generator-and-prisma/page.mdx new file mode 100644 index 00000000000..0dfafddd6e2 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-code-generator-and-prisma/page.mdx @@ -0,0 +1,258 @@ +--- +title: GraphQL Code Generator with TypeScript and Prisma models +authors: giladtidhar +tags: [graphql, typescript, codegen, prisma] +date: 2021-12-19 +description: How to use Prisma models with GraphQL Code Generator +--- + +## Introduction + +GraphQL has some amazing tools that can make your life easier. One of those tools is +[GraphQL Code-Generator](https://graphql-code-generator.com) which helps you create types (and more) +based on your GraphQL schema. + +If you're creating a GraphQL server you'd probably want to have a database behind it with something +like [Prisma](https://prisma.io). + +So, how can you use [Prisma](https://prisma.io) for your database and still use the GraphQL-Codegen? +this article covers the process of using [Prisma](https://prisma.io) with +[GraphQL Code-Generator](https://graphql-code-generator.com), and the configuration flags that will +boost your developer experience. + +## Prisma + +### What Is Prisma? + +[Prisma](https://prisma.io) is an open source fully-typed next-generation ORM. It consists of the +following parts: + +**Prisma Client**: Auto-generated and type-safe query builder for Node.js & TypeScript **Prisma +Migrate**: Migration system **Prisma Studio**: GUI to view and edit data in your database + +Each Prisma project has a schema, which it used to define its models. + +Every project that uses a tool from the Prisma toolkit starts with a Prisma schema file. + +The Prisma schema allows developers to define their application models in an intuitive data modeling +language. + +### Why Prisma? + +Prisma's main goal is to make application developers more productive when working with databases. + +Here are a few examples of how Prisma achieves this: + +- **Thinking in objects** instead of mapping relational data +- **Queries not classes** to avoid complex model objects +- **Single source of truth** for database and application models +- **Healthy constraints** that prevent common pitfalls and antipatterns +- **An abstraction that makes the right thing easy** ("pit of success") +- **Type-safe database queries** that can be validated at compile time +- **Less boilerplate** so developers can focus on the important parts of their app +- **Auto-completion in code editors** instead of needing to look up documentation + + [A tutorial about how to get started with Prisma](https://prisma.io/docs/getting-started/setup-prisma/start-from-scratch) + +## GraphQL Code Generator + +The [GraphQL Code-Generator](https://graphql-code-generator.com) is an easy way to create type +safety with your GraphQL project. + +It automatically generates TypeScript types based on your GraphQL schema. This is very useful +because it reduces the chances to write mistakes, and you can locate bugs at build-time. + +For example, here is an example for JavaScript/TypeScript resolver without the codegen, as you can +see, we need to give everything a type. + +```ts +const resolver = { + Query: { + feed: async ( + parent: unknown, + args: { + filter?: string; + skip?: number; + take?: number; + }, + context: GraphQLContext + ) => {...} + } +} +``` + +But, with the codegen, the manual types are no longer needed, because it generates the types, so +typescript will now know which TypeScript types to use and validate: + +```ts +import { Resolvers } from './generated/graphql' + +const resolvers: Resolvers = { + Query: { + feed: async (parent, args, context) => { + // ... + } + } +} +``` + +As you can see, now that the resolvers are typed, we don't need to define types for each resolver. + +> If you are new to GraphQL-Codegen, you can follow +> [a tutorial about how to get started with GraphQL codegen](https://graphql-code-generator.com/docs/getting-started) + +> You can also +> [find here a blog post about how to use GraphQL Code-Generator with the `typescript-resolvers` plugin](/blog/better-type-safety-for-resolvers-with-graphql-codegen). + +## Benefits of Writing Fully-Typed Code + +- Better code completion and syntax highlighting. +- You can get hints and documentation inside your IDE while you code. This reduces the likelihood of + making incorrect assumptions about the behavior of specific functions/methods. +- It's easier to find things. For any variable or function, you can easily jump to its class + definition without leaving the IDE and without having to know anything about the directory + structure of the project. Conversely, for any class or function definition, you can easily and + unambiguously see where that class or function is used in your code and jump to it without leaving + the IDE. (Statically typed languages make it easier for IDEs to do this). +- Static typing makes it easier to work with relational databases and other systems which also rely + on static types — It helps you catch type mismatches sooner at compile-time. +- It can help reduce the likelihood of some kinds of errors. For example, in dynamically typed + languages, if you're not careful with sanitising user input, you can end up doing weird stuff like + (for example) trying to add a number 10 with the string “8” and you would get the string “108” as + a result instead of the number 18 that you were expecting. + +## Using GraphQL Codegen and Prisma Together + +After learning the benefits of Prisma and GraphQL codegen, you might want to use both together! But +there's a few problems: + +### Name Conflicts + +The Prisma models and the GraphQL models might conflict with each other. + +This is because the GraphQL codegen automatically uses the types from the GraphQL schema, and Prisma +automatically generates types from your Prisma models. + +If your GraphQL schema is using `type User { ... }` and your Prisma model is using `model User { }`, +you might have a naming conflict. + +### Database Types !== GraphQL Types + +The types for your database and not the same as your GraphQL types. In your GraphQL layer, you might +take different limitations/constraints than you have in your database. + +For example, some prisma operations get arguments which are for filtering and paginating, now, if +you have this type of filter in your GraphQL schema, it might look something like this: + +```ts +type Query { + feed(filter: String, skip: Int, take: Int): Feed! +} +``` + +As you can see, the arguments `filter`, `skip` and `take` are nullable which means that GraphQL will +send them as null if left without value. + +**What's the problem with this?** + +Well, for filtering and paginating prisma takes arguments which either have a value or are +`undefined`, but not `null`. + +This is a problem for us because the type the codegen uses for maybe values by default (values that +are `null`able), could be `null` (`null | undefined | T`). + +### How Do We Fix This? + +Well, for the first problem, the code generator has an option called `mappers`. + +Using a mapper gives you the option to map one type to another. + +This option helps us with our problem because we can just tell the codegen to use the Prisma models +instead of the default types generated from the GraphQL schema! + +The second fix is a configuration flag called `inputMaybeValue`. + +Nullable types are represented by Maybe in the GraphQL codegen. The `inputMaybeValue`, lets you +change the types that arguments can be! + +Using the two configuration flags mentioned above, we can tell GraphQL codegen what TypeScript types +to generate and how to map the GraphQL types to Prisma models + +### Using `mappers` + +Mappers are actually really easy to use, all you need to do is add them to your `codegen.yml`! + +For example, lets say I have a Prisma model which is called `User`, and my GraphQL schema also uses +a `type User`. + +For my project to work with its database, it needs to use the Prisma model instead of the GraphQL +one, so I should map my User model from prisma to my User type in GraphQL. + +Here's an example: + +```yaml +schema: http://localhost:3000/graphql +documents: ./src/graphql/*.graphql +generates: + graphql/generated.ts: + plugins: + - typescript-operations + - typescript-resolvers + config: + mappers: + User: .prisma/client#User as UserModel +``` + +Under the `mappers`, you can see we take the GraphQL `User` type, and set it to be using the +exported type automatically created by Prisma. + +We set it to be named `UserModel`, so it won't conflict with the GraphQL definition of the GraphQL +`User` type. + +### Using `inputMaybeValue` + +`inputMaybeValue` is fairly simple to use, just add it under `codegen.yml` config file: + +```yaml filename="codegen.yml" +schema: http://localhost:3000/graphql +documents: ./src/graphql/*.graphql +generates: + graphql/generated.ts: + plugins: + - typescript-operations + - typescript-resolvers + config: + mappers: + User: .prisma/client#User as UserModel + inputMaybeValue: undefined | T +``` + +Now, the default value to `inputMaybe` (The type of nullable arguments) will be either `undefined` +or `T`, leading to an easy type-compatibility between your GraphQL `input` arguments and the Prisma +SDK requirements. + +## What Now? + +Now, run GraphQL-Codegen and the Prisma Codegen and you should get a fully-typed resolver. Here's an +example: + +```ts +import { Resolvers } from './generated/graphql' + +const resolvers: Resolvers = { + Query: { + user: async (parent, args, context) => { + // Codegen will generate Resolvers type, and + // will expect you to return here an object of + // Prisma's User model. + + return context.prisma.user.findOne({ id: args.id }) + } + }, + User: { + name: user => { + return `${user.first_name} ${user.last_name}` + } + } +} +``` diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-code-generator/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-code-generator/page.mdx new file mode 100644 index 00000000000..72c25cb57e5 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-code-generator/page.mdx @@ -0,0 +1,327 @@ +--- +title: Introducing GraphQL Code Generator +authors: dotan +tags: [graphql, codegen] +date: 2017-10-12 +description: + The True GraphQL-First platform. Generate your backend, frontend, types, resolvers, ORM code and + more from your GraphQL schema and documents. +--- + +## TL;DR + +- [https://github.com/dotansimha/graphql-code-generator](https://github.com/dotansimha/graphql-code-generator) +- The [GraphQL codegen library](https://github.com/dotansimha/graphql-code-generator) can generate + **any code for any language** — including type definitions, data models, query builder, resolvers, + ORM code, complete full stack platforms!! and any specific code for your needs +- I wrote the code generator based on my experience with other robust code generators (such as + [Swagger codegen](https://github.com/swagger-api/swagger-codegen)). +- You can **create your own** custom GraphQL codegen templates in **10 minutes**, that fit exactly + your needs — you can even generate an entire application based on your GraphQL schema! +- Template-based implementation is simpler and easier to maintain, and I think that eventually + single engine with multiple templates will match all use-cases (for example, writing the **Flow + template took only 10 minutes**) +- **Real life use-cases always needs customizations** (for example, Shopify wrote their own codegen + that they need to maintain because the other codegen tools weren't flexible enough. If they would + use this tool, they would just need to create a new template — which is simpler and easier to + maintain) +- Share templates with the **community** — move the community forward by having an **eco-system of + templates** that probably match most of the use-cases, learning from the Swagger codegen community + +import { YoutubeIframe } from '#components/youtube-iframe' +import { Callout } from '@theguild/components' + + + +[**_The source code of the video examples are available here_**](https://github.com/dotansimha/graphql-code-generator-usage-example) + +--- + + + This blog post refers to an outdated version, please check https://the-guild.dev/graphql/codegen + for the latest docs! + + +## Do More than Just Generating + +About a year ago, I started writing code and apps using GraphQL. + +Doing that, while coming from a production and Enterprise background, I looked for the tools +available on the GraphQL ecosystem and compared it to the tools I used back then. + +There were a few code generators out there, but immediately I was missing a tool that is similar to +the Swagger code generator. Also, the possibilities GraphQL gives us are much greater than Swagger +so I knew there was a big opportunity here. + +So I started writing the code-generator for GraphQL, and I used my years of experience with Swagger +code generator as inspiration. + +The magic behind every good code generator, is the ability to change and extend the results quickly, +while maintaining a simple (yet flexible) data structure based on your GraphQL schema and documents. + +Code generators are also good for wrapping your data layer with a consistent code — for example, you +can generate a function that executes the GraphQL document through your network interface, fetches +the data and returns the response wrapped in a custom structure. + +--- + +## Use Cases + +**Typings** — So yes, of course you can generate Typescript and Flow typings, Swift, Java and C#. + +But the thing is, that it's much easier to add or change those generators, in fast, creating the +Flow codegen took 10 minutes! + +And that means that you don't need to rely on us to create the generator that you need — you can +create your own custom generator in a matter of minutes and share it with the whole community! + +**Generate your backend** — From your GraphQL definitions, you can generate your backend code! You +can generate your resolvers, your data models, query builders, mutations, filters and more! Now +maybe you can understand what the GraphQL Backend as a service platforms are doing… exactly that! So +you can use the GraphQL-First approach and generate a full functioning backend from just your +GraphQL schema! But there is a big difference — it's completely open source, you can generate just +parts of it and write your own custom code or even change the templates! So you generate backend can +be Node, Typescript, .NET, Java, Ruby, Python or anything you wish! And you can learn from the other +community generators from other languages. + +This is just the start and there is much more to explore, but we are already using it for a few +production apps (for example Schneider-Electric's new online management platform), generating from a +GraphQL schema with decorators a complete [Mongoose ORM](https://mongoosejs.com) models with +Typescript for the whole app! (the generator's templates are +[available here](https://github.com/dotansimha/graphql-code-generator-usage-example/tree/master/src/models), +and soon will be available as part of the built-in generators!) + +In the next weeks we will release more generators and templates using SQL ORMs, but we would love to +hear your use cases and to see which generators the community will come up with. + +**Generate REST APIs** — You can use your GraphQL Schema to generate Swagger definitions and then in +turn generate full functional, completely documented REST APIs without the need to maintain, support +and update them! + +**Generate your frontend** — This is sometimes less useful, but can be very helpful for generic +admin panels or specific generic React components. We've already done that as well, creating a full +React Material admin template that is being completely generated from a GraphQL schema. The nice +thing is that we've made it so that when you edit the template it feels just like you are editing +regular React code with regaulr IDE support. + +**Add your own** — Can you think about another use case? Suggest it on our +[Github repo](https://github.com/dotansimha/graphql-code-generator) and let's try to create a +template for it. + +**Relations to other tools and prior art:** + +- GraphQL as a service platforms — Graphcool, Scaphold and other GBaas are doing very similar things + under the hood, but not open source and for their specific use cases and specific backend stacks + and languages. I would love to help them migrate into our codegen so they won't have to maintain + their own code generators and just focus on their special and unique offerings. +- Apollo Codegen — Apollo codegen has written specific code for each implementation (Typescript, + Swift, etc..). That's great but when there is a small issue, it is much harder to include and + release that change fast. Also, users might requests features that are very specific for their own + use cases and including those changes in the main repo without affecting the rest of the users can + be very hard. I would love to migrate Apollo's current implementation to our templates (most of + them are already included in this version) and help the wonderful Apollo team better maintain and + support their users. Please let me know of any use case or issues you are currently facing and + need support with. + +--- + +# Getting Started + +To get started with the GraphQL code generator, start by installing NodeJS, and then install: + +```sh +npm install graphql-code-generator +``` + +Then, make sure that your GraphQL schema is available for use — either in JSON file or development +server. + +Also, make sure your GraphQL documents are inside .graphql or `.graphqls` files (you don't have to, +you can also put in JavaScript files with `graphql-tag` package, and the generator will find it +automatically!). + +Now, run the generator with your config (this example uses remote GraphQL server with local GraphQL +documents, and generates TypeScript typings to ./types/ directory): + +```sh +gql-gen — url http://localhost:3010/graphql — template typescript — out ./types/graphql-typings.d.ts "./src/**/*.graphql" +``` + +That's it! your schema and documents are now TypeScript typings! and you won't see any IDE or linter +error when using GraphQL! + +This code generator can also generate only the server side schema — so you can use it in both client +and server! + +--- + +## Take It to the Next Level + +As time went by, I noticed that there are more GraphQL developers that struggle with the same issue +— development environments such as Swift does not play along with JSON, and need to have data +structures (structs) defined in order to get a better experience (otherwise, you have to treat you +data as a dictionary). + +I created more templates, and at the moment there are generators for the following: + +- TypeScript +- Flow +- Swift (with Apollo) + +BTW — We are looking for more GraphQL developers which use different environments in order to add +more generators and support those GraphQL communities — for example, someone from the community is +already working on a C# template. + +--- + +## How It Works? + +First, we start with the GraphQL schema defined in our server-side, and try to understand it's +structure and the links between the types and scalars (called GraphQL introspection query), then we +modify the structure of this metadata into a custom structure that will later be simple to use. + +Now, we need to use a code template (usually based on a template engine, such as Handlebars.js or +Mustache), and compile it with the data structure we created earlier. + +Let's take the following GraphQL schema: + +```graphql +type Person { + name: String + age: Int +} +``` + +The code generator transforms this definition into a custom JSON data structure, for example: + +```json +{ + "models": [ + { + "name": "Person", + "fields": [ + { + "name": "name", + "type": "string", + "required": true + }, + { + "name": "age", + "type": "number", + "required": false + } + ] + } + ] +} +``` + +Now, we have a generic JSON structure, and if we want to turn our schema into TypeScript type +definition, we need to compile it with the following template: + +```handlebars +{{#each models}} + export type {{name}} = { + {{#each fields}} + {{name ~}} {{#unless required ~}} ? {{~ /unless ~}} : {{ type }}; + {{/each}} + } +{{/each}} +``` + +Now when we use the two together, we will get: + +```ts +export type Person = { + name: string + age?: number +} +``` + +This is a simple example, because in real life, GraphQL allows use to do much more in our schema: +define enums, custom scalars, unions, interfaces, custom directives, add arguments in each field, +and more. + +Now let's take in to the next level — because GraphQL schema isn't everything GraphQL has — in our +client side, we define our Query, Mutation, Subscription and Fragment, along with directives, +arguments and more — those are called documents. + +The idea is basically the same: we take the client side documents, transform it into a simple +structure, then compile it with a template. + +--- + +## Code Generator Implementation + +The code generator is a CLI util, written in TypeScript and NodeJS, that every developer can use, +regardless the environment or the language in use. + +Using `graphql` npm package, I was able to load the schema and execute +[Introspection query](https://graphql.org/learn/introspection), then recursively iterate over the +types and links, and define a custom structure for the Models. + +Then, do the same for client side documents, while assisting the server side schema to understand +the types (the full custom structure +[is here](https://github.com/dotansimha/graphql-code-generator/blob/master/src/models/interfaces.ts)). + +The trick in client side documents is to create the correct selection set object, in each document. + +For example, when using this schema: + +```graphql +type Person { + name: String + age: Int +} + +type Query { + me: Person +} +``` + +And your query is only for the name field: + +```graphql +query myQuery { + me { + name + } +} +``` + +We want to generate a new type, called MyQuery_Me which based on the server side type Person, but +only with the fields we wanted — name. + +So while building the custom structure, we use a config flag called flatten per each language, +because there are languages that allows us to flatten the inner types we just explained, and group +them all together (for example, TypeScript allows you to create module or namespace, but in Swift, +you have to create a recursive structs). + +The next step is to implement the template for server side and client side, the template engine I +picked is [Handlebars.js](https://handlebarsjs.com) because it comes with a set of great template +utils (such as each, if and unless), but allows you to create a custom helpers — and this is a +feature I wanted to preserve and allow other developers use when creating custom templates — so each +language template can implement it own template, config and template helpers! + +Also, with Handlebars.js you can create a partial templates and use them inside each other, so you +can easily create a recursive template to load itself — which is very useful when dealing with +infinite inner-types (exactly what's GraphQL has) + +--- + +## Summary + +I'm very excited about this release. + +The graphql code-gen has come a long way and is used in many production apps, both from our users +and our clients. + +But it's just the beginning! Please join our community and feel free to +[contact me directly](https://github.com/dotansimha) for any question or help you might need. + +--- + +- [GraphQL Code Generator repository](https://github.com/dotansimha/graphql-code-generator) +- [Documentation](https://github.com/dotansimha/graphql-code-generator#packages) +- [Creating a custom template documentation](https://github.com/dotansimha/graphql-code-generator/blob/master/packages/graphql-codegen-generators/README.md) +- [TypeScript output example](https://github.com/dotansimha/graphql-code-generator/blob/master/dev-test/githunt/types.d.ts) diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-codegen-and-apollo-client-3/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-codegen-and-apollo-client-3/page.mdx new file mode 100644 index 00000000000..37a70fc697f --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-codegen-and-apollo-client-3/page.mdx @@ -0,0 +1,207 @@ +--- +title: What's new with Apollo Client v3 and GraphQL Codegen +authors: dotan +tags: [graphql, codegen, typescript, react, angular, apollo-client] +date: 2020-11-30 +description: + All the new features GraphQL Codegen adds to your type-system with the new Apollo Client 3. +--- + +In recent weeks and months, we've been migrating many of our clients codebases, many at very large +scale (over thousand developers on a single codebase), from Apollo Client 2 to Apollo Client 3. + +While doing all that work, we've improved many of the toolings we are maintaining and created a +bunch of [new ones](/blog/introducing-graphql-eslint). + +A lot of those improvements were fed back into GraphQL Codegen, and we are happy to share all those +new learnings and features with everyone in the community. + +> We've also found and fixed a lot of memory leaks in upstream Apollo Client, thanks +> [@benjamn](https://github.com/benjamn) for the great corporation! + +We hope you would use those new features and improvements to quickly improve your workflow, +type-safety and make your migrations easier. + +And as usual, we would love to hear your feedback and ideas on how we can further improve the +experience of using GraphQL and Typescript in general! + +# `possibleTypes` + +If you are already familiar with GraphQL-Codegen and the plugins it offers, you probably know the +`fragment-matcher` plugin. + +In Apollo-Client v3, +[the structure for fragment matcher has been changed, and now it's called `possibleTypes`](https://apollographql.com/docs/react/data/fragments/#using-fragments-with-unions-and-interfaces). + +The `@graphql-codegen/fragment-matcher@2.0.0` now supports Apollo-Client v3 by default, and it +generates type signature and the `possibleTypes` object automatically based on your GraphQL schema. + +Here's an example of using it with a codegen.yml file: + +```yaml +schema: my-schema.graphql +generates: + ./src/possible-types.ts: + plugins: + - fragment-matcher +``` + +Then, when you create your Apollo Client cache instance, use the generated variable: + +```ts +import introspection from './src/possible-types' + +export default new ApolloClient({ + uri: 'https://countries.trevorblades.com', + cache: new InMemoryCache({ possibleTypes: introspection.possibleTypes }) +}) +``` + +Without this, you'll have to define and maintain the `possibleTypes` object manually, which might +lead to an incorrect or invalid setup that might effect Apollo-Client runtime. + +## Type Policies + +If you are using an +[advanced configuration for your Apollo-Client cache](https://apollographql.com/docs/react/caching/cache-configuration/#data-normalization), +you can customize the behaviour of your cache. + +The configuration you pass to Apollo depends on your GraphQL types and their fields, and instead of +having an arbitrary object, you can have a fully-typed signature generated based on your GraphQL +schema. That would make it much easier to customize, and you will catch errors in advance! (during +build-time, instead during runtime) + +You can use `@graphql-codegen/typescript-apollo-client-helpers` plugin to generate that. + +```yaml +schema: my-schema.graphql +generates: + ./src/type-policies.ts: + plugins: + - typescript-apollo-client-helpers +``` + +Then, use the generated `TypedTypePolicies` to type your object: + +```ts +import { TypedTypePolicies } from './apollo-helpers' + +const typePolicies: TypedTypePolicies = { + // Keys in this object will be validated against the typed on your schema + Product: { + keyFields: ['id'] // Values in this field will be validated against the available fields from the Product type + }, + Person: { + keyFields: ['name', 'email'] + }, + Book: { + // This entire definition is typed, based on available types and fields + fields: { + tags: { + merge: false + } + } + } +} + +const cache = new InMemoryCache({ + typePolicies +}) +``` + +## `TypedDocumentNode` + +Apollo-Client also supports `TypedDocumentNode` now natively (since `v3.2`, +[you can read more about it here](/blog/typed-document-node)). + +You can use it to generate a fully-typed `DocumentNode` you can use with Apollo-Client, and it will +automatically type your variables and responses. + +You can use `@graphql-codegen/typed-document-node` with the following setup to get that: + +```yaml +schema: schema.graphql +documents: query.graphql +generates: + ./typed-document-nodes.ts: + plugins: + - typescript + - typescript-operations + - typed-document-node +``` + +Later, in your code, you can just import the generated `TypedDocumentNode` objects from +`typed-document-nodes` (based on your GraphQL operations), and it will be automatically typed: + +```tsx +import { useQuery } from '@apollo/client' +import { RatesDocument } from './typed-document-nodes' + +export const MyComponent: React.FC = () => { + // We now have types support and auto complete for the + // result type, just by passing `RatesDocument` as `query` to apollo client. + const result = useQuery(RatesDocument, { + variables: { + currency: 'USD' + } + }) + + const rates = result.data.rates + + return
    Rates: {rates}
    +} +``` + +## Ready-To-Use Hooks / HOC / Components + +One of the most powerful features of GraphQL-Codegen is the ability to generate flexible code based +on your GraphQL schema and your GraphQL operations. + +We generate TypeScript types, but that's not all - we can also generate code for you. + +You can generate a fully-typed React Hooks: + +```yaml +schema: schema.graphql +documents: query.graphql +generates: + ./hooks.ts: + plugins: + - typescript + - typescript-operations + - typescript-react-apollo +``` + +Then, just use it directly in your components: + +```tsx +import { useRatesQuery } from './hooks' + +export const MyComponent: React.FC = () => { + // We now have types support and auto complete for the + // result type, just by passing `RatesDocument` as `query` to apollo client. + const result = useRatesQuery(RatesDocument, { + variables: { + currency: 'USD' + } + }) + + const rates = result.data.rates + + return
    Rates: {rates}
    +} +``` + +> Note: This is an alternative for `TypedDocumentNode`. + +## More! + +You can also generate [Svelte-Apollo](https://github.com/ticruz38/graphql-codegen-svelte-apollo), +[apollo-angular](https://apollo-angular.com) types, +[Vue-Apollo](https://graphql-code-generator.com/docs/plugins/typescript-vue-apollo), +[Stencil-Apollo](https://graphql-code-generator.com/docs/plugins/typescript-stencil-apollo) and +other view layers working with Apollo Client 3... + +[You can find a list of all available plugins here](https://graphql-code-generator.com/docs/plugins/index), +and +[here you can find a list of tips for integrating codegen with your frontend applications](/blog/graphql-codegen-best-practices). diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-codegen-best-practices/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-codegen-best-practices/page.mdx new file mode 100644 index 00000000000..18025101e98 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-codegen-best-practices/page.mdx @@ -0,0 +1,767 @@ +--- +title: Integrating GraphQL Code Generator in your frontend applications +authors: dotan +tags: [graphql, codegen, typescript, react, angular] +date: 2020-05-20 +description: + The GraphQL codegen library can generate code for multiple purposes. Make sure you get the most + out of it. +--- + +import { Callout } from '@theguild/components' + +In this article we'll try to explain and demonstrate common patterns for frontend development with +GraphQL and GraphQL Code Generator. + +Most patterns are general and can be applied to most popular frontend frameworks (React, Angular, +Vue, Stencil), and to popular GraphQL client libraries (Apollo / urql), due to extended support of +GraphQL Code Generator, and it's flexibility. + +In this article, we'll cover the development workflow of frontend applications with _TypeScript_ and +_GraphQL Code Generator_, suggest best-practices for GraphQL development for frontend developers, +and try to explain the idea behind it. + +## Why Do I Need GraphQL Code Generator in My Project? + +Let's start by understanding the need for GraphQL Code Generator in your project. + +If you are using TypeScript for frontend development, you probably aim to get the most out of the +TypeScript type-system, that means, that your preference should be to have typed variables all +across your application. + +It starts with the code you write - UI components, services and business logic. You can also have +type safety for your third-party libraries (some built-in, and some with `@types/...` packages). + +The idea behind type-safety is to make sure that your code can be statically analyzed and built, +before running it. It's useful because this way your can detect potential bugs before they happen in +runtime. + +### _But What about the Data Your Fetch from External Services?_ + +So if you are already using GraphQL, you probably know that your GraphQL API is typed, and built as +a GraphQL schema. + +And it doesn't matter which language or platform is used to write your GraphQL API or schema - you +fetch it the same way into your frontend application - with GraphQL operations (`query` / `mutation` +/ `subscriptions`, and `fragment`) and probably over HTTP. + +So if your GraphQL schema is typed already, and your GraphQL operations allow you to choose specific +fields from it (called Selection Set), why not leverage the schema and selection set and turn it +into TypeScript types? + +### Basic Data Fetching with GraphQL + +Let's assume that we have the following simple GraphQL schema: + +```graphql +scalar Date + +type Query { + upcomingEvents: [Event!]! +} + +type Event { + id: ID! + title: String! + date: Date! + location: Location! +} + +type Location { + name: String! + address: String! +} +``` + +And the client-side application consumes it with the following `query`: + +```graphql +query listEvents { + upcomingEvents { + id + title + date + } +} +``` + +If you client-side application only needs `id`, `title` and `date` from the `Event` type - you can +expect to have those fields in your GraphQL response. + +You can also use it in your component code: + +```tsx +export const ListEvents = listEvents => { + return ( +
      + {listEvents.map(event => ( +
    • + {event.title} ({event.date}) +
    • + ))} +
    + ) +} +``` + +In the example above we have a few issues that might be bugs in the future: + +1. We don't know the type of `listEvents` - and we can't really know it without creating a type for + it manually (but this could also break, because the API could change). +2. We can't be sure what are the actual types of `id`, `title` and `date` fields - it's `any`. +3. We can't count of the fields to be there because they GraphQL query can change, and it's not + connected to our code at all. +4. If you'll try to access `location` of the event - you'll just get `undefined` because it's not + part of the selection set. + +With GraphQL Code Generator, you can have a full type safety, based on your GraphQL schema _and_ +your GraphQL operations, and that means: + +1. You can tell what is the exact structure of `listEvents`, what could be `null` and enjoy + auto-complete in your IDE. +2. You can tell what is the data type of all fields. +3. If your selection set changes, it being reflected automatically and you can detect issues while + developing or building (instead while running). +4. Trying to access fields that are not defined in your selection set will show an error in build + time and in your IDE. + +So those are the basic types that codegen can generate for your, and you can get those by using the +`@graphql-codegen/typescript` and `@graphql-codegen/typescript-operations` plugins of GraphQL Code +Generator. + +But that's not all - you can generate much more - you can get React Hooks, Angular Services and +more. + +### How Do I Start? + +You can start by +[trying GraphQL Code Generator plugin in the live-demo here](https://graphql-code-generator.com) and +with the +[Getting started with GraphQL Code Generator](https://graphql-code-generator.com/docs/getting-started/installation). + +## Tips & Best Practices When Using GraphQL Code Generator and TypeScript + +Now that you understand why and how GraphQL Code Generator can help you, it's time to learn new +concepts that might simplify the way your consume GraphQL API, and improve your code quality. + +### Watch Mode + +GraphQL Code Generator also comes with a built-in watch mode. You can use it from the CLI: + +```sh +graphql-codegen --watch +``` + +Or, set it in your `codegen.yml` file: + +```yaml +watch: true +schema: ... +``` + +This way, each time you have changes for your GraphQL schema or GraphQL operations, GraphQL Code +Generator will be executed again and update the generated files. + +### Generate More than Just Types + +GraphQL Code Generator can generate more than just TypeScript types. It can automate some of your +GraphQL development workflow, generate common practices for data fetching, and add type-safety to +code you usually need to write manually. + +Beside TypeScript types, here's a list and examples of _part_ of GraphQL Codegen capabilities: + +#### Dump Remote Schema to a Local File + +If your GraphQL schema is only available for you using an HTTP endpoint, you can always get a copy +of it locally. This is useful for better IDE experience. + +You can do it with the `@graphql-codegen/schema-ast` plugin, and the following configuration: + +```yaml +schema: http://YOUR_SERVER/graphql +generates: + ./src/schema.graphql: + plugins: + - schema-ast +``` + +- [`@graphql-codegen/schema-ast` docs](https://graphql-code-generator.com/docs/plugins/schema-ast) + +#### Save Local GraphQL Introspection + +GraphQL schema can be represented in many ways. One of the is +[introspection](https://graphql.org/learn/introspection). + +You can save a local copy of your schema introspection using `@graphql-codegen/introspection` and +the following: + +```yaml +schema: YOUR_SCHEMA_PATH +generates: + ./src/schema.json: + plugins: + - introspection +``` + +- [`@graphql-codegen/introspection` docs](https://graphql-code-generator.com/docs/plugins/introspection) + +#### Add Custom Content to Output Files + +I wish you to add custom content to the codegen output files, you can use `@graphql-codegen/add` +plugin, and add your content this way: + +```yaml +schema: YOUR_SCHEMA_PATH +generates: + ./src/types.ts: + plugins: + - add: '// THIS FILE IS GENERATED, DO NOT EDIT!' + - typescript +``` + +- [`@graphql-codegen/add` docs](https://graphql-code-generator.com/docs/plugins/add) + +#### React & Apollo: Generate Hooks + +You can generate ready-to-use React hooks for your GraphQL operations, with the following +configuration: + +```yaml +schema: SCHEMA_PATH_HERE +documents: './src/**/*.graphql' +generates: + src/generated-types.tsx: + config: + withHooks: true + plugins: + - typescript + - typescript-operations + - typescript-react-apollo +``` + +And then use it in your code: + +```tsx +import React from 'react' +import { useMyQuery } from './generated-types' + +export const MyComponent: React.FC = () => { + const { data, loading, error } = useMyQuery() + + // `data` is now fully typed based on your GraphQL query + return <> ... +} +``` + +- [`@graphql-codegen/typescript-react-apollo` docs](https://graphql-code-generator.com/docs/plugins/typescript-react-apollo) + +#### React & Apollo: Generate HOC (High-Order-Component) + +You can generate ready-to-use React HOC for your GraphQL operations, with the following +configuration: + +```yaml +schema: SCHEMA_PATH_HERE +documents: './src/**/*.graphql' +generates: + src/generated-types.tsx: + config: + withHOC: true + plugins: + - typescript + - typescript-operations + - typescript-react-apollo +``` + +And then use it in your code: + +```tsx +import React from 'react'; +import { withMyQuery } from './generated-types'; + +const MyViewComponent: React.FC = ({ data, loading, error }) => { + // `data` is now fully typed based on your GraphQL query + + return (<> ... ); +}; + +export const MyComponent = withMyQuery({ + variables: { ... } +})(MyViewComponent); +``` + +- [`@graphql-codegen/typescript-react-apollo` docs](https://graphql-code-generator.com/docs/plugins/typescript-react-apollo) + +#### React & Apollo: Generate Components + +You can generate ready-to-use React data components for your GraphQL operations, with the following +configuration: + +```yaml +schema: SCHEMA_PATH_HERE +documents: './src/**/*.graphql' +generates: + src/generated-types.tsx: + config: + withComponent: true + plugins: + - typescript + - typescript-operations + - typescript-react-apollo +``` + +And then use it in your code: + +```tsx +import React from 'react'; +import { MyQueryComponent } from './generated-types'; + +export const MyComponent: React.FC = ({ data, loading, error }) => { + + return ( + + { + ({ data, loading, error }) => { + // `data` is now fully typed based on your GraphQL query + + return (<> ... ) + } + } + + ); +}; +``` + +- [`@graphql-codegen/typescript-react-apollo` docs](https://graphql-code-generator.com/docs/plugins/typescript-react-apollo) + +#### Angular & Apollo: Generate Services + +You can generate ready-to-use Angular `Service`s for your GraphQL operations, with the following +configuration: + +```yaml +schema: SCHEMA_PATH_HERE +documents: './src/**/*.graphql' +generates: + src/generated-types.ts: + config: + withHooks: true + plugins: + - typescript + - typescript-operations + - typescript-apollo-angular +``` + +And then use it in your code: + +```ts +import { MyFeedGQL, MyFeedQuery } from './generated-types' + +@Component({ + selector: 'feed', + template: ` +

    Feed:

    +
      +
    • {{ item.id }}
    • +
    + ` +}) +export class FeedComponent { + feed: Observable + + constructor(feedGQL: MyFeedGQL) { + this.feed = feedGQL.watch().valueChanges.pipe(map(result => result.data.feed)) + } +} +``` + +- [`@graphql-codegen/typescript-apollo-angular` docs](https://graphql-code-generator.com/docs/plugins/typescript-apollo-angular) + +#### React & urql: Generate Hooks + +If you are using [`urql`](https://github.com/FormidableLabs/urql) as your GraphQL client, you can +generate ready-to-use React hooks for your GraphQL operations, with the following configuration: + +```yaml +schema: SCHEMA_PATH_HERE +documents: './src/**/*.graphql' +generates: + src/generated-types.tsx: + config: + withHooks: true + plugins: + - typescript + - typescript-operations + - typescript-urql +``` + +And then use it in your code: + +```tsx +import React from 'react' +import { useMyQuery } from './generated-types' + +export const MyComponent: React.FC = () => { + const { data, loading, error } = useMyQuery() + + // `data` is now fully typed based on your GraphQL query + + return <> ... +} +``` + +- [`@graphql-codegen/typescript-urql` docs](https://graphql-code-generator.com/docs/plugins/typescript-urql) + +This plugin can also generate HOC or data Component, based on your preference ;) + +#### Vue.js & Apollo: Generate Composition Functions + +If you are using [`Vue.js`](https://vuejs.org) with `@vue/apollo-composable` your GraphQL client, +you can generate composition functions based on your GraphQL operations: + +```yaml +schema: SCHEMA_PATH_HERE +documents: './src/**/*.graphql' +generates: + src/generated-types.ts: + config: + withHooks: true + plugins: + - typescript + - typescript-operations + - typescript-vue-apollo +``` + +And then use it in your code: + +{/* prettier-ignore */} +```vue + + + +``` + +- [`@graphql-codegen/typescript-vue-apollo` docs](https://graphql-code-generator.com/docs/plugins/typescript-vue-apollo) + +#### Apollo: Type-Safe `refetchQueries` + +If you are using Apollo Client, and you wish to refetch a query when a mutation is done, you can do +add +[`@graphql-codegen/named-operations-object`](https://graphql-code-generator.com/docs/plugins/named-operations-object) +plugin to your setup. + +It will generate a `const` object that contains a list of your GraphQL operation names, as found by +the codegen. This is useful because if you'll change the name of your operation, you'll know about +it in build time, and you'll be able to update it: + +This is how to configure it: + +```yaml +schema: SCHEMA_PATH_HERE +documents: './src/**/*.graphql' +generates: + src/generated-types.ts: + plugins: + - typescript + - typescript-operations + - named-operations-object +``` + +And then use it in your code: + +```tsx +import { client } from './apollo'; // this is your Apollo Client instance, for example +import { addTodoMutation, namedOperations } from './generated-types'; + +client.mutate({ + query: addTodoMutation, + variables: { ... }, + refetchQueries: [ + // If you'll change or remove that operation, this will fail during build time! + namedOperations.Query.listTodo, + ] +}) +``` + + + You can use it with any other wrapper of Apollo-Client, such as `apollo-angular` or + `react-apollo`. + + +- [`@graphql-codegen/named-operations-object` docs](https://graphql-code-generator.com/docs/plugins/named-operations-object) + +#### Apollo: Auto-Generated `fragmentMatcher` / `possibleTypes` + +If you are using Apollo-Client and your schema contains GraphQL `union` or `interface`, you'll need +to provide `fragmentMatcher` to your Apollo store instance. + +This is needed in order to improve performance of Apollo store. +[You can read more about this here](https://apollographql.com/docs/react/data/fragments/#fragments-on-unions-and-interfaces). + +You can generate it using the following configuration: + +```yaml +schema: YOUR_SCHEMA_PATH +generates: + ./src/fragment-matcher.ts: + plugins: + - fragment-matcher +``` + +And then pass it directly to your Apollo instance: + +```ts +import { InMemoryCache } from '@apollo/client' +// generated by Fragment Matcher plugin +import introspectionResult from '../fragment-matcher' + +const cache = new InMemoryCache({ + possibleTypes: introspectionResult.possibleTypes +}) +``` + +- [`@graphql-codegen/fragment-matcher` docs](https://graphql-code-generator.com/docs/plugins/fragment-matcher) + +## Name Your Operations + +It's highly important to name your GraphQL operations, because otherwise it will be difficult for +your GraphQL client to cache and manage it. It will also make it difficult for the codegen to create +easy-to-use types, and it will fallback to `Unnamed_Operation_`. + +✅ Do: + +```graphql +query myOperationNameHere { + ... +} +``` + +❌ Don't: + +```graphql +query { + ... +} +``` + + +**Tip**: Duplicate Names + +Ensure you have unique names for your operations. + +Libraries like Apollo Client will have issues and unexpected behavior if you re-use the same +operation name, and GraphQL Code Generator will throw an error in case of name duplications. + + + +## Write Your Operations and Fragments in `.graphql` Files + +You can manage your GraphQL operations in `.graphql` files, without worrying about loading it into +your application with Webpack loaders or anything else. Also, Most IDEs has better support for +autocomplete inside `.graphql` files. + +GraphQL Code Generator plugins for frontend frameworks integrations (such as +`typescript-react-apollo` / `typescript-apollo-angular`) are automatically creates an executable +copy (`DocumentNode`) of your GraphQL operations in the generated code file, and it will +automatically include it withing your wrapper call. + +It will add that to the output file with `Document` suffix, and `FragmentDoc` for fragments. + +So you can maintain your operations in `.graphql` files, but import it from generated code file: + +```ts +// MyQueryDocument and MyUserFragmentDoc are parsed `DocumentNode` +import { MyQueryDocument, MyUserFragmentDoc } from './generated-types' +``` + + +**Tip**: No need to handle imports + +If you have a query that uses a fragment, you can just use the fragment spread as-is, without the +need to import it or maintain it in the same file. + +For example: + +```graphql filename="user.query.graphql" +query user { + userById { + ...UserFields # We don't need to import this, just use the name + } +} +``` + +```graphql filename="userfields.fragment.graphql" +fragment UserFields on User { + id + name +} +``` + +And if you'll import `UserQueryDocument` from your generated file, it will have the fragment +concatenated automatically. + + + +## Fragment per Component + +If you wish to have a simple way to manage your application complexity with multiple queries and +fragments, consider to have small fragments that defines the needs of your components. + +Consider the following structure for example (for a list and item implementation): + +``` +src/ +├── generated-types.tsx +├── list/ +├──── todo-list.tsx +├──── todo-list.query.graphql +├── list-item/ +├──── todo-item.tsx +├──── todo-item.fragment.graphql +├── todo-details/ +├──── todo-details.tsx +├──── todo-details.fragment.graphql +├── user-profile/ +├──── profile-page.tsx +├──── me.query.graphql +├──── authenticated-user.fragment.graphql +``` + +Then, your GraphQL `query` files can just build it's self based on the nested fragments it needs: + +```graphql filename="todo-list.query.graphql" +query todoList { + todos { + ...TodoItemFields + ...TodoDetailsFields + } +} +``` + +```graphql filename="me.query.graphql" +query me { + me { + ...AuthenticatedUserFields + } +} +``` + +And then, GraphQL Code Generator will generate a matching TypeScript type per each component, based +on the fragment or query that it needs. + +So you can use the generated fragment type as the input for your components, and pass it directly +from the parent component easily, with type-safety: + +```tsx filename="todo-list.tsx" +import React from 'react' +import { useTodoList } from '../generated-types' +import { TodoItem } from './todo-item' + +export const TodoList: React.FC = () => { + const { data, loading, error } = useTodoList() + + return ( + <> + {data.todos.map(todo => ( + + ))} + + ) +} +``` + +```tsx filename="todo-item.tsx" +import React from 'react' +import { TodoItemFieldsFragment } from '../generated-types' + +export const TodoItem: React.FC = (todo: TodoItemFieldsFragment) => { + return
    {todo.title}
    +} +``` + + + Please have some judgment before creating fragments, it should represent data structure that is + specific per component. Don't abuse this mechanism by creating fragments with a single field. Try + to group it in a way that matches your components needs. + + +## Access to Nested Generated Types + +If you are already familiar with plugins such as `@graphql-codegen/typescript-operations` output +structure, you probably already know that it's built on operations and fragments. + +It means that each GraphQL `query` and each GraphQL `fragment` that you have, will be converted into +a single TypeScript `type`. + +That means, that accessing nested fields in your generated TypeScript types might looks a bit +complex at the beginning. + +Consider the following query: + +```graphql +query userById($userId: ID!) { + user(id: $userId) { + id + profile { + age + name { + first + last + } + } + } +} +``` + +The `@graphql-codegen/typescript-operations` plugin output for that `query` will be: + +```ts +export type UserByIdQuery = { __typename?: 'Query' } & { + user?: Maybe< + { __typename?: 'User' } & Pick & { + profile?: Maybe< + { __typename?: 'Profile' } & Pick & { + name: { __typename?: 'Name' } & Pick + } + > + } + > +} +``` + +Accessing the actual TypeScript type of `user.profile.name.first` might look a bit intimidating, but +there are several things you can do to simplify the access to it: + +- **Best solution: use fragments** - if you'll use fragments for the `User` fields and for `Profile` + fields, you'll break down the types into smaller pieces (see previous tip). +- Use TypeScript type system: `type FirstName = UserByIdQuery['user']['profile']['name']['first']`. +- You can also do it with `Pick`: + `type FirstName = Pick`. + + +**Tip**: Hate `Pick` in your generated files? + +The `@graphql-codegen/typescript-operations` is the TypeScript representation of your GraphQL +selection set. Just like selection set chooses fields from the GraphQL schema, +`typescript-operations` picks fields from `typescript` plugin (which is the representation of your +GraphQL schema). + +If you wish to have simpler TypeScript output, you can set `preResolveTypes: true` in your +configuration, and it will prefer to use the primitive TypeScript type when possible. + + diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-codegen-hooks-support-react-apollo/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-codegen-hooks-support-react-apollo/page.mdx new file mode 100644 index 00000000000..83df0d7f960 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-codegen-hooks-support-react-apollo/page.mdx @@ -0,0 +1,190 @@ +--- +title: GraphQL Code Generator - Hooks support for React Apollo plugin +authors: leonardo +tags: [graphql, codegen, react, react-apollo, hooks] +date: 2019-03-01 +updateDate: 2020-05-20 +description: + Use and generate React Hooks with Apollo and Typescript with the new version of GraphQL Code + Generator. +--- + +import { YoutubeIframe } from '#components/youtube-iframe' +import { Callout } from '@theguild/components' + +If you follow the React community, you'll know for sure that +[React Hooks](https://reactjs.org/docs/hooks-intro.html) had been one of the most awaited feature in +the ecosystem since their first gist. They have been available since React v16.7-alpha, and many +libraries already started adopting them — +[officially](https://react.i18next.com/latest/usetranslation-hook) or with +[auxiliary libraries](https://github.com/facebookincubator/redux-react-hook). + + + +In case you don't know what hooks are, you may be wondering **what all this buzz is about**. Let the +[React docs](https://reactjs.org/docs/hooks-intro.html) speak for itself: + +> Hooks let you use state and other React features without writing a class. + +This could be a huge improvement by itself (you know, you create a Functional Component, mess with +it, and then you need a bit of state so... let's refactor this to a class, hooray! 🎉 — sarcasm is +intentional), but there is more. + +## React Apollo and Hooks + + +If you already know all about hooks and [@apollo/react-hooks](https://apollographql.com/docs/react/api/react-hooks), and want to see the news about `graphql-code-generator`, just skip this section. + +If you are interested in the long story instead, keep reading! + + + +There are many hooks, like `useEffect` or `useReducer`, that may simplify your code, but I'll leave +this [to your curiosity](https://reactjs.org/docs/hooks-overview.html). I suggest you to read the +[Dan Abramov](https://twitter.com/dan_abramov) +([“Making sense of React Hooks”](https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889) +story if you didn't already. + +What I want to talk about, instead, is `useContext` and how you will fall in love with it, +especially when talking about `react-apollo`. + +**Note:** If you haven't used Context before, just think of it as a way to pass props down the +components' tree without passing props down component-by-component. It should not replace normal +React usage, but is useful when talking about cross-application values like state, translations, +themes, etc. + +The new hook `useContext` allows us to access a React Context +([with its Consumer/Provider api](https://reactjs.org/docs/context.html)) directly from a functional +component, without props nor contextType: + +```tsx +// This is our custom hook +function useThemeColor() { + const theme = useContext(ThemeContext) + return theme.color +} + +function MyComponent(props: Props) { + // Here we go + const color = useThemeColor() + + return

    {props.title}

    +} +``` + +Given this sweet feature, we can now think about all our HOCs / Render Props in our codebase, well, +almost all: Every time we need to access context (_State Management, API Calls, Translations, +Localization_) we can now use hooks. + +Especially using TypeScript, deep +[HOCs tree-hell](https://reddit.com/r/reactjs/comments/85h93f/higher_order_component_hell) or +[render-props callback hell](https://medium.com/@ntgard/why-i-dont-use-render-props-in-react-10f18abdff11) +are a nightmare (Reminding Node.js callback hell, anyone?). Typings are always wrong, you need to +define twenty different interfaces, etc. + +With hooks, you can just use them in a straight, linear, fashion: + +```tsx +function MyComponent(props: Props) { + const translate = useTranslation(); + const { user } = useMappedState(state => state.user); + + return ( + // ... + ); +} +``` + +[**React Apollo**](https://github.com/apollographql/react-apollo) fits perfectly the requirements, +and it now supports Hooks for your GraphQL operations. + +If you are used to `Query` component, in the next example you'll see how we are replacing it with +just the `useQuery` hook: + +```tsx +import { useQuery } from '@apollo/react-hooks' + +const GET_TODOS = gql` + { + todos { + id + description + } + } +` + +function Todos() { + // Here the magic bits + const { data, error, loading } = useQuery(GET_TODOS) + if (loading) + if (error) + // ... + // ... + + return ( +
      + {data.todos.map(todo => ( +
    • {todo.description}
    • + ))} +
    + ) +} +``` + +## React Apollo Hooks and GraphQL Code Generator + +Since the first time I saw hooks, I thought about removing the callback hell caused by render props +in my codebase. Given the awesome work done by Daniel Trojanowski with `react-apollo-hooks`, I +wanted to use **hooks** in our projects, replacing React Apollo classic components (_render-props +based_). + +However, I love even more the [`graphql-code-generator`](https://graphql-code-generator.com) +project, since I want proper **typings** with my Query, Mutation and Subscription components. +Accessing `data` with proper autocomplete and type checking is definitely a game-changer! + +> I'm glad to have the honor to announce the next release of GraphQL Code Generator, that will add +> React Apollo hooks generation to its arsenal + +With this enhancement, now you can choose between React Apollo Components, HOCs or Hooks and even +mix-and-match if you've got an existing project and want to start using Hooks right now! + +Just use GraphQL Code Generator's +[Typescript-React-Apollo Plugin](https://graphql-code-generator.com/docs/plugins/typescript-react-apollo), +set `withHooks: true` to your GraphQL Code Generator config, and add `react-apollo-hooks` to your +dependencies if you already didn't. + +This is an example generated hook, with proper typings: + +```tsx +export function useUserListQuery(baseOptions?: QueryHookOptions) { + return useApolloQuery( + UserListQueryDocument, + baseOptions + ) +} +``` + +And here we can see autocomplete at work: + +![](./vscode.png) + +If you want to see `graphql-code-generator` in action, you can look at the awesome +[WhatsApp-Clone-Client-React](https://github.com/Urigo/WhatsApp-Clone-Client-React) project made by +[The Guild](/). Here is the diff (thanks to [Eytan Manor](https://medium.com/u/5a6962cd709e)) +showcasing the generated hooks applied to a real codebase. + +## Conclusions + +React Hooks will probably be a powerful tool in our toolbelt, and I'm sure we will see many patterns +evolving. Libraries like React Apollo fits perfectly, and I hope having tools to generate typings +like GraphQL Code Generator will increase their adoption. + +I'd like to thank the awesome team behind The Guild, especially Eytan Manor for its continuous +effort reviewing my hooks proposal, Arda TANRIKULU and Dotan Simha for their support and, obviously, +the creation of `graphql-code-generator`. Thanks indeed to Daniel Trojanowski for the great work on +the initial implementation of hooks in `react-apollo-hooks`. + +Thank you for reading this story, I hope you found it useful/interesting. May the hook be with you! diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-codegen-hooks-support-react-apollo/vscode.png b/packages/web/docs/src/app/blog/(posts)/graphql-codegen-hooks-support-react-apollo/vscode.png new file mode 100644 index 00000000000..924f4a3711d Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-codegen-hooks-support-react-apollo/vscode.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-codegen-java/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-codegen-java/page.mdx new file mode 100644 index 00000000000..69ed13f7b48 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-codegen-java/page.mdx @@ -0,0 +1,237 @@ +--- +title: GraphQL Codegen adds new Apollo-Android and Java plugins +authors: dotan +tags: [graphql, codegen, apollo-android, android, java] +date: 2019-06-19 +updateDate: 2020-05-20 +description: GraphQL Codegen adds a new Apollo-Android and a new Java Backend plugins! +--- + +As you probably already know, [GraphQL Code Generator](https://graphql-code-generator.com) has +become the most popular GraphQL code generation tool in the community. + +We constantly improve the underlying tools and also create more generators and plugins. + +One thing that some people often miss about GraphQL Code Generator, is that its programming language +agnostic. + +We have many plugins from the community to generate different language outputs like C#, ReasonML, +Dart and others. + +But today we want to announce something new — we now have 2 new official plugins for **Java**. + +A [Apollo-Android generator](https://github.com/dotansimha/graphql-code-generator/pull/1924) and a +[Java backend](https://graphql-code-generator.com/docs/plugins/java-resolvers) resolvers signature +generator. + +We've created those plugins because we needed them for some of our clients' production applications +and the current tools in the ecosystem that are currently out there weren't sufficient enough for +us. + +## Apollo-Android + +The new plugin allows you to generate Java code if your project is using Apollo-Android. + +It includes a query builder and a response builders (for converting the network response into a +type-safe classes), and it integrates with Apollo-Android runtime. + +The goal of the generated code is to let you use your GraphQL operations (`query`, `mutation`, +`subscription` and `fragment`) without the need to convert JSONs or deal with HTTP requests — you +just need to write your operations, run the codegen and you'll get ready-to-use Java classes. + +The reason we decided to implement our own plugin was because now that we need to use it in large +production apps, the current tools in the ecosystem weren't sufficient enough for us. + +We also think this is a good opportunity for the community to iterate faster because of all the +awesome capabilities the generator already has. + +Using the GraphQL Code Generator infrastructure, we make sure that customization of the generated +code will be much easier, and fixing bugs and adding features will be simpler and faster. + +It also makes it much easier to load your schema from everywhere (either from a url, local file, +introspection, or directly from code files containing strings), customize your GraphQL Scalars and +map them to Java types, customize the names of the generated classes, the output path and more. + +The new plugin will be available today in beta using the following tag — `1.2.2-beta.01`. + +### How to Try It Now? + +Next week we are also planning to make it easier to integrate the plugin by building a Gradle plugin +that wraps the execution of the Codegen, and integrates it with the Java ecosystem. + +On the stable release this Gradle plugin will be available, but for now, if you wish to try it now, +you'll need to configure it manually. + +Because the current GraphQL Code Generator package is released on NPM, you'll need to create a +simple `package.json` file with the following content: + +```json +{ + "name": "java-app", + "scripts": { + "postinstall": "graphql-codegen" + }, + "dependencies": { + "graphql": "15.0.0", + "@graphql-codegen/cli": "1.14.0", + "@graphql-codegen/java-apollo-android": "1.14.0" + } +} +``` + +Next, create the `codegen.yml` config file with the following content: + +```yaml +schema: 'schema.graphql' +documents: './src/main/**/*.graphql' +generates: + generatedJava/: + preset: 'java-apollo-android' + config: + package: 'com.app.generated.graphql' + plugins: + - 'java-apollo-android' +``` + +_You can use your GraphQL endpoint if your schema is not available locally._ + +Next, you need to configure your Gradle build configuration by adding the following dependency to +your `build.gradle`: + +``` +plugins { + id "com.moowork.node" version "1.3.1" +} +``` + +Also, add a build step: + +``` +build.dependsOn yarn +``` + +Now, when you'll run your Gradle build, it will make sure to download the Codegen and it's plugins, +and will generate the Java classes for you. + +The usage of the generated code is the same you had before, just follow +[the instructions of Apollo-Android runtime](https://github.com/apollographql/apollo-android#consuming-code). + +## Java Backend Resolver Plugin + +The other plugin generates resolvers signature, that allow you to integrate your Java models classes +to the generated signature, so you'll get a type-safe implementation of your resolvers. + +It's based on [`graphql-java`](https://graphql-java.com) and compatible with its implementation for +[`DataFetcher`s](https://graphql-java.com/documentation/v13/data-fetching). + +Follow the instructions above, but use this `package.json`: + +```json +{ + "name": "java-app", + "scripts": { + "postinstall": "graphql-codegen" + }, + "dependencies": { + "graphql": "15.0.0", + "@graphql-codegen/cli": "1.14.0", + "@graphql-codegen/java-resolvers": "1.14.0" + } +} +``` + +Then, for the following schema: + +```graphql +type Query { + me: User! +} + +interface Node { + id: ID! +} + +type User implements Node { + id: ID! + username: String! + email: String! + name: String +} + +type Chat implements Node { + id: ID! + users: [User!]! + title: String +} + +union SearchResult = Chat | User +``` + +And the following `codegen.yml` configuration, that maps your GraphQ types into your Java models: + +```yaml +schema: 'src/app/resources/schema.graphql' +generates: + src/app/graphql/generated/Resolvers.java: + plugins: + - java-resolvers + config: + mappers: + User: 'com.myapp.models.UserModel#UserModel' + Chat: 'com.myapp.models.ChatModel#ChatModel' + Node: 'com.myapp.models.BaseModel#BaseModel' +``` + +You'll get a complete type-safe resolvers signature: + +```java +package com.java.generated; + +import com.myapp.models.UserModel; +import com.myapp.models.ChatModel; +import com.myapp.models.BaseModel; +import graphql.schema.TypeResolver; +import graphql.schema.DataFetcher; + +public class Resolvers { + public interface Chat { + public DataFetcher id(); + public DataFetcher> users(); + public DataFetcher title(); + } + + public interface Node extends TypeResolver { + default public DataFetcher id() { return null; } + } + + public interface Query { + public DataFetcher me(); + public DataFetcher> chats(); + public DataFetcher> search(); + } + + public interface SearchResult extends TypeResolver {} + + public interface User { + public DataFetcher id(); + public DataFetcher username(); + public DataFetcher email(); + public DataFetcher name(); + } + +} +``` + +Now you can use it as base `interface` for your `DataFetcher` implementation, and get a type-safe +resolvers! + +### What's Next? + +We are getting close to a stable release for those plugins and want your opinions on them. + +As mentioned, the stable release will also include a Gradle plugin to make it easier to integrate +the Codegen with the Java ecosystem. + +Please try it out today, and send us a feedback. Our goal is to make this plugin easy to use, and +easy to customize, and you would see how quickly we manage, respond and solve issues on our open +source tools, which is something we are very proud of. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-codegen-plugin-typescript-swr/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-codegen-plugin-typescript-swr/page.mdx new file mode 100644 index 00000000000..af98dc1ae7d --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-codegen-plugin-typescript-swr/page.mdx @@ -0,0 +1,118 @@ +--- +title: 'Introducing: GraphQL Codegen plugin for TypeScript & SWR!' +authors: croutonn +tags: [graphql, codegen, typescript, react, hooks, swr] +date: 2020-11-23 +description: Working with GraphQL, TypeScript and SWR is now much simpler! +--- + +The other day I was wondering which GraphQL client library to use for my personal project. + +Apollo-Client is a powerful GraphQL client, but many of its features don't fit my use-case very +well, making it a useless treasure. + +So I tried combining [graphql-request](https://github.com/prisma-labs/graphql-request) with +[SWR](https://swr.vercel.app) (React Hooks library for data fetching), and the bundle size was about +1/3 of the Apollo-Client combined with these two libraries, and I had the best experience of using +the advanced features of SWR! + +One thing was still missing: it's hard to manually write a SWR fetcher every time... + +## Introduction + +Based on the above experience, I have created a +[GraphQL Code Generator](https://graphql-code-generator.com) plugin called +[graphql-codegen-plugin-typescript-swr](https://github.com/croutonn/graphql-codegen-plugin-typescript-swr) +that facilitates the combination of **graphql-request** and **SWR**, and published it to NPM! + +Seeing is believing, so let's first look at an example of the code generated by this plugin: + +```typescript +export function getSdkWithHooks( + client: GraphQLClient, + withWrapper: SdkFunctionWrapper = defaultWrapper +) { + const sdk = getSdk(client, withWrapper) + return { + ...sdk, + useGetPost( + key: SWRKeyInterface, + variables: GetPostQueryVariables, + config?: SWRConfigInterface + ) { + return useSWR(key, () => sdk.GetPost(variables), config) + } + } +} +``` + +> The code above is using the SWR plugin, in combination with `typescript-graphql-request` plugin. + +This way, a wrapper function is generated for each GraphQL operation (query/mutation/subscription). +The generated function uses the base code from the **typescript-graphql-request plugin**, and it +uses **useSWR** to execute the actual request, so the user can get the same result as `useSWR` by +simply entering the query key, variables, and options in the component! + +```typescript +const sdk = getSdkWithHooks(new GraphQLClient(API_URL, options)) + +const PostPage = ({ slug }) => { + const { data, mutate, error } = sdk.useGetPost( + `blog/post/${slug}`, + { slug }, + { refreshInterval: 60 } + ) + return +} +``` + +## Usage + +To get started, start by installing `graphql-codegen-plugin-typescript-swr` in addition to the +**@graphql-codegen** packages: + +``` +npm i -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-graphql-request graphql-codegen-plugin-typescript-swr +``` + +Then configure `codegen.yml`: + +```yaml +overwrite: true +generates: + sdk.ts: + schema: '${MY_GRAPH_API_ENDPOINT_URL}' + documents: './api/my-graph-api/**/*.graphql' + plugins: + - typescript + - typescript-operations + - typescript-graphql-request + - plugin-typescript-swr +config: + # If you have a query that you want to use `useSWRInfinite`, list it in Options + useSWRInfinite: + - GetPost + scalars: + DateTime: string + JSON: unknown + Upload: unknown + Time: string + Date: string + Long: number +``` + +The final `graphql-codegen` command will generate a set of types and SDKs in no time: + +``` +npx graphql-codegen +``` + +[The repository readme](https://github.com/croutonn/graphql-codegen-plugin-typescript-swr#readme) +contains specific use cases and more examples, and documentation for the available configurations. + +## Last but Not Least + +Thanks to [Urigo](https://twitter.com/UriGoldshtein) for providing a place to write an introduction +to my Codegen plugin! + +I would appreciate it if you could use it and give me feedback. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-codegen-relay-compiler/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-codegen-relay-compiler/page.mdx new file mode 100644 index 00000000000..53cb8069127 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-codegen-relay-compiler/page.mdx @@ -0,0 +1,268 @@ +--- +title: Optimizing Apollo Operations - GraphQL Code Generator & Relay Compiler +authors: laurin +tags: [graphql, codegen, relay] +date: 2019-07-15 +description: Optimizing your Apollo Operations with GraphQL Code Generator and the Relay Compiler. +--- + +As a TypeScript developer, I love working with the +[GraphQL Code Generator](https://graphql-code-generator.com). It is a very powerful code generation +tool for both your frontend and your backend. Instead of having to manually create types for +something your GraphQL Schema already provides, due to its type-safe nature, you can actually focus +on building the product. + +Currently, my favorite library for querying GraphQL backends is `react-apollo`. Supercharged with +the GraphQL Code Generator the library unleashes an almost unfair productivity boost compared to +every other data fetching stack I have used in my career as a software developer. + +Of course, I have heard of other libraries before, but until recently I never had the motivation of +actually using another one. + +This, however, changed after watching the +[F8 presentation about the Facebook.com rewrite](https://developers.facebook.com/videos/2019/building-the-new-facebookcom-with-react-graphql-and-relay). + +I totally fell in love with the "component specify their data requirements approach", which is +implemented by utilizing GraphQL Fragments. + +**As an Apollo user, I have used fragments before, but not in the same way Relay does.** + +Let's take a look at the following Fragment: + +```graphql +fragment UserAvatar on User { + id + avatar(width: 10, height: 10) { + id + url + } +} +``` + +How would you reuse this fragment with different values for the `width` and `height` arguments? + +Previously there have been two ways I would have tackled this: + +**1. Write a new fragment with different parameters** + +Well, just creating a new document for our avatar won't really solve the reusability issue. + +**2. Use variables and rely on the query to have those defined** + +Actually, you can already use variables inside fragments. We just need to ensure that the query that +uses the fragment also has those variables in the variable definition. + +Fragment Definition: + +```graphql +fragment UserAvatar on User { + id + avatar(width: $width, height: $height) { + id + url + } +} +``` + +Query Definition: + +```graphql +query ProfileQuery($width: Int!, $height: Int!) { + me { + ...UserAvatar + } +} +``` + +However, we now rely on having those parameters provided in each query that uses that fragment. + +This does not really make the fragment reusable. Imagine having a profile query of a with a friend +list. The profile picture should be bigger than the ones of the friends. + +```graphql +query ProfileQuery($width: Int!, $height: Int!) { + me { + id + ...UserAvatar + friends(first: 10) { + id + ...UserAvatar + } + } +} +``` + +It is basically impossible to use a different width and height for the second usage of the fragment +in that query. + +Furthermore, when using different fragments you have to be really careful with your variable names, +because of variable name clashes. + +Given those limitations, it is pretty obvious that this “solution” does not scale well. + +**I have experienced this limitation before and I am amazed how Relay solves it** + +Relay simply uses custom GraphQL directives to address this issue. + +**Defining Fragment Variables with `@argumentDefinitions`** + +```graphql +fragment UserAvatar on User @argumentDefinitions( + width: { type: “Int”, defaultValue: 10 }, + height: { type: “Int”, defaultValue: 10 } +) { + id + avatar(width: $width, height: $height) { + id + url + } +} +``` + +Providing Fragment Variables with `@arguments` + +```graphql +query ProfileQuery { + me { + id + ...UserAvatar @arguments(height: 20, width: 20) + friends(first: 10) { + id + ...UserAvatar # fallback to defaultValue here + } + } +} +``` + +Pretty powerful, right? + +Unfortunately, you cannot simply use those fragments with your existing GraphQL Server. +`@argumentDefinitions` and `@arguments` are some custom directives that need to be understood by the +server in order to process them. + +However, instead of implementing those directives on the serverside Relay went another route. The +`relay-compiler` removes those directives at build time. That means after our query has been +processed it looks something like the following: + +```graphql +query ProfileQuery { + me { + id + ... on User { + id + avatar(width: 20, height: 20) { + id + url + } + } + friends(first: 10) { + id + ... on User { + id + avatar(width: 10, height: 10) { + id + url + } + } + } + } +} +``` + +Pretty neat. This allows the query the be accepted by every GraphQL server (that, of course, +provides the correct schema), without relying on those custom directives. + +**The `relay-compiler` is awesome!** + +It comes with a lot more transforms. Some of those are specific to the `relay-runtime` (which as the +name says is executed in the browser of the user like react-apollo), but others are definitely also +beneficial to non-relay users. + +Besides the so-called `RelayApplyFragmentArgumentTransform` there is a bunch of more useful stuff. + +E.g. the `FlattenTransform` can improve our query even more: + +```graphql +query ProfileQuery { + me { + id + avatar(width: 20, height: 20) { + id + url + } + friends(first: 10) { + id + avatar(width: 10, height: 10) { + id + url + } + } + } +} +``` + +I also built a [relay-compiler REPL](https://relay-compiler-repl.netlify.com) (use it for convincing +your team 😉). + +Of course, you can also read more about those in the +[Official Relay Documentation](https://relay.dev/docs/en/compiler-architecture#transforms). + +Especially on big queries, that utilize many fragments, those transforms can drastically reduce the +query payload size, resulting in faster response times. For developers that cannot use persisted +queries (because they do not own the server), this is a must-have! + +**After having seen all those benefits that Relay users can leverage I was convinced that I want to +use those as well.** + +But unfortunately, I was still working with Apollo. 😅 + +What if there was a way to use the `relay-compiler` with Apollo? + +I was already generating code with the GraphQL Code Generator. + +**So maybe we can transform the queries before the GraphQL Code Generator outputs the generated +code?** + +After some investigation in the Relay and the GraphQL Code Generator codebase, I had a working +version ready for use. + +Given those Relay superpowers, I now feel even more productive. + +For those curious, I also created a sample project: +[TodoMVC Apollo](https://github.com/n1ru4l/codegen-relay-transform-experiments/tree/master/packages/todo-app-example) +(Converted from the Relay Examples). + +Of course, you can also take a look at (and use!) the +[Relay Operation Optimizer](https://graphql-tools.com/docs/relay-operation-optimizer) + +Example configuration for a react-apollo project: + +**codegen.yml** + +```yaml +overwrite: true +schema: "schema.graphql" +generates: + src/generated-types.tsx: + documents: "src/documents/**/*.graphql" + config: + skipDocumentsValidation: true + flattenGeneratedTypes: true + plugins: + - "typescript" + — "typescript-operations" + — "typescript-react-apollo" +``` + +**I hope you enjoyed reading this! Are you already using Relay? Do you feel like you want to add the +relay-compiler into your GraphQL toolbox?** + +Also, feel free to follow me on these platforms, if you enjoyed this article I ensure you that a lot +more awesome content will follow. I write about JavaScript, Node, React and GraphQL. + +- [Dev.to](https://dev.to/n1ru4l) +- [Medium](https://medium.com/n1ru4l) +- [Twitter](https://twitter.com/n1rual) +- [Github](https://github.com/n1ru4l) + +Have an awesome and productive day! diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-config/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-config/page.mdx new file mode 100644 index 00000000000..49168dc8faf --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-config/page.mdx @@ -0,0 +1,333 @@ +--- +title: GraphQL Config - One configuration for all your tools +authors: kamil +tags: [graphql, graphql-config] +date: 2019-10-25 +description: + One configuration for all your tools. A single place to store all your GraphQL configurations for + any GraphQL based tool. +--- + +## TL;DR + +- Visit our website **[graphql-config.com](https://graphql-config.com)** +- [**GraphQL Config**](https://github.com/kamilkisiela/graphql-config) is **one** simple place to + store **all** your GraphQL Configurations for **any** GraphQL based tool +- [Prisma recently transferred the project](https://prisma.io/blog/the-guild-takes-over-oss-libraries-vvluy2i4uevs) + to [The Guild](/) — and we **completely rewrote it** and addressed **more than ⅔ of the issues** + (and the remaining are waiting for feedback) +- We've already **merged configurations** from [GCG](https://graphql-code-generator.com), + [GraphQL Inspector](https://graphql-inspector.com), + [GraphQL CLI](https://github.com/Urigo/graphql-cli) — and are looking to learn and **integrate** + with GraphiQL, AppSync, Apollo, Gatsby, VS-Code extensions, Relay and the GraphQL team at Facebook + and any GraphQL tool creators +- This is an **alpha phase** — **we want your feedback**, as a user and as a creator — Please + [**create an issue**](https://github.com/kamilkisiela/graphql-config) or + [**join our Discord channel**](https://discord.gg/xud7bH9) + +## How Did We Get Here? + +About 2 years ago Prisma came up with a great idea for the GraphQL community — Why repeat yourself +in creating your configuration for each tool you use in your application. + +So together with many developers from the GraphQL community, +[they introduced the GraphQL Config library and spec](https://prisma.io/blog/new-tooling-to-improve-your-graphql-workflows-7240c81e1ba3) +and many tools have since embraced that standard. + +But time has passed and the library slowly became unmaintained. + +Prisma were very kind and generous with moving the project forward and +[passing it to us](https://prisma.io/blog/the-guild-takes-over-oss-libraries-vvluy2i4uevs). + +So when we took over the GraphQL Config, our main goal was to bring it back to life and push it +forward for the community. + +We asked for feedback, looked at existing and new tools that came out since it was released, went +through all the open issues and PRs, listened to your suggestions and got to work! + +--- + +## Our Main Goals Are + +- Highly **customizable** and **extensible** +- More **useful** structure +- **Flexible** and helpful enough to become a standard in the community +- Make it **platform-agnostic** (Node, Browser) + +--- + +## Try It Out Today + +We've already refactored most of the code, created a new structure, referenced all the related +issues and released a new alpha version: + +```sh +npm install graphql-config@next +``` + +```sh +yarn add graphql-config@next +``` + +Now we want to hear from you — GraphQL developers and GraphQL tool creators. + +Here is a deeper dive into what we've done: + +--- + +## Different Formats of GraphQL Config File + +The new [GraphQL Config](https://github.com/kamilkisiela/graphql-config) now allows to use JSON, +YAML and JavaScript. + +## GraphQL Config Looks for: + +- `.graphqlrc` +- `.graphqlrc.yaml` +- `.graphqlrc.yml` +- `.graphqlrc.json` +- `.graphqlrc.js` +- `graphql.config.js` + +The new config can also be created programmatically too. + +It also accepts a config defined under `graphql` property in your `package.json` file. + +> We're open to expand the range of file names and looking forward to hear more of your use cases. + +## New Structure + +GraphQL Config allowed to indicate a single graphql file, an introspection file or a URL to an +endpoint. That's the past! + +```yaml +schema: './schema.graphql' +documents: './my/app/**/*.graphql' +extensions: + customConfig: + value: true +``` + +## Schema + +We've decided to expand [GraphQL Config](https://github.com/kamilkisiela/graphql-config) for variety +of sources of GraphQL Schema and rename `schemaPath` to just `schema`. + +It accepts now not only a single file but also a glob pattern to match your modularized schema. + +## Allows to Generate Schema from: + +- files matching a glob pattern (or a list of patterns) +- an explicit list of files +- an endpoint +- an introspection file +- **TypeScript and JavaScript files** +- and even more… + +It was possible thanks to the concept of **Loaders** which we'll talk about later in the article. + +## Documents + +Majority of the GraphQL tools depend not only on Schema but Operations and Fragments, so we've +decided to cover that use case too. + +> With the new GraphQL Config, you're able to indicate files containing GraphQL operations and +> fragments (documents) and load them all within a single method. + +GraphQL Config accepts not only `.graphql` files but also extracts documents from **TypeScript and +JavaScript files, including JSX and TSX**. + +```jsx +import React from 'react' +import gql from 'graphql-tag' +import { useQuery } from '@apollo/react-hooks' + +// GraphQL Config is able to extract it! +const GET_USERS = gql` + { + user { + id + name + } + } +` + +export function Users() { + const { loading, error, data } = useQuery(GET_USERS) + + // ... + + return +} +``` + +Thanks to that, you can still write your operations and fragments with `graphql-tag` and put them in +your React components. GraphQL Config is smart enough to find and collect them. + +## Include and Exclude + +`Include` and `Exclude` options are still relevant, but we improved and fixed the logic behind them. + +> Their purpose is to tell config's consumer which files belongs to what project. + +Files covered by schema or documents options are automatically included, there's no need to include +them twice. + +## Extensions + +We also kept `extensions` and turned them into a first class citizen in GraphQL Config, **making +them way more powerful than before**. + +```yaml +schema: './schema/*.graphql' +extensions: + codegen: + generates: + ./src/types.ts: + plugins: + - typescript + - typescript-resolvers +``` + +## Pluggable Loaders + +The source of GraphQL Schema may differ, depending on the setup. In some projects SDL is kept within +graphql files, others store it in code. + +**The new GraphQL Config is capable of loading schema from:** + +- `.graphql` files +- introspection result file +- running endpoints +- Files on **GitHub** +- Files on **Git repository** +- files with documents wrapped with `graphql-tag`and gatsby's `graphql` +- documents with the magic comment `/* GraphQL */` +- single **JavaScript** and **TypeScript** file that exports GraphQLSchema object, DocumentNode or + schema as string + +**The possibilities are endless here!** + +> The main idea behind loaders is to extend the default behavior of GraphQL Config and allow to load +> GraphQL Schema from many different sources. + +Loaders are flexible enough to let you decide what exactly you want to use, even just to keep the +bundle size smaller. + +It also simplifies the codebase of GraphQL tools as they don't need to take care of that work +themselves anymore. + +```typescript +export const RelayLoader = { + loaderId() { + return 'relay-loader' + }, + canLoad(pointer) { + return isReactFile(pointer) + }, + load(pointer) { + const document = extractDocument(pointer) + + return new Source({ + location: pointer, + document + }) + } +} +``` + +We maintain +[a few loaders](https://github.com/ardatan/graphql-toolkit/tree/master/packages/loaders), but we +believe the community will start to cover other use cases as well. + +## All Platforms + +Our goal is to make **GraphQL Config platform-agnostic**. + +The old version relied heavily on Node's file system, which is a blocker in many cases. + +**Loader fits here perfectly.** + +Because it's just an asynchronous function that receives an input and gives back a GraphQL Schema, +it should work fine in browser and in any other environment. + +## Extensions + +In the previous generation of GraphQL Config `extensions` namespace, there was a way to pass custom +information to the consumers of the config file, like libraries and IDE extensions. + +We believe `extensions` should actually **extend** GraphQL Config's behavior. + +Take for example loaders. Imagine you want to collect operations and fragments from your Relay +project. With the new GraphQL Config, you can write a loader and register it through a Relay +Extension. + +```typescript +const RelayExtension = api => { + api.loaders.documents.register(RelayLoader) + + return { + name: 'relay' + } +} +``` + +The new `extensions` allows you to turn GraphQL Config into something fully customizable and to be +used in tools like Webpack! + +## Hooks + +We believe that there's a need to **intercept the schema building process** or to simply **validate +the config**. + +It's not currently available but with your help and suggestions we could make it happen. + +## Environment Variables + +In the new GraphQL Config, we've decided to support environment variables. **It was a long time +hanging and highly requested issue**. Now the usage in JS config file is straightforward. It's also +very easy to use environment variables in YAML and JSON files. + +```yaml +schema: './schema.graphql' +include: ${INCLUDE_GLOB} +``` + +Every `${ENV_VAR}` in the config file is replaced with the actual value. We also allow for defaults. +Using `${ENV_VAR:foo}` results in `foo`. + +## Easier to Contribute + +We also wanted to make the codebase itself easy to understand and contribute to. + +Our first task was to **bring the repository back to life** by updating the build and test setup. + +The `graphql-config` package now ships with **CommonJS** and **ES Modules** thanks to **Rollup** and +**TypeScript**. Tests are done thanks to **Jest**. The codebase stays consistent because of +**Prettier** and **ESLint** on top. + +We also migrated from Travis to **GitHub Actions** and run tests on **Node 8, 10 and 12**. + +To keep dependencies always up to date and to make sure no new release breaks GraphQL Config's +logic, we decided to use **Dependabot**. + +We also addressed more than 70% of the issues and open PRs (and the remaining are waiting for +feedback). + +--- + +Start using it today! + +Even though we are in an alpha phase, If you're the author or maintainer of a GraphQL library or +another related tool, **we encourage you to adopt the GraphQL Config standard**. + +> Please link to this [GitHub issue](https://github.com/kamilkisiela/graphql-config/issues/27) to +> track the progress. + +--- + +If you have a project that use those tools, we encourage you to try it out in your current project. + +We will support and answer all your questions on +[**Github**](https://github.com/kamilkisiela/graphql-config) and on +[**our Discord channel**](https://discord.gg/xud7bH9). diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-cursor-pagination-with-postgresql/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-cursor-pagination-with-postgresql/page.mdx new file mode 100644 index 00000000000..a0cca38dd74 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-cursor-pagination-with-postgresql/page.mdx @@ -0,0 +1,415 @@ +--- +title: GraphQL Cursor Pagination with PostgreSQL +authors: laurin +tags: [graphql, sql, postgresql] +date: 2023-02-15 +description: 'An SQL based approach for understanding and implementing GraphQL Cursor Pagination.' +--- + +import { Callout } from '@theguild/components' + +The GraphQL Cursor Pagination specification is a popular approach for exposing paginated data via an +API. There are many resources out there that describe the behavior of the +[GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm), but few real +world implementations using a real database. + +This guide will cover PostgreSQL concepts for building and optimizing paginated SQL queries. + +## Why Cursors over Offset Pagination? + +A lot of people think offset-based pagination is easier to grasp and easier to implement compared to +cursor pagination. While this fact is true, using offset-based pagination has some other +shortcomings. + +```sql filename="Offset-based Pagination" +SELECT + "id", + "name" +FROM + "users" +ORDER BY + "id" ASC +OFFSET + 10 +LIMIT + 10 +``` + +Compared to offset-based pagination, we need to pick a column and need to use the value of that +column in order to fetch the next items. + +```sql filename="Cursor-based Pagination" +SELECT "id", "name" +FROM "users" +WHERE "id" > $lastId +ORDER BY "id" ASC +LIMIT 10 +``` + +One of the main benefits of cursor-based pagination is better query consistency (no skipped or +duplicated data) as the database is updated and new rows are added. This is because the cursor is +based on an actual value (e.g. the primary key `id`) of a row that stays consistent, compared to +just a number that describes the offset at a given point in time. + +The other main benefit is better performance as the to-be-paginated table becomes large. Using +offset-based pagination requires a full dataset scan to determine the next items, where a cursor +based pagination can simply use an (hopefully existing) index to determine the next items. + +You can learn more details about the superiority of cursor-based pagination in the article +["Is offset pagination dead? Why cursor pagination is taking over"](https://uxdesign.cc/why-facebook-says-cursor-pagination-is-the-greatest-d6b98d86b6c0). + +## Cursor Pagination with Serial Primary Keys + +The easiest way of implementing cursor pagination is to use an existing primary key column in a +table that has consistent sorting, e.g. a serial primary key. + +```diff filename="Dataset users table" +| "id" | "name" | +| ---- | -------- | +| 1 | 'Laurin' | +| 2 | 'Uri' | +| 3 | 'Dotan' | +| 4 | 'Dima' | +| 5 | 'Saihaj' | +``` + +In the first roundtrip, we start without a `cursor` and fetch the first two items. + +```sql filename="Initial SQL query" +SELECT + "id", + "name" +FROM + "users" +ORDER BY + "id" ASC +LIMIT + 2 +``` + +```diff filename="Initial SQL query result" +| "id" | "name" | +| ---- | -------- | +| 1 | 'Laurin' | +| 2 | 'Uri' | +``` + +Afterward we use the cursor (last `id` in the result set) to fetch the next two items. + +```sql filename="Follow-up SQL query" {3} +SELECT + "id", + "name" +FROM + "users" +WHERE + "id" > 2 +ORDER BY + "id" ASC +LIMIT + 2 +``` + +```diff filename="Follow-up SQL query result" +| "id" | "name" | +| ---- | -------- | +| 3 | 'Dotan' | +| 4 | 'Dima' | +``` + +## Cursor Pagination with non-serial Primary Keys + +While using a serial primary key is the easiest way to implement cursor pagination, it is not always +possible to use a serial primary key. E.g. you might be using `uuid` as the primary key datatype, +which is not monotonically orderable. + + + You might consider using `ULID` as an alternative to `UUID`. `ULID`s are lexicographically + sortable. + + +```diff filename="Dataset users table" +| "id" | "name" | "created_at" | +| -------------------------------------- | -------- | -----------------------------------| +| '7c2730cb-a973-4a0a-9e9a-f8aa0044d321' | 'Dima' | '2023-01-10T10:32:07.853915+00:00' | +| '628995bf-2907-49d1-a36f-978566a053c4' | 'Dotan' | '2023-01-11T10:32:07.853915+00:00' | +| 'a0c3e7e5-700a-4c10-9afe-d36df294ddc3' | 'Saihaj' | '2023-01-12T10:32:07.853915+00:00' | +| '8771195f-7a8a-4685-8e4e-ae45d017c11b' | 'Uri' | '2023-01-13T10:32:07.853915+00:00' | +``` + +Let's try using this table with the same SQL query as before and fetch the users after `"Dotan"`. + +```sql filename="SQL query" +SELECT + "id", + "name", + to_json("created_at") AS "created_at" +FROM + "users" +WHERE + "id" > '628995bf-2907-49d1-a36f-978566a053c4' +ORDER BY + "id" ASC +LIMIT + 2 +``` + +```diff filename="SQL query result" +| "id" | "name" | "created_at" | +| -------------------------------------- | -------- | -----------------------------------| +| 'a0c3e7e5-700a-4c10-9afe-d36df294ddc3' | 'Saihaj' | '2023-01-12T10:32:07.853915+00:00' | +| '8771195f-7a8a-4685-8e4e-ae45d017c11b' | 'Uri' | '2023-01-13T10:32:07.853915+00:00' | +``` + +Now let's imagine a new user is added to the dataset. + +```diff filename="Dataset users table" + | "id" | "name" | "created_at" | + | -------------------------------------- | -------- | -----------------------------------| + | '7c2730cb-a973-4a0a-9e9a-f8aa0044d321' | 'Dima' | '2023-01-10T10:32:07.853915+00:00' | + | '628995bf-2907-49d1-a36f-978566a053c4' | 'Dotan' | '2023-01-11T10:32:07.853915+00:00' | ++ | '98a64d9e-5c42-4064-84d9-f9ea51e2fecc' | 'Laurin' | '2023-01-14T10:32:07.853915+00:00' | + | 'a0c3e7e5-700a-4c10-9afe-d36df294ddc3' | 'Saihaj' | '2023-01-12T10:32:07.853915+00:00' | + | '8771195f-7a8a-4685-8e4e-ae45d017c11b' | 'Uri' | '2023-01-13T10:32:07.853915+00:00' | +``` + +If we now execute the same query again, we suddenly get a different result than before. + +```diff filename="SQL query result" + | "id" | "name" | "created_at" | + | -------------------------------------- | -------- | -----------------------------------| ++ | '98a64d9e-5c42-4064-84d9-f9ea51e2fecc' | 'Laurin' | '2023-01-14T10:32:07.853915+00:00' | + | 'a0c3e7e5-700a-4c10-9afe-d36df294ddc3' | 'Saihaj' | '2023-01-12T10:32:07.853915+00:00' | +- | '8771195f-7a8a-4685-8e4e-ae45d017c11b' | 'Uri' | '2023-01-13T10:32:07.853915+00:00' | +``` + +This is not convenient if you want to implement a consistent pagination experience for your users. + +In such a scenario it is necessary to utilize another column that is more stable. A good pick for +that is the `created_at` column that is often added to all tables within a database in order to keep +track of when a row was created. + +If we use such a column with a consistent order as the cursor, the pagination is more consistent. +Before we perform such pagination, we should as always with our SQL queries, add an index to the +column to ensure good performance as the table grows in size. + +```sql filename="Create index" +CREATE INDEX "users_pagination" ON "users" ("created_at" ASC); +``` + +Instead of the `id`, we now use the `created_at` value from the row with the name `Dotan` as the +cursor. + +```sql filename="SQL query" +SELECT + "id", + "name", + to_json("created_at") AS "created_at" +FROM + "users" +WHERE + "created_at" > '2023-01-11T10:32:07.853915+00:00' +ORDER BY + "created_at" ASC +LIMIT + 2 +``` + + + By now you might have noticed that we are using `to_json("created_at") as "created_at"` instead of + simply selecting `"created_at"`. The reason behind this is that this consistently returns the date + as a string in the [`ISO8601` format](https://en.wikipedia.org/wiki/ISO_8601). When working with + pagination it is generally recommended to treat dates as strings as parsing them to numbers and + then back can lead to subtle bugs, especially in languages like JavaScript where numbers are a bit + complicated. + + +While this method so far has been working, it is not perfect. This only shows up as you have items +in the database that share the same `created_at` value. This can often happen when batch inserts are +used throughout an application. + +In this dataset, the users `Dotan`, `Saihaj`, and `Uri` all share the same `created_at` value. + +```diff filename="Dataset users with same created_at date" +| "id" | "name" | "created_at" | +| -------------------------------------- | -------- | -----------------------------------| +| '7c2730cb-a973-4a0a-9e9a-f8aa0044d321' | 'Dima' | '2023-01-10T10:32:07.853915+00:00' | +| '628995bf-2907-49d1-a36f-978566a053c4' | 'Dotan' | '2023-01-11T10:32:07.853915+00:00' | +| 'a0c3e7e5-700a-4c10-9afe-d36df294ddc3' | 'Saihaj' | '2023-01-11T10:32:07.853915+00:00' | +| '8771195f-7a8a-4685-8e4e-ae45d017c11b' | 'Uri' | '2023-01-11T10:32:07.853915+00:00' | +``` + +Again, we will try to fetch the users after `Dotan` using our cursor value from the `created_at` +column. + +```sql filename="SQL query" +SELECT + "id", + "name", + to_json("created_at") AS "created_at" +FROM + "users" +WHERE + "created_at" > '2023-01-11T10:32:07.853915+00:00' +ORDER BY + "created_at" ASC +LIMIT + 2 +``` + +However, this will yield zero results this time. + +```diff filename="SQL query result" + | "id" | "name" | "created_at" | + | -------------------------------------- | -------- | -----------------------------------| +- | 'a0c3e7e5-700a-4c10-9afe-d36df294ddc3' | 'Saihaj' | '2023-01-11T10:32:07.853915+00:00' | +- | '8771195f-7a8a-4685-8e4e-ae45d017c11b' | 'Uri' | '2023-01-11T10:32:07.853915+00:00' | +``` + +This is because the `>` operator is not inclusive. We could try to fix this, using the `>=` +operator, but then we would also get back `Dotan` and end up with multiple `Dotan` showing up in the +UI of our application. + +```sql filename="SQL query" +SELECT + "id", + "name", + to_json("created_at") AS "created_at" +FROM + "users" +WHERE + "created_at" >= '2023-01-11T10:32:07.853915+00:00' +ORDER BY + "created_at" ASC +LIMIT + 2 +``` + +```diff filename="SQL query result" + | "id" | "name" | "created_at" | + | -------------------------------------- | -------- | -----------------------------------| ++ | '628995bf-2907-49d1-a36f-978566a053c4' | 'Dotan' | '2023-01-11T10:32:07.853915+00:00' | ++ | 'a0c3e7e5-700a-4c10-9afe-d36df294ddc3' | 'Saihaj' | '2023-01-11T10:32:07.853915+00:00' | ++ | '8771195f-7a8a-4685-8e4e-ae45d017c11b' | 'Uri' | '2023-01-11T10:32:07.853915+00:00' | +``` + +The only solution here is to also introduce another, truely unique, column to our cursor. In this +case we can utilize the `"id"` column. + +So lets execute the SQL query, but now with both the `id` (`'628995bf-2907-49d1-a36f-978566a053c4'`) +and `created_at` (`'2023-01-11T10:32:07.853915+00:00'`) value of the row `Dotan`. + +```sql filename="SQL query using compound cursor" +SELECT + "id", + "name", + to_json("created_at") AS "created_at" +FROM + "users" +WHERE + ( + "created_at" = '2023-01-11T10:32:07.853915+00:00' + AND "id" > '628995bf-2907-49d1-a36f-978566a053c4' + ) + OR "created_at" >= '2023-01-11T10:32:07.853915+00:00' +ORDER BY + "created_at" ASC, + "id" ASC +LIMIT + 2 +``` + +Note, that we now have to use the `AND` operator to combine the two possible conditions. + +It might be tempting to just write the following condition, but this will not work. + +```sql filename="Wrong Condition" +"created_at" >= '2023-01-11T10:32:07.853915+00:00' +AND "id" > '628995bf-2907-49d1-a36f-978566a053c4' +``` + +This is because the `id` column is not serial and thus not stable for sorting. We only want to +utilize this column for sorting in case there is a conflict for the primary sorting column +`created_at`. + +Furthermore, we now also added the `id` as an additional field to the `ORDER BY` condition. + +In order to yield and preserve the best possible performance we again need to set an index. + +```sql filename="SQL index for compound cursor" +CREATE INDEX "users_pagination" ON "users" ("created_at" ASC, "id" ASC); +``` + +## Cursor Pagination with Additional Filters + +As an application grows additional filters might need to be applied to the pagination query. E.g. +you want to only query for users with a certain role. + +```diff filename="Dataset users with roles" +| "id" | "name" | "role" | "created_at" | +| -------------------------------------- | -------- | -------- | -----------------------------------| +| '7c2730cb-a973-4a0a-9e9a-f8aa0044d321' | 'Dima' | 'admin' | '2023-01-10T10:32:07.853915+00:00' | +| '628995bf-2907-49d1-a36f-978566a053c4' | 'Dotan' | 'admin' | '2023-01-11T10:32:07.853915+00:00' | +| 'a0c3e7e5-700a-4c10-9afe-d36df294ddc3' | 'Saihaj' | 'user' | '2023-01-11T10:32:07.853915+00:00' | +| '8771195f-7a8a-4685-8e4e-ae45d017c11b' | 'Uri' | 'admin' | '2023-01-11T10:32:07.853915+00:00' | +``` + +Again, lets query for all users after `Dotan` using our cursor value from the `created_at` column +with an additional `role` filter. + +```sql filename="SQL query" +SELECT + "id", + "name", + "role", + to_json("created_at") AS "created_at" +FROM + "users" +WHERE + ( + ( + "created_at" = '2023-01-11T10:32:07.853915+00:00' + AND "id" > '628995bf-2907-49d1-a36f-978566a053c4' + ) + OR "created_at" > '2023-01-11T10:32:07.853915+00:00' + ) + AND "role" = 'admin' +ORDER BY + "created_at" ASC, + "id" ASC +LIMIT + 2 +``` + +If you now execute this query it will work, however, if you are operating on a large dataset, the +query might be very slow. + +This is because the `role` filter is not applied to the index. In order to fix this we need to add +the column to it. + +```sql filename="SQL index for compound cursor with additional filter" +CREATE INDEX "users_pagination" ON "users" ("created_at" ASC, "id" ASC, "role" ASC); +``` + + + When you introduce a filter to a SQL query it is always important to add it to the index in order + to guarantee fast responses. A SQL operation that took 1 second to execute could suddenly start + taking several minutes as the database query planner has decided to change its query strategy due + to increasing row count within the table. + +**This is also why we do not recommend allowing arbitrary filters to be applied to your pagination +through user input, as you might not be able to guarantee the performance of your application.** + + + +## Conclusion + +While Cursor Pagination seems to be more complicated to implement than Offset Pagination, it is +still the superior and more future-proof solution. + +By now, you should know the pitfalls and things to consider when implementing cursor pagination in a +PostgreSQL database. Namely, use stable cursor values and keep setting indexes for your paginated +queries. + +In case you have additional questions or feedback, feel free to reach out to us on the comment +section below or by sending a pull request to this blog post. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-1/1.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-1/1.png new file mode 100644 index 00000000000..5783e456767 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-1/1.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-1/2.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-1/2.png new file mode 100644 index 00000000000..50f3f13815a Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-1/2.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-1/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-1/page.mdx new file mode 100644 index 00000000000..e6ae2503ecd --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-1/page.mdx @@ -0,0 +1,605 @@ +--- +title: GraphQL - Diving Deep +authors: tvvignesh +tags: [graphql, graphql-federation] +date: 2020-12-01 +description: Answering all the questions you may have about GraphQL and its ecosystem. +--- + +_This blog is a part of a series on [GraphQL](https://graphql.org) where we will dive deep into +GraphQL and its ecosystem one piece at a time_ + +- Part 1: [**Diving Deep**](/blog/graphql-deep-dive-1) +- Part 2: [**The Usecase & Architecture**](/blog/graphql-deep-dive-2) +- Part 3: [**The Stack #1**](/blog/graphql-deep-dive-3) +- Part 4: [**The Stack #2**](/blog/graphql-deep-dive-4) +- Part 5: [**The Stack #3**](/blog/graphql-deep-dive-5) +- Part 6: [**The Workflow**](/blog/graphql-deep-dive-6) + +The GraphQL specification was open sourced in 2015 by Facebook along with some basic implementations +with a completely unique approach on how to structure, consume, transmit and process data and data +graphs. + +Today, the GraphQL spec and its implementations have been donated by Facebook to the +[GraphQL Foundation](https://graphql.org/community/foundation/) with open license for development +and governance from the community, and it has been great so far. And today, the GraphQL foundation +comprises not just of companies like Facebook but other organizational members as well. + +It was a moment when a lot of people were convinced by its power, utility and promise that the rest +became history. + +And today, there is a [**GraphQL foundation**](https://graphql.org/community/foundation/) which +tries to ensure that GraphQL and the ecosystem thrives over time, +[**a huge landscape of projects**](https://github.com/graphql/graphql-landscape), a huge set of +tools like [**this**](https://graphql.org/community/tools-and-libraries/) and +[**this**](https://github.com/chentsulin/awesome-graphql) and these can just be few of the examples +on how big the ecosystem has grown with a lot of languages, frameworks, tools supporting it as a +first class citizen, so much so that even some [**huge enterprises**](https://graphql.org/users) are +using it today as part of their stack. + +GraphQL is at our heart at [Timecampus](https://twitter.com/timecampus), the heart of everything we +do, and we wanted to share the love we have for GraphQL and the ecosystem and also the hard lessons +we learnt along the way. And It's not just GraphQL, we will be diving deep into a lot of Open Source +Tools, Libraries, Frameworks, Software and Practices as we go along. + +I am pretty sure that we have a lot to talk about as we go along. So, why not start the series with +an FAQ? That's what we are going to do here. I have put together a set of questions and answered +them as well below. + +**If you are new to GraphQL, I would recommend you to start with these links before jumping into +this blog post:** + +[Introduction to GraphQL - Learn about GraphQL, how it works, and how to use it](https://graphql.org/learn) + +[How to GraphQL - The Fullstack Tutorial for GraphQLThe free and open-source tutorial to learn all around GraphQL to go from zero to production](https://howtographql.com) + +[Explore GraphQL - This is your GraphQL study guide. Learn the fundamentals of schemas and queries, then implement some apps](https://graphql.com/tutorials) + +[GraphQL Tutorial - GraphQL is becoming the new way to use APIs in modern web and mobile apps. However, learning new things always takes](https://hasura.io/learn/graphql/intro-graphql/introduction) + +[GraphQL Concepts Visualized - GraphQL is often explained as a "unified interface to access data from different sources"](https://apollographql.com/blog/the-concepts-of-graphql-bc68bd819be3) + +And if you are keen to dig deep into the GraphQL Spec, it is hosted +[**here**](https://spec.graphql.org) + +So, assuming you already know the basics of GraphQL, let's jump right in. + +**Why should I move away from REST to GraphQL? What are the benefits?** + +I would start by saying that GraphQL does not make REST or any other channel of communication +obsolete. It all boils down to your use case. For small projects, the simplicity of REST might +overweight the advantages provided by GraphQL but as you have more teams, an evolving product, +complex lifecycles and a data schema which gets bigger and bigger by the day, that's when you will +truly realize the value that GraphQL has to offer. + +![Image for post](./1.png) + +Credits: [howtographql](https://howtographql.com) + +In REST we try to structure different set of endpoints for different data paths, and if you see the +[REST Specification](https://w3.org/2001/sw/wiki/REST) it does not offer a way to select only the +data you want leading to over-fetching/under-fetching, does not offer type checking, no way to do +introspection (unless you build an OpenAPI based documentation yourself) and this can also quickly +become chatty since you have to end up calling different endpoints from the client to get different +sets of data needed by the application. GraphQL solves all of these like this: + +![Image for post](./2.png) + +Credits: [howtographql](https://howtographql.com) + +And this is the beauty of it. It has a strong Type system, you can select just what you want +avoiding over-fetching/under-fetching, you just have to talk to a single endpoint, the spec clearly +defines about the execution of the queries (serial or parallel resolvers), its protocol independent +unlike REST which relies on **HTTP** to do everything whereas you can even transmit your GQL queries +through http, GRPC, Websockets — you name it. + +**What is the difference between HTTP, GRPC, GraphQL and others?** + +In summary, all of them are different. HTTP is a protocol by itself and does not define about the +structure of the data transmitted via HTTP itself (The latest version is http 3), +[GRPC ](https://grpc.io)uses protocol buffers to send packets using http 2 as the protocol (and in +the future can extend to use http 3 as well) and is often used for inter-service communications and +GraphQL has nothing to do with the transport layer at all. It is just a specification for +structuring and transmitting data to and fro different locations, and it does not even matter if you +compress, encrypt or do anything with the queries and mutations as long as you have a logic to +decompress or decrypt them on the server side. So, in summary they serve different purposes. + +**How do I version my GraphQL endpoints like I do in REST?** + +While there is nothing stopping you from having different versions to the GraphQL endpoints like +`/v1/graphql` `/v2/graphql` or something along the same lines, GraphQL recommends you to have a +continuously evolving version of your data graph. So, you can deprecate fields you no longer use, +removing them at a later point of time, add new fields as and when you need without affecting the +rest of the schema avoiding any conflicts which may occur otherwise. + +**What is the recommended way to define my schema?** + +Over time, people have developed a lot of abstractions on top of GraphQL that suddenly there seems +like there are a lot of ways to define the schema. + +Some ways including + +- Writing the SDL directly as `.gql` or `.graphql` files and then loading and parsing them +- Using a library like [**Typegraphql**](https://typegraphql.com) to write your schema as code +- Define them directly as JS/TS objects as defined [**here**](https://graphql.org/graphql-js/type) + +and there are more and more can evolve over time. + +One thing to understand is that, if you are using Node.js +[graphql-js](https://github.com/graphql/graphql-js) would typically be the underlying implementation +of all libraries and ultimately everything would get converted to JS/TS objects typically an +[AST](https://graphql.org/graphql-js/language) ultimately making all these as abstractions on top of +the existing way to define schemas. Note that the implementation can differ a bit in other languages +or even within Node.js if you are using other ways of implementation like +[graphql-jit](https://github.com/zalando-incubator/graphql-jit) + +**What are some GraphQL servers available and how do they differ?** + +If you are using Node.js there are a lot of implementations of GraphQL servers with a few being +[express-graphql](https://github.com/graphql/express-graphql), +[apollo-server](https://github.com/apollographql/apollo-server), +[GraphQL Yoga](https://the-guild.dev/graphql/yoga-server), +[Hive Gateway](https://the-guild.dev/graphql/hive/docs/gateway), +[mercurius](https://github.com/mercurius-js/mercurius) and more. And if you are using other +languages, you can see a great list [here](https://graphql.org/code) + +Now, talking in context with Node.js it all varies depending on your use case. + +- Are you dependent on [Apollo](https://apollographql.com) or its ecosystem like + [Federation](https://the-guild.dev/graphql/hive/federation)? Go for apollo-server +- Do you use [express](https://github.com/expressjs/express) as your framework? Use express-graphql +- Are you using fastify or are looking for a performant graphql library with comprehensive support? + Go for mercurius +- Are you looking for making things as modular as possible, reduce bloat, and progressively extend + functionality as you go along? Go for [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server). + +Well, there are a lot of things that I have not mentioned but this is just a start to decide which +suggests some factors to keep into account. + +And in fact, if you are keen on understanding how every graphql-server performs, I would recommend +checking out [this](https://github.com/benawad/node-graphql-benchmarks) + +**What is the best way to leverage GraphQL with Typescript?** + +Considering both GraphQL and Typescript are strongly typed, we can actually combine them together to +give us an amazing experience with the help of some tooling. This will help us to make the end-end +request-response lifecycle strongly typed. + +For instance, there are some amazing projects from [**The Guild**](/) like +[**GraphQL Codegen**](https://the-guild.dev/graphql/codegen) which we can use for generating types +based on our local/remote schema with great Typescript integration, and you have a lot of +plugins/recepies you can use along with it as well. + +Want to generate Typescript objects based on GQL documents? You can try out +[**Typed Document Node**](https://github.com/dotansimha/graphql-typed-document-node) + +Or do you want to directly code the schema in Typescript and maintain strict types? Try +[**Typegraphql**](https://typegraphql.com) + +Well, there are more examples like these and this is just a start. + +**How do I setup my Dev environment to work on GraphQL?** + +While this needs a separate blog post all by itself, here are some examples. + +- If you are using [VSCode](https://code.visualstudio.com) and are looking to enable syntax + highlighting, validation, autocomplete, code-completion and so on, you can try using either + [VSCode GraphQL](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) or + [Apollo GraphQL](https://marketplace.visualstudio.com/items?itemName=apollographql.vscode-apollo) + depending on which suits you better. +- If you are working with [Typescript](https://typescriptlang.org) it would be better to have + codegen setup as part of your workflow. +- If you want to validate your schema as and when you push to Version control to maintain sanity, + setup something like [GraphQL Inspector](https://github.com/kamilkisiela/graphql-inspector) + locally and in your CI/CD pipelines to maintain your sanity. If you use the Apollo ecosystem, it + comes inbuilt in the Apollo Studio or the CLI tools which it gives you. +- Want to have ESLint support to enforce standards and maintain sanity across your team, try + something like [GraphQL ESLint](https://github.com/dimaMachina/graphql-eslint) and set it up with + your preferred conventions. +- Setup a [**graphql-config**](https://graphql-config.com) and this will interface with other + tooling like the codegen, VSCode GraphQL extension, GraphQL ESLint and more. This will help a lot + since you have one config to manage all the interfacing tools. If you are using the Apollo Stack, + you might need an + [**apollo-config**](https://apollographql.com/docs/devtools/editor-plugins/#setting-up-an-apollo-config) + as well +- If you want to keep your GraphQL code as modular as possible with support for things like + dependency injection, try something like [**GraphQL Modules**](https://graphql-modules.com) +- Want to interface with multiple different data sources and integrations each with their own format + but still have the experience of GraphQL when developing on top of them? Try something like + [**GraphQL Mesh**](https://graphql-mesh.com) +- Want to use a tool to test GraphQL endpoints? You might need something like + [**Insomnia**](https://insomnia.rest)**,** [**Postman**](https://postman.com)**,** + [**Hoppscotch**](https://hoppscotch.io) **or** + [**VSCode REST Client**](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) + +And while I can talk more about this, it will never end cause the ecosystem is too huge and +thriving. + +**I use REACT/Angular /Vue/Web Components. How do I integrate GraphQL with my components?** + +Again, the front end ecosystem is huge as well with its own set of tooling and libraries. + +In my case, I typically try to work on the frontend without any framework (I use +[Lit Elements](https://lit-element.polymer-project.org) in my case, and we will have a separate blog +on that soon), the tool you use completely depends on your requirements here. + +- Apollo Client does have a good integration with these frameworks including + [React](https://apollographql.com/docs/react), [iOS](https://apollographql.com/docs/ios) and + [**Android**](https://apollographql.com/docs/android) — so, you might want to check that out +- Using React? [Relay](https://relay.dev) can be a + [great choice](https://blog.bitsrc.io/apollo-and-relay-side-by-side-adb5e3844935) +- Using Vue? You can try [**Vue Apollo**](https://apollo.vuejs.org/guide) +- Using web components with Apollo Stack for GQL? You might want to check out + [**Apollo Elements**](https://github.com/apollo-elements/apollo-elements) +- Using vanilla JS or TS or using web components and want to have a framework-independent way of + doing things? You can stick to the GraphQL codegen itself since it takes care of almost everything + underneath. Or if you want, you can also use Apollo Client's vanilla version + `@apollo/client/core`. [**Apollo Elements**](https://github.com/apollo-elements/apollo-elements) + does come with support for a lot of webcomponent libraries like + [Lit](https://lit-element.polymer-project.org), [Fast](https://fast.design) and + [Gluon](https://github.com/ruphin/gluonjs) or even without any of it and hence is quite flexible. +- Or if you are just looking for a lightweight, performant and extensible GraphQL client, + [urql](https://formidable.com/open-source/urql) can be great as well. +- Or if you are looking for a minimal client which runs both in the Browser and Node, you can try + [GraphQL Request](https://github.com/prisma-labs/graphql-request) + +Well, there are a lot of other ways we haven't talked about and this is just a start. + +**What are some ways in which I can maintain performance while using GraphQL?** + +While GraphQL is really promising and helpful, you have to understand that like any technology or +framework, it does come with its own set of problems, most of which have already been addressed. For +instance, you might have heard about the N+1 problem, lack of caching, Query cost and complexity and +so on and these have been addressed by some projects like the +[DataLoader](https://github.com/graphql/dataloader), +[Persisted Queries](https://apollographql.com/docs/apollo-server/performance/apq), +[Caching](https://apollographql.com/docs/apollo-server/performance/caching) and more which you can +setup depending on your needs. + +Ultimately it depends on the degree of the flexibility you want to offer. The more the flexibility, +the more the cost. And it is your decision to decide it based on your use case. + +**What are some principles or standards to be followed when trying to build my datagraph +architecture?** + +Some amazing people have already answered this [**here**](https://principledgraphql.com), and I +highly recommend you to go through it before starting off your journey with GraphQL. + +And if you are looking for some help with the rules and implementation details with respect to +GraphQL, you can find a great doc on this [**here**](https://graphql-rules.com) + +While all of these are principles trying to guide you in the right direction, choose what is best +for your use case and work with it. + +**How do I use GraphQL to interact with multiple sources of data?** + +One of the great examples of real-world implementation of this would be +[**Gatsby**](https://gatsbyjs.com) where irrespective of the source of data, everything ultimately +gets converted to GraphQL with plugins which can then be used in your workflow. + +If you are to build it in the server side, either you can use an out-of-the-box solution like +[**GraphQL Mesh**](https://graphql-mesh.com) or +[**Hive Gateway**](https://the-guild.dev/graphql/hive/docs/gateway) or you can build it on your own +since GraphQL just acts as an abstraction on top. + +Or if you are on the apollo stack and want to connect to multiple data sources, you can have a look +at [**apollo-datasource**](https://apollographql.com/docs/tutorial/data-source) + +Or you want to have a single ORM which closely resembles GraphQL like [Prisma](https://prisma.io) to +integrate with multiple databases underneath + +Ultimately it all boils down to how you structure your resolvers. + +But, it does not stop here. Some databases also support GraphQL either via adapters or natively as +well. + +For e.g. + +- [**Dgraph**](https://dgraph.io) has a native [GraphQL](https://dgraph.io/graphql) implementation +- [**Neo4j**](https://neo4j.com) has a [GraphQL](https://neo4j.com/developer/graphql) adapter +- [**Hasura**](https://hasura.io) provides a GraphQL abstraction on top of your datasources +- [**Postgraphile**](https://graphile.org/postgraphile) can help if you use Postgres + +Well, these are just some tools and services. There are more like this which can help. + +**The GraphQL spec is missing some types like DateTime, GeoLocation and more. How do I implement +that?** + +Yes, this can be painful. But, it is by design to keep GraphQL as lean and lightweight as possible. + +This is where [GraphQL Scalars](https://graphql.org/learn/schema/#scalar-types) really help. You can +define your own types and use them across your schema if they are not supported out of the box. + +But, this can be tedious to implement and using a package like +[**graphql-scalars**](https://graphql-scalars.dev) can actually help since it comes inbuilt with +some commonly used scalars which you can import and use. + +**There are some fields which I find myself repeating between various queries and mutations. How do +I avoid doing this?** + +As the [DRY principle](https://en.wikipedia.org/wiki/Don't_repeat_yourself) goes, we can also make +our operations modular with the help of +[GraphQL Fragments](https://graphql.org/learn/queries/#fragments) and then use those fragments as +applicable anywhere. + +**Can't I convert my Database schema directly to a GraphQL schema or generate a GraphQL schema?** + +While technically, it is possible and this is what database providers who offer a GraphQL layer on +top use like Hasura or Graphcool — It is highly **not recommended** for client consumption, and I +would also recommend you to read [this](https://graphql.org/learn/thinking-in-graphs) to get more +idea. + +The main reason to this according to me is that GraphQL is meant to describe the Data Graph which +revolves around the business/domain terminologies without involving the underlying technical +complexity or details. For instance, one should not care about which table a specific field comes +from, how to join, and so on. + +It should just be about the business implementation for the end users so even a product manager who +does not know about the underlying technical implementation can use it. + +So, while you may use GraphQL as sort of an ORM for your databases or other data sources, exposing +that directly to the clients is not a good option. Rather, there should be one more layer on top +just to have it make sense for any end user and reduce the complexity for clients. + +**Are there some helpers libraries I can use to work with my GraphQL schemas?** + +Yes. [**GraphQL Tools**](https://the-guild.dev/graphql/tools) (which was initially from Apollo and +then taken over by the Guild) is one of those libraries which I highly recommend. You can do a lot +of operations on your SDL or schema like merging multiple schemas, mocking your schemas with test +data, building custom directives, loading remote schemas and so on which you can add as part of your +stack. + +**What is the best strategy to distribute your schema? What if I am using Microservices with +GraphQL?** + +While GraphQL is meant to be a single endpoint or provide a single unified view of the data for the +clients, it is often not possible to do it all at one place since it can create a lot of +bottlenecks. This is why +[**Schema stitching**](https://the-guild.dev/graphql/tools/docs/stitch-combining-schemas) or +[**Apollo Federation**](https://apollographql.com/docs/federation) came into place where multiple +subschemas can contribute to the unified data graph. + +While we can have a separate blog on Schema Stitching versus +[Federation](https://the-guild.dev/graphql/hive/federation) sometime down the line, each have its +own set of merits and demerits which you can understand only if you give both a try. + +These videos can help get some basics (but a lot has changed since these videos were released +especially with GraphQL Tools introducing +[**Type Merging**](https://the-guild.dev/graphql/tools/docs/stitch-type-merging)): + +import { YoutubeIframe } from '#components/youtube-iframe' + + + + + +If you are still confused on what to go for, you can also read +[**this**](https://product.voxmedia.com/2020/11/2/21494865/to-federate-or-stitch-a-graphql-gateway-revisited) +blog about stitching and federation. + +**What are some GraphQL events/conferences to watch out for?** + +Since GraphQL was released, it garnered a huge interest in the community that a lot of conferences, +events and meetups are held around the world keeping GraphQL as the main theme. Some of them are: + +- [**The GraphQL Summit**](https://summit.graphql.com) +- [**GraphQL Conf**](https://graphqlconf.org) +- [**Enterprise GraphQL**](https://hasura.io/enterprisegraphql) +- [**GraphQL Asia**](https://graphql.asia) +- [**GraphQL Galaxy**](https://graphqlgalaxy.com) + +and there are more including meetups like [**these**](https://meetup.com/topics/graphql) and +[**these**](https://graphql.org/community/upcoming-events). You can find most of the previous +sessions recorded on Youtube if you search for it. + +**How can I contribute to GraphQL and its ecosystem?** + +Every bit of help really counts since GraphQL foundation is run by a set of volunteers, and it's all +open source. You can: + +- Write blogs like this to spread knowledge amongst the community +- Host meetups, speak in conferences about your experience and evangelize your best way possible. +- Contribute to the [**GraphQL spec**](https://github.com/graphql/graphql-spec) with your + suggestions (Some suggestions may take years to implement even if it is good, so you may need to + have a lot of patience for this) +- Contribute to the ecosystem of tools leveraging GraphQL be it with documentation, tests, features, + bug fixes, feedback and what not. It will definitely help. +- Facing a challenge with GraphQL which has not been solved before? Build your own tooling and + contribute it to the community +- Create failing tests and reproducible projects +- Answer and help others on GitHub Issues, Discord, Stack Overflow, Twitter, Reddit. There are a lot + of amazing GraphQL communities out there. +- Or if you want to take it to the next level and want to align your entire organization to help the + GraphQL foundation, [become its member](https://foundation.graphql.org/join) and contribute. + +There are a lot of small ways in which you can give back. Small or big does not matter. Every +contribution counts. + +**Are there some case studies which can actually help me in the implementation?** + +Sure. While I can't list them all here, here are some: + +- [Netflix and GraphQL](https://netflixtechblog.com/how-netflix-scales-its-api-with-graphql-federation-part-1-ae3557c187e2) +- [Airbnb and GraphQL](https://medium.com/airbnb-engineering/how-airbnb-is-moving-10x-faster-at-scale-with-graphql-and-apollo-aa4ec92d69e2) +- [Github and GraphQL](https://graphql.com/articles/graphql-at-github) +- [Twitter and GraphQL](https://about.sourcegraph.com/graphql/graphql-at-twitter) + +and you can find more [**here**](https://graphql.com/case-studies) + +**Are there any publicly available GraphQL APIs which I can play around with?** + +Yes. While most of them would require you to authenticate, they are available for you to use. Some +examples: + +- [Github GraphQL Explorer](https://developer.github.com/v4/explorer) +- [Gitlab GraphQL Explorer](https://gitlab.com/-/graphql-explorer) +- [Yelp GraphQL Explorer](https://yelp.com/developers/graphiql) + +You can have a look at more like these [here](https://github.com/APIs-guru/graphql-apis) and play +around with it. + +**I have a legacy architecture/stack as part of my organization. How do I incrementally migrate to +GraphQL?** + +This is one of the places where GraphQL really shines. You need not move everything over at one +piece. Here are some steps which might help. + +- First, build a Datagraph for your entire business without worrying about the underlying + logic/implementation. But don't worry too much since you can always evolve this over time. +- Next, implement resolvers for every part of the schema in such a way that at phase 1, you just + wrap your existing infrastructure with GraphQL. For instance, if your services use SOAP, you can + add a GraphQL layer on top of it and calling that can all the SOAP service underneath and the + client need not worry about it. You can use something like + [GraphQL Mesh](https://graphql-mesh.com) or [SOFA](https://sofa-api.com) which can help in + abstracting these. There is a good blog post on how to migrate from REST to GraphQL + [**here**](/blog/migrating-from-rest). +- Change the client implementation one by one to call the GraphQL gateway instead of the legacy + service. +- Now that you have GraphQL working in your ecosystem, you can incrementally move away from legacy + implementations like SOAP without having to worry about how it will affect the clients + progressively, one component at a time to use a native GraphQL implementation. + +While this is one possible approach, this is not the only approach. There are a lot of other ways in +which you can take this one step at a time without worrying about the legacy code you have. + +**How do I secure my GraphQL endpoint?** + +While the GraphQL spec itself does not recommend any specific way to do this and leaves it to the +person implementing it, you can either use [**JWT**](https://jwt.io)**,** Cookies, Sessions and so +on like you normally would when authenticating through other mechanisms. + +**How do I enable authorization to my GraphQL fields or schema?** + +This is very powerful in GraphQL since you can do a authorization at a very fine-grained level be it +at the type level or at the field level. You can read +[this](https://apollographql.com/blog/authorization-in-graphql-452b1c402a9) blog which suggests +various ways in which you can do authorization. + +You can also use libraries like [**GraphQL Shield**](https://github.com/maticzav/graphql-shield) +which offers powerful middlewares to do this. But remember that authorization does come with +attached cost since you are running a specific logic in/before your resolvers for all the fields +which you want to authorize. + +One often overlooked way is the use of +[**directives**](https://graphql-tools.com/docs/schema-directives) to do authorization, one example +of which is mentioned in +[this](https://blog.grandstack.io/authorization-in-graphql-using-custom-schema-directives-eafa6f5b4658) +blog and this is very powerful and declarative. This way, you can specify the scope and add the +directive to the respective fields in your SDL and it can do the job for you. + +**How do I enable real-time applications like Chat, auto-updates and so on in my application with +GraphQL?** + +There are some options currently to do this. + +- The first would be to use + [GraphQL Subscriptions](https://howtographql.com/graphql-js/7-subscriptions) which is part of the + spec. You have to register the subscriptions upfront and also have support for Websockets if you + want to do this. +- Another way is to do periodic long-time polling which can work at a small scale keeping your + application stateless. +- An another way is to use + [live queries](https://medium.com/open-graphql/graphql-subscriptions-vs-live-queries-e38302c7ab8e) + +Each option comes up with its own set of advantages and disadvantages again. Just remember that it +is not often possible to keep your application stateless if you want something like Subscriptions. +So, make sure you manage the state well and plan for failures and scaling your app. + +And if you are new to subscriptions, you can probably +[watch this](https://youtube.com/watch?v=Wi7P39sF2nw&feature=youtu.be) to get an idea about the +basics of how subscription works. + +**What can I even do with introspection?** + +[**Introspection**](https://graphql.org/learn/introspection) is typically used by the tooling to +understand your GraphQL types and schema. For instance, tools like +[**GraphQL Voyager**](https://apis.guru/graphql-voyager) can introspect your schema and build +amazing graphs, and almost all extensions built around GraphQL leverage this power to understand +your schema, types and everything around it. + +Note that, it is recommended by experts to have introspection disabled in production due to security +and performance reasons. + +**How do I do tracing of all operations in GraphQL?** + +There are various ways in which you can do this. + +- If you want to do this on your own, you can send traces or contexts from within the resolvers + using the [**Jaeger**](https://jaegertracing.io)/[**OpenTelemetry**](https://opentelemetry.io) + SDKs and send all the information manually for tracing. +- OpenTelemetry has recently made support for GraphQL available. You can find it + [**here**](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/master/plugins/node/opentelemetry-instrumentation-graphql) +- But if you find yourself using the Apollo Stack, Apollo comes with its own tracing options like + Apollo Tracing and you can read about it + [**here**](https://apollographql.com/blog/exposing-trace-data-for-your-graphql-server-with-apollo-tracing-97c5dd391385) + +Just remember that Tracing will cause a lot of performance overhead, and it is highly recommended to +have it off unless otherwise needed or probably use it only for specific layers of concern. + +**How do I handle errors gracefully?** + +Again, there are a lot of ways to do this. + +- If you use the Apollo stack, you can use the apollo-errors package as documented + [**here**](https://apollographql.com/docs/apollo-server/data/errors) +- If you use express-graphql or want to use graphql-js natively, they expose error functions as well + based on + [**GraphQLError**](https://github.com/graphql/express-graphql#additional-validation-rules) and can + also use GraphQL extensions to augment with custom payload like error codes and so on which what + you typically do when using servers like graphql-helix. + +Now, this is the case since GraphQL does not have any dependence on the transport layer and thus +status codes like 200, 400 or 500 may not make sense unless they are part of the response and the +spec does not prescribe a specific way to do this as well. + +**Is GraphQL related to Graph databases in some way?** + +While GraphQL encourages you to think of your entire data as graphs of connected information since +that would give a better insight into how to structure your schema leading to a unified data graph, +it has no relation with Graph databases by itself since Graph databases act as a way to represent +and store data in underlying storage systems to allow for fast traversal, walking and retrieval. + +But, that being said, GraphQL and Graph Databases do have a lot of synergy between them. You can +read about that [**here**](https://dgraph.io/blog/post/graphdb-for-your-next-app) and +[**here**](https://datalanguage.com/blog/graphql-and-graph-databases) since its all about +establishing the data schema and its relationships. + +**When exposing REST APIs to end users, I used to bill users based on API calls made. How do I do +this for GraphQL?** + +This can be a challenging problem cause in GraphQL, it's the clients which decide what to +query/mutate and the server might not know that upfront unless you are using something like +persisted queries. + +And here, the CPU consumed can depend on the level of nesting of the queries, the operations which +your resolvers do and so on making it difficult to estimate the costs upfront. You can find a +detailed blog about this +[**here**](https://medium.com/dev-genius/a-principled-approach-to-graphql-query-cost-analysis-8c7243de42c1). + +- One way to handle this only allow persisted queries and approve them and assign costs to them + upfront, but this can get tricky to manage over the long run as number of queries and mutations + increase. +- Another way is to use custom cost directives as in + [this package](https://github.com/pa-bru/graphql-cost-analysis) manually specifying the complexity + and cost and using that to bill your APIs + +This is relatively a new area and still under exploration. For instance, Dgraph bills for Slash +GraphQL based on the nodes accessed as mentioned [here](https://dgraph.io/pricing) which can be +valid for databases using GraphQL but not necessarily for GraphQL api by itself. + +**Here are some other resources which hosts FAQs on GraphQL as well** + +- [Apollo GraphQL FAQ](https://apollographql.com/docs/resources/faq) +- [GraphQL Org FAQ](https://graphql.org/faq) +- [Howtographql FAQ](https://howtographql.com/advanced/5-common-questions) + +And there are more. Just google for it. + +Hope this was informative. Do you have any question which I have not covered in this list or are you +looking for some help? Let me know by reaching out to me [@techahoy](https://twitter.com/techahoy). + +And if this helped, do share this across with your friends, do hang around and follow us for more +like this every week. See you all soon. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-2/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-2/page.mdx new file mode 100644 index 00000000000..68e081bc6a4 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-2/page.mdx @@ -0,0 +1,242 @@ +--- +title: GraphQL - Use case and Architecture +authors: tvvignesh +tags: [graphql, graphql-federation] +date: 2020-12-02 +description: + Architecting for GraphQL and its ecosystem. Looking at how your architecture can look like when + working with GraphQL. +--- + +_This blog is a part of a series on GraphQL where we will dive deep into GraphQL and its ecosystem +one piece at a time_ + +- Part 1: [**Diving Deep**](/blog/graphql-deep-dive-1) +- Part 2: [**The Usecase & Architecture**](/blog/graphql-deep-dive-2) +- Part 3: [**The Stack #1**](/blog/graphql-deep-dive-3) +- Part 4: [**The Stack #2**](/blog/graphql-deep-dive-4) +- Part 5: [**The Stack #3**](/blog/graphql-deep-dive-5) +- Part 6: [**The Workflow**](/blog/graphql-deep-dive-6) + +In the [**last blog post**](/blog/graphql-deep-dive-1), we explored the various questions one might +have when starting off or working with the GraphQL ecosystem and answered them. Now that justice has +been done to clear the clouded thoughts you might have, let's dive into the next important step in +this blog. + +In this blog, we will start looking at how your architecture can look like when working with GraphQL +and its ecosystem. + +**The Architecture** + +Your architecture hugely revolves around your usecase, and you have to be very careful in getting it +right and take proper consultation if needed from experts. While it is very important to get it +right before you start, mistakes can happen, and with a lot of research happening these days, you +can often find any revolution happen any day which can make your old way of thinking obsolete. + +That is why, I would highly recommend you to **Architect for Change** and make your architecture as +**Modular** as possible so that you have the flexibility to do incremental changes in the future if +needed. Let's just talk about architecture in context with GraphQL here. We will explore more deeper +into the rest of the architecture in an another blog post. + +**The Basics** + +There are some things you would have to think of before starting your journey. + +- Am I building a monolith or am I working on microservices? Remember that monoliths still have a + huge place in today's world given the complexity which comes with Microservices as long as your + project is small. +- What does my deployment target going to look like? VM, Containers or Bare Metal? +- What is going to be my orchestration layer? Kubernetes, Mesos, Swarm or OpenStack? +- What are my scaling needs? +- What is the performance that I expect? +- Do I need Offline support? +- Cloud or On-Premise? +- What is the programming language which makes sense for my usecase? + +This list is incomplete. There are more questions like these which you might want to answer yourself +and answering this can give you a lot of clarity as you start building your architecture. + +**The Ingress / Load Balancer** + +This is the first layer that any client would typically hit before making requests to your GraphQL +service. This acts as the single entry point for all traffic (it can be regional as well depending +on your use case). + +This would be the first thing you would have to setup before getting started and this is also the +layer which handles things like SSL termination, caching (in case you have a CDN setup) and so on. + +If you are in the Kubernetes world, you also have a lot of ingress controllers like +[**Nginx Ingress**](https://kubernetes.github.io/ingress-nginx), +[**Ambassador**](https://getambassador.io), [**Kong**](https://konghq.com), +[**Contour**](https://projectcontour.io) and so on which can help. + +**The API Gateway** + +The first thing would be the entry point of all your GraphQL requests. Since GraphQL exposes a +single endpoint e.g. `/graphql` this becomes the single entry point for all your operations. + +But, I highly wouldn't recommend directly exposing your service to client since it can be unsecure, +difficult to manage things like rate-limiting, load balancing and so on. + +Rather, it is always recommended to expose it via an API Gateway of your choice. Be it Ambassador, +Kong, WSO2, Apigee or anything else for that matter. This can also act as sort of kill switch or can +also be used for things like filtering and moderating traffic whenever needed. + +**The GraphQL Gateway** + +As you evolve, you might end up having multiple services or might even move to the microservices +world to enable scale. Now, this means multiple services with its own GraphQL schema, logic and so +on. + +But unlike REST, GraphQL exposes a single endpoint irrespective of the underlying services. This is +where a Gateway plays a major role and comes in at the next layer of our architecture. The role of +orchestrating or composing (both are different) multiple services and schemas together, delegating +queries and mutations to the respective microservices and all of this without the client having to +worry about the complexity underneath. + +While you may choose to go for different architectures like +[**Schema Stitching**](https://graphql-tools.com/docs/schema-stitching) or +[**Federation**](https://the-guild.dev/graphql/hive/federation) depending on your use case, do +remember that sometimes, this may be an overkill. You might not even need a GraphQL Gateway to start +with if you are building something small and this can reduce a lot of complexity. + +**The GraphQL Service** + +The next thing to think of would be the GraphQL service itself (be it a monolith or microservice). +Each service would be responsible for a part of the complete data graph as seen in +[**Federated Implementation**](https://principledgraphql.com/integrity#2-federated-implementation) +and this will make things easier to scale. Note that the way you implement it can be different as +discussed (Schema Stitching or Federation). + +You might also want to modularize your project structure and code within the service and this is +applicable irrespective of whether you use a monolith or microservice to maintain clear separation +of concerns, make everything composable and modular as possible. + +While you can end up discovering your own way to do it (I initially went down this path), but what +is the use of re-inventing the wheel when you have something like +[**GraphQL Modules**](https://graphql-modules.com) which can help you with this. + +You might also want to get your tooling right to reduce as much work you do as possible. Be it +linting and validation, code generation, testing, and so on so that you automate most of your +workflow, and you stay productive while working on any part of the service. + +**The Mode of Communication** + +Now that you have thought about the service(s), you might also want to think about the mode of +communication in between them which is essential to pass data to and fro, synchronously and +asynchronously. This also presents some questions which you might want to answer first before +starting. + +- https ([**1.1**](https://tools.ietf.org/html/rfc2616), + [**2**](https://tools.ietf.org/html/rfc7540) or + [**3**](https://tools.ietf.org/html/draft-ietf-quic-http-32)) or [**grpc**](https://grpc.io) (over + http/2) or [**Thrift**](https://thrift.apache.org) or + [**Websockets**](https://tools.ietf.org/html/rfc6455)? +- Do you need a [**Service Mesh**](https://smi-spec.io)? +- Is GraphQL going to be used for communicating between services? +- Do I need something like [**MTLS**](https://linkerd.io/2/features/automatic-mtls) for securing + inter-service communication? +- How do I do asynchronous communication? Do I use event queues like + [**Kafka**](https://kafka.apache.org), [**RabbitMQ**](https://rabbitmq.com) or + [**NATS**](https://nats.io) ? + +Again, all of these depend on your use case and hence, there is no definite answer to this. But, try +to go for a protocol which offers you less latency, great compatibility with built-in support for +things like compression, encryption and so on. + +These matters cause while all the clients would communicate with the GraphQL endpoint you expose, +you still would have to have some sort of efficient way to do inter-service communication. + +Even if you are going to communicate between your service with GraphQL (which is what I do), you +still have to decide how you transmit the GraphQL queries and mutations in between them. + +**Authentication & Control** + +Like we discussed in the [**previous blog post**](/blog/graphql-deep-dive-1)**,** there are various +ways to do authentication and authorization. You might want to consider them as well while +architecting cause this will decide how chatty your services will be when doing operations, how +secure will it be, and so on. There are various ways as we spoke about, both stateful and stateless. +While stateless would be better for scalability, you might want to choose what works best for you. + +Depending on your use case, you might also want to decide if you need something like persisted +queries or not. This can prevent clients from sending queries which are not authorized, prevent huge +amounts of GraphQL data from being passed over the wire, and so on. + +**The Backend** + +And then comes the backend which you are going to use to store/retrieve data from. There are a huge +number of options out there and to be honest, there is no one database which fits all use-cases. And +they even come with different variants — SQL, NoSQL, Search, Time Series and even Graph Databases. +You can refer [**DBEngines**](https://db-engines.com/en/ranking) for a complete list. + +And you can even put a GraphQL layer or ORM on top of all of them if you want and take the +complexity away from the services (e.g. with [**Prisma 2**](https://prisma.io) or +[**GraphQL Mesh**](https://the-guild.dev/graphql/mesh)). + +You might also want to look at how you minimize the amount of calls you make to the main database. +Do you need caching and have it setup? Have you addressed the N+1 problem with +[**Dataloader**](https://github.com/graphql/dataloader)? + +**More Exploration** + +Now, there are a lot of other things you might want to have in your architecture like Hybrid Cloud +support, CI/CD pipelines, caching and so on. We will probably explore them in future blog posts as +we go along. + +Remember to keep your stack as simple as possible, and you can incrementally have them setup as you +go along. + +**Some Tips** + +- When architecting applications, I try to use the + [**Black Box model**](https://medium.com/timecampus/the-power-of-black-box-a7d8a320e557) as much + as possible. This simplifies a lot of things for me. +- I try to go for the Zero Trust Security Model when building my architecture popularized by + [**Beyondcorp**](https://cloud.google.com/beyondcorp) from Google and while this will create a lot + of friction at start, this makes life a lot better for you in the future. +- There are some questions I ask based on the principles like + [**YAGNI**](https://en.wikipedia.org/wiki/You_aren't_gonna_need_it)**,** + [**DRY**](https://en.wikipedia.org/wiki/Don't_repeat_yourself)**,** + [**KISS**](https://en.wikipedia.org/wiki/KISS_principle), and they play a huge role in making sure + that you don't overwhelm yourself with things you don't want to do right now and prioritize things + right. +- I try to refer case studies and see how others are already solving the same problem and this can + help me save a lot of my time. Avoiding to re-invent the wheel. For GraphQL, you may find them + [**here**](https://graphql.com/case-studies) + +**Deciding the “Right” Stack for “You”** + +Before I pick any tool or technology as part of my tech stack, I do ask a set of questions which +help me better judge and make an informed decision on what I want. Probably it might help you too. +This applies not just to the GraphQL ecosystem, but anything you choose for that matter. + +- Does this tool/library solve my problem well? +- What is the Licensing model? Is it Open Source? If so, is it MIT/Apache/BSD/GPL +- Does it have community support or backed by a Foundation/Enterprise? When was the last commit? How + many contributors? Does it have a clear path to becoming contributors? +- How many people use it in production? What are their experiences? At what scale are they using it? +- What do the stats look like? Stars, Forks, Downloads? +- Is it bloated? Or does it do just one thing well? +- Does it have a clear roadmap for the future? If so, what are the milestones? +- What are the other alternatives? How does it compare to them? +- How is the documentation? Does it have tests? Does it have examples which I can refer to? +- Does it follow standards and is free of Vendor Lockin? +- Are there any security concerns which this tool or library might create? + +While not all of these questions might have been addressed by the library or tool well, what I see +is at least the intent to address them in near-time. + +While most of the things in this blog may not be related to GraphQL itself, these are some things +which you need to keep in mind before starting your journey with GraphQL. In the next blog, I will +show you how my GraphQL Tech Stack looks like as I use it to build +[Timecampus](https://twitter.com/timecampus), and we will dive deeper into each layer of the stack, +one piece at a time. + +Hope this was informative. Do let us know how you prefer to architect with GraphQL in the comments +below, and we will be happy to know more about it. + +If you have any questions or are looking for help, feel free to reach out to me +[@techahoy](https://twitter.com/techahoy) anytime. + +And if this helped, do share this across with your friends, do hang around and follow us for more +like this every week. See you all soon. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/1.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/1.png new file mode 100644 index 00000000000..2ce694d21f7 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/1.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/2.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/2.png new file mode 100644 index 00000000000..45813240336 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/2.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/3.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/3.png new file mode 100644 index 00000000000..d002b6eaa1e Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/3.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/4.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/4.png new file mode 100644 index 00000000000..a2d888c1e28 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/4.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/5.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/5.png new file mode 100644 index 00000000000..181762151d4 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/5.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/6.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/6.png new file mode 100644 index 00000000000..eb3831d619b Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/6.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/page.mdx new file mode 100644 index 00000000000..cc83cdc69ba --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-3/page.mdx @@ -0,0 +1,194 @@ +--- +title: 'GraphQL - The Stack - #1' +authors: tvvignesh +tags: [graphql, graphql-federation] +date: 2020-12-03 +description: + The GraphQL Stack - Part 1. Choosing the right tech stack for your use case and building the + development workflow which suits you best in this blog. +--- + +_This blog is a part of a series on GraphQL where we will dive deep into GraphQL and its ecosystem +one piece at a time_ + +- Part 1: [**Diving Deep**](/blog/graphql-deep-dive-1) +- Part 2: [**The Usecase & Architecture**](/blog/graphql-deep-dive-2) +- Part 3: [**The Stack #1**](/blog/graphql-deep-dive-3) +- Part 4: [**The Stack #2**](/blog/graphql-deep-dive-4) +- Part 5: [**The Stack #3**](/blog/graphql-deep-dive-5) +- Part 6: [**The Workflow**](/blog/graphql-deep-dive-6) + +Now that we have discussed GraphQL, and also about some architectural considerations when starting +off, let's look at the next important step in the puzzle — choosing the right tech stack for your +use case and building the development workflow which suits you best in this blog. + +Technology changes and evolves constantly as we have already seen it happening all these days. So, +rather than worrying too much about the technology you choose, it is better to choose a tool, +library or platform which allows for incremental changes without lockin. Using the list in the +[**previous blog post**](/blog/graphql-deep-dive-2) might actually help in your decision-making +process. + +But, today I am going to assume a tech stack (the GraphQL Tech Stack that I work with every day to +build [Timecampus](https://twitter.com/timecampus)) and walk you through. The reason I say +**"GraphQL" Tech Stack** is because, this is just a part of the complete stack I use and there is +more to it which we will discuss sometime down the line in a different blog. + +**NOTE:** While these work great for me, this is an area of continuous exploration for me, and I +don't mind replacing X with Y as long as the effort is really worth it from a future perspective (we +will explore more on what they are and why we use these as we go along). With that, let's start. + +[**VSCode**](https://code.visualstudio.com) + +There is no doubt that VSCode has become the defacto editor which developers user these days. And it +definitely deserves the recognition and credit it gets. VSCode comes with amazing extensions and +tooling for GraphQL and its ecosystem built by the community and if you work with GraphQL and +Typescript, I would say it is pretty much a standard editor which you would definitely want to use. + +For instance, just do a search for “GraphQL” in the marketplace, and this is what you get: + +![Image for post](./1.png) + +and the ecosystem is growing even more every day and this makes VSCode indispensable for our stack. + +[**GraphQL Config**](https://graphql-config.com) + +GraphQL Config acts as a single configuration point for all that we do with GraphQL. This is +important because when working on projects, it is important to have little to no repetition (DRY +principle) and having a separate config file for every tool will start getting overwhelming and +messy over time since we will have multiple places to maintain. + +We can specify all that we want regarding GraphQL in a single `.graphqlrc` file as mentioned in the +docs starting from the location to the schema, the GraphQL documents (queries and mutations), and +also the configuration for extensions which we use with it. + +Not just this, a single `.graphqlrc` file can be used to specify all the configuration you need for +multiple projects that you use in your workspace. + +For eg. it can integrate with our VSCode GraphQL extension to provide autocompletion, intellisense +and so on, provide all the config needed to do code generation with GraphQL codegen, linting with +GraphQL ESLint and can also pave way to all the other tools we may integrate in the future. + +A `.graphqlrc.yml` file may look something like this: + +![Image for post](./2.png) + +GraphQL Config Snippet + +[**VSCode GraphQL**](https://github.com/graphql/vscode-graphql) + +The next thing which comes to mind is a VSCode extension which can provide the support for all the +things you need to do with GraphQL. Originally developed by the amazing people at +[**Prisma**](https://prisma.io) this extension was later donated to the +[**GraphQL Foundation**](https://foundation.graphql.org) and the reason this extension is really +promising is because, it provides everything you need to work with GraphQL including syntax +highlighting, autocompletion, validation, SDL navigation, execute, operations, support for tagged +template literals and all of this with support for GraphQL Config and it works great. + +**NOTE:** If you are using the Apollo Stack (like +[Federation](https://the-guild.dev/graphql/hive/federation)), I would recommend you to go with +[**Apollo VSCode**](https://apollographql.com/docs/devtools/editor-plugins) instead since it +provides support for things like `apollo.config.js` (which integrates with the schema registry), +federation directives and so on. + +[**GraphQL ESLint**](https://github.com/dimaMachina/graphql-eslint) + +The next thing which is important when you work with GraphQL as a team is following a set of +standards so that everyone is on the same page. This is where using a linter like GraphQL ESLint +would really help. The beauty is that it integrates seamlessly with GraphQL Config, supports ESLint +natively and also provides some +[**inbuilt rules**](https://github.com/dotansimha/graphql-eslint/blob/master/docs/README.md) which +is a great start to work with like consistent case, making naming of operations mandatory, forcing a +deprecation reason and so on which can be of great use as you scale up with GraphQL. + +A sample `.eslintrc` file to be used for GraphQL ESLint would look something like this: + +![Image for post](./3.png) + +GraphQL ESLint snippet + +[**GraphQL Inspector**](https://graphql-inspector.com) + +How do you make collaborating with GraphQL very easy? And how do you do this in such a way that you +have all the information you need to take a specific action? What if there are breaking changes to +your schema? Errors and issues may creep in anywhere and at anytime. + +This is where GraphQL inspector comes in. It provides a platform with various functionalities like +schema validation, coverage, finding similar operations, inspecting the difference between different +versions of the schema, mock your schema with test data and also a Github application to do all this +for you when you raise a pull request. + +For eg. this is how finding the coverage of your operations against the schema looks like: + +![Image for post](./4.png) + +GraphQL Coverage + +And if you want to find similar fields/types within your schema, this is how it will look like: + +![Image for post](./5.png) + +GraphQL Similarity + +[**TypeScript**](https://typescriptlang.org) + +When I initially started off with Typescript few years ago, I was not sure of the advantages it +would provide me over time for the effort I am putting in to make the code I write completely typed. +To be honest, it takes a lot of effort and sometimes can be painful. But, this perception changed +over time especially when I started working with GraphQL and TypeScript. + +The reason GraphQL works great with TypeScript is mainly because of a lot of similarities between +them with both being strongly typed, providing a clear path to documentation, offering great +validations and also a great ecosystem built both on top of TypeScript and GraphQL. + +This will become more evident as we go through this blog. But, writing the types manually for each +and every field in the schema or for every operation and keeping them updated can be a huge task. +This is where a lot of amazing tools come in like GraphQL Codegen, Typed Document Node, TypeGraphQL +and so on. + +And on top of this, the beauty is that, with GraphQL and Typescript, we can actually make the +end-end stack fully typed (which is what we do at [Timecampus](https://twitter.com/timecampus)). And +after seeing all this happening, even `graphql-js` is on its +[path to migration with Typescript](https://github.com/graphql/graphql-js/issues/2104). + +[**Graphql Helix**](https://github.com/contrawork/graphql-helix) + +There are a lot of GraphQL servers out there. And we even spoke about some of those in our +[**first blog post**](/blog/graphql-deep-dive-1). While it is not necessary to pick an +out-of-the-box GraphQL server since you can build your own using `graphql-js` , it may not be a +smart choice since you might not want to reinvent the wheel. + +This is where I use GraphQL Helix which provides me a GraphQL server and also the option to +selectively replace any module that I need to work for your use case. This is very evident from the +[**examples' folder**](https://github.com/contrawork/graphql-helix/tree/master/examples) of the +repository demonstrating various use-cases like subscriptions, csp, graphql-modules, +persisted-queries and so on and also with various frameworks like express, fastify, koa. + +And since there are no outside dependencies except for `graphql-js` there is also no bloat to the +same unlike other graphql servers. If you want to see how other GraphQL servers perform, you might +want to have a look at [this](https://github.com/benawad/node-graphql-benchmarks). + +[**GraphQL Codegen**](https://graphql-code-generator.com) + +We did discuss how Typescript and GraphQL works seamlessly well with each other. But what if we can +generate all that we can from our SDL which provides majority of the information that one needs +including name of the schema, fields, types, and so on. + +And this is where GraphQL Codegen plays a major role. You can generate all the types, interfaces and +so on, and it also comes with a lot of +[**plugins** and **presets**](https://graphql-code-generator.com/plugins) to help you work with not +just Typescript, but also other languages and tooling. All we have to do is import the type we need +and just use it making it really simple. And every time we change the schema, we can just regenerate +the types. Also, it integrates seamlessly with GraphQL Config making it really easy to maintain. + +For eg. this is how the generated types look like: + +![Image for post](./6.png) + +There are more tools, libraries and platforms we have to talk about as part of our GraphQL Stack, +and we will be continuing our discussion in the next blog post. Hope this was insightful. + +If you have any questions or are looking for help, feel free to reach out to me +[@techahoy](https://twitter.com/techahoy) anytime. + +And if this helped, do share this across with your friends, do hang around and follow us for more +like this every week. See you all soon. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/1.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/1.png new file mode 100644 index 00000000000..0153c97b590 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/1.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/10.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/10.png new file mode 100644 index 00000000000..c06bcaa5618 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/10.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/2.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/2.png new file mode 100644 index 00000000000..51a0986a2c3 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/2.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/3.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/3.png new file mode 100644 index 00000000000..afd907e66f6 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/3.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/4.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/4.png new file mode 100644 index 00000000000..21d86608d64 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/4.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/5.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/5.png new file mode 100644 index 00000000000..f31c680c758 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/5.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/6.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/6.png new file mode 100644 index 00000000000..5b7d4c1d8c6 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/6.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/7.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/7.png new file mode 100644 index 00000000000..77f90589629 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/7.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/8.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/8.png new file mode 100644 index 00000000000..8984103ef4d Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/8.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/9.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/9.png new file mode 100644 index 00000000000..12dd9cf336f Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/9.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/page.mdx new file mode 100644 index 00000000000..46797e0eabe --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-4/page.mdx @@ -0,0 +1,311 @@ +--- +title: 'GraphQL - The Stack - #2' +authors: tvvignesh +tags: [graphql, graphql-federation] +date: 2020-12-04 +description: + The GraphQL Stack - Part 2. GraphQL Modules Preset with Codegen since it essentially also splits + your types and generates types. +--- + +_This blog is a part of a series on GraphQL where we will dive deep into GraphQL and its ecosystem +one piece at a time_ + +- Part 1: [**Diving Deep**](/blog/graphql-deep-dive-1) +- Part 2: [**The Usecase & Architecture**](/blog/graphql-deep-dive-2) +- Part 3: [**The Stack #1**](/blog/graphql-deep-dive-3) +- Part 4: [**The Stack #2**](/blog/graphql-deep-dive-4) +- Part 5: [**The Stack #3**](/blog/graphql-deep-dive-5) +- Part 6: [**The Workflow**](/blog/graphql-deep-dive-6) + +In the [previous blog](/blog/graphql-deep-dive-3), we had started going through "The GraphQL Stack" +that we use at [Timecampus](https://twitter.com/timecampus) going through various libraries and +tools like [VSCode](https://code.visualstudio.com), [GraphQL Config](https://graphql-config.com), +[VSCode GraphQL](https://github.com/graphql/vscode-graphql), +[GraphQL ESLint](https://github.com/dimaMachina/graphql-eslint), +[GraphQL Inspector](https://graphql-inspector.com), [TypeScript](https://typescriptlang.org), +[GraphQL Helix](https://github.com/contrawork/graphql-helix) and +[GraphQL Codegen](https://graphql-code-generator.com). In this blog, we will continue our journey +exploring from where we left off. + +Before we continue, one thing I have to say is that the GraphQL ecosystem is so huge and growing +that it is not feasible to look at everything available out there in this GraphQL series, but one +thing we are sure of is that, this can indeed put you a few steps ahead in your journey with GraphQL +and its ecosystem. With that disclaimer, let's start. + +**[GraphQL Modules](https://graphql-modules.com)** + +As we have discussed before, GraphQL does act as a single entry point for all your data giving a +unified data graph which can be consumed by any client which is really powerful. But this does not +mean that you have to mix up all your code in one place making it really difficult to manage. + +As people have already found, both +[Microservices](https://microservices.io/patterns/microservices.html) and +[Monolithic](https://microservices.io/patterns/monolithic.html) architectures comes with its own set +of advantages and challenges and what you go for completely depends on your use case, the scale you +need, your team and talent pool. + +But this does not mean that you should not keep your application non-modular irrespective of the +architecture you go for. Having clear responsibilities, separation of concerns and decomposing your +application into modules gives you great flexibility, power and makes your application less +error-prone because you just do one thing, but you do it well. + +Now, this is where GraphQL Modules really comes in. Yes, you can have your own way of organizing the +code, your own way to pull in the schemas, your own set of tools and so on, but you don't have to +reinvent every wheel there is. + +It helps you decompose your schema, resolvers, types and context into smaller modules with each +module being completely isolated from each other, yet being able to talk to each other. And this +becomes even more powerful as you scale since it comes with concepts like Dependency Injection +allowing you to specify your own providers, tokens, scope and so on. + +**NOTE:** GraphQL Modules overrides the execute call from `graphql-js` to do all its work. So, make +sure that the GraphQL server you use allows you to override it. + +At Timecampus, we use a microservices' architecture, and every microservice is essentially a +monorepo ([PNPM Workspaces](https://pnpm.js.org/en/workspaces)) by itself covering a specific +Domain. For instance, this is how portion of my directory structure looks like. If you notice, I am +able to split every Microservice into multiple modules like this which allows me to manage the code +better. + +![GraphQL Modules Directory Structure](./1.png) + +And this is how a simple provider looks like. If you notice, this makes it very simple to +comprehend. The convention I use is that, I try to group CRUD operations into a single module but it +need not call for a separate microservice all by itself. + +![Sample GraphQL Provider](./2.png) + +And your Mutations become as simple as this, calling the injector, doing the operations and +returning the results: + +![](./3.png) + +And finally all you have to do is compose the schema and resolvers from all the modules in your +server giving a unified GraphQL endpoint you can use. + +Now, this becomes even more powerful if you use the +[**GraphQL Modules Preset**](https://graphql-code-generator.com/docs/presets/graphql-modules) with +Codegen since it essentially also splits your types and generates types for each GraphQL Module +making things even more organized and isolated. + +There is a lot more that we can explore, but I will leave it at this. + +**[GraphQL Mesh](https://graphql-mesh.com)** + +What if you can use GraphQL to do all your operations even when your backend systems, datasources +and the services do not understand GraphQL natively and without spending time converting them to +GraphQL endpoints? And what if you can aggregate and mesh all of them together with GraphQL? This is +where GraphQL Mesh really comes into picture. + +GraphQL Mesh acts as an abstraction layer which can interface with multiple different types of +backends like REST, SOAP, GraphQL, GRPC, OData, Thrift and even databases like MySQL, Neo4j and so +on as documented [**here**](https://graphql-mesh.com/docs/handlers/available-handlers). + +All you need to do is provide a config file `.meshrc.yaml` and it will generate everything for you +and the execution engine will take care of converting your GraphQL queries to native backend +specific queries. + +Think of GraphQL Mesh like a universal ORM not limited to just databases but any data source or +service which produces data and has an execution layer for performing operations on them. + +For eg. you can pass in your OpenAPI spec, and GraphQL Mesh will generate all the necessary things +for you to provide a GraphQL schema which you can use. + +At first, I had to think a bit to see whether GraphQL Mesh is relevant to me, cause my stack +completely uses GraphQL natively anyway (including my data source [Dgraph](https://dgraph.io) which +supports GraphQL Natively) and hence was not sure if it suited my use case. + +But the more I thought about it, I started seeing GraphQL Mesh as an abstraction layer which will +make my stack future-proof irrespective of all the data sources or backends I may add in the future. +And the beauty of it is, there are a lot of ways in which you can use the Mesh (as a separate +service, as a SDK with your service or as a gateway). + +I personally use GraphQL Mesh as a SDK with my services to access the backend data sources running +GraphQL thereby avoiding any bottlenecks if any. And the added advantage you get here is that it +makes all the operations you do fully typed. + +Since I am just in the initial phases of development, this is how my `.meshrc` file looks like where +I interface with Dgraph with GraphQL Mesh + +![GraphQL Meshrc file](./4.png) + +And when I have the SDK generated with GraphQL Mesh, all I have to do is just use the methods the +SDK providers me (based on the GraphQL Mutations and Queries I have provided to it as inputs) like +this: + +![](./5.png) + +Which makes it really powerful to use without worrying about what happens underneath. While there is +a lot we can talk about GraphQL Mesh as well, I will leave it at this for now. + +**[GraphQL Tools](https://graphql-tools.com)** + +When you talk about GraphQL, one simply cannot forget GraphQL Tools irrespective of the architecture +or stack you use. Initially developed by [Apollo](https://apollographql.com) and then taken over by +[The Guild](/), GraphQL Tools provides you a very powerful set of utility functions to work with +GraphQL which you can use in your services irrespective of whether you are using something like +[Apollo Federation](https://apollographql.com/docs/federation) or +[Schema Stitching](https://graphql-tools.com/docs/stitch-combining-schemas). + +It provides you a lot of utility functions which can help you do things like loading a remote +GraphQL schema, merge schemas, mock schema with test data, stitch schemas together with either Type +Merging or Schema extensions, enables you to write GraphQL schema directives and the list goes on. + +And since it is available as scoped packages `@graphql-tools` you can just import only the modules +you want and use it without adding any bloat. + +The reason GraphQL Tools shines is because, it stops you from reinventing the wheel, helping you +focus on the other things which really matter the most in your journey with GraphQL. For eg. if you +see below, I use the functions from GraphQL Tools extensively when I do operations with my schema +like this: + +![GraphQL Tools operations](./6.png) + +And it also helps me write my own directives like this: + +![GraphQL Directives](./7.png) + +And since I have recently moved from [Federation](https://the-guild.dev/graphql/hive/federation) to +Stitching, I am also starting to use +[**Typemerging**](https://graphql-tools.com/docs/stitch-type-merging) from GraphQL Tools to have my +GraphQL Gateway setup as well like this: + +![GraphQL Gateway](./8.png) + +If you are new to schema stitching with Typemerging, I would recommend you check out +[**this repository**](https://github.com/gmac/schema-stitching-demos) from Greg where he does a +great job of explaining all the concepts. + +**[Typed Document Node](https://github.com/dotansimha/graphql-typed-document-node)** + +Typed Document Node holds a special place in my heart cause it was only after coming across this +project that I started understanding the power of marrying GraphQL and Typescript together (I had +ignored Codegen and all the related tooling before coming across this since I did not understand the +importance of it back then). + +Typed Document Node does a simple job of converting your GraphQL documents to Typescript +DocumentNode objects irrespective of whether it is a query, mutation, subscription or fragment. You +can have Codegen generate all the Typed Document Node types for you when you work. + +And the reason it is really good is cause, it works well with other libraries like `@apollo/client` +where you can pass a TypedDocumentNode object generated from your GraphQL operations and the results +will also be fully typed, thus helping you to stop worrying about manually typing your GraphQL +requests. + +For eg. this is how I use TypedDocumentNode to have all my GraphQL operations typed when calling +`@apollo/client/core` in my app. + +![Typed Document Node Example](./9.png) + +All I had to do is pass the document which was generated and if you notice, even my response is +fully typed. + +And this is how the generated Document Nodes look like: + +![Typed Document Node](./10.png) + +Initially I had it running on both the server and the client side but then removed it from the +server side since the SDK from GraphQL Mesh was already doing this job for me. + +There are also plugins like +[**TypeScript GraphQL-Request**](https://graphql-code-generator.com/docs/plugins/typescript-graphql-request) +available when using Codegen which generates an SDK out of GraphQL operations. While I haven't tried +it, I did not opt for it cause I did not want to get coupled to the `graphql-request` library, and +also this was fitting my use case pretty well. + +**[Dgraph](https://dgraph.io)** + +While Dgraph is not necessarily relevant to anyone and everyone and definitely not for legacy +systems, it is of real relevance and significance for us as we work on +[Timecampus](https://twitter.com/timecampus). Dgraph is a scalable and distributed Graph database +written in [**Golang**](https://golang.org) which understands **GraphQL** natively (while it also +has its own query language as well called [**DQL**](https://dgraph.io/docs/dql) which is a +modification of the GraphQL spec to support database specific optimizations). + +As I was building the product, I started off with [**Postgres**](https://postgresql.org) with +[**Prisma**](https://prisma.io) as my ORM. But as I thought more and more and was writing code, I +started noticing a few things. + +- All the entities were increasingly getting connected to each other to various kinds of + relationships +- Initially I was paranoid, and I had a single Postgres database instance for every microservice + following the microservices' architecture conventions, and thus I was left with isolated pools of + datasets which led me to manually do a lot of cross-service calls to get data from the other + databases in case I wanted to relate them +- I had to clearly know which database instance had a respective schema before even making the call + from a service. Hence, things were no longer an implementation detail +- Since I was using Prisma with Postgres (and believe me, Prisma was really amazing to work with), I + also had to manage things like + [**Migrations**](https://prisma.io/docs/concepts/components/prisma-migrate), rolling them back and + forth and also do this in the CI/CD pipelines which was adding more complexity + +Now, there were a lot of other challenges I was facing other than this, but a few things I quickly +realized is that: + +- Almost all the data is connected in some way or the other (or at least the majority was) +- Splitting databases to multiple isolated instances per microservice was just adding more and more + complexity and the effort was not worth according to me +- A database like Postgres (or even other like MySQL, MSSQL) was not originally designed for a + microservices-like architecture (while it definitely works well with it). This makes things like + horizontal scaling across multiple nodes difficult to do (while definitely possible with hacks) +- Also, since I ran my entire stack on Kubernetes, I was also looking for a database with Cloud + Native support + +While I was aware of Graph databases before, a lot of the Graph databases are meant just for storing +the edges and vertices (i.e. the relationships between various nodes) and traversing through them +but does not have support for storing the data in itself for which I have to opt in for another +database to read/write the data. This adds a lot of complexity to everything and you have to keep +both in sync as well which makes it really hard to do. + +Now, Dgraph solves all these problems (and the awesome part as I already told you are that it +supports GraphQL natively which gives me the ability to use all the GraphQL tools with it) . + +While they also offer a hosted solution called [**Slash GraphQL**](https://dgraph.io/slash-graphql), +I opted in for hosting Dgraph Open Source on my own since I wanted to support any environment be it +hybrid cloud or on premise, wanted to have the data as close to me as possible to offer compliance. + +Since it exposes a GraphQL endpoint, I also run the Mesh SDK/Codegen on it, and it gives me +completely typed database operations with the SDK as I mentioned above. + +And the only tool I need to interact with it is a GraphQL client like Insomnia or VSCode Rest Client +(While it does expose its own client called **Ratel** for doing DQL operations and managing the +database). Moreover, the database schema is nothing but a GraphQL schema. So, I had no learning +curve as well. + +And another beautiful thing I liked about it is that, I need not worry about scalability anymore +since it can be horizontally distributed, across multiple nodes or containers in my Kubernetes +Cluster and scaled up/down, and it can handle everything exposing a single GraphQL endpoint without +me having to setup a single database per microservice. + +A single Graph Database instance per microservice did not make sense for me since it will +effectively split the Graph into multiple pieces and the whole point of having a completely +connected database graph would be lost. + +Also, the [feature set was quite promising](https://dgraph.io/compare-features) when comparing other +graph databases and the +[benchmarks were also quite promising](https://dgraph.io/blog/post/benchmark-neo4j) when comparing +the likes of Neo4j, but there is definitely a +[counter argument for that](https://github.com/neo4j/neo4j/issues/8684#issuecomment-274762913). + +But the reason I find Dgraph appealing more is cause the underlying store is +[Badger](https://github.com/dgraph-io/badger) which is made using Golang and hence does come with +its own set of advantages and performance gains. On top of this, +[Dgraph is not the only store which uses badger](https://dgraph.io/docs/badger/projects-using-badger) +which makes it even more exciting to use. + +**Disclaimer:** I don't have experience running Dgraph in production (since we are on our way to +launch), but there are [definitely others who have done it](https://dgraph.io/case-studies). + +Now the reason, I added Dgraph to this stack was that Dgraph offers a great GraphQL native solution +for databases. But if you are looking to go for Neo4j, it does offer a +[GraphQL adapter](https://neo4j.com/labs/grandstack-graphql) too. + +Well, the discussion doesn't end here and there is a lot more we can talk about with respect to +GraphQL and its ecosystem. We will continue in the next blog post. Hope this was insightful. + +If you have any questions or are looking for help, feel free to reach out to me +[@techahoy](https://twitter.com/techahoy) anytime. + +And if this helped, do share this across with your friends, do hang around and follow us for more +like this every week. See you all soon. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/1.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/1.jpg new file mode 100644 index 00000000000..8d5584db1b2 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/1.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/3.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/3.png new file mode 100644 index 00000000000..4995398dbfb Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/3.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/4.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/4.png new file mode 100644 index 00000000000..9617ae2def4 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/4.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/5.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/5.png new file mode 100644 index 00000000000..a0cea79fe09 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/5.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/6.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/6.png new file mode 100644 index 00000000000..e302082235d Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/6.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/7.png b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/7.png new file mode 100644 index 00000000000..3c6825ba235 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/7.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/page.mdx new file mode 100644 index 00000000000..233e4124d12 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-5/page.mdx @@ -0,0 +1,293 @@ +--- +title: 'GraphQL - The Stack - #3' +authors: tvvignesh +tags: [graphql, graphql-federation] +date: 2020-12-06 +description: + The GraphQL Stack - Part 3. In this blog looking at more such tools and services which have + created a great impact in the GraphQL ecosystem. +--- + +import { VideoEmbed } from '#components/video-embed' + +_This blog is a part of a series on GraphQL where we will dive deep into GraphQL and its ecosystem +one piece at a time_ + +- Part 1: [**Diving Deep**](/blog/graphql-deep-dive-1) +- Part 2: [**The Usecase & Architecture**](/blog/graphql-deep-dive-2) +- Part 3: [**The Stack #1**](/blog/graphql-deep-dive-3) +- Part 4: [**The Stack #2**](/blog/graphql-deep-dive-4) +- Part 5: [**The Stack #3**](/blog/graphql-deep-dive-5) +- Part 6: [**The Workflow**](/blog/graphql-deep-dive-6) + +In this series, we had looked at some interesting parts of the GraphQL stack so far with a range of +tools, libraries and frameworks from the community. Let us continue the journey in this blog looking +at more such tools and services which have created a great impact in the GraphQL ecosystem. + +**[GraphiQL](https://github.com/graphql/graphiql)** + +The evolution of GraphQL clients have been really amazing, and I would say, this is one of the great +things about GraphQL given its powerful introspection capabilities, being self documenting and also +providing ability to extend everything with extensions. + +It all started with GraphiQL demonstrating all these back in the day, but then came Playground +([which had recently merged with the GraphiQL team](https://foundation.graphql.org/news/2020/04/03/web-based-graphql-ides-for-the-win-how-why-playground-graphiql-are-joining-forces) +to make things even more interesting), [Altair](https://github.com/imolorhe/altair) and even +desktop/web/editor based clients like [Insomnia](https://insomnia.rest), +[Postman](https://postman.com/graphql), [Hoppscotch](https://hoppscotch.io), +[VSCode Rest Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) and the +list goes on all proving that the developer experience with GraphQL can be made really better with +just some sugar on top. + +But, some reasons why thinking about the future of GraphiQL feels really great is cause of the +[upcoming support for Monaco mode](https://github.com/graphql/graphiql/issues/1445) , +[support for plugins](https://github.com/graphql/graphiql/issues/983) and a lot of amazing features +from Playground to now become as part of GraphiQL as part of the transition according to the blog +linked above. + +Also, embedding a GraphiQL editor is as simple as importing the HTML and related assets as specified +in their [README](https://github.com/graphql/graphiql/blob/main/packages/graphiql/README.md). + +And while the user experience is made as simple as possible, there are a huge number of components +which make it all happen behind the scenes as mentioned in the README and you can have a look at all +of them in the monorepo [here](https://github.com/graphql/graphiql/tree/main/packages) and +[here](https://github.com/graphql/graphql-js/tree/master/src). + +![GraphiQL](./1.jpg) + +Source: GraphiQL + +[Codemirror](https://github.com/graphql/graphiql/tree/main/packages/codemirror-graphql) used to be +the interface which used to provide the editor support for GraphiQL, Playground, Insomnia and other +editors in the ecosystem in `1.x` which is now being succeeded by the +[language service](https://github.com/graphql/graphiql/tree/main/packages/graphql-language-service) +which takes care of providing a web/desktop IDE experience if you are using editors like VSCode, +[Language Parser](https://github.com/graphql/graphiql/tree/main/packages/graphql-language-service-parser) +which takes care of parsing the GraphQL SDL and operations you write and convert them to GraphQL AST +(If you are curious about how the AST looks, like, you can try going to +[ASTExplorer](https://astexplorer.net) select GraphQL, enter your operation and have a look at the +AST which is what the final representation will look like) and so on becoming a platform for not +just GraphiQL but the entire editor ecosystem. + +**[GraphiQL Explorer](https://github.com/OneGraph/graphiql-explorer)** + +Starting with GraphQL or GraphiQL may actually have a learning curve for beginners since it takes a +different approach to dealing with data. And even after people settle down with GraphQL, some people +do feel like life was better for them when they were using something as simple as REST or GRPC. + +This is where tools like GraphiQL Explorer play a major role where all their queries and mutations +can be constructed automatically just by checking all the fields you need from the schema. + +This workflow feels intuitive since it is as simple as checking all the fields you need in your +client. You can read about how Onegraph solves this problem +[here](https://onegraph.com/blog/post/2/how-onegraph-onboards-users-who-are-new-to-graphql) + +It is just a React component which you include with your GraphiQL instance and the rest is history. + + + +**[GraphQL Voyager](https://github.com/APIs-guru/graphql-voyager)** + +The next beautiful tool I would talk about here is the GraphQL Voyager. In fact, this is the first +tool I used when I was new to GraphQL few years back, and it drove me nuts seeing the potential of +what GraphQL can do. + +The reason this is great is because, this leverages the complete power of introspection from +GraphQL. You get to see all the entities and how they are related, search through the schema and +also browse the docs + + + +Source: GraphQL Voyager + +And today, [GraphQL Editor](https://github.com/graphql-editor/graphql-editor) takes this one step +further allowing you to view, edit, browse all the entities and hierarchy making it really a great +tool for anyone who wants to quickly work through the schema. + +**[GraphQL Upload](https://github.com/jaydenseric/graphql-upload)** + +One important thing which GraphQL Spec did not discuss is a way to transmit files over the wire when +using GraphQL. This is where GraphQL Upload comes in. While not an official spec from GraphQL +foundation, Jayden had done a great job to put together a +[multipart spec](https://github.com/jaydenseric/graphql-multipart-request-spec) to address exactly +this problem. + +GraphQL Upload is the library which provides a great implementation of this spec with an ability to +work with various frameworks. One thing to remember is that, while GraphQL Upload definitely does +the job and works well over a significant scale, you might want to stick to HTTP for higher +production workloads because of the +[reasons outlined in this blog](https://apollographql.com/blog/apollo-server-file-upload-best-practices-1e7f24cdc050). + +And if you are using something like a +[GraphQL Gateway](https://the-guild.dev/graphql/hive/docs/gateway) with either +[federated GraphQL](https://the-guild.dev/graphql/hive/federation) or stitching, you might want to +make sure that you don't overload the gateway transmitting files creating probable bottlenecks which +can affect the rest of your requests. So, try striking a balance since GraphQL need not be a +solution for every problem. + +**[GraphQL WS](https://github.com/enisdenjo/graphql-ws)** + +Subscriptions are a powerful part of GraphQL allowing you to track all the operations happening with +the data in near-real time but this mandates the use of a protocol like websockets or use something +like Server Sent Events (SSE). + +While [subscription-transport-ws](https://github.com/apollographql/subscriptions-transport-ws) from +Apollo initially started off this journey, +[it is not actively maintained](/blog/graphql-over-websockets) and GraphQL WS by Denis definitely is +a great replacement to that having no external dependencies and having the ability to work across +many frameworks. + +But do remember that, websocket might loose its lead in the future especially with the introduction +of HTTP/2 and HTTP/3 +[as mentioned here](https://wundergraph.com/blog/deprecate_graphql_subscriptions_over_websockets) +while definitely here to stay. But this wouldn't affect GraphQL in any way since its transport +independent. + +Also note that subscriptions are not the only way to do real time communications in GraphQL. There +are also things like Live Queries with +[great libraries like this](https://github.com/n1ru4l/graphql-live-query) from Laurin which you can +use + +**[Apollo Federation](https://apollographql.com/docs/federation)** + +While Schema Stitching was initially advocated by Apollo with introduction of many helper functions +in GraphQL Tools, their +[direction did change soon](https://apollographql.com/docs/federation/migrating-from-stitching) +after hearing a lot of feedback from their customers and took their call to introduce Apollo +Federation. You can read their reasoning in +[this blog](https://apollographql.com/blog/apollo-federation-f260cf525d21) but this does not mean +that [stitching has lost its relevance](https://github.com/ardatan/graphql-tools/issues/1286) +especially with the introduction of Type Merging. + +Apollo Federation does a great job especially when you use it with the rest of the ecosystem from +Apollo like the [Apollo Studio](https://apollographql.com/docs/studio). Apollo Stack does offer +[a lot of features](https://apollographql.com/pricing) which might be relevant to working with a +data graph in an organization starting from providing a registry where you can upload parts of the +combined schema from all services, version control the changes to your schema validating breaking +changes, providing metrics regarding all the clients consuming the schema, tracing of all +operations, multiple variants to manage multiple environments, alerting across multiple channels, +and a CLI to work with all of these. + +And this can definitely help teams who want to maintain their own part of the schema. + +Federation comes with its +[own specification and directives](https://apollographql.com/docs/federation/federation-spec/#federation-schema-specification) +as part of it which helps people to define all of the relations between multiple GraphQL entities so +that the [Apollo Gateway](https://github.com/apollographql/federation/tree/main/gateway-js) can +combine them all together without having to modify the GraphQL gateway and also functions like +`__resolveReference` which helps in resolving an entity with its reference as specified by the +directives. + +[The Apollo CLI](https://github.com/apollographql/apollo-tooling) when combined with Federation does +come with a lot of helpers to take care of things like pushing the schema, listing the services in +the studio, doing codegen and so on though I am not currently sure why they are +[rewriting it again to Rust](https://github.com/apollographql/rover) apart from the reasons as +suggested [here](https://jakedawkins.com/2020-03-12-learning-rust). + +Let's quickly look at how Apollo Studio lets you manage the schema + +This is how you maintain multiple Data graphs in your organization across environments + +![View Datagraph](./3.png) + +Browse through the schema, its types, documentation and so on + +![Browse Schema](./4.png) + +Track the changelog of your schema over time + +![Schema changelog](./5.png) + +Browse through the SDL of your schema + +![Browse SDL](./6.png) + +Execute GraphQL operations against your schema + +![Run GraphQL Operations](./7.png) + +and does offer a lot more especially when you are a paying customer. + +**NOTE:** +[Federation with Apollo Server does not support subscriptions yet](https://github.com/apollographql/apollo-server/issues/2360), +and you might want to stick with stitching if you are looking for subscriptions support or switch to +some other server like Mercurius +[since it does allow subscriptions over federation](https://github.com/mercurius-js/mercurius/issues/268). + +**[Gatsby](https://gatsbyjs.com)** + +Gatsby is a static site generator powered by React, GraphQL and a lot of plugins contributed by the +community which helps you sites simply by pooling in data from multiple different sources in +multiple different ways, and it really popularized the idea of doing this all via GraphQL. If you +want to know why and how Gatsby uses GraphQL, you can +[give this a read](https://gatsbyjs.com/docs/why-gatsby-uses-graphql). And while Gatsby does offer +both [Server Side Rendering](https://gatsbyjs.com/docs/glossary/server-side-rendering) and +[Static Site Generation](https://gatsbyjs.com/docs/glossary/static-site-generator), I would say it +all boils down to your [use case](https://frontarm.com/james-k-nelson/static-vs-server-rendering). + +While Gatsby did popularize the idea of using GraphQL for static sites, there are a lot of other +static site generators out there like [Eleventy](https://11ty.dev), +[Jekyll](https://github.com/jekyll/jekyll), [Hugo](https://gohugo.io), etc. and I find myself +personally aligning towards Eleventy because of quite a few reasons which may not be right for this +blog. But if you are curious, you can read blogs like [this](https://mtm.dev/static) and +[this](https://snipcart.com/blog/choose-best-static-site-generator) which gives a comparison. + +**[Opentelemetry - GraphQL](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/master/plugins/node/opentelemetry-instrumentation-graphql)** + +Opentelemetry is the new standard for instrumentation (especially after +[Open Tracing and Open Census merging](https://medium.com/opentracing/a-roadmap-to-convergence-b074e5815289) +together) and this makes things really amazing for people since there were quite a few overlap +before in between them which can now be avoided to bring about a powerful tracing standard. + +Opentelemetry is not specific to any language or implementation, and you can find all the amazing +projects from Open Telemetry hosted [here](https://github.com/open-telemetry) + +Now, the exciting thing is that there is now a reference implementation to the same using GraphQL +which you can find +[here](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/master/plugins/node/opentelemetry-instrumentation-graphql) +and also an example to help you out with the same +[here](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/master/examples/graphql) + +This when used with [Jaeger](https://jaegertracing.io), [Zipkin](https://zipkin.io) or +[Tempo](https://grafana.com/oss/tempo) can provide you with Traces for your GraphQL operations which +you can track across your resolvers. Do note that it is not advisable to be turned on for everything +since it has a performance overhead. + +This can give you a context on how your data and context flow irrespective of your architecture in +your resolvers and functions. + +**[GraphQL Faker](https://github.com/APIs-guru/graphql-faker)** + +[Faker.js](https://github.com/Marak/faker.js) has been a great project to quickly generate mock or +sample data providing various types of entities inbuilt. For eg. you can generate random addresses, +images, URLs and so on, helping you to quickly test out your application without relying on the +server or the backend to hold data. + +This has become even more amazing with GraphQL Faker since it allows you to use all the great things +which Faker provides you with directives. Just define what data you want a specific field to +generate by specifying the relevant directives and GraphQL Faker can actually generate all the data +for you using `Faker.js` + + + +Source: GraphQL Faker + +If you are using `@graphql-tools` you can also use `faker.js` directly and combine it with +[Mocking](https://graphql-tools.com/docs/mocking) to get similar results, but without the need to +change your SDL. + +While there are a lot of other tools we can discuss, the GraphQL ecosystem is huge and this pretty +much has no end. But I do presume that these are all the tools you mainly need to start your GraphQL +journey and leverage the ecosystem in the best way possible. + +But with this the GraphQL journey is still not over. We will continue the next blog discussing a few +more interesting things as part of the GraphQL series. + +Is there anything you would like to see me address in this series? Do let me know, and we can +probably do that in an another post. + +If you have any questions or are looking for help, feel free to reach out to me +[@techahoy](https://twitter.com/techahoy) anytime. + +And if this helped, do share this across with your friends, do hang around and follow us for more +like this every week. See you all soon. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-6/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-6/page.mdx new file mode 100644 index 00000000000..4cb7624179d --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-deep-dive-6/page.mdx @@ -0,0 +1,264 @@ +--- +title: GraphQL - The Workflow +authors: tvvignesh +tags: [graphql, graphql-federation] +date: 2020-12-12 +description: The Workflow when taking GraphQL from development to production. +--- + +_This blog is a part of a series on GraphQL where we will dive deep into GraphQL and its ecosystem +one piece at a time_ + +- Part 1: [**Diving Deep**](/blog/graphql-deep-dive-1) +- Part 2: [**The Usecase & Architecture**](/blog/graphql-deep-dive-2) +- Part 3: [**The Stack #1**](/blog/graphql-deep-dive-3) +- Part 4: [**The Stack #2**](/blog/graphql-deep-dive-4) +- Part 5: [**The Stack #3**](/blog/graphql-deep-dive-5) +- Part 6: [**The Workflow**](/blog/graphql-deep-dive-6) + +What an interesting journey has it been so far! We explored some amazing libraries, tools and +frameworks which really empowers GraphQL to be what it is today with almost everything being open +source created with love from the community. But, I do understand that, this can actually be +overwhelming for some of you who are just starting off this journey with GraphQL and may have some +trouble putting it all together to work for you. + +To address this, we will be talking about the workflow with GraphQL and the tools we have looked at +so far and the process of taking it from development to production in this blog. + +**NOTE:** While these steps have been ordered serially, that is just to give you a sense of +understanding of the workflow. Some of these can also be deferred for later or parallelly done if +working with multiple teams. So, with that in mind, let's start. + +**Step 1: Evaluation and Research** + +As we have discussed before in [this blog post](/blog/graphql-deep-dive-2), GraphQL may not be the +solution to every problem. And even if it does satisfy your use case well, this would be the first +thing you would need to do. Understand why you want to use GraphQL, how you would like to use it and +the ecosystem of tools you need to solve your problem. This can be done only if you introspect your +use case and really get to the basics answering few obvious questions around GraphQL and your use +case. + +Also, you have to remember that not every organization is operating at the scale of Google, +Microsoft or Facebook and what works for them need not work for you. So, while you should definitely +be informed on how people do things, do remember that you need to focus on what works for you and +what you really need. + +**Step 2: Get a boilerplate stack ready** + +GraphQL can get overwhelming if you are re-inventing the wheel every time. From putting together +your schema, resolvers, server, to the various tools you would typically use with it like linting, +codegen and so on. Now, doing this every time you work on a new service is not a good use of your +time. + +The best way to avoid this is to put together a boilerplate with all that you would typically want +to use and this can become a starting point of all the services you may develop in the future. This +would also involve things like setting up your GraphQL Gateway (incase you are using something like +[Federation](https://the-guild.dev/graphql/hive/federation) or Stitching) since the gateway becomes +the single point of contact for all your requests from the clients. + +Now if you are using something like Typescript/Javascript, the tooling you might want to start off +with this stage would typically be a GraphQL server like Express GraphQL, Apollo, Helix, Mercurius +or anything else which might work for you, putting together something like GraphQL Modules if you +are looking to split your resolvers into multiple modules, devising a mechanism to merge together +multiple GraphQL schema as the need arises with something like GraphQL Tools, setting up GraphQL +Config to help all the tools work in tandem with your schema, getting Codegen and its +extensions/presets setup so that you can re-use the types as generated from your schema, getting +ESLint setup with your own validation rules, having something like GraphQL Inspector ready so that +you can do various operations with your schema like validation, mocking and everything you would +typically want as part of your tooling and even having your editor/IDE setup with appropriate +extensions and tools to help you with the development process. + +While you can definitely iterate with this as you go along, having the barebones when you start can +definitely take a lot of effort away and save a lot of time in the future. + +**Step 3: Putting together the data graph and documentation** + +All that you do with GraphQL for your use case mainly revolves around your schema and its types +since that becomes the base of everything you would develop on top. Getting your data graph ready +would typically be the next important step and the way you do it does not matter. You can either go +the SDL-first or code-first route depending on what works well for you. + +You might also want to write appropriate documentation parallelly as you work on your schema, +especially since GraphQL is self-documenting, and it is always good to do it when you have a context +of what you are doing rather than as an after-thought. + +Now, if you are working on a microservices architecture and you are looking to split the data graph +into multiple parts to be composed or stitched from multiple services, using something like +Federation or Stitching, you would also need to understand the clear boundaries of the microservices +and how all of them relate to each other through the data graph. + +These boundaries will also decide which service hosts your resolvers/logic to go along with +resolving the various fields in the schema and performing the business logic as needed in isolation. + +**Step 4: Deploying it all as per the need** + +Now that you have your boilerplate and data graph ready, the next step you would typically do before +working on your resolvers or any of your business logic is to actually deploy it all wherever you +want to, and the way you are looking to do it. Be it public cloud, private cloud or on-premise as +containers, VMs or bare metal. + +Doing this will help you proceed forward as per your architecture be it single/multi-tenant and help +you resolve the major questions you might have regarding the end-end flow of data considering all +the compliance policies and laws you might want to cater to. + +**Step 5: Mocking and Testing Client Consumption** + +Now that you have everything deployed and ready, the next step you would typically do is testing it +all together. Now, you might wonder how this will even work without any resolvers, or a backend to +serve the data with. + +While you can definitely spend your time writing the resolvers, business logic or connecting your +backend, you first might want to test out the end-end data flow so that you get a validation on how +clients would typically interact with your GraphQL API. To do this, you can either mock your schema +or hard code the data initially in your resolvers and then serve the schema and test it all end-end. + +This will establish a confidence about your development workflow, give you a clear idea about the +data path, how your GraphQL operations (Mutations and Queries would look like), an insight into how +you can consume the data and also presents you with opportunities doing things like end-end type +checking, code generation and so on with your clients. + +**Step 6: Getting the resolvers and backend setup** + +As you might already know, with GraphQL, your clients don't have to worry about the data source, +your backend logic and the various complexities that go with it since they are all abstracted away +and this helps you scale the backend and frontend independently of each other. + +To do this, try treating your resolvers as just entities which do an operation and respond back with +data given a set of inputs (similar to what you would typically do with a REST API). So, try setting +up your backend/datasources from which you would want to serve the data (be it a database like +[Postgres](https://postgresql.org) or [Mongo](https://mongodb.com) with or without an ORM like +[Prisma](https://prisma.io), [Knex](https://knexjs.org) or [Sequelize](https://sequelize.org), or +even an underlying resource like a REST API maybe with something like +[GraphQL Mesh](https://the-guild.dev/graphql/mesh) or Graph databases like +[Dgraph](https://dgraph.io)) and also your resolvers to process the data as you see fit, adding your +business logic on top and return back the fields as needed by the resolvers. This is the point where +you replace the mocked data with data from the backend. + +**Step 7: Optimizing the data path** + +Now that you have connected your data sources and added your business logic with all the resolvers +you need, the next step would typically be to optimize the data path to make sure that you are not +doing repeated calls to the database increasing the load and bandwidth usage and reduce the +roundtrips and processing needed as much as possible and also provide faster response times as the +clients ask for data. + +This is where you setup things like batching and also solve N+1 problems with something like a +[dataloader](https://github.com/graphql/dataloader), setup caching with something like +[Redis](https://redis.io) or even an [LRU cache](https://npmjs.com/package/lru-cache) to act as a +proxy for the frequently accessed data whenever and wherever possible, optimizing the network +chatter by using something like persisted queries, optimize your resolvers by retrieving as much +data as possible from the parent resolvers, setting up pagination to limit the results returned, +setting up things like query complexity to control the level of nesting and computation performed, +rate-limiting in the gateway to avoid things like +[DDOS](https://cloudflare.com/learning/ddos/what-is-a-ddos-attack) and so on. + +This is really important cause, while GraphQL might provide your clients with a high degree of +flexibility, it also comes with its own set of risks if not used right. So, try to keep even the +worst case scenarios in mind and design for failures. Do remember that sometimes, it is better to +make your application fail and crash rather than having to make it do the wrong thing. + +**Step 8: Controlling and Securing the data path** + +GraphQL provides all its clients with access to any data as they request it and while this might +sound empowering (and it is), it is not without its own set of risks. You have to make sure that +only the authorized clients have access to the data and only the data which they are allowed to +have, only when they need it providing a proper context and purpose to the operation. + +To do this, all the clients need to be authenticated properly whenever and however needed, +authorization rules needs to be setup for all the fields either via directives, resolvers or any +other mechanism which works well for you, have an encryption/decryption mechanism for confidential +data like PII, ability to blacklist specific clients whenever needed and so on thereby controlling +and securing the data as much as possible from your end considering that security must be a +first-class citizen and not after thought. + +**Step 9: Testing** + +Testing plays a major role especially when building scalable systems which have to be reliable even +with a huge stream of changes which might affect it over time. And this is no exception when you +work with GraphQL as well. You can setup automated tests, integration tests and so on as you +normally would to improve the confidence people have on the system. And there are a lot of libraries +which facilitate the same as well as [Mocha](https://mochajs.org), [Jest](https://jestjs.io), +[AVA](https://github.com/avajs/ava) and so on taking a lot of the burden away from you. + +You can test your resolvers, your GraphQL endpoint, your schema and so on. Testing can not just +improve the reliability of your code, but can also act as a secondary source of documentation for +people who are looking to understand what every function is doing and how to use it as part of their +workflow. So, doing this as you go along can help. + +**Step 10: Automating or Scripting the repeatable parts** + +When you work on GraphQL or anything else for that matter, there is often a set of operations which +you repeatedly do again and again. And over time, the cost of doing it would exponentially grow up. + +For eg. you might push your schema to a [registry](https://the-guild.dev/graphql/hive) if you use +something like [Federation](https://the-guild.dev/graphql/hive/federation) for all the changes you +do, +validate/[lint your schema](https://the-guild.dev/graphql/hive/docs/schema-registry/schema-policy), +do code generation as you change the SDL or anything else which is specific to your use case. This +is where automation and scripting plays a major role and I can definitely say that I have saved +countless hours of my valuable time by just scripting out things which I do repeatedly as part of my +workflow. + +Automation, especially with CI/CD becomes even more impactful when you are working with teams. There +are a lot of interesting things +[you can do in your CI/CD pipeline](https://the-guild.dev/graphql/hive/docs/other-integrations/ci-cd) +like [linting your schema](https://the-guild.dev/graphql/hive/docs/schema-registry/schema-policy) +and +[validating it, getting the list of breaking changes](https://the-guild.dev/graphql/hive/docs/get-started/apollo-federation#schema-checks), +pushing it to the [registry](https://the-guild.dev/graphql/hive/docs/schema-registry), running +automated tests, sending notifications to relevant people in your team as needed and so on saving a +lot of time and also providing a high degree of reliability and confidence to what you ship to +production. + +This is when GraphQL platforms like [Hive](https://the-guild.dev/graphql/hive) can come in handy. +Hive provides you with a lot of tools and integrations covering those workflows, plus it's fully +open-source and can be self-hosted. + +This summarizes the most important steps you need to perform as part of your workflow with GraphQL +and there are somethings which have been purposefully avoided in this list like setting up your +infrastructure for file uploads, enabling real-time data exchange with subscriptions/live queries +and so on since it all depends on your use case at hand but if you are interested in those, do have +a look at our previous blog posts where we discuss various tools and libraries which can help you +with it. + +While all of this may seem overwhelming, you need not boil yourself doing it all when you start but +rather do it incrementally as you go along. + +**But, I am not using JavaScript/Typescript** + +While this series addresses most of the questions with examples from Javascript and Typescript, you +must take into note that Javascript / Typescript is not the only language which GraphQL is +compatible with since it is language independent. And you can always draw parallels in other +languages as well. If you find yourself working in other languages, +[**this**](https://graphql.org/code) might help or if you are looking for tutorials, there is a good +catalog [**here**](https://howtographql.com) and as we discussed before, the ecosystem is too huge +and growing with more like this cropping up everyday. + +**Concluding...** + +As all good things come to an end, this blog would be the last of this GraphQL series. But if you +are looking for something specific which we have not addressed in this series, do let us know, and +maybe we can do a follow-up blog post or even add it to this series if it makes sense. The reason we +conclude here is that we intend to keep this series as a guide rather than a tutorial series since +there is a lot of information already out there regarding the various tools, libraries and +frameworks we talked about in this series. + +But rest assured, we will definitely have a lot of blogs like these in the future as we work with +GraphQL more and more and we also intend to provide you with a case study on how we do all of this +at [Timecampus](https://twitter.com/timecampus) sometime down the line. Do stick around for that. +But in the meantime, there are a lot of other blog posts like [these](https://graphql.org/community) +with blogs, videos and books from the community which is really worth checking out. + +Also, I intend to keep the blog posts in this series as living documents rather than one-off blog +posts. Hence, you might find us updating the information shared if needed over time. + +If you are working your way through GraphQL and if this series really did help you in your path, we +would love to know your story. GraphQL is where it is today because of people like you, the +community, and I am very positive about its present and future especially in a data driven world and +the journey towards bringing about a [semantic web](https://en.wikipedia.org/wiki/Semantic_Web). + +If you have any questions or are looking for help, feel free to reach out to me +[@techahoy](https://twitter.com/techahoy) anytime. + +And if this helped, do share this across with your friends, hang around and follow us for more like +this every week. See you all soon. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-error-handling-with-fp/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-error-handling-with-fp/page.mdx new file mode 100644 index 00000000000..c5c9bb0723e --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-error-handling-with-fp/page.mdx @@ -0,0 +1,854 @@ +--- +title: GraphQL error handling to the max with Typescript, codegen and fp-ts +authors: gthau +tags: [graphql, typescript, codegen, fp-ts, error-handling, best-practices] +date: 2022-03-07 +description: Handle unsafe APIs in a typesafe way with Typescript and Functional Programming. +--- + +import { YoutubeIframe } from '#components/youtube-iframe' +import { Callout } from '@theguild/components' + +Contrary to REST, GraphQL does not use Status Code to differentiate a successful result from an +exception. In REST, one can send a response with a appropriate status code to inform the caller +about the kind of exception encountered, e.g.: + +- `400`: bad request (e.g. invalid inputs) +- `401` or `403`: unauthorized (need authentication) or forbidden (insufficient permission) +- `404`: resource not found +- `500`: internal server error +- and more. + +In GraphQL, we manage errors in a different way. We'll see in this article how to: + +- achieve a better error representation than the default offered by GraphQL +- get type safety using Typescript and GraphQL Code Generator +- handle the errors from lower-level code up to the resolvers +- and finally get additional safety using a functional approach + +All the code examples can be found in this +[repository](https://github.com/gthau/graphql-error-handling-with-union-and-fpts). + +## GraphQL Default Error Handling + +In GraphQL, all requests must go to a single endpoint: + +> POST https://example.com/graphql + +A GraphQL API will always return a `200 OK` Status Code, even in case of error. You'll get a 5xx +error in case the server is not available at all. + + +This is actually a common practice, but it is not mandatory. You could send GraphQL responses with different Status Codes. It would however need to be implemented at the server level and not at the operation resolvers level. + +As an example, you can check out +[graphql-helix implementation](https://github.com/contra/graphql-helix/blob/a796f9d2afdd8c2d91b745edbc53c5d49b716ba6/packages/core/lib/process-request.ts#L92-L281) +of some error cases' responses with different Status Codes, e.g. 400 for GraphQL syntax or +validation error, 405 when using not allowed HTTP method (e.g. GET for mutations or using anything +else than POST or GET for queries). + + + +The standard error handling mechanism from GraphQL with return a JSON containing: + +- a `data` key which contains the data corresponding to the GraphQL operation (query, mutation, + subscription) invoked and +- an `errors` key which contains an array of errors returned by the server, with a message and + location. + +```json +{ + "errors": [ + { + "message": "User not found", + "locations": [{ "line": 6, "column": 7 }], + "path": ["user", 1] + } + ] +} +``` + +This standard error handling is not satisfactory for the many reasons: + +1. it is not easily parseable, it is more meant for a human user to read +2. it is not typed: you can add more information, e.g. an error code and other fields, but those are + not part of the schema +3. it is not self-documented: by looking at the operation signature, you don't know what might fail +4. it treats some domain results as errors / exceptions whereas they are just alternate results of + the operation. E.g. an entity not found for a given id and a user not having permission to access + an entity belong to the domain model, whereas runtime exceptions such as the GraphQL server + failing to communicate with the database / external microservice, or an operation timing out + because of slowness are real exceptions: we don't get any result that relates to domain model, + just an unexpected runtime error. + +## Better Modelisation of Errors Using Union and Interfaces + +GraphQL has support for Union types. We can take advantage of it and design our schema to specify +all possible results: expected and unexpected / error cases. + + +GraphQL Union is available for Types only, not for Inputs. However, the [`oneOf` directive](https://github.com/graphql/graphql-spec/pull/825) will bridge the gap in the future. + +If you use [Envelop](https://envelop.dev) or [GraphQL Yoga](https://graphql-yoga.com) (both +developed by [The Guild](/)), you can already benefit from it today by using this +[Envelop plugin](https://envelop.dev/plugins/use-extended-validation#schema-directive-flow)) + + + +We can then do queries like the following: + +```graphql +query { + entity: entity(id: "10", userId: "u-4") { + ...fields + } + notFound: entity(id: "100", userId: "u-1") { + ...fields + } + notAllowed: entity(id: "1", userId: "u-4") { + ...fields + } + invalidEntityId: entity(id: "1a", userId: "u-4") { + ...fields + } +} + +fragment fields on EntityResult { + __typename + ... on Entity { + id + name + } + ... on BaseError { + message + } +} +``` + +And get the expected result or properly typed alternative results in case of errors: + +```json +{ + "data": { + "entity": { + "__typename": "Entity", + "id": "10", + "name": "Entity #10" + }, + "notFound": { + "__typename": "NotFoundError", + "message": "Entity 100 not found" + }, + "notAllowed": { + "__typename": "NotAllowedError", + "message": "User u-4 isn't allowed to access entity 1" + }, + "invalidEntityId": { + "__typename": "InvalidInputError", + "message": "Input validation failed for fields: [entityId]" + } + } +} +``` + +Leveraging GraphQL Union types for error cases brings many benefits: + +1. Type-safety: the errors are also typed +2. The consumer cannot ignore the errors (the must be handled using inline fragments) +3. Self-documented: the operation signature includes all possible cases (result and errors) + therefore less documentation is required to explain the possible error cases +4. The unexpected results are now just other possible results + +### Implementing Union Types Based Error Handling + +Given the following Schema: + +```graphql +type Entity { + id: ID! + name: String! +} + +type Query { + entity(id: ID!, userId: ID!): Entity +} +``` + +If something fails during the execution, we can: + +- throw an Error and use the default GraphQL error mechanism explained above +- return `null`: this is not satisfactory because we can't know the reason the operation returned + `null` + +Let's instead implement error handling using Union types with the following modifications: + +```graphql +interface BaseError { + message: String! +} + +type InvalidInputError implements BaseError { + message: String! +} + +type NotFoundError implements BaseError { + message: String! +} + +type UnknownError implements BaseError { + message: String! +} + +type NotAllowedError implements BaseError { + message: String! +} + +type Entity { + id: ID! + name: String! +} + +union EntityResult = Entity | NotFoundError | NotAllowedError | InvalidInputError | UnknownError + +type Query { + entity(id: ID!, userId: ID!): EntityResult! +} +``` + +We define a `BaseError` interface that all concrete Errors implement. Then, we define the query +result as a Union type of the expected result (an `Entity`) and the other possible results +(`NotFoundError`, `NotAllowedError`, etc.). In this manner, we extensively describe all possible +results, with the added benefit that we can make our result type non-nullable (with the `!` +character). + +For a more detailed explanation, you can read these articles: + +1. By [Sasha Solomon](https://twitter.com/sachee): + [200 OK! Error Handling in GraphQL](https://sachee.medium.com/200-ok-error-handling-in-graphql-7ec869aec9bc) +2. By [Laurin Quast](https://twitter.com/n1rual): + [Handling GraphQL errors like a champ with unions and interfaces](https://blog.logrocket.com/handling-graphql-errors-like-a-champ-with-unions-and-interfaces) +3. By [Marc-André Giroux](https://twitter.com/__xuorig__): + [A Guide to GraphQL Errors](https://productionreadygraphql.com/2020-08-01-guide-to-graphql-errors) +4. Watch this video by [@notrab](https://github.com/notrab): + + + +### Handle Those Schema Changes into the GraphQL Server + +Since we changed the Schema, we also need to change the query, mutation and types resolvers. + +For simple cases, we can just return the correct object from the query resolver: + +```typescript +const resolvers = { + Query: { + entity: async (parent, { id, userId }, context) => { + const entity = await context.api.getEntityById(id) + const isAllowed = await context.api.isUserAllowedForEntity(id, userId) + + if (!entity) { + return { + __typename: 'NotFoundError', + message: `Entity ${id} not found` + } + } + if (!isAllowed) { + return { + __typename: 'NotAllowedError', + message: `User ${userId} not allowed for entity ${id}` + } + } + return { + __typename: 'Entity', + ...entity + } + } + } +} +``` + +Since we include the `__typename` and the fields match the Schema type definitions, there is no need +to define type resolvers. + +For more details on this solution, you can read this article from +[Laurin Quast](https://github.com/n1ru4l): +[Handling GraphQL errors like a champ with unions and interfaces](https://blog.logrocket.com/handling-graphql-errors-like-a-champ-with-unions-and-interfaces). + +## Typesafe Error Handling on the Client-Side + +Let's now take it to the next level using Typescript and GraphQL Code Generator. + +[GraphQL Code Generator](https://graphql-code-generator.com) is a tool developed by [The Guild](/) +which generates type definitions which corresponds to the GraphQL schema. It has several plugins, +and we'll use 3 of those: + +- [typescript](https://graphql-code-generator.com/plugins/typescript): generates the type + definitions for Typescript +- [typescript-resolvers](https://graphql-code-generator.com/plugins/typescript-resolvers): generates + type definitions for all operations resolvers +- [add](https://graphql-code-generator.com/plugins/add): append/prepend text to the generated type + definitions file, it allows us to `import` some of our types into the generated file + +### Specific Error Classes for All GraphQL Schema Error Types + +First we defined corresponding Error classes for the different error cases: + +```typescript +export class InvalidInputError extends Error {} +export class NotFoundError extends Error {} +export class NoUserRightsError extends Error {} +export class UnknownError extends Error {} +``` + + + When using remote APIs, we often have the possibility to generate the types automatically [from a + JSON schema](https://json-schema.org/implementations.html#code-generation) for REST APIs, [from + protobuf files](https://github.com/improbable-eng/ts-protoc-gen) for gRPC-based APIs, [from a + database schema](https://github.com/SweetIQ/schemats), etc. You might even be using an external + API through an SDK that already provides you with all types. In such cases, the creation of + specialized Error classes is not mandatory. However, it might still be a good idea to do so to + provide application-specific errors rather than bubbling up 3rd-party low-level errors. For such + cases, the upcoming Ecma TC39 proposal for [Error + Cause](https://github.com/tc39/proposal-error-cause) is useful as it allows to chain errors. + Polyfills exist: [Pony Cause](https://github.com/voxpelli/pony-cause) or + [error-cause](https://npmjs.com/package/error-cause). + + + +A small workaround is needed because `graphql-js` treats error-returning and error-throwing the same in resolvers, therefore we need to wrap our Errors before we can safely return them from the resolvers. Let's use a simple custom wrapper type and constructor function: + +```typescript +export type WrappedError = { + _tag: 'WrappedError' + err: NonNullable +} + +export const wrappedError = (err: E) => ({ + _tag: 'WrappedError', + err +}) +``` + + + +### `codegen` Mapping + +GraphQL Code Generator is configured with a `codegen.yml` YAML file. In the `mappers` section, we +map every type of our GraphQL Union type to its corresponding Typescript type. + +```yaml +overwrite: true +schema: '**/*/typedefs.graphql' +documents: null +generates: + src/generated/graphql.ts: + plugins: + - add: + content: '/* eslint-disable */' + - add: + content: "import * as E from './src/errors';" + - 'typescript' + - 'typescript-resolvers' + config: + mappers: + InputFieldValidation: 'E.InputFieldError' + InvalidInputError: 'E.WrappedError' + NotFoundError: 'E.WrappedError' + NotAllowedError: 'E.WrappedError' + UnknownError: 'E.WrappedError' + Entity: './src/model/data/entity.data#Entity' +``` + + +We use the `add` plugin to import the `WrappedError` generic wrapper type and the Errors types. If we don't do that, we would have to define the full path for each mapper, e.g.: + +```yaml +NotFoundError: 'import("./src/errors").WrappedError' +``` + + + +This `codegen` configuration generates the Typescript types from the Schema and the proper +signatures for the operations (query/mutation/subscription) and types resolvers. Now we have static +type safety and type inference and hints in the code editor. + +### Help the GraphQL Engine Decide Which Type to Return + +In the first version of the query resolver, we were returning objects that matched the GraphQL types +and were tagged with the `__typename` so the GraphQL engine could just return those without the need +to have type resolvers. But now, we are returning different objects that will be mapped to the +proper GraphQL type. And those objects are not tagged with `__typename`. So how does the GraphQL +engine know to map the value returned by the query resolver? + +In the `codegen` configuration, we have defined mappers between the GraphQL schema types and the +Typescript types. But this is not used at runtime. This configuration is only used by GraphQL Code +Generator to generate the proper types and resolvers signatures so that you get compile-time type +safety and code editor hints. + +The type resolvers have a special field resolver for this purpose: `__isTypeOf` is a field resolver +function on each of the GraphQL type resolver of the query union type result. This field resolver is +executed on all types that form the query resolver's result Union type: when one returns `true`, the +GraphQL engine will use this type resolver to generate the proper result object. + +### Rewrite the Resolvers to Use the Errors Types and `__isTypeOf` + +Now that we have defined the proper mapping between the GraphQL schema types and the Typescript +types, let's rewrite the resolvers: + +```typescript +const resolvers = { + Query: { + async entity(parent, { id, userId }, context) { + const entity = await context.api.getEntityById(id); + const isAllowed = await context.api.isUserAllowedForEntity(id, userId); + + const error = ( + !entity ? new NotFoundError(`Entity ${id} not found`) : + !isAllowed ? new NotAllowedError(`User ${userId} not allowed for entity ${id}`) : + null + ); + + if (error) { + return wrappedError(error); + } + + return entity; + }, + }, + + Entity: { + __isTypeOf: (parent): parent instanceof Entity, + id: (parent) => parent.id, + name: (parent) => parent.name, + }, + + NotFoundError: { + __isTypeOf: (parent) => parent.err instanceof NotFoundError, + message: (parent) => parent.err.message, + }, + + // same for all other error types: NotAllowedError, InvalidInputsError, UnknownError +}; +``` + +### Query Fields Selection + +Our query can now return several types, therefore we need to adapt the query fields selection and +use fragments: + +```graphql +query entity($id: ID!, $userId: ID!) { + entity(id: $id, userId: $userId) { + __typename + ... on Entity { + id + name + } + ... on BaseError { + message + } + } +} +``` + +A good practice is to always have a fragment that matches the `BaseError` interface: it makes the +client code future-proof in case one more error is added to the Union result type. + +## Even Safer Error Handling Using Functional Programming Paradigm + +The previous section was dedicated to showing how to get type safety and inference in resolvers and +how to generate the proper GraphQL type from a query resolver Union type value. + +The query resolver code above to get an Entity and check if User is allowed is oversimplified. In +real-life, we would probably get this data from one or two data sources (database, distributed +cache) or even another external microservice with a REST or gRPC interface. Those data fetching +calls might throw errors, or return `null`. + +We want to handle those errors, transform meaningless and unsafe `null` values into meaningful +errors, while also being able to bubble up those errors to the query resolvers in a safe manner. + +### Monads to the Rescue + +In order to make our throwing and unsafe APIs into safe data fetching functions, we'll use data +types and techniques from the Functional Programming paradigm. In particular, we'll use the +[fp-ts](https://gcanti.github.io/fp-ts) library (but you can choose the FP library of your choice, +e.g. [purify](https://gigobyte.github.io/purify/getting-started), +[effect](https://github.com/Effect-TS/core), [monio](https://github.com/getify/monio), and +[others](https://github.com/stoeffel/awesome-fp-js#libraries)). + +`fp-ts` exposes several useful data types and functions which allows to work on safe abstractions +using composable functions. In particular, we'll use: + +- `Either`: represents the result of a computation that might fail +- `Option`: represents possible null/undefined values in a safe way +- `Task`: lazy Promise, represents the result of an async operation +- `TaskEither`: represents the result of an async operation that might fail (combination of `Task` + and `Either` as its name implies) + +### Make the APIs Safer + +Let's consider a simple mock API for the purpose of this article. It represents a throwing API, such +as a gRPC API. + +```typescript +// returns an Entity or throws +export async function fetchEntity(id: number): Promise { + const entity = entities.find(e => e.id === id); + if (!entity) { + throw new Error(`Entity ${id} not found`)); + } + return new Entity(entity); +} + +// returns a User or throws +export async function fetchUser(id: UserId): Promise { + const user = users.find(u => u.id === id); + if (!user) { + throw new Error(`User ${id} not found`); + } + return new User(user); +} +``` + +Since we have an async API that can throw, we'll use `TaskEither` to wrap those calls. +`Either` (and so `TaskEither`) is parametrized by two types: `E` which represents the +Error and `A` which represents the type of the data if the call was successful. By convention, the +left side represents the failure, the right side the data type of the successful result. + +We can create an instance of it by using one of its basic constructors: `of`, `left` (to directly +create an Error), `right` (to create a success) or one of its _convenience_ constructors: + +- `tryCatch`: if the function supplied throws, store a customized Error in the left side, else store + the successful result in the right side +- `fromPredicate`: if the predicate applied to the value is falsy, store a customized Error in the + left side, else store the value in the right side +- `fromNullable`: if the value is `null` or `undefined`, store a customized Error in the left side, + else store the value in the right side + +This is now our safe APIs: + +```typescript +export const getEntity = (id: number): TE.TaskEither => + TE.tryCatch( + () => fetchEntity(id), + e => new NotFoundError(e.message) + ) + +export const getUser = (id: UserId): TE.TaskEither => + TE.tryCatch( + () => fetchUser(id), + e => new NotFoundError(e.message) + ) +``` + +Not only have we made our API safer code-wise, but also better from a documentation standpoint: now +we can also see which Errors the API returns, which was not the case when we were using Promise and +`reject` (or `try/catch`). + +Now that we have dealt with the data fetching, let's consume this data. The first functionality we +add is checking the user permissions. In this simple example, we don't query an external service, we +got the data in the Entity and User types. We can create the checking function without worrying +about whether the user or entity is present since this will be handled by the monads (Either, +TaskEither, Option). We can also decide to represent the absence of permission as an Error: in the +second function, we use the `Either.fromPredicate` to transform the `false` value into a custom +`NotAllowedError`: + +```typescript +// unsafe API +const isUserAllowedForEntity = async (user: User, entity: Entity): Promise => { + try { + return !entity.restrictions.includes(user.country) + } catch (e) { + throw e + } +} + +// safe API +export const getIsUserAllowedForEntity = ( + user: User, + entity: Entity +): TE.TaskEither => + TE.tryCatch( + () => isUserAllowedForEntity(user, entity), + e => new UnknownError(e.message) + ) + +const isNotAllowedAsError = + (errorMsg: string) => + (isAllowed: boolean): TE.TaskEither => + pipe( + isAllowed, + TE.fromPredicate(Boolean, _ => new NotAllowedError(errorMsg)) + ) + +export const isUserAllowedForEntityAsError = + (user: User) => + (entity: Entity): TE.TaskEither => + pipe( + getIsUserAllowedForEntity(user, entity), + TE.chainW(isNotAllowedAsError(`User ${user.id} isn't allowed to access entity ${entity.id}`)) + ) +``` + +Now that both the Entity and User permission fetching calls are safer, we can combine them to create +the Entity fetching function with User permission check incorporated: + +```typescript +export const getEntityForUser = ( + id: number, + userId: UserId +): TE.TaskEither => + pipe( + getUser(userId), + TE.chainW(user => pipe(getEntity(id), TE.chainFirstW(isUserAllowedForEntityAsError(user)))) + ) +``` + +We fetch the User, then the Entity and when we have both, we check the User permission for this +entity. Two things to note here: + +1. because the `isUserAllowedForEntityAsError` depends on both the User and the Entity and we + fetched those separately, we have a nested pipeline (the nested pipe needs to close over the + `user`). We'll see next an alternate syntax to get rid of this nesting. +2. the `chainFirst` function allows us to execute computations in sequence but keep only the result + of the first computation if the second computation is successful. Here what it does is: + 1. get the Entity (as a `TaskEither`) + 1. if it's a left (some `Error`), it won't execute the `isUserAllowedForEntityAsError` + 2. if it's a right (`Entity`), it executes the `isUserAllowedForEntityAsError` + 3. if it's a left (`NotAllowedError`), it returns the `TaskEither` holding this Error + 4. if it's a right (`true`), it ignores it and returns the result of the previous computation + (the `TaskEither` holding the `Entity`) + +### Avoid Nested `pipe` with the `Do` Notation + +The `Do` notation is an adaptation of its native Haskell counterpart. It allows to build a context +object that keeps track of the unwrapped values produced by the different operations in the +pipeline. After its creation using the ADT `Do` or `bind`/`bindTo` functions, every subsequent +operation in the pipeline has access to this context object, therefore eliminating the need for +nesting. + +Let's rewrite our function using the `Do` notation: + +```typescript +export const getEntityForUser = ( + id: number, + userId: UserId +): TE.TaskEither => + pipe( + getUser(userId), + TE.bindTo('user'), + TE.bind('entity', _ => getEntity(id)), + TE.bind('isAllowed', ({ user, entity }) => isUserAllowedForEntityAsError(user)(entity)), + TE.map(({ entity }) => entity) + ) +``` + +Because the `bind`/`bindTo` functions return instances of ADTs (`TaskEither` here), we keep the +fail-fast behaviour. In this example, if the `getUser` call returns a `Left`, we +won't call `getEntity` nor `isUserAllowedForEntityAsError`, it will return the `TaskEither` holding +the `NotFoundError`. + + + With the `Do` notation, we gain nest-free pipeline, but we lose point-free programming. + + +### Query Resolver + +Now that we have a safe API, we can move on to the query resolver. We'll first validate the inputs +and then query the data. + +#### Inputs Validation + +To validate the inputs, we'll also create a function that runs a validator on each input and returns +an `Either`. By doing this we can integrate the result of the validation into the pipeline. + +```typescript +Query: { + entity(_, args, _2) { + const { id: entityId, userId } = args; + + const validatedInputs = validate({ + entityId: validateIsNumber(entityId, 'entityId'), + userId: validateIsUserId(userId, 'userId'), + }); + + return pipe( + validatedInputs, + // more code here + )(); + } +}, +``` + +The `validateIsNumber` and `validateIsUserId` are functions that return +`Either<{ field: string; message: string }, TypeOfTheInput>`. If the input fails the validation, we +get an object of field name and validation message in the `Left` side, otherwise we get the input +value in the `Right` side. + +The `validate` function takes a record of field to validator function and returns either an +`InvalidInputError` or an object of input name to input value. + +#### Fetching the Data and Returning + +```typescript +Query: { + entity(_, args, _2) { + const { id: entityId, userId } = args; + + const validatedInputs = validate({ + entityId: validateIsNumber(entityId, 'entityId'), + userId: validateIsUserId(userId, 'userId'), + }); + + return pipe( + validatedInputs, + TE.fromEither, + TE.chain(({ entityId, userId }) => getEntityForUser(Number(entityId), userId)), + TE.foldW( + taskWrappedError, + T.of, + ), + )(); + } +}, +``` + +Once the inputs are validated, the next step in the pipeline is to fetch the data using +`getEntityForUser`. Because this call returns a `TaskEither`, we need to transform the previous +`Either` into a `TaskEither` using `TaskEither.fromEither`. Of course, because of the fail-fast +nature of the ADT, this won't be called if the inputs failed the validation. + +Finally, we _exit the abstraction_ by using the `TaskEither.fold` (also called `match`) destructor: +this function does pattern matching on the `TaskEither` and executes the first callback if it's a +`Left`, or the second callback if it's a `Right`. At this point we want to return a `WrappedError` +or an `Entity` from our query resolver, we do not want to return a `TaskEither` because we want +simple type resolvers that acts on the correct type. The ADT abstraction stays within the boundaries +of our program and we consider the query (or mutation or subscription) resolver to be this boundary. + +### Bonus: Make the Errors Type Resolvers Better Using `currying` + +`Currying` is the technique of converting a function that takes multiple arguments into a sequence +of functions that each takes a single argument. It enables +[point-free programming](https://en.wikipedia.org/wiki/Tacit_programming) which allows for cleaner +callback or function composition in pipeline. + +We used above on several occasions: + +```typescript +pipe( + // ... + getEntity(id), + TE.chainFirstW(isUserAllowedForEntityAsError(user)) + // ... +) +``` + +The `isUserAllowedForEntityAsError` is a curried function: when called with the `user` input, it +returns a function that takes an `entity` input. If the function was not curried, we would have +written: + +```typescript +pipe( + // ... + getEntity(id), + TE.chainFirstW(entity => isUserAllowedForEntityAsError(user, entity)) + // ... +) +``` + +With this knowledge, we can improve the repetitive type resolver code: + +```ts +NotFoundError: { + __isTypeOf: (parent) => parent.err instanceof NotFoundError, + message: (parent) => parent.err.message, +}, +NotAllowedError: { + __isTypeOf: (parent) => parent.err instanceof NotAllowedError, + message: (parent) => parent.err.message, +}, + +// same for all other error types: InvalidInputsError, UnknownError +``` + +This code will be the same for all errors (with maybe a few additional field resolvers for some +errors). To make it better, we'll create a curried typeguard function and a curried field extraction +for the error: + +```typescript +// curried typeguard +const isWrappedError = + (errorClass: Type) => + (v: any): v is WrappedError => + v?._tag === 'WrappedError' && v.err instanceof errorClass + +// curried field extractor +const wrappedErrorField = + (errorClass: Type) => + (prop: TKey) => + (wrappedErr: WrappedError): E[TKey] => + wrappedErr.err[prop] +``` + +Now we can rewrite our error type resolvers like so: + +```typescript +NotFoundError: { + __isTypeOf: isWrappedError(NotFoundError), + message: wrappedErrorField(NotFoundError)('message'), +}, +NotAllowedError: { + __isTypeOf: isWrappedError(NotAllowedError), + message: wrappedErrorField(NotAllowedError)('message'), +}, + +// same for all other error types: InvalidInputsError, UnknownError +``` + +And since this code is common, we can extract it into a type resolver factory function: + +```typescript +const errorTypesCommonResolvers = (errorClass: Type) => ({ + __isTypeOf: isWrappedError(errorClass), + message: wrappedErrorField(errorClass)('message') +}) +``` + +And rewrite our errors type resolvers like so: + +```typescript +export const queryResolvers: Resolvers = { + NotFoundError: errorTypesCommonResolvers(NotFoundError), + NotAllowedError: errorTypesCommonResolvers(NotAllowedError) + + // same for all other error types: InvalidInputsError, UnknownError +} +``` + +And if we have an error with more fields (e.g. we add a `validations` field to `InvalidInputError`), +we can still customize it by using the spread operator on the type resolver object: + +```typescript +export const queryResolvers: Resolvers = { + NotFoundError: errorTypesCommonResolvers(NotFoundError), + NotAllowedError: errorTypesCommonResolvers(NotAllowedError), + // same for UnknownError type + + // customized error type resolver + InvalidInputError: { + ...errorTypesCommonResolvers(InvalidInputError), + validations: wrappedErrorField(InvalidInputError)('validations') + } +} +``` + +## Wrapping Up + +GraphQL gives you the power to model your domain errors and deliver them to the caller in a +documented and type-safe way. Using Typescript you get type safety for most of your code. Combined +with GraphQL Code Generator, you even get your resolvers functions fully typed. And finally with +powerful functional programming concepts applied to your GraphQL resolvers, you can get the ultimate +safe handling of your unsafe APIs. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-eslint-3.14/graphql-js-suggestions.gif b/packages/web/docs/src/app/blog/(posts)/graphql-eslint-3.14/graphql-js-suggestions.gif new file mode 100644 index 00000000000..5289fe90eb4 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-eslint-3.14/graphql-js-suggestions.gif differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-eslint-3.14/image.png b/packages/web/docs/src/app/blog/(posts)/graphql-eslint-3.14/image.png new file mode 100644 index 00000000000..ebb95581e58 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-eslint-3.14/image.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-eslint-3.14/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-eslint-3.14/page.mdx new file mode 100644 index 00000000000..8c59f70cf92 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-eslint-3.14/page.mdx @@ -0,0 +1,333 @@ +--- +title: GraphQL-ESLint v3.14 - What's New? +tags: [graphql, eslint, linter] +authors: dimitri +date: 2022-12-28 +description: + The best GraphQL linter becomes even better! Check out what we added in the new version. +skipSync: true +--- + +import NextImage from 'next/image' +import { Comparison } from '#components/comparison' +import { Callout } from '@theguild/components' +import graphqlJsSuggestions from './graphql-js-suggestions.gif' + +## Introduction + +Hi! I am Dimitri 👋, the current maintainer of the +[GraphQL-ESLint](https://github.com/dimaMachina/graphql-eslint) linter, and today I want to share +with you all changes that were made in the last version. This is a small minor version update, but a +bunch of new rules and options were added. + + + If you are not familiar with GraphQL-ESLint check out [here](/blog/introducing-graphql-eslint) an + introduction blog post by GraphQL-ESLint creator [Dotan Simha](https://github.com/dotansimha). + + +## Support New ESLint Flat Config System + +> Say Hello to the new flat config system! + +GraphQL-ESLint v3.14 fully supports +[the new ESLint flat config system](https://eslint.org/blog/2022/08/new-config-system-part-2), +[most of the examples](https://github.com/dimaMachina/graphql-eslint/tree/master/examples) were +updated to show you how you will set it up in the newly `eslint.config.js` file in the following +years! + +## ESLint Suggestions for `graphql-js` Rules + +All `graphql-js` rules that contain "Did you mean" suggestions now can be fixable via ESLint +suggestions API in your editor 🎉. + + + +## Update of `graphql-config` + +GraphQL-ESLint comes with a new GraphQL Config v4.4 that no longer requires a `typescript` +dependency to be installed. + + + Also, GraphQL Config is no longer bundled with `cosmiconfig-toml-loader` and + `cosmiconfig-typescript-loader`. You must install it manually in case of using TOML or TypeScript + config. The benefit of this is that bundle size is reduced by 35%. + + +## New Rules + +The new version introduces 4 new rules: + +### Rule `require-nullable-fields-with-oneof` + +Require `input` or `type` fields to be non-nullable with `@oneOf` directive + +```json filename=".eslintrc.json" +{ + "rules": { + "@graphql-eslint/require-nullable-fields-with-oneof": "error" + } +} +``` + + +```graphql filename="❌ Incorrect" +input Input @oneOf { + foo: String! + bar: [Int!]! +} +``` +```graphql filename="✅ Correct" +input Input @oneOf { + foo: String + bar: [Int!] +} +``` + + +### Rule `require-type-pattern-with-oneof` + +Enforce types with `@oneOf` directive have `error` and `ok` fields. + +- It's easier to communicate user errors in the response +- Errors can contain any additional info the client might need (error code, validation info and so + on) +- Reduce the need to use `... on Error` and so on, no need to specify type names in the query + +```json filename=".eslintrc.json" +{ + "rules": { + "@graphql-eslint/require-type-pattern-with-oneof": "error" + } +} +``` + + +```graphql filename="❌ Incorrect" +type DoSomethingMutationResult @oneOf { + notok: DoSomethingSuccess + noterror: Error +} +``` +```graphql filename="✅ Correct" +type DoSomethingMutationResult @oneOf { + ok: DoSomethingSuccess + error: Error +} +``` + + +### Rule `lone-executable-definition` + +Require queries, mutations, subscriptions or fragments to be located in separate files. + +```json filename=".eslintrc.json" +{ + "rules": { + "@graphql-eslint/lone-executable-definition": "error" + } +} +``` + + +```graphql filename="❌ Incorrect" +# users.graphql +query Users { + users { + ...UserFields + } +} +fragment UserFields on User { + id +} +``` +```graphql filename="✅ Correct" +# users.graphql +query Users { + users { + ...UserFields + } +} +``` + + +### Rule `no-one-place-fragments` + +An original proposal for a rule to suggest inline fragments that are spread only in one place was +asked [2 years ago](https://github.com/dimaMachina/graphql-eslint/issues/72#issue-715054179) and +finally, this rule is a part of GraphQL-ESLint. + +```json filename=".eslintrc.json" +{ + "rules": { + "@graphql-eslint/no-one-place-fragments": "error" + } +} +``` + + +```graphql filename="❌ Incorrect" +fragment UserFields on User { + id +} + +{ user { ...UserFields } } + +```` +```graphql filename="✅ Correct" +{ + user { + ...UserFields + friends { + ...UserFields + } + } +} +```` + + + +## New Options + +### `groups` Option for `alphabetize` Rule + +`alphabetize` rule is one of the powerful GraphQL-ESLint rules, that can sort all the things in your +GraphQL, it also supports ESLint suggestions fixes. + +Unfortunately, before it was not possible to configure to put some properties at the start or at the +end, but now it was fixed with new option `groups`. + +Here is an example of configuration and cases: + +```json filename=".eslintrc.json" +{ + "rules": { + "@graphql-eslint/alphabetize": [ + "error", + { + "fields": ["ObjectTypeDefinition"], + "groups": ["id", "*", "createdAt", "updatedAt"] + } + ] + } +} +``` + +`*` symbol is mandatory, which means everything else. + + +```graphql filename="❌ Incorrect" +type User { + createdAt: DateTime! + email: Email! + firstName: String! + id: ID! + lastName: String! + updatedAt: DateTime! +} +``` +```graphql filename="✅ Correct" +type User { + id: ID! + email: Email! + firstName: String! + lastName: String! + createdAt: DateTime! + updatedAt: DateTime! +} +``` + + +### `rootField` Option for `require-description` Rule + +This option enforces each field of root type (`Query`, `Mutation` and `Subscription`) to have a +description. + +```json filename=".eslintrc.json" +{ + "rules": { + "@graphql-eslint/require-description": ["error", { "rootField": true }] + } +} +``` + + +```graphql filename="❌ Incorrect" +type Mutation { + createUser: User +} + +type User { name: String } + +```` +```graphql filename="✅ Correct" +type Mutation { + "Create a new user" + createUser: User +} + +type User { + name: String +} +```` + + + +### `prefix` Option for `match-document-filename` Rule + +Previously in this rule, we had only a `suffix` option but in our open-source SAAS project +[Hive](/), we prefix all files with executable definitions with +[their operation types](https://github.com/kamilkisiela/graphql-hive/tree/main/packages/web/app/src/graphql). + +Now, everybody can take benefit from this new option 🎉. + +```json filename=".eslintrc.json" +{ + "rules": { + "@graphql-eslint/match-document-filename": [ + "error", + { + "mutation": { + "style": "kebab-case", + "prefix": "mutation." + } + } + ] + } +} +``` + + +```graphql filename="❌ Incorrect" +# createUser.graphql + +mutation CreateUser { + +# ... + +} + +```` +```graphql filename="✅ Correct" +# mutation.create-user.graphql + +mutation CreateUser { + # ... +} +```` + + + +## Next Steps + +Check out +[the roadmap for the V4 version](https://github.com/dimaMachina/graphql-eslint/issues/981), the next +major will be released once ESLint 9 will out and of course, propose your suggestions 🙂. + +Don't forget to give a star ⭐️ for [GraphQL-ESLint](https://github.com/dimaMachina/graphql-eslint) +if you like it! + +And Happy New 2023 Year! 🎄🎉 🥂 + +### Community + +Thanks to our contributors @FloEdelmann, @TuvalSimha and @tshedor. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/game-client.png b/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/game-client.png new file mode 100644 index 00000000000..8b215fbd2d2 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/game-client.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/game-flow.png b/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/game-flow.png new file mode 100644 index 00000000000..77e053f55a8 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/game-flow.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/game-state.png b/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/game-state.png new file mode 100644 index 00000000000..79539ec86a6 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/game-state.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/page.mdx new file mode 100644 index 00000000000..15a96ace554 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/page.mdx @@ -0,0 +1,149 @@ +--- +title: Open source FPS with Apollo GraphQL — GeoStrike (Alpha) +authors: david +tags: [cesium, graphql, angular] +date: 2018-02-15 +description: A GeoReferenced Multiplayer First-Person Shooter Game. +--- + +import { VideoEmbed } from '#components/video-embed' + +**TLDR:** We've built a multiplayer shooter and used Apollo's GraphQL subscriptions to keep game +state in sync at a very high frequency. Here's our impressions! **Tools used:** +[Angular-cesium](https://angular-cesium.com), +[Apollo Angular](https://github.com/kamilkisiela/apollo-angular), [CesiumJS](https://cesiumjs.org), +[Angular](https://angular.io) **Go Play:** [geo-strike.com](https://geo-strike.com) + +## First, Why? + +Well, to put it simply, because we can! + +Instead of working hard on creating a special serializable, efficient but also unreadable, strict +and esoteric binary protocol, we decided on going with the well-defined, readable and flexible +GraphQL based API, and improve on it, if needed. + +Turns out we can manage synchronization of player positions, state and actions across all clients +quite easily! + +## The Game + +There are two ways you can understand our challenges: + +- [Go and play the game](https://geo-strike.com) — Feel free to open the network tab on the dev + tools and look at the `subscriptions` websocket frames. +- Read what our game is all about. + + + +For those of you that have not played the game (yet!) I'll explain a bit what it is. + +Our game is a multiplayer shooting game that is taking place in the vicinity of the Times Square. +The game mode is a "team deathmatch" battle where you spawn on the map with your team, and you need +to find and shoot the other team's players. The winning team is the one that stayed alive while the +other team's players are dead. If you die, you can only view the rest of the game from a bird's eye +view. + + + +We use CesiumJS — a Google Earth like, open source, mapping engine — as our scene, so when I say the +game takes place in the Times Square, It's an actual representation of the Times Square with the +buildings being streamed to the client using Cesium's 3d Tiles. As a matter of fact, there is +nothing keeping you at the Times Square only, you can wonder off to anywhere on earth. Keep in mind +that you will be able to see buildings only in Manhattan. + +## Real-Time State Synchronization + +Because the game is a multiplayer shooter, we need to synchronize the multiple players position, +orientation and state at a rate that the user would not feel “lag”. + +In order to do just that, we decided that our game server will be the only source of truth. It keeps +each game in a simple map in memory so resolving this data whenever asked should be as quick as it +gets. Next we wrote a small GraphQL schema that defines our Game entity. + +It ended up looking like so: + +![](./structure.png) + +Some parts of this graph will change very rapidly (~10 times a sec) and some parts of it will change +rarely. Getting to this realization we decided to expose this graph with both Query type for less +frequently changed data and Subscription type for higher rate changes. + +This gives the client the flexibility to decide what parts of the data graph he needs to be steamed +to it. + +In our game, this subscription query turned out to be the only parts we need updated frequently: + +![](./game-state.png) + +At the beginning we went with the naive implementation and set things up so that we will get any +change made by any client as it happens. Basically publish on the mutation resolver. But we quickly +realized this is not a good approach as this is not very scalable and we, quickly over-stress our +graphql server. + +The approach we decided on taking is usually referred to as “game tick”. We set up a timer that +triggers every 200ms and publishes the game state to the gameData subscription. This was one of the +most significant changes we made to allow for low latency, in our state synchronization process, and +it was done with a couple lines of code, thanks to the way GraphQL subscriptions work. + +![](./game-flow.png) + +Not only that we were able to reduce latency, we were also able to scale the number of players quite +easily. + +## Reflecting State Changes on the Map + +All we had left is handle those high frequency events on the client and have it work well (e.g. +without conflicting or lagging) with the other of the data operations. + +But for those challenges, Apollo and `angular-apollo` had us covered! Apollo client handled the +state subscription and updated the normalized client store seamlessly. + +`angular-apollo` exposes the state as a Rx Observable which allowed us to plug that stream to our +amazing angular-cesium framework and have the players magically move and update on a 3d scene +rendered with CesiumJS! + +![](./game-client.png) + +## Not Just One Subscription + +As I've mentioned above, we are publishing the game state every 200ms which is quite good for +positioning players, but there are events on our game that cannot tolerate this kind of delay and +still feel good to the user. + +After analyzing our graph, we determined that there are two such events. Shooting (obviously) and +game state notifications. To handle those subscriptions on a ASAP manner, we've simply added two +different subscriptions that the client should subscribe to. This makes sure that as soon as someone +shoots, we will play the shooting sound according to his relative location on all other clients, and +as soon a player is killed, all other players will get a notification indicating that. + +## Further Improvements + +![](./pov.png) + +As our game is played on the same size scene as our own earth, we cannot help but thinking of future +game modes where whole armies could play one against the other to take over / save the world! + +This kind of massive multiplayer online game calls for some next level optimizations. + +For instance: + +- Optimize the amount of data sent to each client by filtering players state based on the player's + current position in the world. +- Optimize the payload size by converting GraphQL responses to + [Google's Binary ProtoBuff](https://developers.google.com/protocol-buffers). +- Use Apollo Engine's dynamic persistent queries to optimize the data that should be sent to our + game server. + +## Who Are We, You Ask? + +This game is the product of a collaboration between [Webiks](https://webiks.com), a software +development company that specialize in high-end data analysis and real time situational awareness +web based applications. And [The Guild](/), a group of freelance developers, open source +contributors and the creators of [angular-cesium](https://angular-cesium.com). + +If you are interested in other non-graphql challenges we faced in this project, Omer, the CEO of +Webiks wrote a [blog post that showcases all of it](https://webiks.com/introducing-geostrike-alpha). + +Oh, and it's all open source! Come over, [star and contribute](https://github.com/Webiks/GeoStrike)! + +Thanks for Reading! diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/pov.png b/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/pov.png new file mode 100644 index 00000000000..e191e0719a2 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/pov.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/structure.png b/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/structure.png new file mode 100644 index 00000000000..45a046165b4 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-geo-strike/structure.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/async-sync-inserts.png b/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/async-sync-inserts.png new file mode 100644 index 00000000000..cff8e9495f2 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/async-sync-inserts.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/clients.png b/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/clients.png new file mode 100644 index 00000000000..4437841f889 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/clients.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/data-collecting-part.png b/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/data-collecting-part.png new file mode 100644 index 00000000000..02a93ebf3b9 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/data-collecting-part.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/data-pipeline.png b/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/data-pipeline.png new file mode 100644 index 00000000000..81a27c13ada Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/data-pipeline.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/data-processing-part.png b/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/data-processing-part.png new file mode 100644 index 00000000000..2162f2005eb Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/data-processing-part.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/date-ranges.png b/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/date-ranges.png new file mode 100644 index 00000000000..8ab7f4419ed Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/date-ranges.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/page.mdx new file mode 100644 index 00000000000..f7cffc43b8f --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/page.mdx @@ -0,0 +1,329 @@ +--- +title: How ClickHouse helps us track billions of GraphQL requests monthly +tags: [graphql, graphql-hive] +authors: [kamil] +date: 2022-11-08 +description: + Learn how ClickHouse enabled GraphQL Hive to scale from millions to billions of requests monthly. +--- + +import { LinkCard } from '#components/link-card' +import { Callout } from '@theguild/components' + +You will learn how ClickHouse and Kafka enabled GraphQL Hive to collect billions of requests monthly +without a drop of sweat. We will also cover the challenges we faced and how we overcame them by +covering Hive's architecture in detail. + +## What Is GraphQL Hive? + +[GraphQL Hive](https://the-guild.dev/graphql/hive) is a Schema Registry, Monitoring, and Analytics +solution for GraphQL APIs. A fully open-source tool that helps you to track the history of changes +prevents breaking the API and analyzing the traffic of your API. + +GraphQL Hive makes you aware of how your GraphQL API is used and what is the experience of its final +users. + +We [launched GraphQL Hive](https://www.the-guild.dev/blog/announcing-graphql-hive-release) a few +months ago, +[after a year-long preview program](https://www.the-guild.dev/blog/graphql-hive-preview). In case +you missed it, we recently +[announced a self-hosted version](https://www.the-guild.dev/blog/announcing-self-hosted-graphql-hive). + +## Monitoring Performance and Usage of GraphQL API + +It is important to be aware of how a GraphQL API is used and what is the experience of its final +users. To answer these questions GraphQL Hive collects +[Usage Reports](https://the-guild.dev/graphql/hive/docs/specs/usage-reports). By looking at the +sample below, you can get an idea of how GraphQL Hive knows when it's safe to delete a type or a +field of your GraphQL API. This is the most important building block of all data available in +GraphQL Hive. + +```json +{ + "size": 1, + "map": { + "762a45e3": { + "operationName": "users", + "operation": "query users { users { id } }", + "fields": ["Query", "Query.users", "User", "User.id"] + } + /* ... */ + }, + "operations": [ + { + "operationMapKey": "762a45e3", // points to the 'users' query + "timestamp": 1663158676589, + "execution": { + "ok": true, + "duration": 150000000, // 150ms in nanoseconds + "errorsTotal": 0 + }, + "metadata": { + "client": { + "name": "demo", // Name of the GraphQL API consumer + "version": "0.0.1" // Version of the GraphQL API consumer + } + } + } + /* ... */ + ] +} +``` + +## Why ClickHouse? + +The kind of data GraphQL Hive deals with is not meant to be mutated (it never changes) and the +database has to support a good set of functions for analytics and data aggregation. + +Our first choice was **ElasticSearch**. It seemed like a good option, but we hit the first scaling +issues very quickly. **After reaching the goal of 100M operations per month, the queries to +ElasticSearch were unusable.** We tried to improve it, but we weren't happy with ElasticSearch +anyway. + +After looking for alternatives we decided to switch to ClickHouse. We highly recommend reading the +["100x Faster: GraphQL Hive migration from Elasticsearch to ClickHouse"](https://clickhouse.com/blog/100x-faster-graphql-hive-migration-from-elasticsearch-to-clickhouse) +article we wrote with the [ClickHouse Cloud](https://clickhouse.com/cloud) team. It covers in detail +the problems we were facing and the decision-making process that lead us to love ClickHouse. + +## An Overview of the Architecture + +The data pipeline in GraphQL Hive is relatively simple but has proven to be highly scalable and +reliable. Here's a high-level data flow of the usage reporting part of GraphQL Hive. + +![](./data-pipeline.png 'An overview of the architecture') + +We can distinguish two parts here, the **collecting** and **processing** of data. + +The `Usage service` is responsible for receiving and validating the usage report and of course the +token-based authorization. Once the report is validated, it is added to the batch of reports and +sent to **Kafka** (in one message). + +Besides the batching we also perform a **_self-learning Kafka message size estimation_**. + +In short, Kafka message has a limit of 1MB, with the size estimator, we make sure to not go over the +limit but squeeze the most we can at a time. Instead of sending many small messages (~20kb), we send +less often but almost at full capacity (on average about 998kb). **It reduces the cost of the hosted +Kafka by 10 times**. + +It sounds mysterious, but we find this part extremely interesting, that's why we're going to write a +dedicated article about it soon. + +![](./data-collecting-part.png 'Data collecting part') + +Thanks to Kafka and its persistence of messages, the collecting and processing parts are nicely and +reliably separated. + +Now, let's take a look at the next piece of the puzzle. + +The Usage Ingestor service receives the messages from Kafka, then it unpacks the usage reports from +each message and processes them. Once the processing of reports is done, the service writes data to +Clickhouse. + +![](./data-processing-part.png 'Data processing part') + +The data pipeline does not end here, there're still a few quite interesting techniques we use within +ClickHouse. + +> ClickHouse supports Kafka natively, we could insert data directly from it, but the reason why we +> decided not to was to reduce the traffic to Kafka and have better control of the writes. + +## Squeezing the Most Performance + +To understand the things we do with ClickHouse, you first need to understand the core of it, the +MergeTree family and Materialized Views. This is what ClickHouse documentation says about the +MergeTree family. + +> Table engines from the MergeTree family are the core of ClickHouse data storage capabilities. They +> provide most features for resilience and high-performance data retrieval: columnar storage, custom +> partitioning, sparse primary index, secondary data-skipping indexes, etc. + +It sounds very technical, but you can think of the MergeTree family as a group of table engines +built for a different purposes, dedicated to many different use cases. + +We will cover only the regular `MergeTree` and `SummingMergeTree`. + +Think of `MergeTree` table engine as a regular table, the one you're familiar with from PostgreSQL. +Nothing special here. + +An overview of our ClickHouse tables. + +![](./tables.png 'An overview of our ClickHouse tables') + +In GraphQL Hive, we use `MergeTree` to store received GraphQL requests in the +[`operations` table](https://github.com/kamilkisiela/graphql-hive/blob/64e53f207b413f8bb222eabd6aeb437e3300dbfd/packages/services/storage/migrations/clickhouse.ts#L29-L49). + +```sql +CREATE TABLE 'operations' ( + target STRING, + timestamp DateTime ('UTC'), + expires_at DateTime ('UTC'), + hash STRING, + ok UInt8, + errors UInt16, + duration UInt64, + client_name STRING, + client_version STRING +) +``` + +Every GraphQL request from the collected billions of requests monthly is represented by a row in the +`operations` table. + +Even though ClickHouse is extremely fast, it's still quite a lot of rows to read and analyze data +from, every second. + +That's why we use the `SummingMergeTree`. It is a bit unique version of `MergeTree`. + +In short, it replaced all rows (grouped by a combination of columns) with one row which contains +summarized values for the columns. + +Sounds like a good table engine for grouping GraphQL operations and aggregating data! We use it in +the +[`operation_collection` table](https://github.com/kamilkisiela/graphql-hive/blob/64e53f207b413f8bb222eabd6aeb437e3300dbfd/packages/services/storage/migrations/clickhouse.ts#L7-L25). +It allows us to read all GraphQL operations within milliseconds and get the total number of calls. +Without the `SummingMergeTree` we would have to either maintain our own aggregation logic and write +data to the `MergeTree` table or always scan through billions of rows of the `operations` table. + + + We are not data scientists, at least not yet… but what we learned by developing GraphQL Hive is + that in order to squeeze the most performance of a database, you need to design the DB structure + with the end goal in mind. + + +What do we mean by that? Here's a good example. + +GraphQL Hive has the "Operations" page that presents the total number of requests, latency (p90, +p95, p99) of all and individual operations, and a lot more other things. + +The page contains a date range filter. + +![](./date-ranges.png 'Operations page and the date range filter') + +Calculating the percentiles of the latency for a large number of GraphQL requests (stored as rows) +might be a challenge, even for ClickHouse. Maybe not for "Last 24 hours" as we get only 1/30 of all +rows, compared to the "Last 30 days" option. Obviously, it won't be performant enough at some point. + +Here's where the `Materialized Views` become super handy. GraphQL Hive defines two "virtual" tables, +[`operations_hourly`](https://github.com/kamilkisiela/graphql-hive/blob/64e53f207b413f8bb222eabd6aeb437e3300dbfd/packages/services/storage/migrations/clickhouse.ts#L52-L82) +and +[`operations_daily`](https://github.com/kamilkisiela/graphql-hive/blob/64e53f207b413f8bb222eabd6aeb437e3300dbfd/packages/services/storage/migrations/clickhouse.ts#L86-L117) +that are Materialized Views of the original `operations` table. + +In short, the `operations_hourly` stores summarized data of all GraphQL requests that happen during +an hour. It allows us to store one row per hour instead of millions. How much impact it has on +ClickHouse performance? For a 24h window, ClickHouse needs to read only 24 rows instead of 100s of +millions. + + + Thanks to Materialized Views, the latency of our queries went from 4-10 seconds to less than 1 + second. + + +You can see how `SummingMergeTree` and `Materialized Views` are crucial to ClickHouse performance. + +As you know, GraphQL Hive is open source and built in public, which means you can watch our journey +and learn from our mistakes. Here's an excellent example of what we mean, it's an issue where we +discussed the new data structure of ClickHouse. It shows the latency of the existing queries and how +we improved it. + + + +Here's yet another, this time extreme example of a table designed with an end goal in mind. + +![](./clients.png 'Operations page and the Clients section') + +GraphQL Hive allows you to understand who are the consumers of your GraphQL API. + +Here's the SQL query that we previously used to generate the charts: + +```sql +SELECT + COUNT(*) AS total, + client_name, + client_version +FROM + operations +WHERE + timestamp >= subtractDays (now(), 30) +GROUP BY + client_name, + client_version +``` + +The latency was awfully slow, the more data we had, the slower it was. For a few billion rows, **it +took 56 seconds**, a ridiculously poor performance. + +We decided to create a special materialized view, designed specifically for the UI component. + +```sql +CREATE MATERIALIZED VIEW 'clients_daily' ( + target STRING, + client_name STRING, + client_version STRING, + hash STRING, + timestamp DateTime ('UTC'), + expires_at DateTime ('UTC'), + total UInt32 +) +``` + +Here's +[a full SQL definition](https://github.com/kamilkisiela/graphql-hive/blob/64e53f207b413f8bb222eabd6aeb437e3300dbfd/packages/services/storage/migrations/clickhouse.ts#L121-L150) +of that table. + +Now the SQL query looks like this: + +```sql +SELECT + sum(total) AS total, + client_name, + client_version +FROM + clients_daily +WHERE + timestamp >= subtractDays (now(), 30) +GROUP BY + client_name, + client_version +``` + + + After we shipped a dedicated table (for the component we showed you), the numbers went down from 56 seconds to **56 + milliseconds**. + +**It was a 1000x improvement.** + + + +Another fantastic feature of ClickHouse is **Asynchronous Inserts**. This opt-in mode allows to pile +up inserted data and flush it in a single batch. Thanks to the async inserts we're able to accept a +higher rate of INSERT queries, without worrying about the performance. + +![](./async-sync-inserts.png 'Difference between synchronous and asynchronous inserts in ClickHouse') + +Every read operation in ClickHouse is being cached, writing too often translates to a lot of cache +misses. To avoid that, we either use the async inserts mode or synchronously write data less often +but in higher volumes. We decided to use the async inserts mode, as it's a built-in feature and +makes our setup less complex. + +## Conclusion + +We did many, many mistakes, and we still do, but what we learned by working on GraphQL Hive and a +large set of data is that the end goal should dictate the shape of data and tables. + +> You can use the fastest database on earth and still experience poor performance. + +Our advice is also to not think too much ahead of time, you can always iterate and improve your +setup as you scale up. There's really no need to overcomplicate things just for the sake of making +it "highly scalable". We think it's better to make mistakes and learn from them, instead of copying +the architecture of Cloudflare and other big tech companies. + + + If you find this article interesting, please let us know and tell us what else you wish to learn + about. We're going to write a couple of articles about technical bits and pieces of GraphQL Hive + in the upcoming weeks, so stay tuned! + diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/tables.png b/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/tables.png new file mode 100644 index 00000000000..41c6448bcac Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-hive-and-clickhouse/tables.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-hive-improvements-in-schema-registry/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-hive-improvements-in-schema-registry/page.mdx new file mode 100644 index 00000000000..62748fd1ad5 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-hive-improvements-in-schema-registry/page.mdx @@ -0,0 +1,35 @@ +--- +title: GraphQL Hive - Improvements in Schema Registry +authors: kamil +tags: [graphql, graphql-hive] +date: 2023-02-06 +description: + GraphQL Hive announces new and improved schema registry model with improved workflow and user + experience. Access to old model will end soon, switch recommended. +--- + +import { Callout } from '@theguild/components' + +We are excited to announce the implementation of a new and improved schema registry model. This new +model is designed to provide you with a **more optimized workflow** and an **improved user +experience**. + +It is important to note that access to **the old schema registry model will be discontinued in a few +months' time**. We strongly recommend that you switch to the modern model as soon as possible to +take advantage of its many benefits. + +The steps to switch can be found in the settings of your project. + +In comparison to the old model, the new model has superior defaults, follows best practices, and has +a streamlined workflow. A comparison table available in the settings of your projects will help you +understand the differences between the two models. + +![](./upgrade-project-sample.png 'A preview of the "Upgrade Project" section in settings') + +One of the most notable improvements in the new model is the **availability of the delete feature**. +With the modern model, you can now delete a schema from the registry with ease. This is a feature +that was not available in the old model. + +In conclusion, we are confident that the new schema registry model will provide you with a more +efficient and effective workflow. If you have any questions or concerns about the transition, please +do not hesitate to reach out to us. We are here to assist you every step of the way. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-hive-improvements-in-schema-registry/upgrade-project-sample.png b/packages/web/docs/src/app/blog/(posts)/graphql-hive-improvements-in-schema-registry/upgrade-project-sample.png new file mode 100644 index 00000000000..4ea4b112cfc Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-hive-improvements-in-schema-registry/upgrade-project-sample.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-hive-preview/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-hive-preview/page.mdx new file mode 100644 index 00000000000..2980b84403a --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-hive-preview/page.mdx @@ -0,0 +1,58 @@ +--- +title: GraphQL Hive - Manage Your GraphQL API Workflow +authors: kamil +tags: [graphql, graphql-hive, graphql-federation] +date: 2021-03-26 +description: + Announcing the beta testing program of GraphQL Hive - schema registry for any GraphQL workflow. +--- + +Today, we're happy to announce +[**GraphQL Hive**](https://the-guild.dev/graphql/hive?utm_source=blog&utm_medium=link&utm_campaign=blog_hive) - +**a registry of schemas** to manage, safeguard and optimize your **team's workflows** when deploying +**GraphQL services**. Hold your horses, we're not publicly launching it yet, it's only the start of +a beta testing program we give out today. **Sign up for early access!** + +## Short Introduction + +GraphQL Hive is a registry of schemas with many additional features to enhance your day-to-day work +with GraphQL. + +We think of GraphQL Hive as a beehive where instead of honey, GraphQL Schemas are being managed and +produced. + +GraphQL Hive will be released in two forms: An **open-source** collaboration tool and a **hosted and +managed service**. That is in order we can support any type of GraphQL workflow, including custom +workflows and on-premise needs. + +> Also, just like the bees, we're continuously working hard (or hardly working) to ship new +> features. + +## All Schemas + +[GraphQL Hive](https://the-guild.dev/graphql/hive) **supports regular GraphQL, +[Apollo Federation](https://the-guild.dev/graphql/hive/federation) and Schema Stitching services**. +In a later release we also aim to support other types of schemas, like open-api, gRPC, SOAP and +others that are not originally GraphQL thanks to the power of our +[GraphQL Mesh library](https://the-guild.dev/graphql/hive). That is in order that you will be able +to enjoy all the powerful features from GraphQL Hive, even if you don't use GraphQL. + +![Use GraphQL Hive with GraphQL Mesh](https://og-image-guild.vercel.app/**Beyond**%20GraphQL.png?theme=light&md=1&fontSize=100px&images=https://graphql-mesh.com/img/mesh-text-logo.svg&images=https%3A%2F%2Fgraphql-hive.com%2Flogo.svg&widths=auto&widths=380) + +## Registry but Not Only + +Our roadmap includes monitoring, safe-listing of operations, breaking change detection, changelogs +and many others. Our next task on the list is to support persisted queries for most GraphQL clients. +Let us know what you wish to see in GraphQL Hive! + +All of that to get a clear picture of how the GraphQL APIs are being developed and consumed. + +![Join the early access program](https://og-image-guild.vercel.app/**Join**%20the%20early%20access%20program.png?theme=light&md=1&fontSize=100px&images=https%3A%2F%2Fthe-guild.dev%2Fstatic%2Flogo.svg&images=https%3A%2F%2Fgraphql-hive.com%2Flogo.svg) + +## Request for Early Access Today! + +If you're interested to try it out and give us feedback, please visit +[graphql-hive.com](https://the-guild.dev/graphql/hive/?utm_source=blog&utm_medium=link&utm_campaign=blog_hive) +and sign up for the early access program. The benefits are that you will get to work closely with +us, while we review your use-cases and workflows and make sure GraphQL Hive supports you in the best +way possible! diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-let/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-let/page.mdx new file mode 100644 index 00000000000..c2161ce3e69 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-let/page.mdx @@ -0,0 +1,136 @@ +--- +title: graphql-let - A Webpack loader for GraphQL Code Generator +authors: soichi +tags: [graphql, codegen, webpack] +date: 2020-11-25 +description: graphql-let is a webpack loader of GraphQL code generator. +--- + +[`graphql-let`](https://github.com/piglovesyou/graphql-let) is a wrapper tool that makes using +GraphQL Code Generator smoother and easier. In this article, I'll explain what graphql-let is and +how it relates to GraphQL Code Generator. + +## What Is `graphql-let`? + +"It's a webpack loader" would be a simple explanation to start. It lets you directly import GraphQL +documents and use GraphQL Code Generator's result as below: + +```tsx +// You can directly import typed objects generated by GraphQL code generator⚡️ +import { useNewsQuery } from './news.graphql' + +const News: React.FC = () => { + // Yes, fetched data is typed too💪 + const { data: { news } } = useNewsQuery() + if (news) return
    {news.map(...)}
    +} +``` + +The fastest way to try graphql-let is to use +[the Next.js example from their official repository](https://github.com/vercel/next.js/tree/canary/examples/with-typescript-graphql) +by hitting the following command. + +```bash +yarn create next-app --example with-typescript-graphql with-typescript-graphql-app +``` + +`graphql-let` is made for enhancing the development pattern using **TypeScript and GraphQL**, the +most effective combination to solve many front-end problems in 2020. It heavily depends on GraphQL +Code Generator, so in other words, if you don't use any of TypeScript and GraphQL Code Generator, +you don't need `graphql-let` either. + +graphql-let's configuration file, `.graphql-let.yml`, looks intentionally similar to `codegen.yml` +of GraphQL Codegen. Next, I'll explain what `graphql-let` does by calling GraphQL Code Generator's +API under the hood. + +## What Does `graphq-let` Add on Top of GraphQL Code Generator API? + +GraphQL Code Generator is an awesome tool. It's an infrastructure to expand the possibility of +GraphQL nowadays. graphql-let is in `devDependencies` as well as GraphQL Code Generator, so it does +nothing in runtime-level too. graphql-let eases two pain points while you develop using `codegen` +command of GraphQL Code Generator. + +With GraphQL Code Generator, + +- You need to point to the output path as `import "../../../__generated__/out.tsx"` everywhere +- You need to run the `graphql-codegen` command manually every time you change the single file + +With `graphql-let`, + +- You can directly import GraphQL source as `import "./news.graphql"` thanks to the webpack power. +- You can process codegen in HMR too, thanks to webpack again. +- It achieved the above by getting rid of the `generates` option, where graphql-let takes care of + generated paths and lets you forget them. + +Please note that there are limitations, mainly because graphql-let controls output paths. I'd +recommend you +[read the documentation](https://github.com/piglovesyou/graphql-let/#configuration-is-compatible-with-codegenyml-except) +once to get the picture of how graphql-let and GraphQL Code Generator different. + +## Other Features + +There are other convenient features graphql-let provides to make it more practical. + +### `#import` Syntax Support + +`#import` is useful to share fragments of GraphQL document, especially when your project codebase +becomes big. If you have a fragment file named `partial.graphql` like this, + +```graphql +fragment Partial on User { + id + name +} +``` + +you can share it from other places by importing it. + +```graphql +# import Partial from './partial.graphql' +query Viewer { + viewer { + ...Partial + } +} +``` + +### Jest Transformer + +graphql-let exports `graphql-let/jestTransformer` that you can use in Jest testing. Please +[read the docs](https://github.com/piglovesyou/graphql-let/#jest-transformer) for more information. + +### Babel Plugin for Inline GraphQL Documents + +It's still partial support, but `graphql-let` provides a babel plugin to allows developers to write +inline GraphQL documents. + +```ts +import gql from 'graphql-let' + +// Typed️⚡️ +const { useViewerQuery } = gql(` + query Viewer { + viewer { name } + } +`) +``` + +It generates `.d.ts` files in `node_modules/@types/graphql-let` by default to apply overload +signatures on the function `gql`. Ideally, it should be available in both a babel plugin and the +webpack loader but there needs to be more effort. + +## Why I Made `graphql-let` + +I made this for front-end devs to adopt the TypeScript and GraphQL even more in their project😄 + +When I actively maintained +[kriasoft/react-starter-kit](https://github.com/kriasoft/react-starter-kit) a few years ago, I +realized it's so happy to use Apollo Server and Apollo Client. At the moment, though, Apollo +primarily supported HOCs instead of React Hooks, which leads to troublesome to match types of +GraphQL operations and fetched data manually. Then, `@graphql-codegen/typescript-react-apollo` +appeared and solved all the problems, except ones that graphql-let deals with later. + +The less setup process GraphQL development requires, the more people can adopt GraphQL I believe, +simply because GraphQL itself solves many problems web development has been struggling with without +it for a decade. I couldn't be happier if a few of you get to know the power of GraphQL through +using graphql-let. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-mesh-subscriptions/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-mesh-subscriptions/page.mdx new file mode 100644 index 00000000000..c5e5e745865 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-mesh-subscriptions/page.mdx @@ -0,0 +1,138 @@ +--- +title: Webhooks as GraphQL Subscriptions using GraphQL Mesh +authors: arda +tags: [subscriptions, webhooks, graphql, mesh] +date: 2020-10-05 +description: + Consume your legacy webhooks and subscriptions from any data source as GraphQL Subscriptions with + GraphQL Mesh. +--- + +GraphQL Mesh lets you query any data source and protocol with GraphQL by using powerful handlers +(Swagger/OpenAPI, gRPC, SOAP and others...) Today we are happy to announce that GraphQL Mesh now +allows you to consume your existing HTTP Webhooks as GraphQL Subscriptions! + +Not only that, but you could extend the GraphQL Subscriptions result, so you could subscribe to a +webhook and get back an enriched data result, with data from other sources that don't have push +communication abilities. + +And it's also possible even if your webhooks don't have any schema! + +Let's see how to create a GraphQL Subscription from a Webhook, once without a schema and once +automatically from an OpenAPI schema. + +> We are already able to consume our existing REST APIs in GraphQL Mesh even if they don't have any +> schema by using our JSON Schema Handler. [See more](/blog/migrating-from-rest) + +### Add a New Field to Our Existing Configuration for an HTTP Webhook + +In the following example configuration, we have a regular GraphQL Mesh configuration for schemaless +REST API. + +```yaml +sources: + - name: Example + handler: + jsonSchema: + baseUrl: http://localhost:4001 + operationHeaders: + Content-Type: application/json + operations: + - type: Query + field: todos + path: /todos + method: GET + responseSample: ./todos.json + - type: Mutation + field: addTodo + path: /todo + method: POST + requestSample: ./addTodo.json + responseSample: ./todo.json + responseTypeName: Todo +``` + +Assume that we point our REST API to send HTTP requests to our Mesh server's `/webhooks/todo_added` +path, so we need to configure Mesh to listen to that path. + +```yaml +serve: + port: 4000 + handlers: + - path: /webhooks/todo_added + pubsubTopic: todoAdded +``` + +Under `serve` we define the port we want Mesh to listen to and add a handler in a declarative way +without a single line of code. `pubsubTopic` is the unique name for that webhook. This topic will +receive upcoming HTTP requests so that JSON Schema handler will consume it as a GraphQL +Subscription. + +Let's define a new field under `Subscription` field under `operations` in the handler level; + +```yaml +operations: + - type: Subscription + field: todoAdded + pubsubTopic: todoAdded + responseSample: ./todo.json +``` + +Now we have a `todoAdded` field that listens to a webhook through Mesh's PubSub instance and this +field has a return type generated based on `todo.json` sample response. + +You can start `mesh serve` and start listening for that webhook by the following GraphQL operation; + +```graphql +subscription { + todoAdded { + id + title + content + } +} +``` + +> [You can find a complete working example on GitHub](https://github.com/Urigo/graphql-mesh/tree/master/examples/subscriptions-example) + +### Other Methods for Subscriptions Such as RabbitMQ or Kafka + +It is possible to configure Mesh to handle other schemaless subscription solutions like RabbitMQ and +Kafka using JSON Schema handler but this is the topic of another blog post. + +### Generating GraphQL Subscriptions from OpenAPI Callbacks + +Mesh can listen to webhooks through a PubSub engine easily. Mesh will generate the necessary typings +and subscriptions fields connected to your PubSub engine from the OpenAPI schema. You only need Mesh +to know what path the server should listen to like below; + +```yaml +sources: + - name: OpenAPICallbackExample + handler: + openapi: + source: ./openapi.yml + baseUrl: http://localhost:4001 + +serve: + port: 4000 + handlers: + - path: /callback + pubsubTopic: http://localhost:4000/callback +``` + +In this example configuration, we assume that we have an API server running on 4001 port and it has +a webhook that will forward requests from `/callback` path to `http://localhost:4000/callback` topic +because OpenAPI Handler uses callback url as PubSub topic name. If you want to have a more +customized way of handling incoming webhook requests, you can always write your own Express handler +and define it instead of `pubsubTopic`. You will find Mesh's PubSub engine under the `request` +object as `request.pubsub`. + +You can check +[this example for more](https://github.com/Urigo/graphql-mesh/tree/master/examples/openapi-subscriptions) + +### Now You + +We would love to hear your use cases and feedback to make Mesh better and more powerful! Feel free +to submit issues on GitHub to share your ideas or you can also join our community channel on +Discord! diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-mesh-v1-hive-gateway-v1/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-mesh-v1-hive-gateway-v1/page.mdx new file mode 100644 index 00000000000..fa83eab2215 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-mesh-v1-hive-gateway-v1/page.mdx @@ -0,0 +1,160 @@ +--- +title: Introducing GraphQL Mesh v1 and Hive Gateway v1 +authors: arda +tags: [graphql, graphql-mesh, graphql-hive, graphql-federation] +date: 2024-09-10 +description: + We released Version 1 of GraphQL Mesh and Hive Gateway, enabling flexible, open-source GraphQL + Federation with both GraphQL and non-GraphQL sources. +--- + +We, The Guild, are proud to announce both the Version 1 release of +[GraphQL Mesh](https://the-guild.dev/graphql/mesh) and +[Hive Gateway](https://the-guild.dev/graphql/hive/docs/gateway), and we believe this release brings +a new perspective to [GraphQL Federation](https://the-guild.dev/graphql/hive/federation) that we +call “Query Anything, Run Anywhere”. + +After several years of experience with composed APIs with early v0 versions of GraphQL Mesh and +Schema Stitching, we have completely rebuild GraphQL Mesh from scratch for GraphQL Federation. + +> **TL;DR**: The previous GraphQL Mesh is now split into two complimentary projects: +> +> - **GraphQL Mesh**: Compose and Transform Datasources into a Federated GraphQL Schema. Query +> anything anywhere. [Get started with GraphQL Mesh](https://the-guild.dev/graphql/mesh) +> - **Hive Gateway**: Our Federation Gateway for that integrates with GraphQL Mesh and Schema +> registries such as GraphQL Hive or Apollo Studio. +> [Get started with Hive Gateway](https://the-guild.dev/graphql/hive/docs/gateway) + +## What is Hive Gateway? {/* eslint-disable-line mdx/remark */} + +Hive Gateway is our fully +[open source](https://github.com/ardatan/graphql-mesh/tree/master/packages/hive-gateway) and +[MIT](https://github.com/ardatan/graphql-mesh/blob/master/LICENSE)-licensed GraphQL Gateway with +native support for GraphQL Federation. + +It is designed to fully integrate with with our GraphQL Hive Platform offering, but also neatly +integrates with other Schema Registries such as Apollo Studio. + +Hive Gateway is built on top of our existing and widely used open-source packages such as +[GraphQL Yoga](https://the-guild.dev/graphql/yoga-server) and +[GraphQL Tools](https://the-guild.dev/graphql/tools). + +With Hive Gateway adding these features is now a simple configuration change, **no enterprise +license is required**! + +- [GraphQL Subscriptions](https://the-guild.dev/graphql/hive/docs/gateway/subscriptions) (via + WebSocket, SSE, or HTTP Callbacks) +- [GraphQL API Usage and Analytics Reporting (GraphQL Hive or Apollo Studio)](https://the-guild.dev/graphql/hive/docs/gateway/usage-reporting) +- [Authentication and Authorization (JWT)](https://the-guild.dev/graphql/hive/docs/gateway/authorization-authentication) +- [Role-based Access Control (RBAC)](https://the-guild.dev/graphql/hive/docs/gateway/authorization-authentication#policy-directive-to-fetch-the-roles-from-a-policy-service) +- [Observability with Open Telemetry and Prometheus](https://the-guild.dev/graphql/hive/docs/gateway/monitoring-tracing) +- [Caching](https://the-guild.dev/graphql/hive/docs/gateway/other-features/performance) +- [Rate Limiting](https://the-guild.dev/graphql/hive/docs/gateway/other-features/security/rate-limiting) +- [Persisted Documents](https://the-guild.dev/graphql/hive/docs/gateway/persisted-documents) +- [Query Complexity Analysis](https://the-guild.dev/graphql/hive/docs/gateway/other-features/security/cost-limit) +- And more… + +We listened to the feedback of the companies using Version 0 of GraphQL Mesh, and decided that Hive +Gateway should be as easy-to-use and be batteries-included by default. Many of our users are not as +versed and deep within the JavaScript and npm eco-system. The new configuration is designed in a way +to be approachable for users from all backgrounds. + +Getting started is easy, whether you're already using the Hive Schema Registry, Apollo GraphOS, or +local subgraphs. With just a few lines of configuration, you'll end up with a running Gateway in no +time. + +### How well does Hive Gateway support GraphQL Federation? {/* eslint-disable-line mdx/remark */} + +We put in a lot of effort over the last year into having a Federation compatibility test suite. We +rebuilt our Federation Query Planner and Execution Engine completely from scratch from these +learnings and now open-sources the test suite so other Apollo Federation Gateways can too benefit +from our research as well. It already helped to discover and report some regressions in Apollo +Router to the Apollo Team. We hope that this test suite will overall enhance the quality of all the +GraphQL gateways on the market. + +### Do I have to use the Hive Registry? {/* eslint-disable-line mdx/remark */} + +No. You can gradually migrate to the Hive platform if desired. If you want to use your existing +schema registry (e.g. GraphOS), Hive Gateway supports these registries as well. You don't have to +migrate to the Hive Platform if you are just looking for a replacement to Apollo Router or Apollo +Gateway. + +### Deploy and Run Anywhere {/* eslint-disable-line mdx/remark */} + +Hive Gateway can run anywhere. No need to mess with JavaScript and Node.js. You can choose your +preferred way: + +- Single Executable Binary +- Docker Image +- NPM package +- If you are a JavaScript Tinkerer + - Node.js, Bun, or Deno + - Cloudflare Workers, AWS Lambda, Azure Functions and Google Cloud Functions. + - Integrate with your existing server framework such as Express, Fastify, Hapi and Koa. + +## What about GraphQL Mesh? {/* eslint-disable-line mdx/remark */} + +Previously what is now known as Hive Gateway has been one of the many building block of GraphQL +Mesh. The other significant building block of GraphQL Mesh is the possibility to convert and compose +any amount of APIs such as REST/OpenAPI, SOAP, gRPC or even a database like MySQL into a single +unified GraphQL API, while applying a wide range of transforms e.g. for renaming or filtering the +output API types. + +A lot of our clients have been using this approach to transform legacy APIs and all kind of other +sources for many years with great success. + +From now on, GraphQL Mesh now only focuses only on composing datasources into a single annotated +GraphQL/Supergraph SDL file that is 100% compatible with Apollo Federation. + +Serving the composed GraphQL schema is now a concern of Hive Gateway, which seamlessly integrates +with GraphQL Mesh. + +All of the features introduced in GraphQL Mesh v0 remain unchanged. However, GraphQL Mesh now builds +on top of the Apollo Federation specification. That means you do all the conversion, generation and +stitching process at build time once, and then you generate a supergraph or subgraph SDL that can be +consumed by any +[Apollo Federation compatible gateway](https://the-guild.dev/graphql/hive/federation-gateway-audit). + +To summarize, GraphQL Mesh is a framework that allows you to convert any API (REST, SOAP etc) into +the Apollo Federation subgraph to be consumed by a gateway. For the best experience, we recommend +using GraphQL Hive Gateway. + + + +### How can I migrate to v1? {/* eslint-disable-line mdx/remark */} + +While GraphQL Mesh v1 introduces several breaking changes, the core functionality remain the same as +in v0. All transforms, plugins, and other features are still available through the combination of +GraphQL Mesh and Hive Gateway. Simply follow our migration guides to make the transition. And as +always, feel free to reach out to us directly for assistance. + +However, there's no need to rush your migration — we will continue to maintain v0 for the +foreseeable future. + +### What is the relationship between GraphQL Mesh and Hive Gateway? {/* eslint-disable-line mdx/remark */} + +GraphQL Mesh generates a GraphQL Federation subgraph from a non GraphQL source (OpenAPI, SOAP, gRPC +etc) and/or a GraphQL API that doesn't have federation metadata. Then you can either compose that +subgraph with Mesh, or publish it to the schema registry, or compose it using other tools like +Apollo Rover to get a Federation Supergraph. Then this generated supergraph is consumed by a GraphQL +Gateway like Hive Gateway. + +## Query Anything, Run Anywhere + +As our initial motto from the earlier stages of GraphQL Mesh. We still follow it. + +This time we combined this idea with Apollo Federation. GraphQL Mesh now federates non GraphQL and +GraphQL sources on top of the official Apollo Federation specification. + +Hive Gateway is there to orchestrate calls to those APIs and serve the responses to the client. You +can use them together or take one of them and use with other alternatives. + +Both are free, open-source, MIT-licensed, without the need to pay extra for enterprise grade +features. + +- [Learn more about Hive Gateway](https://the-guild.dev/graphql/hive/docs/gateway) +- [Learn more about GraphQL Mesh](https://the-guild.dev/graphql/mesh) diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-mesh/cover.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/cover.jpg new file mode 100644 index 00000000000..a5a40c21f95 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/cover.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-mesh/gateway-1.png b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/gateway-1.png new file mode 100644 index 00000000000..be459cdc3d5 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/gateway-1.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-mesh/gateway-2.png b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/gateway-2.png new file mode 100644 index 00000000000..799b0c6b379 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/gateway-2.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-mesh/img-1.png b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/img-1.png new file mode 100644 index 00000000000..c4eda3b1b16 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/img-1.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-mesh/img-2.png b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/img-2.png new file mode 100644 index 00000000000..3029b17c3e5 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/img-2.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-mesh/libraries.png b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/libraries.png new file mode 100644 index 00000000000..213d6b67bca Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/libraries.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-mesh/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/page.mdx new file mode 100644 index 00000000000..c6bd00ae91a --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/page.mdx @@ -0,0 +1,395 @@ +--- +title: GraphQL Mesh - Query anything, run anywhere +authors: uri +tags: [graphql, graphql-mesh, graphql-federation] +date: 2020-03-23 +description: + We are excited to announce GraphQL Mesh. A powerful open source library enabling developers to + leverage the power of GraphQL without requiring changes +--- + +We are excited to announce [GraphQL Mesh](https://github.com/Urigo/graphql-mesh) — a powerful open +source library enabling developers to leverage the power of GraphQL without requiring changes to the +technologies your servers already use. + +We've been using this tool to significantly improve our clients stacks — improving the areas that a +classic GraphQL gateways would have left untouched + +![Mesh SDK](./sdk.png 'Mesh SDK') + +## The Basics + +- GraphQL Mesh takes **any source schema** (openapi/Swagger, json-schema, SOAP, gRPC, SQL, Mongoose, + GraphQL, Federated GraphQL, queue systems, and others) and let you query it as **GraphQL without + changing the source service** +- Choose the fields you want, in the shape you want them +- Get full type-safety for remote APIs +- **Schema unification/Stitching for any source** +- GraphQL Mesh **runs anywhere**. As a standalone gateway, as a datasource, or run as a local, + distributed GraphQL SDK to simplify service-to-service communications +- Each step of the transformation is fully customizable +- Ability to provide your API consumers the GraphQL experience without the operational overhead + +## Intro + +GraphQL Mesh allows you to use GraphQL query language to access data in remote APIs that don't run +GraphQL (and also ones that do run GraphQL). + +It can be used as a gateway to other services, or run as a local GraphQL schema SDK (data source) +that aggregates data from remote APIs. + +The goal of GraphQL Mesh is to let developers easily access services that are written in other APIs +specs (such as gRPC, OpenAPI, Swagger, oData, SOAP, GraphQL and even SQL) with GraphQL queries, +mutations and subscriptions. + +GraphQL Mesh gives the developer the ability to modify the output schemas, link types across schemas +and merge schema types. You can even add custom GraphQL types, resolvers, links and more. + +It allows developers to control the way they fetch data, and overcome issues related to backend +implementation, legacy API services, chosen schema specification and non-typed APIs. + +Here are some use cases and how GraphQL Mesh can be used to support them: + +- Querying legacy/non-typed APIs with an easy query language that is fully type-safe +- Aggregate, transform and link data across multiple APIs +- Proxy and have a simple way to access data from various API specifications without the need to + learn each one specifically +- Access and fetch data across microservices (a merged Data Graph) without adding a central failure + point to your deployment architecture + +![Pipeline Architecture](./pipeline-architecture.png 'Pipeline Architecture') + +The way GraphQL Mesh works is: + +- Collect API schema specifications from services, registries or simple files +- Create a runtime instance of fully-typed SDK for each service +- Convert the API specs of each service to GraphQL schema +- Applies custom schema transformations and schema extensions +- Creates fully-typed, single schema, GraphQL SDK to fetch data from your services. + +GraphQL Mesh is acting as a proxy to your data, and uses common libraries to wrap your existing API +services. You can use this proxy as an SDK in your service by running the GraphQL schema locally +(with GraphQL `execute`), or you can deploy this as a gateway layer to your internal service. + +> **Note:** Our goal with GraphQL Mesh is to create an easy-to-use GraphQL proxy to your own data +> and services. You may want to consider implementing another layer to expose your data publicly. + +## A Short Break — Who Are We + +GraphQL Mesh was created by the open source group called [The Guild](/). + +We currently maintain the following libraries: + +![Libraries](./libraries.png 'Libraries') + +We love what we do and are fulfilled by that, but if you wish to support us, here are some possible +ways: + +- **Work with us** - The best way to support us is to let us work with you :) We work with clients + from all over the world and in the process learn from them how to make our libraries better. We + work with companies from fortune 50 to small startups with different types of engagements. We help + enhance developer teams, making individuals more productive and help to develop themselves. + [Reach out](https://github.com/urigo) directly for a free meeting, even just for a fun chat to + share ideas and get feedback! +- **Tell people about us** — Many people who use our libraries, don't know who we are and don't know + about all the other libraries we've created. Help us spread the word! +- **Contribute** — Better docs -> answer issues -> Reproduce bugs and create tests — we can't stress + how valuable this would be and we would love to support your journey into open source! Also, as + The Guild is distributed, create your own new libraries under your own name and share it with us, + we'll help you promote and maintain it if you want! + +We have a large community as well which you can join by +[following the repositories](https://github.com/the-guild-org/Stack) and our +[Discord channel](https://discordapp.com/invite/xud7bH9). We are having our first new monthly Guild +community meeting very soon! + +## Use Cases (3 Example Use Cases) + +### 1 — the Simple Use Case (GraphQL Data Source per Endpoint) + +So you got yourself a new GraphQL server! + +It sits between your frontend and your former REST backends. And you got the benefits of having type +safety over the network. + +But what about the services and data sources you **query from your resolvers**? + +In your resolvers you still call regular REST endpoints? They might not be typed and might not +return the shape of response you want them to return. + +![Any](./img-1.png 'Any') + +Wouldn't it be nice if you could use those data sources as GraphQL sources? + +One option would be to migrate those services to GraphQL or build a GraphQL wrapper for each, which +might not be so feasible, or would take too long for the following reasons: + +- In most large companies, wrapping each service in GraphQL or adding a GraphQL implementation to + each service is **not a realistic option**. The service maintainers need to learn, agree and add + new tasks for their already busy backlog. +- The capabilities the GraphQL engine brings us (orchestration, restructuring) are very powerful but + usually runs on the provider. That means that each service will now behave differently on + different consumers. In many Enterprise architectures, that behaviour can be harder to cache and + puts a burden on the service team that might not be the right responsibility for them. +- It might not always be a good idea to dictate a single protocol for all your services to use. It's + a bit like dictating a single programming language for all your services in the organization. + +Can we **use the benefits of GraphQL without rewriting our services** and moving execution burden to +them? + +That way we could get type safety and querying abilities (you don't really care about less data over +the network between services). + +That's a first place where GraphQL Mesh can help you — simply input **any schema file** (For example +Swagger definitions), from any source to the Mesh config, and we'll generate a **fully typed GraphQL +SDK**, take would make it look like those sources exposed GraphQL themselves. + +That SDK can run locally on your GraphQL server without affecting the underlining services at all. + +![No Any](./img-2.png 'No Any') + +### 2 — the Merging Use Case + +Great! So now you have a lot of “GraphQL endpoints” that simplify and automate a lot of the work you +needed to do in your resolvers. + +But maybe some of those services could be **combined into a single entity Graph**? + +The second place GraphQL Mesh can come in handy is that it lets you stitch together those converted +GraphQL endpoints into a single endpoints and a Graph. + +**schema stitching for any source!** + +So now you can delegate a lot of logic from your manual GraphQL server into GraphQL Mesh. + +### 3 — the Service Mesh Use Case + +So you've done all that work on your GraphQL gateway. + +But there are other services in your network that might need to query other services that only +expose an HTTP/SOAP/gRPC endpoints. + +They still need to orchestrate those calls, validate them and shape them to the structure they need +(a good example might be a ML service that crunches data from many other services). + +One of the interesting things about Mesh is that it doesn't impose any changes to the services and +that it can run as an SDK anywhere. + +So now each service can get an SDK of the full Graph, which it can run locally in a distributed way, +instead of having all services calls going through a central gateway. + +## Usage + +[**Check out all the examples on our repo**](https://github.com/Urigo/graphql-mesh/tree/master/examples) + +To get started with GraphQL Mesh, create a simple `.yaml` file that points to your existing API +services spec — your Swagger file, `.proto` files or your GraphQL service, and choose the correct +handler for it. + +Here's a simple example for fetching and aggregating data across multiple external services. It uses +2 independent OpenAPI data sources, and links them together. + +**Before:** + +```typescript +async function getCityAndWeather(city: string): Promise<{ city: string; weather: any }> { + const cities /* any */ = await fetchApi(`https://cities-api/cities?search=${city}`) + const city /* any */ = cities.data[0] + const weather /* any */ = await fetchApi( + `https://weather-api/forecast?lat=${city.latitude}&lon=${city.longitude}` + ) + + return { city, weather } +} +``` + +Code isn't typed, and it requires you to write code that fetches and merges it manually. It also +means that your code deals with request-response and the next time you'll need to fetch data from +those sources, you'll need to duplicate code and apply those merging logics again. + +It also means that your code should be fully aware of what APIs it fetches from, and what data they +return. + +**After:** + +First, you can configure GraphQL Mesh to load your remote services, as following: + +```yaml +sources: + # This source will create: + # type City { id: ID! name: String! latitude: Float! longitude: Float! } + # type CitiesResult { data: [City!]! } + # type Query { cities(search: String!): CitiesResult } + - name: Geo + handler: + openapi: + source: https://cities-api/cities/schema.json + headers: + Authorization: API-KEY + + # This source will create + # type Forecast { clouds: Int minTemp: Int maxTemp: Int } + # type ForecastResult { data: Forecast! } + # type Query { forecastByLatLon(lat: Int!, lon: Int!): ForecastResult } + - name: Weather + handler: + openapi: + source: https://weather-api/forecast/schema.json +``` + +The data aggregation logic can be moved into a custom GraphQL resolver: + +```typescript +import { Resolvers } from './mesh/types.generated' + +export const resolvers: Resolvers = { + dailyForecast: async (city, args, { Weather }) => { + return Weather.api.forecastDailyByLatLon(city.latitude, city.longitude).then(r => r.data) + } +} +``` + +We are using `city` parent value, which is being fetched by the Cities API, and fetch data from the +weather API based on the city location. + +And your data fetching function can use GraphQL to query for the data, and GraphQL Mesh will +generate the corresponding method `getCityAndWeather` for you: + +```graphql +query getCityAndWeather($search: String!) { + cities(search: $search) { + data { + name + latitude + longitude + forecast { + clouds + minTemp + maxTemp + } + } + } +} +``` + +This way, the implementation of the `getCityAndWeather` is being generated as a typed function, and +the GraphQL query is agnostic to the services it fetches data from. + +The logic linking those API services is now part of your GraphQL engine — you only need to deal with +writing a GraphQL query to fetch the data you need. + +### GraphQL Mesh as a Local Schema + +The output of GraphQL Mesh is a generated code, so you can import it directly from the generated +directory. You can load the schema and use GraphQL `execute` to run queries and mutations. + +Using this method, the query and the mutation will be executed locally in your application, and the +GraphQL engine will be in charge of running the actual data fetching, using the source APIs. + +This method brings the GraphQL schema management closer to the developer, and allows you to modify +and manage the GraphQL schema according to your application needs. + +![GraphQL Mesh as an SDK running inside the services](./gateway-1.png 'GraphQL Mesh as an SDK running inside the services') + +### GraphQL Mesh as a Gateway + +You can use the generated schema and run it on its own server, as a gateway to your data fetching. +You can share a single gateway with multiple applications — it means that changing and manipulating +the schema is being managed by the gateway itself, and not the consuming applications. + +Using GraphQL Mesh output as a gateway means that the consuming applications will execute GraphQL +over the network to the gateway, and from the gateway to the source APIs. + +![GraphQL Mesh as a self hosted gateway](./gateway-2.png 'GraphQL Mesh as a self hosted gateway') + +### Querying Data from Mesh + +Because the output of GraphQL Mesh is a GraphQL schema, you can choose how to execute and query it, +according to your needs and preference: + +- You can use simple GraphQL queries and mutations directly, and fetch data from the generated + schema. +- You can use GraphQL queries and mutations with GraphQL Code Generator, in order to generate a + ready-to-use SDK based on your GraphQL operations. It will also make sure that your result types + are types according to your GraphQL selection set. +- You can use any other GraphQL client (graphql-request, apollo-client, gqless). + +## How Does It Work? + +_This is a short version, In the upcoming week we'll write more in depth article about each layer of +the architecture and our thoughts and choices while implementing the library — Spec conversions, +code-generation vs. runtime, type safe SDKs, schema transformations, merging types, caching, data +loaders and optimizations_ + +### Converting APIs Specification to GraphQL + +GraphQL Mesh initially loads your existing services' APIs specification — either from a remote +source (where introspection/reflection is available) or from local files. + +It then attempts to convert your specification into a GraphQL schema, in an optimal way. + +Converting APIs specifications to GraphQL schema it not always simple — some of the traditional API +specification uses a request-response approach (each endpoint or method has a type for it's request +and a type for it's response, and it's not necessarily shared), compared to GraphQL, which uses +type-based schema that allow links and re-use of types (hence the graph). + +Erik Wittern, Alan Cha, and Jim A. Laredo from IBM wrote a very interesting +[article about the difficulties of converting REST-like API to a GraphQL schema](https://arxiv.org/abs/1809.08319). +Those concepts are implemented in [`openapi-to-graphql`](https://github.com/IBM/openapi-to-graphql) +library, which is being used by GraphQL Mesh and we are contributing to. We apply similar approach +to other API specifications. + +Aside from wrapping the service API with a GraphQL layer, GraphQL Mesh also allow you to easily +extend or modify the converted schema, all with a fully-typed SDK, tailored specifically to the API +service you are using + +It also means that creating new fields or links between the schemas is easier, and you can always be +sure it's using the correct object structure. + +### Schema Transformations (Extensions, Merging) + +GraphQL Mesh allows you to do manipulations on schemas. You can either manipulate the schema before +unifying it (for example, prefix all types, change a type name, delete a field) or after unifying +all schemas (link field across schemas or merge types). + +All phases of Mesh are customizable and pluggable — Which means you can adjust it with any strategy +that works for you. + +One benefit is that we can now choose between Schema Stitching and +[Federation](https://the-guild.dev/graphql/hive/federation)! + +In our Schema Stitching example, we use `graphql-tools-fork` and it's powerful schema-stitching +implementation to create new merge types on the output schema. + +In our Federation example, we used Apollo Server's Federation and Gateway libraries, together with +the [`graphql-transform-federation`](https://github.com/0xR/graphql-transform-federation) library, +which let us add the Federation metadata to existing GraphQL schemas. So we use Federation as a +local merging strategy, together with its query planner executor. + +We can now compare those two strategies next to each other and show the benefits and downsides of +each approach for many different use cases. + +### SQL as a Source + +Right before we decided to open source the library, we had a thought — why only focus on API +protocols. + +There are many inspiring projects out there that convert database schemas to SQL. + +So why not integrate those schemas as well? + +So we decided to do a POC using the amazing +[Postgraphile project](https://graphile.org/postgraphile)! + +Check out our +[example for merging Geo DB with GitHub](https://github.com/Urigo/graphql-mesh/tree/master/examples/postgres-geodb#locationsdevelopers-examples)! +We would love to hear and help you integrate with more databases and sources! + +## Summary + +GraphQL Mesh has been a huge upgrade for us and our clients. But we believe this is only the start +of the conversation around GraphQL and other protocols, merging remote schemas and execution. Try it +out today and contact us with all the questions, use cases and feedback you might have! + +[Our repositories](https://github.com/the-guild-org/Stack), +[Discord channel](https://discordapp.com/invite/xud7bH9) and newsletter. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-mesh/pipeline-architecture.png b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/pipeline-architecture.png new file mode 100644 index 00000000000..584d59c8b98 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/pipeline-architecture.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-mesh/sdk.png b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/sdk.png new file mode 100644 index 00000000000..c8d3fe3f778 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-mesh/sdk.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules-auth/cover.jpeg b/packages/web/docs/src/app/blog/(posts)/graphql-modules-auth/cover.jpeg new file mode 100644 index 00000000000..4ed150cbd5b Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-modules-auth/cover.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules-auth/graphql-modules.png b/packages/web/docs/src/app/blog/(posts)/graphql-modules-auth/graphql-modules.png new file mode 100644 index 00000000000..35a3eb5cc53 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-modules-auth/graphql-modules.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules-auth/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-modules-auth/page.mdx new file mode 100644 index 00000000000..dfc0d5891bf --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-modules-auth/page.mdx @@ -0,0 +1,674 @@ +--- +title: Authentication and Authorization in GraphQL +authors: dotan +tags: [graphql, graphql-modules, authentication] +date: 2019-06-19 +description: + After a few years of working with GraphQL, we've learned some lessons about GraphQL, and how to + authenticate and authorize GraphQL API. +--- + +After a few years of working with GraphQL, as open-source developers and as infrastructure team in +large enterprises, we've learned some lessons about GraphQL, and how to authenticate and authorize +GraphQL API. + +Authentication and authorization should be simple, because for most cases, it's just a piece of code +that we wish to run before letting users access certain resources. + +In this article we'll go through all the different ways of implementing authentication and +authorization, the benefits and downsides to each one, and offer a solution that we believe is the +best philosophy to do so. + +## Authentication / Authorization? + +I found +[good answer in StackOverflow](https://stackoverflow.com/questions/6367865/is-there-a-difference-between-authentication-and-authorization/6367931#6367931) +covers the main differences between authentication and authorization: + +![StackOverflow Answer](./stackoverflow-answer.png 'StackOverflow Answer') + +In this article, I'll cover the difference between authentication and authorization with GraphQL +APIs, explain how to implement them with GraphQL server, and with the +[GraphQL-Modules framework](http://graphql-modules.com). + +We learned that a good implementation for GraphQL authentication has the following features +regarding _authentication_: + +- Your authentication implementation should eventually provide the `currentUser` to the resolvers +- Your resolvers should not know about the authentication logic (separation between the business + logic and the authentication logic) +- You wish to protect only parts of your GraphQL schema, and not all of it. +- You want to be able to authenticate parts of your schema, on a `field` level. + +And the following features regarding _authorization_: + +- You wish to protect some fields, according to custom rules. +- You wish to run custom logics that protects parts of your GraphQL schema. +- Our custom rules should not be coupled to a specific resolver. + +## Where to Put Your Authentication? + +To understand why implementing authentication in GraphQL might be tricky, let's start with the +following chart: + +![Where to put your authentication?](./where-to-put-graph.png 'Where to put your authentication?') + +A request comes from the network, go through an HTTP server (for example, `express`) — That's **#1** +in the chart. Then it goes through GraphQL server, which builds a `context`, and then it runs your +resolvers — **#2**. Then, you have your business logic, that you might want to separate from the +resolvers, that's **#3**. + +Basically, you can implement GraphQL authentication on each part of this chart. But there are +differences between those points that might effect the behaviour of your server, and might limit you +later doing some things. + +Let's focus on the features requirements I mentioned before, and try to understand how the +implementation selection might affect you. + +So let's take **#1**: If you implement your authentication on **#1** (between the network request +and the HTTP server), you probably does it with express middleware or something similar. The benefit +in this is the separation between authentication logic and your business logic. But, you can't +connect your data from the HTTP request to your GraphQL server, and it might be difficult later to +get access to the currentUser. It also protects the entire GraphQL endpoint, and not only parts of +it. **So that's nice, but not perfect**. + +Regarding **#3**: Implementing authentication as part of your resolver code or business logic code +isn't a good idea. It usually means that your app code knows too much about the authentication, and +it's probably coupled to it. It might to simpler to implement, but much more difficult to reuse +later. + +And, **#2**: **You can implement authentication between your HTTP server and your GraphQL server, by +using the GraphQL `context`**. By implementing a custom `context` building function, you can access +the network request and build your `context` object, and add `currentUser` to it. Then, your +`resolvers` getting called by the GraphQL engine. + +**From our experience, we can tell that implementing authentication in this part of your GraphQL +server gives you control over your authentication flow, if it's done right.** + +In the next chapter we'll dive in, and see how easy it's to implement in with Apollo-Server. + +## Getting Started with Authentication + +We'll start by implementing a simple authentication flow in GraphQL server (using +[Apollo-Server 2.0](https://github.com/apollographql/apollo-server)). + +I'm assuming that you already have your own favorite way to authenticate, whether it's with headers, +cookies or any other way. In the point of view of the server, we expect to get a token, and we'll +assume that it comes from the headers for the simplicity. + +Let's start by creating a simple Apollo-Server instance, with a very simple schema. We'll focus on +exposing the `currentUser` as a query field. + +```ts +import { ApolloServer } from 'apollo-server' +import gql from 'graphql-tag' + +const typeDefs = gql` + type Query { + me: User + } + type User { + id: ID! + username: String! + } +` + +const server = new ApolloServer({ typeDefs }) + +server.listen().then(({ url }) => { + console.log(`🚀 Server ready at ${url}`) +}) +``` + +Now, let's implement `context` , and see if we can get a valid user out of the values we get there. +Our goal in this part is only to validate the token, and try to trade it for a user. If the token or +the user are not valid — we don't want to throw an exception here, because that way, our server will +not support public queries. So we're just trying to get a user and a token, and return them to +Apollo-Server, so it will use it as the context for our resolvers. If something went wrong with the +validation — we just return `null` for the `user` and `authToken`. + +```ts filename="auth-helpers.ts" +export interface User { + _id: string + username: string +} + +export async function tradeTokenForUser(token: string): Promise { + // Here, use the `token` argument, check its validity, and return + // the user only if the token is valid. + // You can also use external auth libraries, such as jsaccounts / passport, and + // trigger its logic from here. +} +``` + +```ts +import { ApolloServer } from 'apollo-server' +import gql from 'graphql-tag' +import { tradeTokenForUser } from './auth-helpers' + +const HEADER_NAME = 'authorization' + +const typeDefs = gql` + type Query { + me: User + } + type User { + id: ID! + username: String! + } +` + +const server = new ApolloServer({ + typeDefs, + context: async ({ req }) => { + let authToken = null + let currentUser = null + + try { + authToken = req.headers[HEADER_NAME] + + if (authToken) { + currentUser = await tradeTokenForUser(authToken) + } + } catch (e) { + console.warn(`Unable to authenticate using auth token: ${authToken}`) + } + + return { + authToken, + currentUser + } + } +}) + +server.listen().then(({ url }) => { + console.log(`🚀 Server ready at ${url}`) +}) +``` + +_Note that in a good GraphQL server implementation, the `context` is built once per request, and +shouldn't change over time. So they only way to modify it is using the `context` function that +builds it before the resolver getting called._ + +Now, we can finally implement the `resolvers` for the server: + +```ts +import { ApolloServer } from 'apollo-server' +import gql from 'graphql-tag' +import { tradeTokenForUser } from './auth-helpers' + +const HEADER_NAME = 'authorization' + +const typeDefs = gql` + type Query { + me: User + } + type User { + id: ID! + username: String! + } +` + +const resolvers = { + Query: { + me: (root, args, context) => context.currentUser + }, + User: { + id: user => user._id, + username: user => user.username + } +} + +const server = new ApolloServer({ + typeDefs, + resolvers, + context: async ({ req }) => { + let authToken = null + let currentUser = null + + try { + authToken = req.headers[HEADER_NAME] + + if (authToken) { + currentUser = await tradeTokenForUser(authToken) + } + } catch (e) { + console.warn(`Unable to authenticate using auth token: ${authToken}`) + } + + return { + authToken, + currentUser + } + } +}) + +server.listen().then(({ url }) => { + console.log(`🚀 Server ready at ${url}`) +}) +``` + +This is a naive implementation, that assumes that the token is valid, because otherwise the +`context.currentUser` will be `null`. That leads us to the next steps: implementing guards. + +To add guards to your resolvers, you can use a simple Middleware approach, and wrap your resolver +with a function that checks if `context.user` is valid, and otherwise, throws an error. You are +getting full control over your authentication flow, because you can choose which resolvers to wrap. + +So let's implement a simple guard, and use it in our server. I also added a _public_ part of the +schema (`Query.serverTime`), that should be public, and not effected by the authentication flow. So +it's not wrapped with our guard. + +```ts filename="authenticated-guard.ts" +export const authenticated = next => (root, args, context, info) => { + if (!context.currentUser) { + throw new Error(`Unauthenticated!`) + } + + return next(root, args, context, info) +} +``` + +```ts +import { ApolloServer } from 'apollo-server' +import gql from 'graphql-tag' +import { tradeTokenForUser } from './auth-helpers' + +const HEADER_NAME = 'authorization' + +const typeDefs = gql` + type Query { + me: User + serverTime: String + } + type User { + id: ID! + username: String! + } +` + +const resolvers = { + Query: { + me: authenticated((root, args, context) => context.currentUser), + serverTime: () => new Date() + }, + User: { + id: user => user._id, + username: user => user.username + } +} + +const server = new ApolloServer({ + typeDefs, + resolvers, + context: async ({ req }) => { + let authToken = null + let currentUser = null + + try { + authToken = req.headers[HEADER_NAME] + + if (authToken) { + currentUser = await tradeTokenForUser(authToken) + } + } catch (e) { + console.warn(`Unable to authenticate using auth token: ${authToken}`) + } + + return { + authToken, + currentUser + } + } +}) + +server.listen().then(({ url }) => { + console.log(`🚀 Server ready at ${url}`) +}) +``` + +Great! So now our server is authenticated and we can get the `currentUser` in our resolvers. It also +protects only the part of the GraphQL schema that we wanted to protect, and the authentication flow +is separated from the resolvers, so each resolver does only what it needs. + +## Getting Started with Authorization + +Now that we understand the concept of authentication in GraphQL servers, we can also implement +authorization. + +Implementing Authorization is no different from implementing the `authenticated` guard we did +before. It's just another guard, or resolver wrapper we can use. + +Let's add `type Article` to the schema, and allow creating article only to users with `role` set to +`EDITOR` . It's that simple: + +```ts +import { ApolloServer } from 'apollo-server' +import gql from 'graphql-tag' +import { tradeTokenForUser } from './auth-helpers' +import { authenticated } from './authenticated-guard' +import { validateRole } from './validate-role' + +const HEADER_NAME = 'authorization' + +const typeDefs = gql` + type Query { + me: User + serverTime: String + } + type User { + id: ID! + username: String! + } + type Article { + id: ID! + title: String! + content: String! + } + type Mutation { + publishArticle(title: String!, content: String!): Article! + } +` + +const resolvers = { + Query: { + me: authenticated((root, args, context) => context.currentUser), + serverTime: () => new Date() + }, + Mutation: { + publishArticle: authenticated( + validateRole('EDITOR')((root, { title, content }, context) => + createNewArticle(title, content, context.currentUser) + ) + ) + }, + User: { + id: user => user._id, + username: user => user.username + } +} + +const server = new ApolloServer({ + typeDefs, + resolvers, + async context({ req }) { + let authToken = null + let currentUser = null + + try { + authToken = req.headers[HEADER_NAME] + + if (authToken) { + currentUser = await tradeTokenForUser(authToken) + } + } catch (e) { + console.warn(`Unable to authenticate using auth token: ${authToken}`) + } + + return { + authToken, + currentUser + } + } +}) + +server.listen().then(({ url }) => { + console.log(`🚀 Server ready at ${url}`) +}) +``` + +```ts filename="validate-role.ts" +export const validateRole = role => next => (root, args, context, info) => { + if (context.currentUser.role !== role) { + throw new Error(`Unauthorized!`) + } + + return next(root, args, context, info) +} +``` + +## Next-Level Implementation with GraphQL-Modules + +![GraphQL Modules](./graphql-modules.png 'GraphQL Modules') + +If you wish to take your GraphQL server to the next level, and build a scalable, testable and +readable server, I recommend to give [GraphQL-Modules](https://graphql-modules.com) a try. + +With GraphQL-Modules, you can separate your schema to smaller pieces, and creates modules that are +in-charge of small parts of your code. It also provides a `resolversComposition` feature, that acts +like a powerful middleware mechanism, that let you write your modules without knowing about the +authentication flow, and just wrap your resolvers with guards in the app-level, so modules are +independent and do only what they need, and the app that uses these modules decide which resolvers +to authenticate. + +It also super powerful, because you can implement your GraphQL-Module as standalone module, use it +in multiple applications, and then apply different authentication/authorization rules in each app. + +To implement the same example above with GraphQL-Modules, you can create a module called `auth` and +move the `context` building logic to there. Then, create another module called `common` and the +general parts, and articles to manage the articles features. + +### This Way — Each Module Is in Charge of a Different Feature of Our App + +```ts filename="articles-module.ts" +import { GraphQLModule } from '@graphql-modules/core' + +export const articlesModule = new GraphQLModule({ + name: 'articles', + // authModule must be imported, because this module uses currentUser in the context + imports: [authModule], + typeDefs: gql` + type Article { + id: ID! + title: String! + content: String! + } + type Mutation { + publishArticle(title: String!, content: String!): Article! + } + `, + resolvers: { + Mutation: { + publishArticle: (root, { title, content }, { currentUser }) => + createNewArticle(title, content, currentUser) + } + } +}) +``` + +```ts filename="auth-module.ts" +import { GraphQLModule } from '@graphql-modules/core' +import { tradeTokenForUser } from './auth-helpers' + +const HEADER_NAME = 'authorization' + +export const authModule = new GraphQLModule({ + name: 'auth', + typeDefs: gql` + type Query { + me: User + } + type User { + id: ID! + username: String! + } + `, + resolvers: { + Query: { + me: (root, args, { currentUser }) => currentUser + }, + User: { + id: user => user._id, + username: user => user.username + } + }, + async contextBuilder({ req }) { + let authToken = null + let currentUser = null + + try { + authToken = req.headers[HEADER_NAME] + + if (authToken) { + currentUser = await tradeTokenForUser(authToken) + } + } catch { + console.warn(`Unable to authenticate using auth token: ${authToken}`) + } + + return { + authToken, + currentUser + } + } +}) +``` + +```ts filename="common-module.ts" +import { GraphQLModule } from '@graphql-modules/core' + +export const commonModule = new GraphQLModule({ + name: 'common', + typeDefs: gql` + type Query { + serverTime: String + } + `, + resolvers: { + Query: { + serverTime: () => new Date() + } + } +}) +``` + +```ts filename="resolvers-composition.ts" +import { authenticated } from './authenticated-guard' +import { validateRole } from './validate-role' + +export const resolversComposition = { + 'Query.me': [authenticated], + 'Mutation.publishArticle': [authenticated, validateRole] +} +``` + +```ts filename="server.ts" +import { ApolloServer } from 'apollo-server' +import { resolversComposition } from './resolvers-composition' + +const app = new GraphQLModule({ + name: 'app', + imports: [commonModule, authModule, articleModule], + resolversComposition +}) + +const { schema, context } = app + +const server = new ApolloServer({ + schema, + context +}) + +server.listen().then(({ url }) => { + console.log(`🚀 Server ready at ${url}`) +}) +``` + +### The Future of Authentication with GraphQL-Modules and GraphQL @directives + +If you wish to get rid of the `resolversComposition` wrapping, and use a more declarative way, you +can use GraphQL directives to decorate your schema and put the authentication/authorization there. + +To get started, add the following @directives to your `auth` module's schema: + +```graphql +directive @auth on FIELD +directive @protect(role: String) on FIELD +``` + +Then, you can easily alter your `resolversComposition` into a function that accepts `GraphQLSchema` +, and then check using your schema object, which resolves require which logic, and do a mapping: + +```ts +import { getFieldsWithDirectives } from '@graphql-modules/utils' +import { authenticated } from './authenticated-guard' +import { validateRole } from './validate-role' + +const DIRECTIVE_TO_GUARD = { + auth: () => authenticated, + protect: ({ role }) => validateRole(role) +} + +export const resolversComposition = ({ typeDefs }) => { + const fieldsAndTypeToDirectivesMap = getFieldsWithDirectives(typeDefs) + + for (const fieldPath in fieldsAndTypeToDirectivesMap) { + const directives = fieldsAndTypeToDirectivesMap[fieldPath] + + if (directives.length > 0) { + result[fieldPath] = directives + .map(directive => { + if (DIRECTIVE_TO_GUARD[directive.name]) { + const mapperFn = DIRECTIVE_TO_GUARD[directive.name] + + return mapperFn(directive.args) + } + + return null + }) + .filter(Boolean) + } + } + + return result +} +``` + +The, you add the directives to your schema declaration: + +```graphql +type Mutation { + publishArticle(title: String!, content: String!): Article! @auth @protect(role: "EDITOR") +} + +type Query { + me: User @auth +} +``` + +### What's Next? + +Another thing we are working on, is having some of your repeated authentication and authorization +logic installable through npm! + +We are working with the creators of the [js-accounts](https://github.com/accounts-js/accounts) +libraries to create a [GraphQL Modules module](https://github.com/accounts-js/accounts/pull/493) out +of their awesome packages, so you could just add it from npm and extend your server and schema. + +You can read this blog post to learn to use this library with GraphQL-Modules in a few steps; + +/blog/accountsjs-graphql-modules + +--- + +Authentication and authorization is a fundamental part of your GraphQL server. + +It should be easy and secure to implement and understand how to do that in order to help grow the +GraphQL community + +In this article we wanted to give you a complete overview of authentication and authorization in the +GraphQL ecosystem and give you the tools and the knowledge to implement it on your GraphQL servers. + +Please try all those different approaches and let us know your feedback about the best way for you +and if we can improve it even further. + +### All Posts about GraphQL Modules + +- [GraphQL Modules — Feature based GraphQL Modules at scale](/blog/graphql-modules) +- [Why is True Modular Encapsulation So Important in Large-Scale GraphQL Projects?](/blog/modular-encapsulation-graphql-modules) +- [Why did we implement our own Dependency Injection library for GraphQL-Modules?](/blog/dependency-injection-library-in-graphql-modules) +- [Scoped Providers in GraphQL-Modules Dependency Injection](/blog/graphql-modules-scoped-providers) +- [Writing a GraphQL TypeScript project w/ GraphQL-Modules and GraphQL-Code-Generator](/blog/graphql-typescript-modules-codegen) +- [Authentication and Authorization in GraphQL (and how GraphQL-Modules can help)](/blog/graphql-modules-auth) +- [Authentication with AccountsJS & GraphQL Modules](/blog/accountsjs-graphql-modules) +- [Manage Circular Imports Hell with GraphQL-Modules](/blog/manage-circular-imports-hell-in-graphql-modules) diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules-auth/stackoverflow-answer.png b/packages/web/docs/src/app/blog/(posts)/graphql-modules-auth/stackoverflow-answer.png new file mode 100644 index 00000000000..d1394a62d58 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-modules-auth/stackoverflow-answer.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules-auth/where-to-put-graph.png b/packages/web/docs/src/app/blog/(posts)/graphql-modules-auth/where-to-put-graph.png new file mode 100644 index 00000000000..ed35b22d9f7 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-modules-auth/where-to-put-graph.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules-scoped-providers/330126a7ddd518886b068e1f1e23ee41.png b/packages/web/docs/src/app/blog/(posts)/graphql-modules-scoped-providers/330126a7ddd518886b068e1f1e23ee41.png new file mode 100644 index 00000000000..28997af64e0 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-modules-scoped-providers/330126a7ddd518886b068e1f1e23ee41.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules-scoped-providers/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-modules-scoped-providers/page.mdx new file mode 100644 index 00000000000..ba222aadea6 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-modules-scoped-providers/page.mdx @@ -0,0 +1,303 @@ +--- +title: Scoped Providers in GraphQL-Modules +authors: arda +tags: [graphql, graphql-modules, di] +date: 2019-01-11 +description: Understand how scoped providers work in GraphQL-Modules Dependency Injection. +--- + +We recently released a new version of GraphQL-Modules with a new feature called Scoped Providers in +the dependency injection system of GraphQL-Modules. + +Dependency injection in **GraphQL-Modules** is optional, and you can use it according to your +preference. It provides a solution for writing `Provider`s, which are just classes, you can use to +write your business-logic, and this way to separate it from your API declaration, reuse it, and +communicate between modules easily. + +This new feature allows you to define `Provider`s to be used in different scopes; + +## Application Scope + +If you define a provider in this scope which is default, the provider will be instantiated on +application-start and will be same in the entire application and all the following requests. The +providers in this scope can be considered as a shared state across all users' interactions with our +application. It's basically means that the instance will be treated as +[Singleton](https://en.wikipedia.org/wiki/Singleton_pattern). + +For example, you have a provider called `ExampleApplicationProvider` , then this provider has a +counter in it; + +```typescript +import { Injectable, ProviderScope } from '@graphql-modules/di' + +@Injectable({ + scope: ProviderScope.Application +}) +export class ExampleApplicationProvider { + counter = 0 + increment() { + counter++ + return counter + } +} +``` + +And let's assume that we have a module declaration something like below; + +```ts +import { GraphQLModule } from '@graphql-modules/core' +import { ExampleApplicationProvider } from './ExampleApplicationProvider' + +export const ExampleModule = new GraphQLModule({ + providers: [ExampleApplicationProvider], + typeDefs: ` + type Mutation { + increment: Int + } + `, + resolvers: { + Mutation: { + increment: (root, args, context) => + context.injector.get(ExampleApplicationProvider).increment() + } + } +}) +``` + +Finally, let's try to call the following GraphQL Request in multiple times; + +```graphql +mutation ExampleMutation { + increment +} +``` + +In first call, you will get `1` , but in the second call you will get `2` . And in every request +this value will be incremented; because Application-Scoped Providers are kept in memory until the +application is terminated; and the same instance of that provider will be used on each request. +Let's see other types of providers to understand this one better. + +## Session Scope + +When a network request is arriving to your GraphQL-Server, GraphQL-Server calls the context factory +of the parent module. The parent module creates a session injector together with instantiating +session-scoped providers with that session object which contains the current context, session +injector and network request. This session object is passed through module's resolvers using +module's context. + +In other words, providers defined in the session scope are constructed in the beginning of the +network request, then kept until the network request is closed. While application-scoped providers +is kept during the application runtime, and shared between all the following network requests and +resolvers inside these requests, this type of providers would not be shared between different +requests but in resolver calls those belong to same network request. + +Let's try the same thing in that scope by creating a new provider called `ExampleSessionProvider` . + +```typescript +import { Injectable, ProviderScope } from '@graphql-modules/di' + +@Injectable({ + scope: ProviderScope.Session +}) +export class ExampleSessionProvider { + counter = 0 + increment() { + counter++ + return counter + } +} +``` + +Then change our module declaration to use this provider; + +```typescript +import { GraphQLModule } from '@graphql-modules/core' +import { ExampleSessionProvider } from './ExampleApplicationProvider' + +export const ExampleModule = new GraphQLModule({ + providers: [ExampleSessionProvider], + typeDefs: ` + type Mutation { + increment: Int + } + `, + resolvers: { + Mutation: { + increment: (root, args, context) => context.injector.get(ExampleSessionProvider).increment() + } + } +}) +``` + +So at this time, in every mutation call our Session-Scoped Provider's increment method will be +called and its value will be returned as a result of that mutation. + +```graphql +mutation ExampleMutation { + increment +} +``` + +In first call, you will get `1` , but in the second call you will get `1` again. But why? Because on +each request `ExampleSessionProvider` is instantiated from scratch specifically for that network +request which is called session in our DI system. But to see the point of the session scope, let's +assume we have two more resolvers called `multiply` . First let's add another method called +`multiply` that takes one argument to be multiplied by our counter value; + +```typescript +import { Injectable, ProviderScope } from '@graphql-modules/di' + +@Injectable({ + scope: ProviderScope.Session +}) +export class ExampleSessionProvider { + counter = 0 + increment() { + counter++ + return counter + } + multiply(times: number) { + counter = counter * times + return counter + } +} +``` + +After that, let's use this new method in our new resolver; + +```typescript +import { GraphQLModule } from '@graphql-modules/core' +import { ExampleApplicationProvider } from './ExampleApplicationProvider' + +export const ExampleModule = new GraphQLModule({ + providers: [ExampleApplicationProvider], + typeDefs: ` + type Mutation { + increment: Int + multiply(times: Int): Int + } + `, + resolvers: { + Mutation: { + increment: (root, args, context) => + context.injector.get(ExampleApplicationProvider).increment(), + multiply: (root, args, context) => + context.injector.get(ExampleApplicationProvider).multiply(args.times) + } + } +}) +``` + +When we call the following GraphQL request, + +```graphql +mutation ExampleMutation { + increment + multiply(times: 2) +} +``` + +In first call, the initial counter value `0` will be incremented . Then the result becomes `1` and +this value will be returned as a result of `increment` mutation, but on the same request we have +`multiply` as well. So the same counter value will be multiplied by `2` , and this value will be +returned as a result of `multiply` mutation. + +So, on every request the result will be: + +```json +{ + "increment": 1, + "multiply": 2 +} +``` + +But if the provider is in ApplicationScope, the result will be `[3,6]` in the second call. + +## Request Scope + +If you have request-scoped providers in your GraphQL Module, these providers are generated in every +injection. This means a request-scoped provider is never kept neither application state, nor session +state. So, this type of providers works just like +[Factory](https://en.wikipedia.org/wiki/Factory_method_pattern). It creates an instance each time +you request from the injector. + +Let's assume we do the similar changes for RequestScope. The results will be like below on each +request: + +```json +{ + "increment": 1, + "multiply": 0 +} +``` + +Because request-scoped providers are constructed on each `injector.get` call. They are kept in the +current function scope, they're not kept in any session or DI container. That's why, we can consider +them `factory` . + +As you can see the example and the results above, `ExampleApplicationProvider` is shared between +different network requests, while `ExampleSessionProvider` is shared between resolver-calls inside +the same network request. But, `ExampleRequestProvider` is only kept in the same resolver call. + +## New `ModuleSessionInfo` Built-In Provider + +Let's assume that you have a token required for your authentication process. And this token is +probably stored in request header. And GraphQL-Modules provides you access to network request in a +built-in provider called **_ModuleSessionInfo_**. This session object is the raw request/response +object passed by your HTTP Server. For example, if you're using Express, you will get something like +`{ req: IncommingMessage, res: Response }` . + +Every GraphQL-Module creates a _ModuleSessionInfo_ instance in each network request that contains +raw `Session` from the GraphQL Server, `SessionInjector` that contains Session-scoped instances +together with Application-scoped ones and `Context` object which is constructed with +`contextBuilder` of the module. But, notice that you cannot use this built-in provider. + +```typescript +@Injectable({ + scope: ProviderScope.Session +}) +class ExampleSessionScopeProvider { + constructor(private moduleSessionInfo: ModuleSessionInfo) { + moduleSessionInfo // { session, context, injector } + } +} + +@Injectable({ + scope: ProviderScope.Application +}) +class ExampleInvalidApplicationScopeProvider { + constructor(private moduleSessionInfo: ModuleSessionInfo) {} // Error: ModuleSessionInfo is not valid provider +} + +@Injectable({ + scope: ProviderScope.Application +}) +class ExampleApplicationScopeProvider implements OnRequest { + onRequest(moduleSessionInfo) { + moduleSessionInfo // { session, context, injector } + } +} +``` + +As you can see, you cannot use `ModuleSessionInfo` in Application Scope, because application-scoped +providers belong to the whole application, and application-scoped providers are completely +independent of the network request. + +However, you can use `onRequest` hook to have `ModuleSessionInfo` in your Application-Scoped +provider which is called on each network request. + +## What's Next? + +We are constantly trying to improve the set of tools provided by **GraphQL-Modules**, and we welcome +you to try it and share your experience and thoughts. + +### All Posts about GraphQL Modules + +- [GraphQL Modules — Feature based GraphQL Modules at scale](/blog/graphql-modules) +- [Why is True Modular Encapsulation So Important in Large-Scale GraphQL Projects?](/blog/modular-encapsulation-graphql-modules) +- [Why did we implement our own Dependency Injection library for GraphQL-Modules?](/blog/dependency-injection-library-in-graphql-modules) +- [Scoped Providers in GraphQL-Modules Dependency Injection](/blog/graphql-modules-scoped-providers) +- [Writing a GraphQL TypeScript project w/ GraphQL-Modules and GraphQL-Code-Generator](/blog/graphql-typescript-modules-codegen) +- [Authentication and Authorization in GraphQL (and how GraphQL-Modules can help)](/blog/graphql-modules-auth) +- [Authentication with AccountsJS & GraphQL Modules](/blog/accountsjs-graphql-modules) +- [Manage Circular Imports Hell with GraphQL-Modules](/blog/manage-circular-imports-hell-in-graphql-modules) diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules-v1/di-hierarchy.png b/packages/web/docs/src/app/blog/(posts)/graphql-modules-v1/di-hierarchy.png new file mode 100644 index 00000000000..ce537245adf Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-modules-v1/di-hierarchy.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules-v1/flat-structure.png b/packages/web/docs/src/app/blog/(posts)/graphql-modules-v1/flat-structure.png new file mode 100644 index 00000000000..26fd0be41e4 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-modules-v1/flat-structure.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules-v1/new-year.png b/packages/web/docs/src/app/blog/(posts)/graphql-modules-v1/new-year.png new file mode 100644 index 00000000000..32692954aa6 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-modules-v1/new-year.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules-v1/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-modules-v1/page.mdx new file mode 100644 index 00000000000..c59c7251d3a --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-modules-v1/page.mdx @@ -0,0 +1,180 @@ +--- +title: The New GraphQL Modules +authors: kamil +tags: [graphql, graphql-modules] +date: 2020-12-21 +description: + GraphQL Modules showed up on NPM almost 3 years ago, but now we decided to rewrite it from + scratch. +--- + +## History Lesson + +[GraphQL Modules](https://graphql-modules.com) showed up on NPM almost **3 years ago**, but now we +decided to rewrite it from scratch. Throughout those years the library improved by a lot of thanks +to the community and the companies we as The Guild worked with. + +> We tried to implement new ideas and revamp the library without touching the core. + +That was until now, we decided to finally change the fundamentals of GraphQL Modules according to +the collected feedback from our users. + +## New Major Version + +We were planning a new major release from quite some time now and after few months of work, it's +finally available! + +- Much richer error messages +- More advanced and much more flexible Dependency Injection +- Flat structure - one application, many modules +- Testing utilities +- Functions instead of classes +- Better performance +- Simpler codebase +- In general, better Developer Experience +- GraphQL Codegen support with GraphQL Modules preset + +We know those are just words, but we've been using it for a while, and we love it! + +### Single Package + +We decided to merge two existing packages `@graphql-modules/core` and `@graphql-modules/di` into a +single package `graphql-modules`. There's no regression in terms of bundle size, because `core` was +importing `di` anyway. + +```sh +yarn remove @graphql-modules/core @graphql-modules/di +yarn add graphql-modules +``` + +Making GraphQL Modules a single package should improve the developer experience. + +### Flat Structure + +In `1.0`, we changed the structure of GraphQL Modules to be flat. We also added `Application` level +that represents the root of your GraphQL API. + +![Flat structure](./flat-structure.png) + +You no longer need to define imports and strict dependencies between modules, since they are all +flatten and merged together by the Application. + +### Improved Dependency Injection + +The previous version was lacking proper abstraction, it was extremely hard to test and in general +very strict. + +Thanks to the flat structure of GraphQL Modules we were able to rethink the implementation of +Dependency Injection. What are the benefits? + +- [Sharing Services](https://graphql-modules.com/docs/di/providers#global-providers-and-token) is + now easier than ever, all you need to do is to set `global: true` flag in `@Injectable` decorator. +- There's a space for abstraction. + [Each Module has its own Injector that has Injector of an application as a parent](https://graphql-modules.com/docs/di/introduction). +- [Only two kinds of scope: `Singleton` and `Operation`](https://graphql-modules.com/docs/di/scopes). + We think it's simpler this way and the naming is much more intuitive. +- [Richer error messages](https://github.com/Urigo/graphql-modules/blob/master/packages/graphql-modules/tests/di-errors.spec.ts), + new DI gives you more details about what went wrong. + +![Dependency Injection hierarchy](./di-hierarchy.png) + +### Stronger Type-Safety + +GraphQL Modules expose a global namespace called `GraphQLModules`, so there's no need to pass the +same signature over and over again as part of generic types of different APIs. + +Context is global and shared across modules and application which means you can define it once, and +it applies automatically everywhere. + +Use and extend `GraphQLModules.GlobalContext` like this: + +```typescript +declare global { + namespace GraphQLModules { + interface GlobalContext { + request: any + } + } +} +``` + +We created a special **GraphQL-Code-Generator preset for GraphQL Modules**. + +It generates a complete, unified, type signature for your schema, and sub-files per each module, +containing only the GraphQL types declared/extended in your specific module. + +To get started, +[follow the instructions in graphql-code-generator.com website](https://graphql-code-generator.com/docs/presets/graphql-modules). + +### Shared Context + +In v0, you could create a context per each module. In v1, context is external for GraphQL-Modules, +and it's not directly in use. You can do whatever you want with that, and just access it in +GraphQL-Modules if you need, but we no longer require you to do specific things with your context. + +### Dynamic Singletons - `@ExecutionContext` + +Execution Context means the context of execution of GraphQL Operation. In short, it's the context +value created by your GraphQL server. + +Singletons can't directly access Operation scoped services, meaning they probably can't also +directly access the context object created per each operation. Directly. + +Thanks to @ExecutionContext decorator, every Singleton provider gets access to the GraphQL Context +and the Operation scoped Injector. + +```typescript +import { ExecutionContext, Injectable } from 'graphql-modules' +import { Config } from './config' + +@Injectable() +export class Data { + constructor(private config: Config) {} + + @ExecutionContext() + private context: ExecutionContext + + someMethod() { + console.log('Environment', this.config.env) + + const value = this.context.injector.get(SOME_TOKEN) + } +} +``` + +You gain a lot in terms of performance, because the `Data` class is instantiated only once and used +in many operations. + +### Testing Kit + +GraphQL Modules provides +[a set of utilities for testing](https://graphql-modules.com/docs/essentials/testing) your modules +and also for more granular testing of module's smaller units, like providers and middlewares. + +To access the testing utilities, import `testkit` object from `graphql-modules` package: + +```ts +import { testkit } from 'graphql-modules' +``` + +The testkit object and its API will grow over time, we expect to implement more and more useful +features in upcoming releases. + +## Migration + +The v1.0 is a total rewrite, there are of course breaking changes. + +We prepared an entire chapter in documentation that covers pretty much all breaking changes and new +features. Please read ["Migration from v0.X"](https://graphql-modules.com/docs/recipes/migration) +chapter and give us a feedback. + +The documentation of `0.x` version of GraphQL Modules +[is available under legacy name](https://graphql-modules.com/docs/legacy/introduction/getting-started). +The same applies to the codebase, there's the +[`legacy` branch](https://github.com/Urigo/graphql-modules/tree/legacy) on the repository. + +--- + +## Oh, and Happy New Year! + +![Happy New Year](./new-year.png) diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules/0f1a8490480abbc46d06daf9cf727171.png b/packages/web/docs/src/app/blog/(posts)/graphql-modules/0f1a8490480abbc46d06daf9cf727171.png new file mode 100644 index 00000000000..83cd18686c3 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-modules/0f1a8490480abbc46d06daf9cf727171.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules/297c11db8129c64380966f6a15dc8dd8.png b/packages/web/docs/src/app/blog/(posts)/graphql-modules/297c11db8129c64380966f6a15dc8dd8.png new file mode 100644 index 00000000000..4c24c8f3e46 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-modules/297c11db8129c64380966f6a15dc8dd8.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules/ab6a60456476a8bc274f6d388b0c7178.png b/packages/web/docs/src/app/blog/(posts)/graphql-modules/ab6a60456476a8bc274f6d388b0c7178.png new file mode 100644 index 00000000000..56af507e141 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-modules/ab6a60456476a8bc274f6d388b0c7178.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules/c806f0ae70dca7a4d1e434736f241ed5.png b/packages/web/docs/src/app/blog/(posts)/graphql-modules/c806f0ae70dca7a4d1e434736f241ed5.png new file mode 100644 index 00000000000..47378c68850 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-modules/c806f0ae70dca7a4d1e434736f241ed5.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules/d57986e2af5dd85000392ee831ba0162.png b/packages/web/docs/src/app/blog/(posts)/graphql-modules/d57986e2af5dd85000392ee831ba0162.png new file mode 100644 index 00000000000..adfbee62425 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-modules/d57986e2af5dd85000392ee831ba0162.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-modules/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-modules/page.mdx new file mode 100644 index 00000000000..7bfa4ff2672 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-modules/page.mdx @@ -0,0 +1,533 @@ +--- +title: GraphQL Modules  —  Feature based GraphQL Modules at scale +authors: uri +tags: [graphql, graphql-modules] +date: 2018-11-05 +description: + Today we are happy to announce that we are open sourcing a framework we've been using for the past + couple of months in production, GraphQL Modules! +--- + +Today we are happy to announce that we are open sourcing a framework we've been using for the past +couple of months in production — [GraphQL Modules](https://graphql-modules.com)! + +Yet another framework? well, kind of GraphQL Modules is a set of extra libraries, structures and +guidelines around the amazing [Apollo Server 2.0](https://apollographql.com/docs/apollo-server). + +You can and should use them as completely separate packages, each one is good for different use +cases, but all together they represent our current philosophy of building large scale GraphQL +servers. + +We would love to get feedback from the Apollo team and if they wish to use those ideas and integrate +them into Apollo Server we would love to contribute. That's why we've developed it as a set of +independent tools under a single monorepo. + +The basic concept behind GraphQL Modules is to separate your GraphQL server into smaller, +**reusable,** **feature based** parts. + +A basic and initial implementation of a GraphQL server usually includes: + +![](./d57986e2af5dd85000392ee831ba0162.png) + +A more advanced implementation will usually use a context to inject things like data models, data +sources, fetchers, etc., like Apollo Server 2.0 provides us with: + +![](./297c11db8129c64380966f6a15dc8dd8.png) + +Usually, for simple use cases, the example above will do. + +But as applications grow, their code and schematic relationships becomes bigger and more complex, +which can make schema maintenance a hard and agonizing thing to work with. + +Some of the more old school, MVC frameworks adds few more layers after the resolvers layer, but most +of them just implement separation based technical layers: controllers, models, etc. + +We argue that there is a better approach of writing your GraphQL Schema and implementation. + +We believe you should **separate your GraphQL schema by modules**, or **features**, and includes +anything related to a specific part of the app under a “module” — which is just a simple directory. +Each one of the GraphQL Modules libraries would help you in the gradual process of doing that. + +The modules are being defined by their GraphQL schema — so we've taken the “GraphQL First” approach +lead by Apollo and combined it together with classic modularization tooling to create new ways of +writing GraphQL servers! + +![](./0f1a8490480abbc46d06daf9cf727171.png) + +The GraphQL Modules toolset has tools to help with: + +- **Schema Separation** — declare your GraphQL schema in smaller pieces, which you can later move + and reuse. +- **Tools designed to create independent modules** — each module is completely independent and + testable, and can be shared with other applications or even open sourced if needed +- **Resolvers Composition** — with GraphQL Modules you can write your resolver as you wish, and let + the app that hosts the module to wrap the resolvers and extend them. It's implemented with a basic + middleware API, but with more flexibility. That means that you can, for example, implement your + entire module without knowing about the app authentication process, and assume that `currentUser` + will be injected for you by the app. +- **A clear, gradual path** from a very simple and fast, single-file modules, to scalable + multi-file, multi-teams, multi-repo, multi-server modules. +- **A scalable structure for your GraphQL servers** — managing multiple teams and features, multiple + microservices and servers and more advanced tools, which you can choose to include when your + schema gets into massive scale: +- **Communication Bridge** — We also let you send custom messages with payload between modules — + that means you can even run modules in different microservices and easily interact between them. +- **Dependency Injection** — Implement your resolvers, and later, only when you see fit, improve + their implementation by gradually introducing dependency injection. It also includes richer + toolset around testing and mocking. + +--- + +## A Basic Example + +In the following example, you can see a basic implementation for GraphQL Modules server, with 2 +modules: `User` and `Chat`. Each module declares only the part that is relevant to it, and extends +previously declared GraphQL types. + +So when `User` module is loaded, the `type User` is created, and when `Chat` module is loaded, the +`type User` being extended with more fields. + +```typescript +import { GraphQLModule } from '@graphql-modules/core' +import { ChatModule } from './chat-module' +import { UserModule } from './user-module' + +export const appModule = new GraphQLModule({ + imports: [UserModule, ChatModule] +}) +``` + +```typescript +import { GraphQLModule } from '@graphql-modules/core'; +import gql from 'graphql-tag'; + +export const ChatModule = new GraphQLModule({ + typeDefs: gql` + # Query declared again, adding only the part of the schema that relevant + type Query { + myChats: [Chat] + } + + # User declared again- extends any other `User` type that loaded into the appModule + type User { + chats: [Chat] + } + + type Chat { + id: ID! + users: [User] + messages: [ChatMessage] + } + + type ChatMessage { + id: ID! + content: String! + user: User! + } + `, + resolvers: { + Query: { + myChats: (root, args, { getChats, currentUser }) => getChats(currentUser), + }, + User: { + // This module implements only the part of `User` it adds + chats: (user, args, { getChats }) => getChats(user), + }, + }, +}); +``` + +```typescript +import { ApolloServer } from 'apollo-server' +import { appModule } from './modules/app' + +const { schema, context } = appModule + +const server = new ApolloServer({ + schema, + context, + introspection: true +}) + +server.listen() +``` + +```typescript +import { GraphQLModule } from '@graphql-modules/core'; +import gql from 'graphql-tag'; + +export const UserModule = new GraphQLModule({ + typeDefs: gql` + type Query { + me: User + } + + # This is a basic User, with just the basics of a user object + type User { + id: ID! + username: String! + email: String! + } + `, + resolvers: { + Query: { + me: (root, args, { currentUser ) => currentUser, + }, + User: { + id: user => user._id, + username: user => user.username, + email: user => user.email.address, + }, + }, +}); +``` + +You can and should adopt GraphQL Modules part by part, and you can try it now with your existing +GraphQL server. + +What does a “_module_” contain? + +- **Schema (types declaration)** — each module can define its own Schema, and can extend other + schema types (without explicitly providing them). +- **Thin resolvers implementation** — each module can implement its own resolvers, resulting in thin + resolvers instead of giant files. +- **Providers** — each module can have its own Providers, which are just classes/values/functions + that you can use from your resolvers. Modules can load and use providers from other modules. +- **Configuration** — each module can declare a strongly-typed config object, which the consuming + app can provide it with. +- **Dependencies** — modules can be dependent on other modules (by its name or its `GraphQLModule` + instance, so you can easily create an ambiguous dependency that later could be changed). + +--- + +## GraphQL Modules Libraries + +GraphQL Modules is built as a toolkit, with the following tools, which you should individually and +gradually adopt: + +**@​graphql-modules/epoxy** + +- That will probably be the first tool you want to introduce into your server. The first step into + organizing your server in a feature based structure +- Epoxy is a small util that manages the schema merging. it allow you to merge everything in your + schema, starting from types to enums, unions, directives and so on. +- This is an important feature of GraphQL Modules — you can use it to separate your GraphQL types to + smaller pieces and later on combine them into a single type. +- We took the inspiration from + [merge-graphql-schemas](https://github.com/okgrow/merge-graphql-schemas), and added some features + on top of it to allow custom merging rules to make it easier to separate your schema. + +**@​graphql-modules/core** + +- [Resolvers Composition](https://graphql-modules.com/docs/introduction/resolvers-composition) — + manages the app's resolvers wrapping +- [Context building](https://graphql-modules.com/docs/introduction/context) — each module can inject + custom properties to the schema, and other modules can use it (for example, auth module can inject + the current user, and other modules can use it) +- [Dependency injection and module dependencies management](https://graphql-modules.com/docs/introduction/dependency-injection) + — when you start, there is no need of using DI is your server. but when your server gets big + enough with a large number of modules which depends on each other, only then, DI becomes a very + help thing that actually simplifies your code a lot. USE ONLY WHEN NECESSARY ;) + +You can find more tooling at your disposal like: + +**@​graphql-modules/sonar —** a small util that helps you find GraphQL schema and resolvers files, +and include them. + +**@​graphql-modules/logger —** a small logger, based on +[winston 3](https://github.com/winstonjs/winston), which you can easily use in your app. + +## Get Started + +First thing, don't go full in! Start by simply moving your code into feature based folders and +structures with your existing tools. + +Then head over to [https://graphql-modules.com/](https://graphql-modules.com) and check out our +tools and use them only when you see that they solve a real problem for you! (for us it has) + +Also check out [the repo](https://github.com/Urigo/graphql-modules)'s README and +[a number of example apps](https://github.com/Urigo/graphql-modules#examples). + +You probably have many questions — How does this compare to other tools, how to use those libraries +with X and so on. + +We will publish a series of blog posts in the coming weeks that will dive deep into each of the +design decisions made here, so we want to hear your thoughts and questions, please comment here or +on the [Github repository](https://github.com/urigo/graphql-modules)! + +Going to [GraphQL Summit](https://summit.graphql.com)? [I](https://github.com/urigo) will be there +and would love to get your questions and feedback on behalf of our team. + +All those tools were built by a passionate group of individual open source developers, otherwise +known as [The Guild](/). + +Below there is a section of more deep dive thoughts that we will publish separate posts about in the +coming weeks: + +## Core Concepts and Deep Dive + +### Modularizing a Schema + +Everyone is talking about schema stitching and GraphQL Bindings. Where does that fit into the +picture? + +Schema stitching is an amazing ability and concept, which helps you merge separated GraphQL servers +into a single endpoint and opens up a lot of exciting use cases. + +But, with all the excitement, we've missed something much more basic than that — sometimes we still +want to work on a single logical server, but we just want to separate the code according to +features. + +We want to be able to do most of the merging work at build time, and only if really necessary, do +the rest of the merging at runtime as a last resort. + +We want to split the code into separate teams and even create reusable modules which define their +external APIs by a GraphQL Schema. + +Those modules can be npm modules, microservices or just separate folders inside a single server. + +Separating your schema to smaller parts is easier when you are dealing with `typeDefs` and +`resolvers`— it's more readable and easy to understand. + +We also wanted to allow developers to extend only specific types, without creating the entire +schema. With GraphQL schema, you have to specify at least one field under `Query` type, which is +something that we did not want to enforce on our users. + +We see our approach as complementary to Schema Stitching and works together with it. + +## Feature-Based Implementation + +One of the most important things in GraphQL Module's approach is the feature-based implementation. + +Nowadays, most frameworks are separating the layers based on the role of the layer — such as +controllers, data-access and so on. + +GraphQL Modules has a different approach — separate to modules based on your server's features, and +allow it to manage its own layers within each module implementation. + +It's easier to think about apps in a modular way, for example: + +Your awesome app needs a basic authentication, users management, user profiles, user galleries and a +chat. + +Each one of these could be a module, and implement its own GraphQL schema and its own logic, and it +could depend on other modules to provide some of the logic. + +Here's a simple example for a GraphQL Schema as we described: + +![](./ab6a60456476a8bc274f6d388b0c7178.png) + +But if we think of apps in terms of features and then separate the schema by module, the modules +separation will look like so: + +![](./c806f0ae70dca7a4d1e434736f241ed5.png) + +This way, each module can declare only the part of the schema that it contributes, and the complete +schema is a representation of all merged type definitions. Module can also depend, import and extend +and customize the contents on other modules (for example, `User` module, comes with `Auth` inside +it) + +The result of course, will be the same, because we are merging the schema into a single one, but the +codebase will be much more organized and each module will have its own logic. + +## Reusability of Backend Modules + +So now that we understood the power of feature-based implementation, it's easier to grasp the idea +behind code reusability. + +If we could implement the schema and the core of Auth and User module as “plug-and-play” — we will +be able later to import it in other projects, with very minor changes (using configuration, +dependency injection, or module composition). + +**How could we reuse complete modules that hold part of a schema?** + +For example, let's take a `User` type. + +Most of `User` type schemas will contain `id`, `email` and `username` fields. The Mutation type will +have `login` and the `Query` will have `user` field to query for a specific user. + +We can re-use this type declaration. + +The actual implementation might differ between apps, according to the authentication provider, +database and so on, but we can still implement the business logic in a simple resolver, and use +dependency injector and ask the app that's using the module to provide the actual authentication +function (of course, with a complete TypeScript interface so we'll know that we need to provide it +;) ). + +Let's take it one step further. If we would like to add a profile picture to a user, we can add a +new module named `UserProfile` and re-declare the `User` and `Mutation` types again: + +```graphql +type User { + profilePicture: String +} + +type Mutation { + uploadProfilePicture(} +``` + +This way, GraphQL Modules will merge the fields from this `User` type into the complete `User` type, +and this module will only extend the `User` type and `Mutation` type with the required actions. + +So let's say that we have the schema — how can we make this module generic and re-use it? + +This is how you declare this module: + +```typescript +import gql from 'graphql-tag' +import { GraphQLModule } from '@graphql-modules/core' +import { UserModule } from '../user' +import { Users } from '../user/users.provider' + +export interface IUserProfileModuleConfig { + profilePictureFields?: string + uploadProfilePicture: (stream: Readable) => Promise +} + +export const UserProfileModule = new GraphQLModule({ + imports: [UserModule], + typeDefs: gql` + type User { + profilePicture: String + } + type Mutation { + uploadProfilePicture( } + `, + resolvers: config => ({ + User: { + profilePicture: (user: User, args: never, context: ModuleContext) => { + const fieldName = config.profilePictureField || 'profilePic' + + return user[fieldName] || null + } + }, + Mutation: { + uploadProfilePicture: async ( + root: never, + { image }: { { injector, currentUser }: ModuleContext + ) => { + // using https://apollographql.com/docs/guides/file-uploads.html + const { stream } = await image + + // Get the external method for uploading files, this is provided by the app as config + const imageUrl = config.uploadProfilePicture(stream) + + // Get the field name + const fieldName = config.profilePictureField || 'profilePic' + + // Ask the injector for "Users" token, we are assuming that `user` module exposes it for us, + // then, update the user with the uploaded url. + injector.get(Users).updateUser(currentUser, { [fieldName]: imageUrl }) + + // Return the current user, we can assume that `currentUser` will be in the context because + // of resolvers composition - we will explain it later. + return currentUser + } + } + }) +}) +``` + +> We declare a config object, and the app will provide it for us, so we can later replace it with a +> different logic for uploading. + +## Scaling the Codebase + +Now that we broke our app into individual modules, once our codebase grows, we can scale each module +individually. + +What do I mean by scaling a codebase? + +Let's say we start to have code parts we want to share between different modules. + +The current way of doing it in the existing GraphQL world is through a GraphQL context. + +This approach has proven itself to work, but at some point it becomes a big hassle to maintain, +because GraphQL context is an object, which any part of the app can modify, edit and extend, and it +can become really big pretty quickly. + +GraphQL modules let each module extend and inject fields to the \`context\` object, but this is +something that you should use with caution, because I recommend the \`context\` to contain the +actual \`context\` — which contains data such as global configuration, environment, the current user +and so on. + +GraphQL modules only adds one field under the `context`, called `injector` which is the bridge that +lets you access your GraphQLApp and the application Injector, and it lets you fetch your module's +config and providers. + +Modules can be a simple directory in a project or in a monorepo, or it could be a published NPM +module — you have the power to choose how to manage your codebase according to your needs and +preferences. + +## Dependency Injection + +GraphQL Modules' dependency injection is inspired by .NET and Java's dependency injection which has +proven itself to work pretty well over the years. With that being said, there were some issues with +.NET and Java's APIs, which we've tried to list and go through. We ran into some pretty interesting +conclusions. + +We've learn that it's not something that should be forced. Dependency injection makes sense in some +specific use cases and you should need to use it only when it's necessary and when it helps you move +faster. So this concept should come more and more in handy as we scale up, we can simplify things, +maintain our code with ease and manage our teams' contributions! + +Having GraphQL Modules deployed across all of our Enterprise customers while also being used on our +smaller applications, lead us to believe that we've found the optimal point of where you should use +the concept of dependency injection, and when not. + +We've also came with the optimal API for dependency injection. It's extremely easy to understand, +and use. + +After a long research of the existing dependency injection solutions for JavaScript, we've decided +to implement a simple Injector, that supports the needs of GraphQL-Modules ecosystem, and support +circular dependencies and more. + +We've simplified the Dependency Injection API and exposed to you only the important parts, that we +believe that are necessary for a GraphQL server development. + +## Authentication + +Check out the related blog post we wrote about it: +[/blog/graphql-modules-auth](/blog/graphql-modules-auth) + +## Testing and Mocking + +On our Enterprise applications, when we started using dependency injection, we no longer had to +manage instances and bridge them together. + +We gained an abstraction that allowed us to test things easier and mock all http requests. + +Yes, mocking. DI really shines here. + +Thanks to mocking we can simulate many scenarios and check the backend against them. + +And when your codebase grows, you need to start thinking about managing dependencies between modules +and how to avoid things like circular dependencies — unless you use DI which solves that problem for +you. + +With the power of dependency injection, you can easily create a loose connection between modules, +and base this connection on a token and on a TypeScript interface. + +It also means that testing is much easier — you can take your class/function and test it as an +independent unit, and mock its dependencies easily. + +## Summary + +We see GraphQL Modules as the framework that finally being built from the ground up on the new and +exciting capabilities of GraphQL and Apollo, while combining it in the right way with good old +software best practices for scale like modularizations, strong typings and dependency injection. + +Now go and try it out at graphql-modules.com + +--- + +## All Posts about GraphQL Modules + +- [GraphQL Modules — Feature based GraphQL Modules at scale](/blog/graphql-modules) +- [Why is True Modular Encapsulation So Important in Large-Scale GraphQL Projects?](/blog/modular-encapsulation-graphql-modules) +- [Why did we implement our own Dependency Injection library for GraphQL-Modules?](/blog/dependency-injection-library-in-graphql-modules) +- [Scoped Providers in GraphQL-Modules Dependency Injection](/blog/graphql-modules-scoped-providers) +- [Writing a GraphQL TypeScript project w/ GraphQL-Modules and GraphQL-Code-Generator](/blog/graphql-typescript-modules-codegen) +- [Authentication and Authorization in GraphQL (and how GraphQL-Modules can help)](/blog/graphql-modules-auth) +- [Authentication with AccountsJS & GraphQL Modules](/blog/accountsjs-graphql-modules) +- [Manage Circular Imports Hell with GraphQL-Modules](/blog/manage-circular-imports-hell-in-graphql-modules) diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-over-internet/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-over-internet/page.mdx new file mode 100644 index 00000000000..dfeee5e6585 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-over-internet/page.mdx @@ -0,0 +1,245 @@ +--- +title: GraphQL over Internet +tags: [graphql, http, ws, sse] +authors: enisdenjo +date: 2023-04-06 +description: HTTP, WebSockets, Server-Sent Events, undergoing standardization and libraries. +--- + +## What Is GraphQL? + +Some of you have heard this question many times over... But for the beginners out there: GraphQL is +a data language. And that's it, nothing more and nothing less. It a system of communication that +defines the grammar and the vocabulary. It helps you request the exact data you want, and also helps +the server fulfill that request. Like in the real-world — those that know the language can +communicate. Please keep in mind, GraphQL is not a database, nor is it a server - it is simply - +**just a language**. + +### GraphQL Is like a Pizza Order + +Imagine ordering a pizza - you call and ask for a pizza with special toppings, and then the person +on the other end takes the order and tells the pizza chef what to make. Well, GraphQL is kinda' like +that phone call, except that instead of ordering pizza, you ask a server for some data; instead of +speaking to a person, you're sending a specially formatted message to a server called a GraphQL +query. Just like you ask for specific toppings, you ask for specific data from a server; and just +like the pizza chef makes your pizza with the exact toppings, the server responds to the GraphQL +query with the exact data you queried. + +Immediately we can see that there are two agreements on both sides: + +- You know you're ordering pizza (and therefore you know which toppings you can choose from) +- You also know that both you and the waiter speak the same language + +Subsequently you know exactly what to expect when picking up your order: a pizza with the toppings +you communicated over the phone. + +**However, neither of you knows, or cares, about how a telephone works!** + +Synonymously, GraphQL itself deliberately does not care about the transport - it doesn't care about +how you send the request, how you receive the response, and what is the delivery path — all of this +is out of the GraphQL's scope. It is quite literally just a data language. + +## [GraphQL over HTTP](https://github.com/graphql/graphql-over-http) + +HTTP is the most common choice when transporting GraphQL because of its ubiquity. With this in mind, +a group of superheroes formed in 2019 to solve exactly this problem. A working group of avid GraphQL +lovers all with the same goal - standardize the process of transporting GraphQL over HTTP. The group +mostly works asynchronously, but there are Zoom meetings, where progress is shared, and important +topics are discussed. + +This is a joint effort, so everyone's opinion matters, and everyone is welcome to contribute! + +There is already a +[specification being standardized named "GraphQL over HTTP"](https://graphql.github.io/graphql-over-http/). +At the time of writing, it is in the proposal stage. It currently describes exclusively single +response operations, this is the main focus; so, no file uploads, no incremental deliveries, yet... + +### [Reference Implementation](https://github.com/graphql/graphql-http) + +To accompany the spec, the +[reference implementation `graphql-http`](https://github.com/graphql/graphql-http) is developed. It +is a zero-dependency server, client and audit suite. + +The client is very simple to use and offers some great niceties - one of them being silent retries +of failed requests. + +Beware that the library exclusively implements the +[current official "GraphQL over HTTP" spec](https://graphql.github.io/graphql-over-http/). It does +so in the clearest and most explicit manner so that others implementing their own server can quite +literally use [`graphql-http`](https://github.com/graphql/graphql-http) as a reference of the spec. + +The library is server agnostic and, as such, can run in any JavaScript environment. Additionally, it +maintains the list of compliant servers and their reports. You're more than welcome to send PRs with +your own implementation! + +### [`graphql-http.com`](https://graphql-http.com) + +Checking for compliance was never easier! [graphql-http.com](https://graphql-http.com) is a website +for swiftly auditing servers - even those that are running locally! It is always up-to-date with the +current spec. + +Of course, security is very important - the website audits run exclusively in the browser, and there +is no tracking, data sharing, or external dependencies! Meaning you can download the website on your +computer and have a portable offline checker. + +## [GraphQL over WebSocket](https://t.ly/IiwW) + +Before we talk about the current state of GraphQL WebSocket support, let's start with a brief +overview of the history. Initially, there was a +[`subscriptions-transport-ws`](https://github.com/apollographql/subscriptions-transport-ws) library +that was built and maintained by [Apollo](https://www.apollographql.com/). It also had an +[accompanying protocol](https://t.ly/9CN_). But as time passed, they haven't been able to spend as +much time on it — probably due to increased demand in other areas. The repo grew in issues, and PRs +were never reviewed. Eventually, it became deprecated. However, this library actually kick-started +and pushed the evolution. It was a big inspiration and a great influence for an upcoming refresh! + +[The WebSocket Protocol](https://www.rfc-editor.org/rfc/rfc6455) provides a persistent, full-duplex +communication channel between the client and server, allowing data to be transmitted in real-time. +Full-duplex is just a fancy word for bidirectional communication which means that the server and the +client can exchange messages through the connection channel whenever they want. When used in +conjunction with GraphQL, it allows clients to subscribe and receive events from the server in real +time. This makes it ideal for applications that require real-time updates and push notifications. + +The [GraphQL over WebSocket protocol](https://t.ly/b5N_) leverages all pros of the WebSocket and +gives shine to real-time GraphQL. Please note that it is **not** backwards compatible with +[Apollo's protocol behind `subscriptions-transport-ws`](https://t.ly/L-GA). + +⚠️ Beware that the specification is currently an RFC and is open to changes! + +### [Reference Implementation](https://github.com/enisdenjo/graphql-ws) + +A specification goes best with a reference implementation. Say hello to +[`graphql-ws`](https://github.com/enisdenjo/graphql-ws). It is a zero-dependency server and client, +very simple and bare-bone. But don't be mistaken, it's very versatile and extendable! + +A common struggle when working with WebSockets is re-connecting. `graphql-ws` is all about stable +connections. Silent retries with custom strategies are easy to use and extend. + +Oh and, it is completely server agnostic - run it on any JavaScript environment! + +## [GraphQL over SSE](https://t.ly/DnRO) + +> Traditionally, a web page has to send a request to the server to receive new data; that is, the +> page requests data from the server. With server-sent events, it's possible for a server to send +> new data to a web page at any time, by pushing messages to the web page. These incoming messages +> can be treated as [Events](https://developer.mozilla.org/en-US/docs/Web/API/Event) + data inside +> the web page. + +[_MDN on Server-sent events_](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) + +### Why Not Just `EventSource`? + +Before we proceed, you might ask why not just use the +[browser native EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource)? Do we +even need a whole specification? Are we overthinking it? + +That's an excellent question, actually! Here are a few gotchas' about the browser native +`EventSource`: + +- Headers cannot be customized +- If the HTTP response is erroneous and not a 200 OK, `EventSource` raises an ultra vague error +- The requests cannot have an accompanying body +- The retry mechanism is too simple, it will simply retry following a fixed interval +- Server might limit the length of the URL +- EventSource will keep reconnecting indefinitely if the server is the one that closes the + - Meaning, if a subscription ends and the server closes the connection as a result - the browser + will automatically reconnect + +### The Specification + +Keeping everything from the previous section in mind, the [GraphQL over SSE spec](https://t.ly/DnRO) +is created. It basically lifts all usual limitations of SSE while keeping the browser native +`EventSource` in mind - so you're not locked to a library, you're still free to use it! + +It supports two working modes: + +- ["distinct connections mode"](https://t.ly/DnRO#distinct-connections-mode) - basically the regular + way you'd use SSEs, each request is a connection on its own +- ["single connection mode"](https://t.ly/DnRO#single-connection-mode) - a single SSE connection is + maintained for streaming, but other accompanying requests dictate the behavior + +⚠️ Beware that the specification is currently an RFC and is open to changes! + +#### Distinct Connections Mode + +The first mode I want to talk about is "distinct connection mode". It's pretty much what you'd +usually do with SSE - each connection is a subscription on its own. The operation requests conform +the the requests in the [GraphQL over HTTP spec](https://graphql.github.io/graphql-over-http/), but +with two differences: + +- `Content-Type` header must have the `text/event-stream` value +- All GraphQL errors must be reported through the SSE connection + - So you first accept the connection and then through it you stream the error in form of a + message. This allows `EventSource`s to have proper error reporting with descriptive errors • +- An additional `complete` event message that is used to indicate to the client that the + subscription was completed from the sever + - This makes `EventSource`s aware when the subscription has ended server-side so that it can be + closed and with that avoid unexpected reconnects + +#### Single Connection Mode + +The other mode of operating is called "single connection mode". A often talked about limitation of +SSE is HTTP/1. HTTP/1 powered servers are limited to +[only 6 active connections per domain](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#listening_for_custom_events), +meaning you can only have 6 concurrently active subscriptions (compared to HTTP/2 powered servers +that are by default limited to 100+ active connection per domain). + +The "singe connection mode" creates a single connection to the server which is used exclusively for +streaming events and messages. Then you issue separate HTTP requests to execute operations. This +property makes it safe for HTTP/1 environments, but also for subscription heavy apps even in HTTP/2 +environments (simply because subscriptions are typically best when really granular and this can +sometimes exceed even the 100+ limit of HTTP/2). + +These HTTP operation requests conform to the +[GraphQL over HTTP spec](https://graphql.github.io/graphql-over-http/) with just one difference: +successful operation requests are responded with `202: Accepted` and the results are streamed to the +single established connection. + +### [Reference Implementation](https://github.com/enisdenjo/graphql-sse) + +Of course, a reference implementation goes along with the spec. +[`graphql-sse`](https://github.com/enisdenjo/graphql-sse) is a zero-dependency implementation of +both the server and the client. + +One cool thing is the fact that the server automatically detects the operation mode (single +connection vs distinct connection) and behaves accordingly. + +The client is pretty advanced too, it offers a few interesting elements like: + +- Custom retry strategies +- You can decide exactly when you want to retry connecting (like you might have a health check, and + only after the server becomes health do you want to retry) +- Since `graphql-sse` is packing a custom implementation of SSE and is not using the browser native + EventSource, you're able to: + + - Set custom headers + - Add a body to the request + - Experience very descriptive errors + +Like all of the libraries mentioned in the presentation - `graphql-sse` too is server agnostic - you +can run it in any JavaScript environment! + +## WebSockets vs. SSEs + +There's an abundance of articles, tables and benchmarks on the internet comparing WebSockets to +SSEs, so I'll just briefly touch the obvious. + +| | WebSockets | SSEs | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Communication | Real-time and bi-directional | Transported over simple HTTP instead | +| Firewalls | Has troubles with corporate and outdated firewalls | No trouble with _any_ firewall doing packet inspection | +| Latency | Lower because of persisted TCP connection | Can be higher sometimes because of multiplexing | +| Retries | Needs to be developed in user-land | Browsers have built-in retry mechanisms | +| Support | Browser native through [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket). No custom implementations for the browser | Browser native through [`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource). But, can be custom implemented on [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) | + +## Presentation + +Check out my presentation from [GraphQL Zurich Meetup](https://www.meetup.com/graphql-zurich/) +sponsored by [SwissLife](https://www.swisslife.ch/). + +import { YoutubeIframe } from '#components/youtube-iframe' + + diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-over-sse/graphql-over-sse.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-over-sse/graphql-over-sse.jpg new file mode 100644 index 00000000000..88b0657415c Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-over-sse/graphql-over-sse.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-over-sse/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-over-sse/page.mdx new file mode 100644 index 00000000000..afde4e8364c --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-over-sse/page.mdx @@ -0,0 +1,321 @@ +--- +title: GraphQL over SSE (Server-Sent Events) +tags: [graphql, graphql-subscriptions] +authors: enisdenjo +date: 2021-09-01 +description: + Zero-dependency, HTTP/1 safe, simple, GraphQL over Server-Sent Events Protocol server and client. +--- + +## Introduction + +You've probably been faced with a challenge of making sure your app is up-to-date at all times +without user interaction. Yes, +[WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) are a great fit! But, what +if I've told you there is something else? Something "simpler" and +[widely supported](https://caniuse.com/?search=server-sent%20events)? I humbly present to you: +[Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). + +Server-Sent Events (abbr. SSE) are persisted HTTP connections enabling simplex communication from +the server to connected clients. In comparison to WebSockets, which enable full-duplex communication +(both connected parties can send information at any time), simplex communication is just a fancy +word for channels that send data in one direction only, in this case from the server to the client. +You may think that this is a drawback, at least when comparing SSEs to WebSockets; but, think +again - is it really? + +When subscribing to an information source, you make one descriptive request in order to receive +multiple responses. Specifically, when using GraphQL subscriptions or streaming operations (like +with +[`@defer` and `@stream` directives](https://graphql.org/blog/2020-12-08-improving-latency-with-defer-and-stream-directives)), +you do exactly this - you send one request and expect multiple responses (events) in return. Having +said this, Server-Sent Events seem like a perfect fit! + +Not only does it fit like a glove, but it even has one more small leverage over WebSockets - it is +firewall-proof. If you've used WebSockets extensively before, you must've been faced with a +situation where WebSocket connections were declined and you have had absolutely no idea why - yes +sir, you were bamboozled by an outdated corporate firewall that rejects +[HTTP Upgrade requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Upgrade) crippling +WebSocket's full potential. However, SSEs are plain ol' HTTP requests whose TCP connection is kept +alive, they're immune to rogue firewalls. + +## Limitations with SSEs + +Ah, if the world was only that simple... There are a few limitations when considering SSEs, some of +you might've already discovered them, but I'll go over them briefly. + +#### Maximum Number of Open Connections + +> When **not used over HTTP/2**, SSE suffers from a limitation to the maximum number of open +> connections, which can be specially painful when opening various tabs as the limit is per browser +> and set to a very low number (6). The issue has been marked as "Won't fix" in +> [Chrome](https://bugs.chromium.org/p/chromium/issues/detail?id=275955) and +> [Firefox](https://bugzilla.mozilla.org/show_bug.cgi?id=906896). This limit is per browser + +> domain, so that means that you can open 6 SSE connections across all the tabs to +> `www.example1.com` and another 6 SSE connections to `www.example2.com`. (from +> [Stackoverflow](https://stackoverflow.com/a/5326159/1905229)). When using HTTP/2, the maximum +> number of simultaneous HTTP streams is negotiated between the server and the client (defaults to +> 100). + +_[WebAPIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/EventSource)_ + +#### Browser's [`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) + +Not only are you not able to customise the HTTP request by providing custom headers or changing the +request method, but the +[`EventSource.onerror` event handler](https://developer.mozilla.org/en-US/docs/Web/API/EventSource/onerror) +will tell you nothing about why the request failed, no status code, no message, no body - you're in +the dark. + +#### How to GraphQL over SSE? + +If you've done your Googling, you probably came across hot discussions like +["Does HTTP/2 make websockets obsolete?"](https://stackoverflow.com/questions/28582935/does-http-2-make-websockets-obsolete) +or +["WebSockets vs. Server-Sent events/EventSource"](https://stackoverflow.com/questions/5195452/websockets-vs-server-sent-events-eventsource). +Or even the somewhat harsh +["@deprecate WebSockets: GraphQL Subscriptions using SSE"](https://wundergraph.com/blog/deprecate_graphql_subscriptions_over_websockets) +article from WunderGraph. + +With all this insightful talking and knowledgeable discussions, you'd expect integrating SSE in your +next project would be easy? You should have options, not be limited to Socket.IO or WebSockets, +right? Absolutely, it _is_ easy and you _do_ have options! + +## Bye Bye Limitations, Hello [`graphql-sse`](https://github.com/enisdenjo/graphql-sse) 👋 + +I am happy to introduce the lost plug-n-play, zero-dependency, HTTP/1 safe, simple, +[GraphQL over Server-Sent Events Protocol](https://github.com/enisdenjo/graphql-sse/blob/master/PROTOCOL.md) +server and client. + +Aforementioned limitations are taken care with a specialised SSE client (inspired by the awesome +[`@microsoft/fetch-event-source`](https://github.com/Azure/fetch-event-source)) and two separate +connection modes: +[the HTTP/1 safe "single connection mode"](https://github.com/enisdenjo/graphql-sse/blob/master/PROTOCOL.md#single-connection-mode) +that uses a single SSE connection for receiving events with separate HTTP requests dictating the +behaviour, and +[the HTTP/2+ "distinct connections mode"](https://github.com/enisdenjo/graphql-sse/blob/master/PROTOCOL.md#distinct-connections-mode) +that uses distinct SSE connections for each GraphQL operation, accommodating the parameters in the +request itself. + +[`graphql-sse`](https://github.com/enisdenjo/graphql-sse) is a reference implementation of the +[GraphQL over Server-Sent Events Protocol aiming to become a part of the GraphQL over HTTP standard](https://github.com/graphql/graphql-over-http/pull/163). + +### How Can I Try It Out? + +I thought you'd never ask! Here is how: + +#### Install + +```sh +yarn add graphql-sse +``` + +#### Create a GraphQL Schema + +```ts +import { GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql' + +/** + * Construct a GraphQL schema and define the necessary resolvers. + * + * type Query { + * hello: String + * } + * type Subscription { + * greetings: String + * } + */ +const schema = new GraphQLSchema({ + query: new GraphQLObjectType({ + name: 'Query', + fields: { + hello: { + type: GraphQLString, + resolve: () => 'world' + } + } + }), + subscription: new GraphQLObjectType({ + name: 'Subscription', + fields: { + greetings: { + type: GraphQLString, + subscribe: async function* () { + for (const hi of ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo']) { + yield { greetings: hi } + } + } + } + } + }) +}) +``` + +#### Start the Server + +##### With [`http`](https://nodejs.org/api/http.html) + +```ts +import http from 'http' +import { createHandler } from 'graphql-sse' + +// Create the GraphQL over SSE handler +const handler = createHandler({ + schema // from the previous step +}) + +// Create a HTTP server using the handler on `/graphql/stream` +const server = http.createServer((req, res) => { + if (req.url.startsWith('/graphql/stream')) return handler(req, res) + return res.writeHead(404).end() +}) + +server.listen(4000) +console.log('Listening to port 4000') +``` + +##### With [`http2`](https://nodejs.org/api/http2.html) + +_Browsers might complain about self-signed SSL/TLS certificates. +[Help can be found on StackOverflow.](https://stackoverflow.com/questions/7580508/getting-chrome-to-accept-self-signed-localhost-certificate)_ + +```sh +openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \ + -keyout localhost-privkey.pem -out localhost-cert.pem +``` + +```ts +import fs from 'fs' +import http2 from 'http2' +import { createHandler } from 'graphql-sse/lib/use/http2' + +// Create the GraphQL over SSE handler +const handler = createHandler({ + schema // from the previous step +}) + +// Create a HTTP/2 server using the handler on `/graphql/stream` +const server = http2.createSecureServer( + { + key: fs.readFileSync('localhost-privkey.pem'), + cert: fs.readFileSync('localhost-cert.pem') + }, + (req, res) => { + if (req.url.startsWith('/graphql/stream')) return handler(req, res) + return res.writeHead(404).end() + } +) + +server.listen(4000) +console.log('Listening to port 4000') +``` + +##### With [`express`](https://expressjs.com) + +```ts +// yarn add express +import express from 'express' +import { createHandler } from 'graphql-sse' + +// Create the GraphQL over SSE handler +const handler = createHandler({ schema }) + +// Create an express app serving all methods on `/graphql/stream` +const app = express() +app.all('/graphql/stream', handler) + +app.listen(4000) +console.log('Listening to port 4000') +``` + +##### With [`fastify`](https://fastify.io) + +```ts +// yarn add fastify +import Fastify from 'fastify' +import { createHandler } from 'graphql-sse' + +// Create the GraphQL over SSE handler +const handler = createHandler({ schema }) + +// Create a fastify instance serving all methods on `/graphql/stream` +const fastify = Fastify() +fastify.all('/graphql/stream', (req, res) => + handler( + req.raw, + res.raw, + req.body // fastify reads the body for you + ) +) + +fastify.listen(4000) +console.log('Listening to port 4000') +``` + +#### Use the Client + +```ts +import { createClient } from 'graphql-sse' + +const client = createClient({ + // singleConnection: true, use "single connection mode" instead of the default "distinct connection mode" + url: 'http://localhost:4000/graphql/stream' +}) + +// query +;(async () => { + const result = await new Promise((resolve, reject) => { + let result + client.subscribe( + { + query: '{ hello }' + }, + { + next: data => (result = data), + error: reject, + complete: () => resolve(result) + } + ) + }) + + expect(result).toEqual({ hello: 'world' }) +})() + +// subscription +;(async () => { + const onNext = () => { + /* handle incoming values */ + } + + let unsubscribe = () => { + /* complete the subscription */ + } + + await new Promise((resolve, reject) => { + unsubscribe = client.subscribe( + { + query: 'subscription { greetings }' + }, + { + next: onNext, + error: reject, + complete: resolve + } + ) + }) + + expect(onNext).toBeCalledTimes(5) // we say "Hi" in 5 languages +})() +``` + +## Want to Find Out More? + +Check the repo out to for +[Getting Started](https://github.com/enisdenjo/graphql-sse#getting-started) quickly with some +[Recepies](https://github.com/enisdenjo/graphql-sse#recipes) for vanilla usage, or with +[Relay](https://relay.dev) and [Apollo Client](https://apollographql.com/docs/react). Opening +issues, contributing with code or simply improving the documentation is always welcome! + +I am [@enisdenjo](https://github.com/enisdenjo) and you can chat with me about this topic on the +[GraphQL Discord workspace](https://discord.graphql.org) anytime. + +Thanks for reading and happy coding! 👋 diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-over-websockets/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-over-websockets/page.mdx new file mode 100644 index 00000000000..c89fe1bb177 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-over-websockets/page.mdx @@ -0,0 +1,262 @@ +--- +title: GraphQL over WebSockets +tags: [graphql, graphql-subscriptions] +authors: enisdenjo +date: 2020-09-15 +description: + Coherent, zero-dependency, lazy, simple, server and client implementation of the new, security + first, GraphQL over WebSocket Protocol. +--- + +## GraphQL over WebSockets + +> A couple of years ago, while I was still working at Apollo, +> [Lee Byron](https://github.com/leebyron), [Rob Zhu](https://github.com/robzhu), +> [Dotan Simha](https://github.com/dotansimha) and [I](https://github.com/urigo) worked on the +> GraphQL Subscriptions spec and the reference implementation. + +> During that work, we created and merged the +> [reference implementation into graphql-js](https://github.com/graphql/graphql-js/pull/846) and +> created two supporting libraries: +> [graphql-subscriptions](https://github.com/apollographql/graphql-subscriptions) and +> [subscriptions-transport-ws](https://github.com/apollographql/subscriptions-transport-ws). Here is +> a [talk with deep dive into all the details](https://youtu.be/Wi7P39sF2nw). + +> Since leaving Apollo, +> [not a lot of work has been gone into those libraries](https://github.com/apollographql/subscriptions-transport-ws/issues/777). +> That's why I was so thrilled that [Denis](https://github.com/enisdenjo) decided to pick up that +> important work and create a new library for the WebSocket Protocol. + +> We will support his work of maintaining the library and standardizing it with the GraphQL over +> HTTP working group. This is a part of a lot of exciting work coming up about GraphQL and +> real-time, so follow us for new things in this space! + +> Denis will take it away from here! + +## Introduction + +At some point in time during your web development journeys, you may've been faced with the challenge +to add a real-time component. The synonym for realtime in the browser is the WebSocket API. +[This is how MDN's describes it](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API): + +> The WebSocket API is an advanced technology that makes it possible to open a two-way interactive +> communication session between the user's browser and a server. With this API, you can send +> messages to a server and receive event-driven responses without having to poll the server for a +> reply. + +Google Chrome was the first browser to support the WebSocket Protocol, adding support in 2009 - two +years before it was officially standardised in 2011. As the need for full-duplex server-client +communications rose, other browsers followed suit. + +As of today, all major browsers support the WebSocket Protocol - see the +[Can I Use table](https://caniuse.com/?search=Web%20Sockets). + +## WebSockets ❤️ GraphQL + +Okay, so, how do I use WebSockets to add support for the +[GraphQL `subscription` operation](https://spec.graphql.org/June2018/#sec-Subscription)? Doing a +basic Google search, you'd be faced with a single solution, namely +[`subscriptions-transport-ws`](https://github.com/apollographql/subscriptions-transport-ws). Looking +through the repository, checking recent comments, reading through the issues and open PRs - might +have you notice the abundance of bugs and their security implications. +[A summary can be found here](https://github.com/enisdenjo/graphql-ws/issues/3). + +The authors have done a brilliant job, but unfortunately other projects and needs have reduced their +disposable time for maintaining and nurturing this well accomplished, yet needy, idea. But, it did +indeed inspire me to take up their legacy and revitalised it! Having faced various WebSocket +challenges with GraphQL myself - I jumped in writing a new library from scratch. + +With no further ado - I humbly introduce [`graphql-ws`](https://github.com/enisdenjo/graphql-ws). A +coherent, feature-full, zero-dependency, plug-n-play, lazy, simple, server and client implementation +of the +[new, security first GraphQL over WebSocket Protocol](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md) +with full support for all 3 GraphQL operations: `Queries`, `Mutations` and `Subscriptions`. The +protocol aims to be standardised and become a part of GraphQL with the help of the foundation's +[GraphQL over HTTP work group](https://github.com/graphql/graphql-over-http/pull/140). + +The library is written in TypeScript and the full implementation totals merely ~1000 lines of well +formatted, commented, code. It leverages modern JavaScript primitives and the well established +presence of WebSockets. + +## How Do I Get Started? + +I am glad you asked! This is how: + +#### Install + +```sh +yarn add graphql-ws +``` + +#### Create a GraphQL Schema + +```ts +import { buildSchema } from 'graphql' + +// Construct a schema, using GraphQL schema language +const schema = buildSchema(` + type Query { + hello: String + } + type Subscription { + greetings: String + } +`) + +// The roots provide resolvers for each GraphQL operation +const roots = { + query: { + hello: () => 'Hello World!' + }, + subscription: { + greetings: async function* sayHiIn5Languages() { + for (const hi of ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo']) { + yield { greetings: hi } + } + } + } +} +``` + +#### Start the Server + +##### With [`ws`](https://github.com/websockets/ws) + +```ts +import { useServer } from 'graphql-ws/lib/use/ws' +// yarn add ws +import ws from 'ws' + +const server = new ws.Server({ + port: 4000, + path: '/graphql' +}) + +useServer( + // from the previous step + { schema, roots }, + server +) + +console.log('Listening to port 4000') +``` + +##### With [`uWebSockets.js`](https://github.com/uNetworking/uWebSockets.js) + +```ts +import { makeBehavior } from 'graphql-ws/lib/use/uWebSockets' +// yarn add uWebSockets.js@uNetworking/uWebSockets.js# +import uWS from 'uWebSockets.js' + +uWS + .App() + .ws( + '/graphql', + makeBehavior( + // from the previous step + { schema, roots } + ) + ) + .listen(4000, listenSocket => { + if (listenSocket) { + console.log('Listening to port 4000') + } + }) +``` + +##### With [`fastify-websocket`](https://github.com/fastify/fastify-websocket) + +```ts +// yarn add fastify +import Fastify from 'fastify' +// yarn add fastify-websocket +import fastifyWebsocket from 'fastify-websocket' +import { makeHandler } from 'graphql-ws/lib/use/fastify-websocket' + +const fastify = Fastify() +fastify.register(fastifyWebsocket) + +fastify.get( + '/graphql', + { websocket: true }, + makeHandler( + // from the previous step + { schema, roots } + ) +) + +fastify.listen(4000, err => { + if (err) { + fastify.log.error(err) + return process.exit(1) + } + console.log('Listening to port 4000') +}) +``` + +#### Use the Client + +```ts +import { createClient } from 'graphql-ws' + +const client = createClient({ + url: 'ws://welcomer.com:4000/graphql' +}) + +// query +;(async () => { + const result = await new Promise((resolve, reject) => { + let result + client.subscribe( + { + query: '{ hello }' + }, + { + next: data => (result = data), + error: reject, + complete: () => resolve(result) + } + ) + }) + + expect(result).toEqual({ hello: 'Hello World!' }) +})() + +// subscription +;(async () => { + const onNext = () => { + /* handle incoming values */ + } + + let unsubscribe = () => { + /* complete the subscription */ + } + + await new Promise((resolve, reject) => { + unsubscribe = client.subscribe( + { + query: 'subscription { greetings }' + }, + { + next: onNext, + error: reject, + complete: resolve + } + ) + }) + + expect(onNext).toBeCalledTimes(5) // we say "Hi" in 5 languages +})() +``` + +## Want to Find Out More? + +Check the repo out to for [Getting Started](https://github.com/enisdenjo/graphql-ws#getting-started) +quickly with some [Recepies](https://github.com/enisdenjo/graphql-ws#recipes) for vanilla usage, or +with [Relay](https://relay.dev) and [Apollo Client](https://apollographql.com/docs/react). Opening +issues, contributing with code or simply improving the documentation is always welcome! + +I am [@enisdenjo](https://github.com/enisdenjo) and you can chat with me about this topic on the +[GraphQL Slack workspace](https://graphql.slack.com) anytime. + +Thanks for reading and happy coding! 👋 diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-request-cancellation/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-request-cancellation/page.mdx new file mode 100644 index 00000000000..6834f26f12e --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-request-cancellation/page.mdx @@ -0,0 +1,286 @@ +--- +title: GraphQL Request Cancellation in JavaScript +tags: [graphql, javascript, typescript] +authors: laurin +date: 2024-04-29 +description: + Learn how to cancel GraphQL requests on the client side and how to handle cancellations on the + server side with JavaScript. +--- + +import { Callout } from '@theguild/components' + +Canceling in-flight requests in an application if a user navigates away from a view has become a +standard in modern applications. GraphQL client libraries such as Apollo Client and urql support +this by default. But is it also possible to cancel ongoing logic on the server if the client stops +the request? + +## `AbortController` and `fetch` on the Client {/* eslint-disable-line mdx/remark */} + +After the introduction of the +[`fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), sometime later we also +got the [`AbortController` API](https://developer.mozilla.org/en-US/docs/Web/API/AbortController). +These both in combination made HTTP request cancellation logic straightforward. + +In the following React code snippet, we implement a React hook that uses fetch for fetching some +data using the `useEffect` hook. The `fetch` call receives the `AbortSignal` from an +`AbortController` . Within the cleanup function returned from `useEffect`, that is invoked when the +component containing this hook unmounts, the request is aborted via the call of the `abort` method +on the `AbortController`. + +```tsx filename="React AbortController example" +function useProfileData() { + const [state, setState] = useState() + + useEffect(() => { + const controller = new AbortController() + async function fetchData() { + const response = await fetch('/graphql', { + method: 'POST', + body: JSON.stringify({ query: '{ me { id name } }' }), + headers: { 'Content-Type': 'application/json' }, + signal: controller.signal + }) + const data = await response.json() + setState(data) + } + + fetchData().catch(err => console.log(err)) + return function onUnmount() { + controller.abort() + } + }, []) + + return state +} +``` + +When checking the network tab within the browser, we can see that the request was canceled. + + + **Note:** When a pending fetch request is canceled, the Promise returned from the fetch call will + reject with a [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException)`. It + is recommended to always add a catch handler for your fetch calls to avoid unhandled promise + rejections. + + +All modern GraphQL clients use this under the hood for optimizing the user experience, as it is +unnecessary to load data that is no longer needed, e.g. because the user navigated away to another +view. This is especially true for devices where low data/roaming usage is desired. + +## Isomorphic APIs + +One of the great things about JavaScript is that APIs that were formerly exclusive to browser +environments are finally being added to various server-side runtimes. Whether Bun.js, Deno or +Node.js, all of them support both the `fetch` and `AbortController` API in recent versions! + +| Runtime | fetch | AbortController | +| ------- | ------------------ | ------------------ | +| Node.js | ✅ (since v18.0.0) | ✅ (since v15.4.0) | +| Deno | ✅ (since v1.0.0) | ✅ (since v1.0.0) | +| Bun | ✅ (since v1.0.0) | ✅ (since v1.0.0) | + +This enables us to run the exact same code from the client example on our server. + +In addition to `AbortController` now being able on the server, all kind of libraries that do +asynchronous tasks such as IO, can similarly to the `fetch` API accept an `AbortSignal` option for +canceling any pending work in case the calling code is no longer interested in the result. An +example of this could be that a request from the client has been canceled and the server no longer +needs to load all the data and construct a response as requested previously. + +## Cleaning up Resources and Pending Tasks Is Hard + +While this is not particularly true for all programming languages, JavaScript servers are by default +single-threaded and no new thread or worker process is spawned for every incoming HTTP request. +Thus, we can not simply kill a thread or worker process that is performing some work in case the +request got aborted. + +Because of that, cleaning up resources and canceling pending tasks is a cumbersome task that is left +to the user instead of being performed through magic. + +On top of that a lot of tooling and libraries, yet need to catch up and provide the APIs (e.g. pass +`AbortSignal`) to allow cleaning up resources. + +Some examples (on the server in case of an aborted client request): + +- Abort a non-transactional, expensive, and long-running SQL query (or query/read on any other + database) +- Abort pending HTTP requests to third-party services for fetching some data +- Abort the GraphQL execution and stop the resolver waterfall calls for resolving all the data + specified by the GraphQL operation sent by the client + +The latter point is a superset of the two former points, as in most cases within the GraphQL +execution, the business logic as called within or specified in your resolvers will result in a +variation of reads from remote services or databases. + +A good enough solution for many people could be to only have the latter and only further optimize +for the former two points when additional performance gains are justified. + +## How GraphQL Execution Cancelation Works + +When a GraphQL operation is sent to the server, the server will start executing the operation by +calling the resolver functions as specified by the GraphQL operation selection set. Some resolver +functions are light and will just read a property from an object, whereas others will call a remote +service or database. + +Here is an example GraphQL operation causing the server to call several services in sequence. + +```graphql filename="Example GraphQL Operation" +query Profile($userId: ID!) { + user(id: $userId) { + id + name + avatar { + id + url + } + posts(first: 10) { + edges { + node { + id + title + body + likeCount + comments(first: 5) { + edges { + node { + id + body + author { + id + name + avatar { + id + url + } + } + } + } + } + } + } + } + } +} +``` + +Here is a simplified view of what kind of operations will be performed as the resolvers are called. +The arrows indicate what action and resolver will be executed after another one has finished. For +simplification, we only show the resolvers that are performing IO operations. + +```mermaid +flowchart + id1["Load user (Query.user)"] + id21["Load user avatar (User.avatar)"] + id22["Load user posts (User.posts)"] + id31["Load total post likes (Post.likeCount)"] + id32["Load comments for each post (Post.comments)"] + id4["Load author for each comments (Comment.author)"] + id5["Load author avataer for each comment author (User.avatar)"] + + id1 --> id21 + id1 --> id22 + id22 --> id31 + id22 --> id32 + id32 --> id4 + id4 --> id5 +``` + +So given the above operation, if the client cancels the request at the stage +`Load user posts (User.posts)`, there is no need for the server to perform all the resolver calls +and data-fetching happening after that point. Furthermore, event the pending +`Load user posts (User.posts)` call could be canceled. + +```mermaid +flowchart + id1["Load user (Query.user)"] + id21["Load user avatar (User.avatar)"] + id22["Load user posts (User.posts)"] + id31["Load total post likes (Post.likeCount)"] + id32["Load comments for each post (Post.comments)"] + id4["Load author for each comments (Comment.author)"] + id5["Load author avataer for each comment author (User.avatar)"] + + id1 --> id21 + id1 --> id22 + id22 -- client request cancel --> id31 + id22 -- client request cancel --> id32 + id32 --> id4 + id4 --> id5 + + classDef opacity opacity:0.5 + class id31,id32,id4,id5 opacity + linkStyle 2 opacity:0.5; + linkStyle 3 opacity:0.5; + linkStyle 4 opacity:0.5; + linkStyle 5 opacity:0.5; +``` + +## Why We Forked GraphQL.js + +GraphQL.js is the reference implementation of the GraphQL specification. GraphQL.js is also the +GraphQL engine that is used by almost all JavaScript GraphQL servers that exist today. + +The reference implementation serves as a proofing ground for new features (e.g. the `@stream` and +`@defer` directives). However, there is one major drawback with GraphQL.js. Things generally move +slow and the process of testing new features and provide feedback is cumbersome as you will have to +maintain your own fork. If you want to test out multiple newly proposed features at the same time, +it gets even more complicated. We wanted to take that burden from GraphQL server builders! + +When we revived Yoga Server, we wanted to make in-progress “GraphQL specification” features easily +accessible. That is why we forked GraphQL.js and now maintain this fork, where new features can be +enabled via flags. + +This also allowed us to add support for passing `AbortSignal` for the GraphQL execution, which +allows canceling the GraphQL data resolving waterfall in case the incoming request got canceled. + +## How to Enable GraphQL Execution Cancelation in GraphQL Yoga + +Execution cancellation is not yet a feature that is enabled by default as we are still gathering +intel on its behavior in a real-world environment (on our [Hive](https://the-guild.dev/graphql/hive) +GraphQL API). + +For now, opting into this feature is achieved via a plugin. + +```ts filename="GraphQL Yoga cancelation plugin" +import { createYoga, useExecutionCancellation } from 'graphql-yoga' +import { schema } from './schema' + +// Provide your schema +const yoga = createYoga({ + plugins: [useExecutionCancellation()], + schema +}) + +// Start the server and explore http://localhost:4000/graphql +const server = createServer(yoga) +server.listen(4000, () => { + console.info('Server is running on http://localhost:4000/graphql') +}) +``` + +After that, you should immediately benefit from GraphQL execution cancellation without having to do +any further optimizations. + +Depending on whether you are performing other advanced GraphQL optimizations such as data-fetching +lookaheads or similar, you might also want to implement `AbortSignal` propagating in those API +surfaces. For that, we recommend reading the +[Yoga documentation on GraphQL execution cancellation](https://the-guild.dev/graphql/yoga-server/docs/features/execution-cancellation). + +## Conclusion + +HTTP request and database read cancelations are powerful tools that can be handy when encountering +performance problems for often aborted paths. + +Similarly, aborting the GraphQL execution can potentially free up a lot of resources otherwise +wasted for fetching data and constructing a response that is no longer needed. + +Writing cancelation logic for HTTP requests and database read cancelation can be cumbersome, but +implemented via an abstraction layer if needed. + +Yoga server is a powerful JavaScript runtime agnostic GraphQL Server that supports GraphQL execution +cancellation. +[You can learn more here](https://the-guild.dev/graphql/yoga-server/docs/features/execution-cancellation). + +If you are facing related or other issues with GraphQL APIs, feel free to reach out! We offer a wide +range of services and are experts in building and maintaining GraphQL API tooling. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-response-caching-with-envelop/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-response-caching-with-envelop/page.mdx new file mode 100644 index 00000000000..6326ad2b2c0 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-response-caching-with-envelop/page.mdx @@ -0,0 +1,405 @@ +--- +title: GraphQL Response Caching with Envelop +tags: [graphql] +authors: laurin +date: 2021-08-19 +description: + Caching GraphQL endpoints can be tricky. Let's take a look at the theory behind making it possible + and learn how to do it with Envelop. +canonical: https://the-guild.dev/graphql/envelop/docs/guides/adding-a-graphql-response-cache +--- + +## A Brief Introduction to Caching + +Huge GraphQL query operations can slow down your server as deeply nested selection sets can cause a +lot of subsequent database reads or calls to other remote services. Tools like `DataLoader` can +reduce the amount of concurrent and subsequent requests via batching and caching during the +execution of a single GraphQL operation. Features like `@defer` and `@stream` can help with +streaming slow-to-retrieve result partials to the clients progressively. However, for subsequent +requests we hit the same bottle-neck over and over again. + +What if we don't need to go through the execution phase at all for subsequent requests that execute +the same query operation with the same variables? + +A common practice for reducing slow requests is to leverage caching. There are many types of caching +available. E.g. We could cache the whole HTTP responses based on the POST body of the request or an +in memory cache within our GraphQL field resolver business logic in order to hit slow services less +frequently. + +Having a cache comes with the drawback of requiring some kind of cache invalidation mechanism. +Expiring the cache via a TTL (time to live) is a widespread practice, but can result in hitting the +cache too often or too scarcely. Another popular strategy is to incorporate cache invalidation logic +into the business logic. Writing such logic can potentially become too verbose and hard to maintain. +Other systems might use database write log observers for invalidating entities based on updated +database rows. + +In a strict REST API environment, caching entities is significantly easier, as each endpoint +represents one resource, and thus a `GET` method can be cached and a `PATCH` method can be used for +automatically invalidating the cache for the corresponding `GET` request, which is described via the +HTTP path (`/api/user/12`). + +With GraphQL such things become much harder and complicated. First, we usually only have a single +HTTP endpoint `/graphql` that only accepts `POST` requests. A query operation execution result could +contain many different types of entities, thus, we need different strategies for caching GraphQL +APIs. + +SaaS services like FastQL and GraphCDN started popping providing proxies for your existing GraphQL +API, that magically add response based caching. But how does this even work? + +## How Does GraphQL Response Caching Work? + +### Caching Query Operations + +In order to cache a GraphQL execution result (response) we need to build an identifier based on the +input that can be used to identify whether a response can be served from the cache or must be +executed and then stored within the cache. + +**Example: GraphQL Query Operation** + +```graphql +query UserProfileQuery($id: ID!) { + user(id: $id) { + __typename + id + login + repositories + friends(first: 2) { + __typename + id + login + } + } +} +``` + +**Example: GraphQL Variables** + +```json +{ + "id": "1" +} +``` + +Usually those inputs are the Query operation document and the variables for such an operation +document. + +Thus, a response cache can store the execution result under a cache key that is built from those +inputs: + +``` +OperationCacheKey (e.g. SHA1) = hash(GraphQLOperationString, Stringify(GraphQLVariables)) +``` + +Under some circumstances it is also required to cache based on the request initiator. E.g. a user +requesting his profile should not receive the cached profile of another user. In such a scenario, +building the operation cache key should also include a partial that uniquely identifies the +requestor. This could be a user ID extracted from an authorization token. + +``` +OperationCacheKey (e.g. SHA1) = hash(GraphQLOperationString, Stringify(GraphQLVariables), RequestorId) +``` + +This allows us to identify recurring operations with the same variables and serve it from the cache +for subsequent requests. If we can serve a response from the cache we don't need to parse the +GraphQL operation document and furthermore can skip the expensive execution phase. That will result +in significant speed improvements. + +But in order to make our cache smart we still need a suitable cache invalidation mechanism. + +### Invalidating Cached GraphQL Query Operations + +Let's take a look at a possible execution result for the GraphQL operation. + +**Example: GraphQL Execution Result** + +```json +{ + "data": { + "user": { + "__typename": "User", + "id": "1", + "login": "dotan", + "repositories": ["codegen"], + "friends": [ + { + "__typename": "User", + "id": "2", + "login": "urigo" + }, + { + "__typename": "User", + "id": "3", + "login": "n1ru4l" + } + ] + } + } +} +``` + +Many frontend frameworks cache GraphQL operation results in a normalized cache. The identifier for +storing the single entities of a GraphQL operation result within the cache is usually the `id` field +of object types for schemas that use global unique IDs or a compound of the `__typename` and `id` +field for schemas that use non-global ID fields. + +**Example: Normalized GraphQL Client Cache** + +```json +{ + "User:1": { + "__typename": "User", + "id": "1", + "login": "dotan", + "repositories": ["codegen"], + "friends": ["$$ref:User:2", "$$ref:User:3"] + }, + "User:2": { + "__typename": "User", + "id": "2", + "login": "urigo" + }, + "User:3": { + "__typename": "User", + "id": "3", + "login": "n1ru4l" + } +} +``` + +Interestingly, the same strategy for constructing cache keys on the client can also be used on the +backend for tracking which GraphQL operations contain which entities. That allows invalidating +GraphQL query operation results based on entity IDs. + +For the execution result entity IDS that could be used for invalidating the operation are the +following: `User:1`, `User:2` and `User:3`. + +And also keep a register that maps entities to operation cache keys. + +```text +Entity List of Operation cache keys that reference a entity + +User:1 OperationCacheKey1, OperationCacheKey2, ... +User:2 OperationCacheKey2, OperationCacheKey3, ... +User:3 OperationCacheKey3, OperationCacheKey1, ... +``` + +This allows us to keep track of which GraphQL operations must be invalidated once a certain entity +becomes stale. + +The remaining question is, how can we track an entity becoming stale? + +As mentioned before, listening to a database write log is a possible option - but the implementation +is very specific and differs based on the chosen database type. Time to live is also a possible, but +a very inaccurate solution. + +Another solution is to add invalidation logic within our GraphQL mutation resolver. By the GraphQL +Specification mutations are meant to modify our GraphQL graph. + +A common pattern when sending mutations from clients is to select and return affected/mutated +entities with the selection set. + +For our example from above the following could be a possible mutation for adding a new repository to +the repositories field on the user entity. + +**Example: GraphQL Mutation** + +```graphql +mutation RepositoryAddMutation($userId: ID, $repositoryName: String!) { + repositoryAdd(userId: $userId, repositoryName: $repositoryName) { + user { + id + repositories + } + } +} +``` + +**Example: GraphQL Mutation Execution Result** + +```json +{ + "data": { + "repositoryAdd": { + "user": { + "id": "1", + "repositories": ["codegen", "envelop"] + } + } + } +} +``` + +Similar to how we build entity identifiers from the execution result of query operations for +identifying what entities are referenced in which operations, we can extract the entity identifiers +from the mutation operation result for invalidating affected operations. + +In this specific case all operations that select `User:1` should be invalidated. + +Such an implementation makes the assumption that all mutations by default select affected entities +and, furthermore, all mutations of underlying entities are done through the GraphQL gateway via +mutations. In a scenario where we have actors that are not GraphQL services or services that operate +directly on the database, we can use this approach in a hybrid model with other methods such as +listening to database write logs. + +## Envelop Response Cache + +The Envelop response cache plugin now provides primitives and a reference in memory store +implementation for adopting such a cache with all the features mentioned above with any GraphQL +server. + +The goal of the response cache plugin is to educate how such mechanisms are implemented and +furthermore give developers the building blocks for constructing their own global cache with their +cloud provider of choice. + +Adding a response cache to an existing envelop GraphQL server setup is as easy as adding the plugin: + +```ts +import { envelop } from '@envelop/core' +import { useResponseCache } from '@envelop/response-cache' + +const getEnveloped = envelop({ + plugins: [ + // ... other plugins ... + useResponseCache() + ] +}) +``` + +If you need to imperatively invalidate you can do that by providing the cache to the plugin: + +```ts +import { envelop } from '@envelop/core' +import { createInMemoryCache, useResponseCache } from '@envelop/response-cache' +import { emitter } from './event-emitter' + +const cache = createInMemoryCache() + +emitter.on('invalidate', entity => { + cache.invalidate([ + { + typename: entity.type, + id: entity.id + } + ]) +}) + +const getEnveloped = envelop({ + plugins: [ + // ... other plugins ... + useResponseCache({ cache }) + ] +}) +``` + +The caching behavior can be fully customized. A TTL can be provided global or more granular per type +or schema coordinate. + +```ts +import { envelop } from '@envelop/core' +import { useResponseCache } from '@envelop/response-cache' + +const getEnveloped = envelop({ + plugins: [ + // ... other plugins ... + useResponseCache({ + // cache operations for 1 hour by default + ttl: 60 * 1000 * 60, + ttlPerType: { + // cache operation containing Stock object type for 500ms + Stock: 500 + }, + ttlPerSchemaCoordinate: { + // cache operation containing Query.rocketCoordinates selection for 100ms + 'Query.rocketCoordinates': 100 + }, + // never cache responses that include a RefreshToken object type. + ignoredTypes: ['RefreshToken'] + }) + ] +}) +``` + +Need to cache based on the user? No problem. + +```ts +import { envelop } from '@envelop/core' +import { useResponseCache } from '@envelop/response-cache' + +const getEnveloped = envelop({ + plugins: [ + // ... other plugins ... + useResponseCache({ + // context is the GraphQL context that would be used for execution + session: context => (context.user ? String(context.user.id) : null), + // never serve cache for admin users + enabled: context => (context.user ? isAdmin(context.user) === false : true) + }) + ] +}) +``` + +Don't want to automatically invalidate based on mutations? Also, configurable! + +```ts +import { envelop } from '@envelop/core' +import { useResponseCache } from '@envelop/response-cache' + +const getEnveloped = envelop({ + plugins: [ + // ... other plugins ... + useResponseCache({ + // some might prefer invalidating only based on a database write log + invalidateViaMutation: false + }) + ] +}) +``` + +Want a global cache on Redis? Build a cache that implements the `Cache` interface and share it with +the community! + +```ts +export type Cache = { + /** set a cache response */ + set( + /** id/hash of the operation */ + id: string, + /** the result that should be cached */ + data: ExecutionResult, + /** array of entity records that were collected during execution */ + entities: Iterable, + /** how long the operation should be cached */ + ttl: number + ): PromiseOrValue + /** get a cached response */ + get(id: string): PromiseOrValue> + /** invalidate operations via typename or id */ + invalidate(entities: Iterable): PromiseOrValue +} +``` + +More information about all possible configuration options can be found on +[the response cache docs on the Plugin Hub](https://envelop.dev/plugins/use-response-cache). + +import { YoutubeIframe } from '#components/youtube-iframe' + + + +## What Is Next? + +We are excited to explore new directions and make enterprise solutions accessible for all kinds of +developers. + +What if the response cache could be used as a proxy on edge cloud functions distributed around the +world, which would allow using envelop as a http proxy to your existing GraphQL server? This is +something we would love to explore more (or even see contributions and projects from other +open-source developers). + +We also want to make other practices such as rate limits based on operation cost calculation as used +by huge corporations like Shopify available as envelop plugins. + +Do you have any ideas, want to contribute or report issues? Start a GitHub discussion/issue or +contact us via the chat! diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-scalars/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-scalars/page.mdx new file mode 100644 index 00000000000..421648fdec6 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-scalars/page.mdx @@ -0,0 +1,94 @@ +--- +title: GraphQL Scalars 1.0 is out! +authors: arda +tags: [graphql, graphql-scalars] +date: 2019-08-08 +description: More Types, Data Integrity and Strict Validations on GraphQL. +--- + +The GraphQL Specification has the`Int`, `Float`, `String`, `Boolean`and `ID` Scalar types by +default. Those scalar types help you identify the data and validate it before transferring it +between client and server. But you might need more specific scalars for your GraphQL application, to +help you better describe and validate your app's data. + +## Validation Using Scalars + +For example, you have a `String` field, but you need to validate upcoming or ongoing string data +using regular expressions. So you should have this validation on each end; one in the client, the +other one in the server and maybe there is another on a source. Instead of duplicating the same +logic in different parts of the project, you can use `EmailAddress` scalar type that does the +validation inside GraphQL for you. + +## Serialization and Parsing + +The other benefit of using GraphQL scalar types is parsing and serializing while transferring data. +For example, you have `DateTime` data, but it is transferred as `String` due to restrictions of +JSON, and each time you receive and pass the data, you have to parse the string and create a +JavaScript `Date` instance while also serializing it to string before passing it to the client. +Instead of having that logic in your implementation, you can just use `DateTime` scalar and you +would work with native JavaScript`Date` instances directly like it is one of primitive types such as +string, number and boolean. + +## What's New? + +[We've](/) recently [taken over the maintenance](/blog/taking-over-merge-graphql-schemas) of +[GraphQL-Scalars](https://graphql-scalars.dev) library from the amazing team of OK-Grow! + +Since then, we completely rewrote the library using TypeScript, upgraded all dependencies, closed +all the issues and PRs and increased the number of scalars in the package with new scalars like: +`BigInt(Long)` , `GUID` , `HexColorCode` , `Hexadecimal` , `IPv4` , `IPv6` , `ISBN` , `MAC` , `JSON` +and more. You can see all scalars in the +[**README**](https://graphql-scalars.dev/docs/introduction). + +## Mocking + +Apollo Server provides mocks built-in scalars such as `Int` , `String` , `Float` , `ID` and +`Boolean` . What if you need same thing for our scalars? So, we provide you mocking functions for +each scalar in this package. You can add those easily in your server for mocking the schema. + +```typescript +import { ApolloServer } from 'apollo-server' +// import all scalars and resolvers +import { mocks, resolvers, typeDefs } from 'graphql-scalars' +import { makeExecutableSchema } from 'graphql-tools' + +// Alternatively, import individual scalars and resolvers +// import { DateTimeResolver, DateTimeTypeDefinition, DateTimeMock, ... } from "graphql-scalars" + +const server = new ApolloServer({ + typeDefs: [ + // use spread syntax to add scalar definitions to your schema + ...typeDefs + // DateTimeDefinition, + // ... + // ... other type definitions ... + ], + resolvers: { + // use spread syntax to add scalar resolvers to your resolver map + ...resolvers + // DateTimeResolver, + // ... + // ... remainder of resolver map ... + }, + mocks: { + // use spread syntax to add scalar resolvers to your resolver map + ...mocks + // DateTimeMock, + // ... + // ... other mocks ... + } +}) +``` + +## Special Thanks + +Thanks to OK-Grow for creating this package, +[adriano-di-giovanni](https://github.com/adriano-di-giovanni) for being generous and giving us the +[`graphql-scalars`](https://npmjs.com/package/graphql-scalars) name on npm, to +[Saeris](https://github.com/Saeris) for letting us take other scalar implementations from his fork, +[stems](https://github.com/stems) for their +[`graphql-bigint`](https://github.com/stems/graphql-bigint) package, +[abhiaiyer91](https://github.com/abhiaiyer91) for his +[`graphql-currency-scalars`](https://github.com/abhiaiyer91/graphql-currency-scalars) package and +[taion](https://github.com/taion) for his +[`graphql-type-json`](https://github.com/taion/graphql-type-json). diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-stencil-apollo/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-stencil-apollo/page.mdx new file mode 100644 index 00000000000..d0cd383ebbd --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-stencil-apollo/page.mdx @@ -0,0 +1,136 @@ +--- +title: Stencil-Apollo - Stencil meets GraphQL +authors: arda +tags: [graphql, stencil, stencil-apollo] +date: 2019-03-06 +description: Stencil-Apollo lets you easily use GraphQL in Web Components. +--- + +## Stencil-Apollo Lets You Easily Use GraphQL in Web Components + +**GraphQL** is everywhere! Together with **Typescript** it is one of those technologies **everyone +wants to get their hands on** in 2019. + +When using GraphQL on the client, the most popular library is the powerful Apollo Client. Today, +[Apollo](https://github.com/apollographql/apollo-client) currently has an official support for +leading Web Frameworks like React and Angular. + +But what if you don't have to use those frameworks to create a really powerful and performant apps? +Or what if you want to expose Web Components with shared logic like GraphQL data fetching to +developers who use different frameworks? + +## Web Components and GraphQL + +That's where [Stencil](https://stenciljs.com) come in. It's a Web Components compiler that combines +some of the best features from Angular, React and Vue to create web applications or re-usable +platform-agnostic UI libraries based on Web Components. + +But what if you want to use GraphQL in your Stencil projects or expose that power to your Web +Components users? + +## The Solution! + +Today, I am happy to announce the release of a new open source library + +> [stencil-apollo](https://github.com/ardatan/stencil-apollo) + +Now it is much easier to use StencilJS and create GraphQL-based applications. **Stencil-Apollo has +the same functionalities as its React sibling** React-Apollo, and the usage is really simple. + +```tsx + { + if (loading) return 'Loading...' + return ( +
      + {data.posts.map(post => ( +
    • + {post.title}({post.votes} votes) +
    • + ))} +
    + ) + }} +/> +``` + +## Stencil and Apollo + +Because [Stencil-Apollo](https://github.com/ardatan/stencil-apollo) uses Apollo-Client, you're still +able to take advantage of **an entire ecosystem of Apollo Links and Cache implementations**. + +> Everything in Stencil-Apollo is based on Web Components + +There are exactly 4 of them: + +- `apollo-provider` defines an Apollo-Client so other + [Stencil-Apollo](https://github.com/ardatan/stencil-apollo) components can use it (same as + React-Apollo's `ApolloProvider`). + +As you know in GraphQL there are three kinds of operations and +[Stencil-Apollo](https://github.com/ardatan/stencil-apollo) got all them covered: + +- `apollo-query` lets you fetch data +- `apollo-mutation` is responsible to call mutations +- `apollo-subscription` handles subscription for you + +As you can see, their names are pretty easy to remember! + +[Stencil-Apollo](https://github.com/ardatan/stencil-apollo) combines the change detection mechanism +of Apollo Client and StencilJS's Virtual DOM, like React-Apollo does for React's VDOM. + +To start using Stencil-Apollo check out our examples and docs: + +[https://github.com/ardatan/stencil-apollo/](https://github.com/ardatan/stencil-apollo/ 'ardatan/stencil-apollo +Stencil Apollo Library. Contribute to ardatan/stencil-apollo development by creating an account on GitHub.github.com') + +[Stencil-Apollo](https://github.com/ardatan/stencil-apollo) works quite well with Ionic 4, and we're +planning to give support for all other frameworks, the same way as Ionic 4 does by using the power +of Web Components. + +## Ready-To-Use and Strongly Typed GraphQL Components + +The [GraphQL Code Generator](https://graphql-code-generator.com) can be used to generate TypeScript +typings for both server and client. + +This tool also generates strongly typed +[React-Apollo components](/blog/codegen-typescript-react-apollo) and +[Apollo-Angular services](/blog/apollo-angular-12) to improve the development experience. It turns +your GraphQL operations into ready to use elements. + +> Now it also supports Stencil-Apollo! + +import { VideoEmbed } from '#components/video-embed' + + + +> [Check out our example](https://github.com/ardatan/stencil-apollo/tree/master/example-with-codegen) +> with GraphQL-Code-Generator + +## WhatsApp PWA Clone Using Stencil and Ionic 4 + +We're also working on an example Stencil PWA project that uses +[Stencil-Apollo](https://github.com/ardatan/stencil-apollo) which will be another variation of our +[WhatsApp Clone](/blog/whatsapp-clone-react-hooks-graphql-typescript-and-postgresql). + +Follow the process here: + +[https://github.com/Urigo/WhatsApp-Clone-Client-React/issues/158](https://github.com/Urigo/WhatsApp-Clone-Client-React/issues/158 'Another Clone using Stencil & Ionic 4 🚀 · Issue #158 · Urigo/WhatsApp-Clone-Client-React +Pure Progressive Web Application Example using Stencil and Ionic 4 with; PWA Support / 100% Lighthouse Audit Result HMR…github.com') + +--- + +- [Stencil-Apollo repository](https://github.com/ardatan/stencil-apollo) +- [Documentation](https://github.com/ardatan/stencil-apollo) +- [An example of code generation](https://github.com/ardatan/stencil-apollo/tree/master/example-with-codegen) diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-tools-v6/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-tools-v6/page.mdx new file mode 100644 index 00000000000..01c9fe63155 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-tools-v6/page.mdx @@ -0,0 +1,159 @@ +--- +title: GraphQL Tools - next generation schema stitching and new leadership +authors: arda +tags: [graphql, graphql-tools, schema-first, schema-stitching, graphql-federation] +date: 2020-05-21 +description: GraphQL Tools v6 with more tools, improved schema stitching and bug fixes. +--- + +We are happy to announce the new GraphQL Tools v6 together with +[the new documentation website.](https://graphql-tools.com) + +As the Guild, we recently took over the popular GraphQL Tools repository from the team at Apollo, +who created this amazing library. We released v5 as a temporary stopgap release with a lot of +bug-fixes around schema stitching and are now proud to reintroduce graphql-tools to the community +with v6. + +During that process we've reviewed and fixed hundreds of issues and pull requests and reduced the +number of issues to a few that are being reviewed and processed speedily. + +The low number of issues is despite the fact that the package is widely used with quick issue triage +and turnaround -- a new standard for open source maintenance that we apply to +[all of our open source tools](https://github.com/the-guild-org/Stack). + +The original GraphQL Tools enabled users to: + +- **build “SDL-first“ GraphQL schemas** by attaching resolvers (and the other non-SDL GraphQL + entities) to type definitions written in graphql schema definition language. +- **modify schemas** simply by annotating them with reusable schema directives +- **create and transform “remote“ GraphQL schemas** , i.e. executable GraphQL schemas that proxy + queries to a remote GraphQL endpoint. +- “**stitch“ together multiple subschemas** by combining subschemas into a new schema, adding new + fields, and enabling delegation of all or some of the new fields to existing subschemas. +- **mock schemas** by defining a default resolver on a per-type basis + +You asked and we listened! With the integration of the Guild's +[graphql-toolkit](https://github.com/ardatan/graphql-toolkit), +[merge-graphql-schemas](https://github.com/urigo/merge-graphql-schemas), and the +[graphql-import](https://github.com/ardatan/graphql-import) libraries into a new graphql-tools +monorepo, The new GraphQL Tools does all of the above and more: + +- **merge schemas** prior to schema creation by merging typeDefs and/or resolvers + ([merge-graphql-schemas](https://github.com/urigo/merge-graphql-schemas)) +- **import schemas** within.. (previously + [graphql-import](https://github.com/ardatan/graphql-import)). +- optimize client side queries with relay compiler (relay-operation-optimizer) +- load resolvers and type definitions from your file system using glob expressions +- [much, much more ...](https://graphql-tools.com/docs/introduction) + +## New Monorepo Structure + +The new GraphQL Tools is split into different packages by functionality under the @graphql-tools +scope. You can pick individual packages according to your needs instead of having one large package +with unused functionalities in your project, which will **significantly decrease bundle size**. When +bundle size is not an issue, you can still directly import all functionality directly from the +`graphql-tools` package... + +Some functions have been renamed to avoid conflicts between packages - see the migration guide for +further help in upgrading. + + + graphql-tools.com/docs/migration-from-tools-v5 + + +Let's talk about the improvements we have made in each area. + +## Building "SDL-First" GraphQL Schemas + +The primary use case of the graphql-tools library is still the makeExecutableSchema function that +enables users to start building their schemas directly from type definitions. This approach makes +the SDL the single “source of truth“ for the entire graph. + +Under the hood, as before, makeExecutableSchema calls out to upstream graphql-js functions to build +the initial schema from type definitions, with the major contribution from graphql-tools of the +addResolversToSchema function that can add resolvers and the other non-SDL GraphQL entities. + +In older versions of GraphQL Tools, addResolversToSchema and the other schema modification functions +did their work “in place,“ mutating the initial schema. GraphQL Tools now almost never does this, +making the underlying schema modification functions safe and useful for other schema generation +workflows and hopefully form a valuable independent contribution to the community. + +## Modify Schemas with Directives + +For v6, we built a new -- and more flexible -- way of modifying existing schemas with directives. +Instead of defining a new class that inherits from SchemaDirectiveVisitor, we export two powerful +new functions that can work in tandem to modify schemas via one or more directives: mapSchema and +getDirectives. + +The mapSchema function allows one to create a new schema from an existing schema by defining +individual mapping functions that map each GraphQL entity as specified. + +The getDirectives function reaches from a given GraphQL entity to retrieve the directives with any +arguments defined in the original SDL for that entity. + +By passing user-defined mapping functions to mapSchema that make use of getDirectives function, one +can introspect a schema for one (or more!) directives in as simple or complex a fashion as desired. + + + graphql-tools.com/docs/schema-directives + + +## Create and Transform "Remote" GraphQL Schemas + +Many users use GraphQL-Tools primarily to wrap remote GraphQL schemas, transforming them as +necessary. In v6, multiple new transforms allow modification of output types on a per field level, +including even initial support for wrapping and hoisting of fields. + +In a performance boost, GraphQL Tools no longer requires two rounds of delegation to transform +remote schemas. Previously, a remote schema was created with makeRemoteExecutableSchema and then +transformSchema would wrap that schema with another layer of delegation, transforming as necessary. + +In v6, wrapSchema now takes a GraphQL Tools-defined “schema config“ object instead of a schema, +where the schema config properties allow specifying how to delegate to the remote schema as well as +the transforms to apply. These changes mean that transforming a remote schema only requires one +round of delegation instead of two. + +Another important v6 change is that remote schema configuration properties are now executor and +subscriber functions that return generic graphql-js results -- rather than Apollo specific links. +This increases interoperability between GraphQL Tools and the entire ecosystem, including paving the +way for any changes to the Apollo link protocol coming in the future. Don't worry, we provide +linkToExecutor and linkToSubscriber functions +[for full Apollo link backwards compatibility!](https://graphql-tools.com/docs/remote-schemas) + +## Stitch Together Multiple Subschemas + +We know [GraphQL federation](https://the-guild.dev/graphql/hive/federation) and other large scale +GraphQL backend approaches are already out there, but GraphQL Tools schema stitching method remains +-- to our knowledge -- the only way to combine and transform multiple remote schemas into a new and +**graphql-js community-wide compatible** executable schema. + +Schema stitching previously used up to at least three rounds of delegation when stitching (x1) to a +transformed (x2) remote schema (x3). Now you can specify remote schema and transform properties +directly when schema stitching, reducing that to a single delegation. + +Schema stitching also now supports type merging from multiple remote subschemas, à la Federation -- +without requiring the remote schemas to be annotated with the Federation directives. + + + graphql-tools.com/docs/schema-stitching + + +At the Guild, we use the schema stitching approach in our own tools. For example, you can see how we +apply schema stitching to the different remote schemas generated from different sources in +[GraphQL Mesh repository](https://graphql-mesh.com). + +## Thank You to the GraphQL Community! + +Thanks go to the Apollo team in opening up the graphql-tools repository, the steady work previously +done within graphql-tools-fork, and also to the wider community, who have contributed additional +functionality, tests, bug reports, and discussion, without which this release would not be possible. + +GraphQL Tools is an extremely popular library that is used by many other popular libraries. + +We've gone ahead and helped them with the upgrade, for example Gatsby. + +github.com/gatsbyjs/gatsby/pull/24158 + +At [The Guild](/), we are hopeful to continue engaging the community and pushing for further +improvements within the new GraphQL Tools and within the wider ecosystem. Let us know what you are +looking for in GraphQL Tools v7! diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-tools-v7/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-tools-v7/page.mdx new file mode 100644 index 00000000000..740a166919d --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-tools-v7/page.mdx @@ -0,0 +1,201 @@ +--- +title: GraphQL Tools v7 delivers the next generation GraphQL API Gateway +authors: arda +tags: [graphql, graphql-tools, schema-first, schema-stitching, graphql-federation] +date: 2020-11-03 +description: + GraphQL Tools v7 - Brings Schema Stitching to a whole new level thanks to automation and + performance enhancements. +--- + +Ever since [The Guild took over GraphQL Tools](/blog/graphql-tools-v6), we've been getting feedback +from companies using a variety of industry tools and have been working to transform Schema Stitching +into the best GraphQL gateway solution for their needs. Today we are happy to announce GraphQL Tools +v7, which brings Schema Stitching to a whole new level thanks to automation and performance +enhancements. + +> Try out Schema Stitching by following the instructions on the +> [new docs](https://graphql-tools.com/docs/stitch-combining-schemas), or follow the +> [migration guides](https://graphql-tools.com/docs/migration-from-tools) + +## Why Schema Stitching + +We believe Schema Stitching is the simplest and most extensible way to federate your GraphQL +services, building upon some new and existing features: + +- **[Type merging](https://graphql-tools.com/docs/stitch-type-merging)** allows partial definitions + of a type to exist in any subschema, all of which are merged into one unified type in the gateway + schema. When querying for a merged type, the gateway smartly delegates portions of a request to + each relevant subschema in dependency order, and then combines all results for the final return. + +In addition to just merging basic object types, schema stitching allows interfaces, unions, and +element descriptions to be merged across services. This merging paradigm encourages +[independently valid subschemas](https://graphql-tools.com/docs/stitch-type-merging#unidirectional-merges), +versus patterns such as [GraphQL federation](https://the-guild.dev/graphql/hive/federation) where +subservices are left with holes to be backfilled by the top-level gateway service. + +- **[Computed fields](https://graphql-tools.com/docs/stitch-type-merging#computed-fields)** allow + type merging to also implement the + [Federation service pattern](https://apollographql.com/docs/apollo-server/federation/federation-spec/#resolve-requests-for-entities) + for those seeking the best of both worlds. While basic merge patterns are simpler and more + durable, you may still find cases where it's useful to send representations of a type into a + service (federation pattern), rather than exposing a service's types to other services (merge + pattern). Users coming from Apollo Federation may also appreciate how stitching's decentralized + graph allows keys to be selected from anywhere versus just from a dedicated “origin” service. + +- **[Batch performance](https://graphql-tools.com/docs/stitch-type-merging#batching)** is now a core + offering of Schema Stitching thanks to built-in tools for field-level and query-level batching. + With the flip of a switch, query batching will consolidate all requests made to a service into a + single GraphQL query, thus flattening out both networking and querying costs to a single operation + per service per generation of data. + +- **Extensibility** remains a core concern of Schema Stitching, so classic schema extensions and + transforms remain fully interoperable with the new automated merging features. These features + support wrapping, hoisting, and bridging fields in any custom configuration that you may require. + This interoperability makes migrating to the newer merge features quite simple – just enable + merging, and then replace your classic extensions with merges one at a time. As always, the end + product of schema stitching is a gateway schema fully compatible with all the tools of the + JavaScript GraphQL ecosystem. + +- **Subscriptions Support** … + +- **Run anywhere** One interesting option when you use Schema Stitching, is the ability to run it + completely client-side. It might sound weird at first, but many times you can actually use it to + get started quickly, without the need of introducing a new gateway or server into your stack. + Checkout + [this talk](https://youtube.com/watch?v=1bJ_M_v-tso&list=PLhCf3AUOg4PgQoY_A6xWDQ70yaNtPYtZd&index=3) + discussing why and how. + +Above although – Schema Stitching lets you use **“Just GraphQL”**. No complex DSLs or Node-specific +subservice packages are required. This makes Schema Stitching easy to use atop GraphQL services +implemented in any language, and easy to explain while onboarding team members. Also, (mostly thanks +to our wonderful community) we have very detailed docs on all stitching features. Make sure to +[check them out](https://graphql-tools.com/docs/stitch-combining-schemas)! + +But don't take our word for it, Check out +**[this comprehensive blog post](https://product.voxmedia.com/2020/11/2/21494865/to-federate-or-stitch-a-graphql-gateway-revisited)** +from [Vox Media's](https://voxmedia.com) [product team](https://twitter.com/VoxProduct) about their +evaluation of Schema Stitching vs. Apollo Federation, their conclusions and their usage of Schema +Stitching in production and at scale. + +## Relationship with GraphQL Mesh + +The Guild is also maintaining the popular [GraphQL Mesh](https://the-guild.dev/graphql/mesh) +library, and often we get asked about the relationship of GraphQL Mesh with Schema Stitching and +Apollo Federation. + +The main goal of GraphQL Mesh is to connect **non-GraphQL** services (Swagger/OpenAPI, gRPC, +json-schema, SOAP and others) into your gateway, whether your gateway uses Schema Stitching or +Apollo Federation as your merging strategies. + +In GraphQL Mesh you can pick and choose between Schema Stitching and Apollo Federation. Recently, +with all the new improvements to Schema Stitching and the need to support GraphQL Subscriptions in +GraphQL Mesh, we have made Schema Stitching the default choice, with merging via Apollo Federation +still easily configurable. + +Currently, you are able to use both Schema Stitching and Apollo Federation merging strategies with +the declarative API of GraphQL Mesh without writing single line of code or adding anything in +SDL.The idea is to apply necessary transforms and metadata for the gateway by keeping the original +source of the services. + +Here is an example for Schema Stitching; + +```yaml +sources: # Here we have two independent sources + - name: Covid + handler: + graphql: + endpoint: https://covid-19.dava.engineer/api/graphql + - name: WorldPop + handler: + jsonSchema: + baseUrl: https://datasource.kapsarc.org/api/records/1.0/search + operations: + - type: Query + field: population + path: /?dataset=world-population&q={args.country}&rows=1&sort=year&facet=year&facet=country_name + method: GET + responseSchema: ./src/json-schemas/worldPop.json + +# In Schema Stitching, we define how to stitch those schemas on root level instead of source level +# as in federation + +# Also in schema stitching, we extend the unified schema instead of transforming the source schemas. +additionalTypeDefs: | + extend type Case { + population: ApiPopulation + } + extend type Query { + stat(country: String!): Stat + } + type Stat { + confirmedRatio: Float + deathRatio: Float + recoveredRatio: Float + case: Case + population: ApiPopulation + } +# In here, we define our stitching resolvers without using any single line of code +additionalResolvers: + - type: Case + field: population + requiredSelectionSet: | + { + countryRegion + } + targetSource: WorldPop + targetMethod: population + args: + country: '{root.countryRegion}' +``` + +Here is an example for Apollo Federation; + +```yaml +merger: federation # Apollo Federation is used to merge the following sources as federated services +sources: + - name: accounts + handler: + graphql: + endpoint: http://localhost:4001/graphql # This is a standalone GraphQL API that doesn't have any federation metadata + transforms: # Using federation transform, we can convert it to a federated service + - federation: + types: + - name: Query + config: + extend: true + - name: User + config: + keyFields: + - id + resolveReference: + targetSource: accounts + targetMethod: user + args: + id: root.id +``` + +## What's Next? + +We are working to continuously improve Schema Stitching and the rest of the GraphQL Tools, and we +want together as much feedback as possible in terms of community experiences, feature requests, bug +reports, and even new directions. + +That said, our next goal is to improve the overall experience working with multiple services and +updating the gateway accordingly. + +First, we are aiming for a new schema registry package. We plan on using +[GraphQL Inspector](https://the-guild.dev/graphql/inspector) to check for breaking changes on +individual services and the schema gateway as a whole. And thanks to GraphQL Mesh, that process will +work for any type of service schema, not just GraphQL! + +Second, we are hoping to simplify type merging configuration via a declarative API -- similar to the +work with computed fields -- without losing the power you get today with Schema Stitching's type +merging. + +Finally, naming - Naming is hard. While Schema Stitching is a great name, the above improvements +have been transformative, and it may be time for a new term to describe this powerful and +configurable method of creating a gateway schema. + +We are thinking about GraphQL Portal or just GraphQL Gateway to describe this new functionality. As +always, we are hoping for your feedback and ideas! diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-tools-v8/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-tools-v8/page.mdx new file mode 100644 index 00000000000..ce5b03a6389 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-tools-v8/page.mdx @@ -0,0 +1,181 @@ +--- +title: GraphQL Tools V8 - Stitch Federation Services +authors: arda +tags: [graphql, graphql-tools, schema-stitching, relay, graphql-federation] +date: 2021-07-28 +description: GraphQL Tools - Faster and more flexible Schema Stitching! +--- + +Ever since [The Guild has taken over GraphQL Tools](/blog/graphql-tools-v6), we kept our promise and +like every open source library we maintain, +[we keep improving and supporting](/blog/tag/graphql-tools) it on a daily basis. + +Many things are continuously happening and improving with the library, and now, as we are releasing +a new major version, we wanted to share some new things we've added. + +It's important for us to say thank you to all our users and open source contributors. The driving +force behind this tremendous amount of work is you! + +TL;DR + +- We significantly improved GraphQL Stitching Performance +- You can now manage [GraphQL federation](https://the-guild.dev/graphql/hive/federation) services + using Schema Stitching Gateway +- You can consume Relay services in the Gateway +- We support upcoming graphql-js features like `@defer` and `@stream` +- Many more improvements in the release notes + +## Faster Schema Stitching + +We heavily worked on improving the performance of Schema Stitching by refactoring some parts of the +query planner, schema delegation and more. So we got much better performance than before. Please +check the release notes to make sure you are good to start using v8 to benefit from all these +improvements! + +## More Flexible Schema Stitching + +The modern Schema Stitching is fairly comparable to Apollo Federation with automated query planning, +merged types, and declarative schema directives. + +But it is also more configurable. You can configure your gateway and services in service level using +Stitching Directives or in the gateway level using Type Merging configuration. + +### Manage Apollo Federation Services with Schema Stitching Gateway + +You can even consume your existing Apollo Federation services inside Schema Stitching without any +changes by using the `federationToStitchingSDL` utility function from +`@graphql-tools/stitching-directives`. Please check the Stitching Handbook to learn more. + +[Federation Services - Stitching Handbook](https://github.com/gmac/schema-stitching-handbook/tree/master/federation-services) + +### Relay-Based Gateway Possible? + +If you have multiple services using +[Relay Specification](https://relay.dev/docs/guides/graphql-server-specification/#schema), you can +easily combine them with the `handleRelaySubschemas` utility function from `@graphql-tools/stitch` +package, and your unified schema will handle `Node` interface and `node` operation automatically +using Type Merging. + +[You can check the unit tests to see the complete usage](https://github.com/ardatan/graphql-tools/blob/master/packages/stitch/tests/relay.spec.ts) + +It is pretty new, and we will improve the documentation for this use case gradually. + +### What Is Type Merging? + +Type merging allows partial definitions of a type to exist in any subschema, all of which are merged +into one unified type in the gateway schema. When querying for a merged type, the gateway smartly +delegates portions of a request to each relevant subschema in dependency order, and then combines +all results for the final return. + +Type merging is now the preferred method of including GraphQL types across subschemas, replacing the +need for schema extensions (though does not preclude their use). To migrate from schema extensions, +simply enable type merging and then start replacing extensions one by one with merges. + +![Type Merging Flow](./type-merging-flow.png) + +Check out our documentation and stitching handbook to learn more about Type Merging! + +- [Type Merging Docs](https://graphql-tools.com/docs/stitch-type-merging) +- [Stitching Handbook](https://github.com/gmac/schema-stitching-handbook) + +Also, please watch this great presentation from Greg MacWilliam! + +import { YoutubeIframe } from '#components/youtube-iframe' + + + +### Future-Proof Schema Delegation + +GraphQL Tools v8 is getting prepared for incoming GraphQL-js features. With defer and stream, +GraphQL execution will return Async Iterables even for `query` and `mutation` operations like +`subscription` ones. So we decided to remove `Subscriber` because we will need to handle Async +Iterables in `Executor` eventually. + +You can easily merge your existing `Executor` and `Subscriber` functions by checking the +`operationType` of `ExecutionParams`; + +```ts +function myExecutor({ document, variables, operationType }) { + if (operationType === 'subscription') { + return callWSClient(...); + } + return callHTTPClient(...); +} +``` + +### Create `GraphQLSchema` Instances for Your Remote APIs + +URL Loader from `@graphql-tools/url-loader` package creates executable GraphQLSchema instances for +you to call your remote GraphQL APIs by using different protocols; + +- Server Sent Events ✔️ +- New [GraphQL-WS](https://github.com/enisdenjo/graphql-ws) ✔️ +- Legacy subscriptions-transport-ws ✔️ +- [Multipart File Uploads](https://github.com/jaydenseric/graphql-multipart-request-spec) ✔️ + +```ts +import { loadSchema } from '@graphql-tools/load' +import { UrlLoader } from '@graphql-tools/url-loader' + +const schema = await loadSchema(`https://my-graphql-api.com`, { + loaders: [new UrlLoader()], + // Enable File Uploads + multipart: true, + // Choose your Subscription protocol + subscriptionProtocol: SubscriptionProtocol.SSE +}) +``` + +#### Create Executors for Schema Stitching / Delegation + +You can easily create executors for subschemas like below: + +```ts +import { stitchSchemas } from '@graphql-tools/stitch' +import { UrlLoader } from '@graphql-tools/url-loader' +import { introspectSchema } from '@graphql-tools/wrap' + +const urlLoader = new UrlLoader() +const executor = urlLoader.getExecutorAsync(`https://my-graphql-api.com`, {}) + +const mySubschema = { + schema: await introspectSchema(executor), + executor +} + +const stitchedSchema = await stitchSchemas([mySubschema, myOtherSubschema]) +``` + +## And More... + +- Git Loader now supports glob patterns + - GraphQL Tools is used by GraphQL Config, GraphQL Code Generator, GraphQL Inspector and more to + download the type definitions and operation documents from the different sources with GraphQL + Tools Loaders. + - For example to compare your schema against the master, you need to dump your schema into a + single “schema.graphql” file and point the GraphQL Inspector to that file. But now you can use + glob patterns to point multiple files on your Git repo, and you don't need to have a generated + `schema.graphql` in the codebase. +- No more a huge `graphql-tools` package; + - GraphQL Tools has a lot of packages for different use cases and previously we were publishing + `graphql-tools` package to the npm that includes all scoped packages we have in the repo. Let's + say when someone only needs `makeExecutableSchema`, NPM installs every single package with a lot + of unused dependencies together with those. From now on, we decided to deprecate `graphql-tools` + and encourage the users to migrate to the scoped packages. For example, you should install + `@graphql-tools/schema` for `makeExecutableSchema`. You can check API Reference and the rest of + the documentation to find what packages you need. +- We have removed some functions, methods and stuff that are not widely used by the community; + - `makeRemoteExecutableSchema` + - In the new GraphQL Tools, you don't need this at all. You can write your own executor by + following the documentation and use wrapSchema instead. Or you can use UrlLoader directly + without the need of writing all the remote execution logic. + - This was already being deprecated after v4 in favor of `wrapSchema`. + [You can find the new usage in the docs.](https://graphql-tools.com/docs/stitch-combining-schemas#stitching-remote-schemas) + - Legacy Schema Directives, `visitSchema` and `directiveResolvers` + - We suggest you to + [check the new approach for schema directives and schema visiting in the documentation instead of these old approaches.](https://graphql-tools.com/docs/schema-directives#full-mapschema-api) + - And there are some other utility functions you might have been using from GraphQL Tools. You can + check CHANGELOG to see rest of them. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-tools-v8/type-merging-flow.png b/packages/web/docs/src/app/blog/(posts)/graphql-tools-v8/type-merging-flow.png new file mode 100644 index 00000000000..5375174b3b8 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-tools-v8/type-merging-flow.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-typescript-modules-codegen/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-typescript-modules-codegen/page.mdx new file mode 100644 index 00000000000..1e29369ea60 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-typescript-modules-codegen/page.mdx @@ -0,0 +1,100 @@ +--- +title: GraphQL TypeScript - w/ GraphQL-Modules and GraphQL-Code-Generator +authors: arda +tags: [graphql, graphql-modules, codegen, typescript] +date: 2018-12-04 +description: Writing a GraphQL TypeScript project w/ GraphQL-Modules and GraphQL-Code-Generator. +--- + +Last year we've released [GraphQL-Modules](https://graphql-modules.com) — which applies modular +approach to large scale, GraphQL-based applications. + +We've also released a new version of [GraphQL Code Generator](https://graphql-code-generator.com) +which generates server-side typings and signatures for resolvers from GraphQL Schemas. + +Those are two separate projects, but because we use both in production across many different +applications, we wanted to write about the benefits of the tight integration of those projects and +how it provides us with great development experience while implementing our projects in TypeScript. + +GraphQL Code Generator can take a JS/TS export from GraphQL-Modules to generate server-side typings +(or anything else that its [plugins can generate](https://graphql-code-generator.com/plugins)). So, +let's export typeDefs without any business logic that includes resolvers, DI, providers etc… + +You can create a `schema.ts` file to expose `typeDefs` to GraphQL Code Generator: + +```ts filename="schema.ts" +import { AppModule } from './modules/app' + +export default AppModule.typeDefs + +// If your module needs a configuration + +export default AppModule.forRoot({ ...dummyConfiguration }).typeDefs +``` + +Create a `codegen.yml` config file, including `schema.ts` as a schema source, and generate +[common](https://graphql-code-generator.com/docs/plugins/typescript-common) and +[server](https://graphql-code-generator.com/docs/plugins/typescript-server) typings for TypeScript. +We use `transpile-only` to prevent errors related to TypeScript on typing generation phase to let +`ts-node` handle this on running the actual application. + +```yaml filename="codegen.yml" +overwrite: true +schema: ./src/schema.ts +require: + - ts-node/register/transpile-only +generates: + ./src/generated-models.ts: + plugins: + - typescript + - typescript-resolvers + config: + contextType: @graphql-modules/core#ModuleContext +``` + +We can add a script to `package.json` to run: + +```jsonc +{ + //... + "scripts": { + //... + "generate": "gql-gen", + // You can use nodemon to watch changes on graphql files + "generate:watch": "nodemon --exec yarn generate -e graphql", + //... + //... +} +``` + +Then, we can use these typings in our project. This example shows a resolvers handler that +implements resolvers for `Query` type with generated types. + +```ts +import { IResolvers } from '../../generated-models' +import { UsersProvider } from '../providers/users.provider' + +export default { + Query: { + // all parameters and return value are typed + users: (root, args, context, info) => context.injector.get(UsersProvider).getUsers(args) + } +} +``` + +At the end, we have a strictly-typed backend project, based of separate feature modules, and each of +those modules uses GraphQL and TypeScript. + +You can check our working example that is created by using this approach; +[https://github.com/darkbasic/graphql-modules-seed](https://github.com/darkbasic/graphql-modules-seed) + +# All Posts about GraphQL Modules + +- [GraphQL Modules — Feature based GraphQL Modules at scale](/blog/graphql-modules) +- [Why is True Modular Encapsulation So Important in Large-Scale GraphQL Projects?](/blog/modular-encapsulation-graphql-modules) +- [Why did we implement our own Dependency Injection library for GraphQL-Modules?](/blog/dependency-injection-library-in-graphql-modules) +- [Scoped Providers in GraphQL-Modules Dependency Injection](/blog/graphql-modules-scoped-providers) +- [Writing a GraphQL TypeScript project w/ GraphQL-Modules and GraphQL-Code-Generator](/blog/graphql-typescript-modules-codegen) +- [Authentication and Authorization in GraphQL (and how GraphQL-Modules can help)](/blog/graphql-modules-auth) +- [Authentication with AccountsJS & GraphQL Modules](/blog/accountsjs-graphql-modules) +- [Manage Circular Imports Hell with GraphQL-Modules](/blog/manage-circular-imports-hell-in-graphql-modules) diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-with-typescript-done-right/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-with-typescript-done-right/page.mdx new file mode 100644 index 00000000000..0a08fef38d1 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-with-typescript-done-right/page.mdx @@ -0,0 +1,499 @@ +--- +title: GraphQL with TypeScript done right +authors: charlypoly +tags: [graphql, typescript, codegen, node, react] +date: 2021-04-29 +description: How to get the most of React application types with GraphQL Code Generator. +canonical: https://charlypoly.com/publications/typescript-with-graphql-done-right +--- + +[Generics](https://charlypoly.com/publications/typescript-generics-and-overloads), and Mapped types, +are key to build types on top of existing ones by making them configurable (Generics) or iterables +(Mapped types). + +Advanced types give to your code and open-source libraries the power of providing an API that +manipulates data (your application objects) without breaking the “types chain”. + +> The best TypeScript configuration ensures that the “types chain” is continuous + +## The TypeScript "Types Chain" + +TypeScript helps with typing data and following how the data is used and transformed by subsequent +functions or method calls. + +The example below shows how easily this “types chain” can be broken: + +```ts +const a = '1' // a is a string + +const stringToInt = (num: string): any => parseInt(num, 10) + +const b = stringToInt('5') // b is of type any +``` + +_How to break the TypeScript “types chain” +([playground demo](https://typescriptlang.org/play?ssl=8&ssc=1&pln=1&pc=1#code/MYewdgzgLgBAhjAvDA5ARhTA9F+MCWEe0ATvmAOYBQNoksp5FAKiAJJizIAUYArgFsAXDEaUAlCLhgAnkgB8MAA5wSEAKYcovQQBoYaAAziaVOtBgAjJKKhlKrLdxQBWFOOy5rhGCABmMFAySurwslRAA))_ + +Since React 16.8 brought ubiquitous functional components, a React application can be seen as a mix +of functional components dealing with state and data in order to provide UI to the users. + +Like with plain JavaScript functions, the same rules of the “types chain” apply to your React +application that will look to something similar to the following: + +![./typescript-with-graphql-done-right-1.png](./typescript-with-graphql-done-right-1.png) + +_Most modern React applications have the following data setup: centralized data store passed down to +components through contexts, transformed by custom hooks down to UI components._ + +Since React applications are built on top of data, we can conclude that: + +> The strength of your React application's types is based on the stability of your data types + +### The Flawed "Handwritten" Data Types + +Most React projects type remote data (from APIs) manually, either at the component level with +interfaces or in a global dedicated `.d.ts` file. + +```ts +interface User { + id: string + email: string +} + +interface Chat { + id: string + user: User + messages: Message[] +} + +//… + +const userQuery = gql` + query currentUser { + me { + id + email + } + } +` + +const Login = () => { + const { data } = useQuery(userQuery) + const user = data ? (data.me as User) : null + // ... +} +``` + +_Example of data-types definition and linked usage, common in many projects_ + +Manually writing and maintaining those type can lead to human errors: + +- **outdated typing** (regarding the API current implementation) +- **typos** +- **partial typing** of data (not all API's data has a corresponding type) + +As we saw earlier, the strength of your React TypeScript types is based on your data types, +therefore, any mistake on your manually maintained data types will ripple in many of your React +components. + +![./typescript-with-graphql-done-right-2.png](./typescript-with-graphql-done-right-2.png) + +_In our hypothetical application, the User type has some typos that will impact the stability of the +associated components at runtime, defecting the benefits of TypeScript._ + +Fortunately, thanks to the GraphQL introspection feature, many tools arose to solve this problem by +providing data-types - and even more - generation tools. + +## Robust React Application's Types with GraphQL + +[GraphQL Code Generator](https://graphql-code-generator.com), given the mutations and queries used +by the app and the access to the target GraphQL API, generates the corresponding TypeScript types. + +![./typescript-with-graphql-done-right/typescript-with-graphql-done-right-3.png](./typescript-with-graphql-done-right-3.png) + +GraphQL Code Generator is doing all the heavy lifting by getting from the API the definitions of the +data types used by the React applications queries and mutations. + +Let's see an example with our hypothetical application Login component relying on the User type. + +### Stronger Generated TypeScript Types + +First, let's create a `queries.graphql` file in a `src/graphql` folder: + +```graphql filename="queries.graphql" +query currentUser { + me { + id + email + } +} +``` + +then, the following GraphQL Code Generator configuration at the root of our project: + +```yaml +schema: http://localhost:3000/graphql +documents: ./src/graphql/*.graphql +generates: + graphql/generated.ts: + plugins: + - typescript-operations + - typescript-react-apollo + config: + withHooks: false +``` + +_codegen.yml_ + +And after running `graphql-codegen` CLI, we can refactor our `` component: + +```ts +import { currentUserDocument, CurrentUserQueryResult } from '../graphql/generated.ts' + +// no need to create the User type or `gql` query, we import them from the generated file +const Login = () => { + const { data } = useQuery(currentUserDocument) + // user is typed! + const user = data ? data.me : null + + // ... +} +``` + +_src/components/Login.tsx_ + +The configuration and refactoring were straightforward, directly impacting our data types, which are +now directly linked to the GraphQL API Schema, making our React application more stable! + +Contrary to the manually maintained data types, using the GraphQL Code Generator puts the data-types +maintenance on the GraphQL API side. + +Maintaining data types on the front-end side only consist of running the GraphQL Code Generator tool +to update types according to the last GraphQL API version. + +Let's now see some more advanced configurations that bring more stability. + +### Getting the Most of Your GraphQL Code Generator Configuration + +When used with React Apollo Client, GraphQL Code Generator offers three main configuration modes: + +**Generate TypeScript types definitions** + +This is the configuration that we used in our previous example: + +```yaml +schema: http://localhost:3000/graphql +documents: ./src/graphql/*.graphql +generates: + graphql/generated.ts: + plugins: + - typescript-operations + - typescript-react-apollo +config: + withHooks: false +``` + +_codegen.yml_ + +This configuration will generate a `src/graphql/generated.ts` file that will contain: + +- GraphQL document nodes +- TypeScript Query/Mutation Result types (return type of our GraphQL operations) +- TypeScript Query/Mutation Variables types (variables types of our GraphQL operations) + +Here an example of GraphQL Code Generator output given our previous `currentUser` Query: + +```ts +import { gql } from '@apollo/client' +import * as Apollo from '@apollo/client' + +export type CurrentUserQueryVariables = Exact<{ [key: string]: never }> +export type CurrentUserQuery = { __typename?: 'Query' } & { + me: { __typename?: 'User' } & Pick +} + +export const CurrentUserDocument = gql` + query currentUser { + me { + id + } + } +` + +export type CurrentUserQueryResult = Apollo.QueryResult +``` + +_src/graphql/generated.ts_ + +We already saw the benefits of these generated types on the `` component refactoring. + +However, we can agree that having to provide both the query TypeScript type +(`CurrentUserQueryResult`) and the query GraphQL document node (`currentUserDocument`) to +`useQuery()` is cumbersome: `useQuery(currentUserDocument)` + +Let's see how we can improve that in the next configuration mode. + +**Generate Typed React Hooks** + +GraphQL Code Generator is capable of more than just generating TypeScript types, it can also +generate JavaScript/TypeScript code. + +Let's see how we can ask GraphQL Code Generator to generate Typed React hooks, so we don't have to +provide the TypeScript types to `useQuery()` every time. + +Let's use the following configuration: + +```yaml +schema: http://localhost:3000/graphql +documents: ./src/graphql/*.graphql +generates: + graphql/generated.ts: + plugins: + - typescript-operations + - typescript-react-apollo +``` + +_codegen.yml_ + +This configuration will generate a `src/graphql/generated.ts` file that will contain: + +- GraphQL document node +- TypeScript Query/Mutation Result types (return type of our GraphQL operations) +- TypeScript Query/Mutation Variables types (variables types of our GraphQL operations) +- One custom hook for each defined GraphQL operation + +Example given our previous `currentUser` Query: + +```ts +import { gql } from '@apollo/client' +import * as Apollo from '@apollo/client' + +const defaultOptions = {} +export type CurrentUserQueryVariables = Exact<{ [key: string]: never }> +export type CurrentUserQuery = { __typename?: 'Query' } & { + me: { __typename?: 'User' } & Pick +} + +export const CurrentUserDocument = gql` + query currentUser { + me { + id + } + } +` + +export function useCurrentUserQuery( + baseOptions?: Apollo.QueryHookOptions +) { + const options = { ...defaultOptions, ...baseOptions } + return Apollo.useQuery(CurrentUserDocument, options) +} +export type CurrentUserQueryHookResult = ReturnType +export type CurrentUserQueryResult = Apollo.QueryResult +``` + +_src/graphql/generated.ts_ + +Which will give us this updated version of our `` component: + +```ts +import { useCurrentUserQuery } from '../graphql/generated.ts' + +// no need to create the User type or `gql` query, we import them from the generated file + +const Login = () => { + const { data } = useCurrentUserQuery() + // user is typed! + const user = data ? data.me : null + + // ... +} +``` + +_src/components/Login.tsx_ + +Nice! Isn't it? + +**Generate Typed Documents** + +GraphQL Code Generator is providing another simple way to use typed GraphQL Query and Mutations, +called [`TypedDocumentNode`](https://graphql-code-generator.com/docs/plugins/typed-document-node). + +With the following configuration: + +```yaml +schema: http://localhost:3000/graphql +documents: ./src/graphql/*.graphql +generates: + graphql/generated.ts: + plugins: + - typescript-operations + - typed-document-node +``` + +_codegen.yml_ + +GraphQL Code Generator will generate the following file: + +```ts +import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core' + +export type CurrentUserQueryVariables = Exact<{ [key: string]: never }> +export type CurrentUserQuery = { __typename?: 'Query' } & { + me: { __typename?: 'User' } & Pick +} + +export const CurrentUserDocument: DocumentNode = { + kind: 'Document', + definitions: [ + { + kind: 'OperationDefinition', + operation: 'query', + name: { kind: 'Name', value: 'currentUser' }, + selectionSet: { + kind: 'SelectionSet', + selections: [ + { + kind: 'Field', + name: { kind: 'Name', value: 'me' }, + selectionSet: { + kind: 'SelectionSet', + selections: [{ kind: 'Field', name: { kind: 'Name', value: 'id' } }] + } + } + ] + } + } + ] +} +``` + +_src/graphql/generated.ts_ + +This allows us the following refactoring of our `` component: + +```ts +import { CurrentUserDocument } from '../graphql/generated.ts' + +// no need to create the User type or `gql` query, we import them from the generated file + +const Login = () => { + const { data } = useQuery(CurrentUserDocument) + // user is typed! + const user = data ? data.me : null + + // ... +} +``` + +_src/components/Login.tsx_ + +In my experience, it is more scalable to go for the +[`TypedDocumentNode`](https://graphql-code-generator.com/docs/plugins/typed-document-node) approach +instead of the hooks generation. + +The generation of one custom hook per GraphQL operation (`Query`/`Mutation`) can generate a LOT of +hooks at scale along with a lot of imports, which is not necessary given the `useMutation()` +`useQuery` provided by Apollo Client. + +### Tips: Leverage GraphQL Fragments for Scalable Types + +Now that we have many ways to generate **stable **data types, let's see how to make them easier to +use and maintain in time. + +Let's take a look at the following helper: + +```ts +import { CurrentUserQuery } from "src/graphql/generated"; + +const isUserEmailValid = (user: CurrentUserQuery["me']) => !!user.email +``` + +Here, instead of using our `currentUser` query `CurrentUserQuery[“me”]` type, we would prefer to +rely on a `User` type. + +We can achieve this with zero maintainability by leveraging GraphQL Fragments. + +When Fragments are provided, GQL Code Generator will produce the corresponding TypeScript types. + +Here is our updated `src/graphql/queries.graphql`: + +```graphql +query currentUser { + me { + ...User + } +} +``` + +The `...User` indicates to GraphQL that we want to expand our `User` fragment here, similar to the +[object spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax). + +In order to do so, we need to provide to GraphQL Code Generator the definition of the `User` +fragment that we will place in a new `src/graphql/fragments.graphql` file: + +``` +fragment User on users { + id + email +} +``` + +_src/graphql/fragments.graphql_ + +Please note that a fragment needs to be defined against an existing type of the GraphQL API Schema, +here `users`. + +Here is our updated helper code: + +```ts +import { UserFragment } from 'src/graphql/generated' + +const isUserEmailValid = (user: UserFragment) => !!user.email +``` + +Leveraging GraphQL Fragments allows you to build your React app data types on top of the GraphQL API +types. + +Please note that multiple fragments can be defined on a single GraphQL Schema type: + +```graphql +fragment User on users { + id + email +} +fragment UserProfile on users { + id + email + firstName + lastName +} +``` + +_src/graphql/fragments.graphql_ + +A good practice is to ensure that all your Query and Mutations responses use fragment, this will +ensure that your React application can benefit from well-defined data types of different +specificity, ex: + +- `User` type carries the necessary base properties +- `UserProfile` type carries the minimum user info for display +- `UserExtended` type carries all the `users` properties + +## Conclusion + +The TypeScript type system is powerful and valuable only if used properly. + +In React applications, most of the components rely on data, doing your data typing at the center of +your application stability. + +Thanks to GraphQL Code Generator and with a fast setup, you will be able to ensure the stability of +your React application data types, along with your application's global stability. + +If you decide to use GraphQL Code Generator, make sure to: + +- move all your `gql` definitions in dedicated `.graphql` files +- Favor the + [`TypedDocumentNode`](https://graphql-code-generator.com/docs/plugins/typed-document-node) + configuration mode +- Make sure that all your Queries and Mutations relies on well-defined GraphQL Fragments diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-with-typescript-done-right/typescript-with-graphql-done-right-1.png b/packages/web/docs/src/app/blog/(posts)/graphql-with-typescript-done-right/typescript-with-graphql-done-right-1.png new file mode 100644 index 00000000000..08f69141e79 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-with-typescript-done-right/typescript-with-graphql-done-right-1.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-with-typescript-done-right/typescript-with-graphql-done-right-2.png b/packages/web/docs/src/app/blog/(posts)/graphql-with-typescript-done-right/typescript-with-graphql-done-right-2.png new file mode 100644 index 00000000000..531dcbc98e5 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-with-typescript-done-right/typescript-with-graphql-done-right-2.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-with-typescript-done-right/typescript-with-graphql-done-right-3.png b/packages/web/docs/src/app/blog/(posts)/graphql-with-typescript-done-right/typescript-with-graphql-done-right-3.png new file mode 100644 index 00000000000..40a785ffd74 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-with-typescript-done-right/typescript-with-graphql-done-right-3.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-yoga-nestjs-v9/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-yoga-nestjs-v9/page.mdx new file mode 100644 index 00000000000..274b418b6fc --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-yoga-nestjs-v9/page.mdx @@ -0,0 +1,41 @@ +--- +title: GraphQL Yoga NestJS integration now supports NestJS v9 and Yoga v3 +tags: [graphql, nodejs, typescript, yoga, nestjs] +authors: [enisdenjo] +date: 2022-12-06 +description: A new version of @graphql-yoga/nestjs with NestJS v9 support is out! +--- + +The latest version +[v0.2.0 of `@graphql-yoga/nestjs`](https://github.com/charlypoly/graphql-yoga-nestjs/releases/tag/v0.2.0), +the NestJS GraphQL Yoga driver, added support for NestJS v9. + +That also includes updating GraphQL Yoga to the latest +[v3 release with all its new features](/blog/graphql-yoga-v3)! + +## NestJS V9 + +Just recently, NestJS, a framework for building efficient, scalable Node.js server-side +applications, has released a new version v9. Fixes and improvements are encompasing the complete +framework, be sure to do an update! + +Read more about the new release on the +["NestJS v9 is now available!" announcement](https://trilon.io/blog/nestjs-9-is-now-available). + +### NestJS V8 Still Supported + +Even with the added v9 support, v8 will still be supported. You do not have to make any adjustments +to your code. + +### Migrating from NestJS V8 + +We recommend you consult the great +[Migration guide from NestJS](https://docs.nestjs.com/migration-guide) for migrating NestJS itself +and simply doing an update to +[v0.2.0 of `@graphql-yoga/nestjs`](https://github.com/charlypoly/graphql-yoga-nestjs/releases/tag/v0.2.0). + +We love supporting our NestJS users and the NestJS community as a whole. We see more and more NestJS +developers migrating to Yoga, the best and most feature rich GraphQL server out there. + +Please try it out, share with us your feedback and let us know how we can make NestJS developers +lives better! diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-yoga-v3/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-yoga-v3/page.mdx new file mode 100644 index 00000000000..be0b1d40d98 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-yoga-v3/page.mdx @@ -0,0 +1,179 @@ +--- +title: Announcing GraphQL Yoga v3 +tags: [graphql, nodejs, typescript, yoga, graphql-federation] +authors: [saihaj, arda, enisdenjo, laurin] +date: 2022-11-15 +description: The new version of GraphQL Yoga is out! Learn what has improved and what is new! +--- + +After months of experimentation and learning from production use cases we are back again with a +ground breaking release for GraphQL Yoga. + +## The Yoga V3 Experience + +The goal with Yoga is to empower developers, improve the developer facing API and make caching, +security and real-time easy and run anywhere. Let's highlight some of the groundbreaking new +features and improvements. + +### One Package, Runs Everywhere + +From GraphQL Yoga v2 we figure that using the `fetch` API as standard is the way to go. Now GraphQL +Yoga has a single NPM package for all environments `graphql-yoga` instead of `@graphql-yoga/common` +and `@graphql-yoga/node`. We +[created a library that lets you create cross-platform HTTP servers](/blog/fetch-for-servers) which +GraphQL Yoga v3 uses under the hood. + +[Learn more](https://the-guild.dev/graphql/yoga-server/docs) + +### The Makeover + +GraphQL Yoga v3 comes with the brand new GraphiQL 2.0. + +The development of GraphiQL 2.0 has been a long ongoing process and we are so happy to finally +replace the outdated and old-fashioned GraphiQL IDE with the new fancy and snappy version 2. + +Thanks to all parties involved that made this happen. + +![GraphiQL](https://raw.githubusercontent.com/graphql/graphiql/main/packages/graphiql/resources/graphiql.png) + +[Learn more](https://the-guild.dev/graphql/yoga-server/docs/features/graphiql) + +### 100% Compliant + +As of today, GraphQL Yoga is the only GraphQL server in the JavaScript ecosystem that fully +satisfies all mandatory and optional sections of the GraphQL over HTTP specification. + +| Name | Mandatory ✅ | Optional ⚠️ | Errors ❌ | +| ------------------------ | ------------ | ----------- | --------- | +| `graphql-yoga` | 72 | 0 | 0 | +| `apollo-server@4.1.1` | 36 | 37 | 0 | +| `mercurius@11.3.0` | 43 | 30 | 0 | +| `express-graphql@0.12.0` | 38 | 35 | 0 | +| `graphql-helix@1.13.0` | 39 | 32 | 2 | + +[Learn more](https://github.com/graphql/graphql-http/tree/master/implementations/graphql-yoga) + +### Yoga as a Gateway + +The ease of using GraphQL Yoga anywhere and performance make it a great candidate to run as a +gateway. Yoga is compliant with Apollo Federation spec, so you can use it for +[federated services](/federation) as your Supergraph and/or as your Subgraph. + +[Learn more](https://the-guild.dev/graphql/yoga-server/docs/features/apollo-federation) + +### Defer and Stream Support + +In +[Yoga v2 we added support for `@defer` and `@stream`](/blog/announcing-graphql-yoga-v2#graphql-features-from-the-future) +but it was not simple for users to use it, they had to deal with multiple versions of GraphQL.js +which is not easy thing to do. + +For GraphQL Yoga v3 we created a custom execution engine (more detailed information and insights on +that soon!) and you can simply enable powerful features like defer and stream by using a Yoga +plugin. + +```ts +import { createServer } from 'node:http' +import { createYoga } from 'graphql-yoga' +import { useDeferStream } from '@graphql-yoga/plugin-defer-stream' +import { schema } from './schema.js' + +const yoga = createYoga({ + schema, + plugins: [useDeferStream()] +}) + +const server = createServer(yoga) + +server.listen(4000, () => { + console.info('Server is running on http://localhost:4000/graphql') +}) +``` + +[Learn more](https://the-guild.dev/graphql/yoga-server/docs/features/defer-stream) + +### Not Only GraphQL but Also REST + +There are many reasons why one would want a REST endpoint. The Sofa API plugin makes it easy to +convert any GraphQL API to REST API and in Yoga v3. + +```ts +import { createServer } from 'node:http' +import { createYoga } from 'graphql-yoga' +import { useSofaWithSwaggerUI } from '@graphql-yoga/plugin-sofa' +import { schema } from './schema.js' + +export const yoga = createYoga({ + schema, + plugins: [ + useSofaWithSwaggerUI({ + basePath: '/rest', + swaggerUIEndpoint: '/swagger', + servers: [ + { + url: '/', // Specify Server's URL. + description: 'Development server' + } + ], + info: { + title: 'Example API', + version: '1.0.0' + } + }) + ] +}) + +const server = createServer(yoga) +server.listen(4000, () => { + console.info('Server is running on http://localhost:4000/graphql') +}) +``` + +[Learn more](https://the-guild.dev/graphql/yoga-server/docs/features/sofa-api) + +### Response Caching + +We now have a dedicated response caching plugin that can help reducing server load by caching +GraphQL Query operation execution results. + +[Learn more](https://the-guild.dev/graphql/yoga-server/docs/features/response-caching) + +### GraphQL Subscriptions + +GraphQL Yoga supports GraphQL subscriptions over Server Sent Events (SSE). Executing an operation is +a s simple as sending an HTTP request and does not require any additional complex protocol or +libraries on the frontend. For convenience, we offer an optional client package for both Apollo +Client and Urql for easily connecting to the server. + +[Learn more](https://the-guild.dev/graphql/yoga-server/docs/features/subscriptions) + +### Request Batching + +GraphQL Yoga v3 supports request batching out of the box. While we do not recommend using request +batching for new projects, we know that there are many existing projects that use it, and we want to +make it easy for them to migrate to Yoga. + +[Learn more](https://the-guild.dev/graphql/yoga-server/docs/features/request-batching) + +### File Uploads + +Sometimes it is handy to directly upload and process a file on your GraphQL server. Yoga does not +stop you from doing so and even better does not require any complicated setup. + +[Learn more](https://the-guild.dev/graphql/yoga-server/docs/features/file-uploads) + +### What Is Next? + +We want **you to leverage all the GraphQL ecosystem** by being compatible with most of the existing +schema-design, HTTP server libraries, and deployment environments. There are many more features so +don't forget to check those out +[https://the-guild.dev/graphql/yoga-server/docs](https://the-guild.dev/graphql/yoga-server/docs) + +We have +[migration guide](https://the-guild.dev/graphql/yoga-server/v3/migration/migration-from-yoga-v2) try +it out! We can't wait answer your +[questions](https://github.com/dotansimha/graphql-yoga/discussions) and get your feedback on how we +can make GraphQL Yoga even more better! + +Don't hesitate to reach out to us on Twitter and support us by +[sharing this article!](https://twitter.com/intent/tweet?text=Check%20out%20GraphQL%20Yoga%20v3%20by%20%40TheGuildDev%20-%20http%3A//the-guild.dev/blog/graphql-yoga-v3%0A) diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/cover.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/cover.jpg new file mode 100644 index 00000000000..cca01c22e35 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/cover.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/execute-sample-query.png b/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/execute-sample-query.png new file mode 100644 index 00000000000..b8f338eb7db Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/execute-sample-query.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/execute-user-query.png b/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/execute-user-query.png new file mode 100644 index 00000000000..7a4020c1ef5 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/execute-user-query.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/page.mdx new file mode 100644 index 00000000000..a41718c0644 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/page.mdx @@ -0,0 +1,410 @@ +--- +title: Build a GraphQL server running on Cloudflare Workers. +authors: chimame +tags: [graphql, nodejs, typescript, yoga] +date: 2023-05-31 +description: + This course aims to build a practical GraphQL server on Cloudflare Workers using GraphQL Yoga, + Pothos, Kysely, etc. +--- + +## Summary + +- Building a GraphQL servers with Cloudflare Workers is now at a "production ready" stage +- Database connection over TCP is also works well (although it's beta, so there might be some eage + cases) +- Node.js compatibility with Cloudflare Workers is not perfect, so it is better to use a Web Server + Framework that was built to support CF Workers + +## Motivation + +Cloudflare Workers is often used as a proxy on a CDN or as a backend for rendering Remix or Next.js. + +When it comes to APIs for retrieving and updating data from the front end, most of the time I think +it is necessary to set up and build a separate backend server. I myself often set up a backend +server in Node.js in this pattern, and when I do so, I choose Cloud Run as my first choice because +it is easy to set up. Cloud Run itself is a great service, but it is human beings who become even +more greedy. + +- Deployment speed needs to be faster. +- I want to keep the price a little lower. + +The former is a container service, so it requires "container build" and then "container +replacement", but "container replacement" as well as "container build" takes a lot of time there. No +matter how fast you do these two, it will take 5 to 10 minutes. The latter means minimizing cold +standby (startup speed) and asynchronous means using the minimum number of containers, Always CPU, +etc. That will cost a certain amount of money. I still think Cloud Run is cheaper, though. + +#### A Note about Database Connections + +I believe that Cloudflare Workers can fulfill these requests, but when its necessary to set up an +API server, a database connection is also required. if you are using a database that can communicate +via PostgREST API such as [Supabase](https://supabase.com/), Cloudflare Workers can connect to the +database via the PostgREST API, but making a direct connection to the database has not been possible +until now. However, this situation has changed with the recent announcement of support for +connections over TCP. + +https://blog.cloudflare.com/workers-tcp-socket-api-connect-databases + +I think that just because it is possible to connect via TCP, the advantage of the edge location is +lost a bit, because when an application that runs on the edge, connects to a database in another +location to retrieve data, there is an overhead for that connection. So I'm waiting with +anticipation for the [Cloudflare D1](https://developers.cloudflare.com/d1) to come out in the +future, as it will maximize the advantages of that location. + +Another point is that running your GraphQL server on edge means you can also store its cache on +edge, which would means faster response times. + +Cloudflare Workers is as great a service as Cloud Run, so the purpose of this article is to actually +run it as a backend API server on top of Cloudflare Workers. + +## Architectures + +Let's proceed under the assumption that the backend API will be prepared as a GraphQL server, and I +will write down what tools I used to build it. + +### Cloudflare Workers + +https://workers.cloudflare.com + +The first assumption is this. The choice of Cloudflare Workers as a service or runtime narrows the +technologies that can be used in an application. Therefore, it is written as an assumption. + +### Web Server + +https://the-guild.dev/graphql/yoga-server + +Many existing/older Web Servers have limited support for Cloudflare Workers. That's because they +were built at a time to only support Node.js. + +Newer Web Server frameworks like [feTS](https://the-guild.dev/openapi/fets/server/quick-start) or +[Hono](https://hono.dev) are modern and have better support for Cloudflare Workers. + +In our case, we will use [Yoga Server](https://the-guild.dev/graphql/yoga-server) because it +supports GraphQL and it also uses the same powerful +[@whatwg-node library](https://github.com/ardatan/whatwg-node) as +[feTS](https://the-guild.dev/openapi/fets/server/quick-start), in order to support +[any Javascript runtime](https://the-guild.dev/blog/fetch-for-servers), including Cloudflare +Workers. + +### GraphQL Schema Builder + +https://pothos-graphql.dev + +This is a preference. There are two patterns: "schema-first" and "code-first." I prefer "code first" +and have the schema automatically generated from the code, and I have chosen +[Pothos](https://pothos-graphql.dev), which I have used and like when using +[Prisma](https://www.prisma.io). + +### ORM (Query Builder) + +https://github.com/kysely-org/kysely + +Unfortunately, even Cloudflare Workers, which can now connect directly over TCP, does not work with +Prisma. I won't go into details here, but Prisma has two major processes, client and engine, but +there is no mode provided to run the engine part on Cloudflare Workers. This is why it does not +work. The options are [Kysely](https://github.com/kysely-org/kysely) or +[Drizzle](https://github.com/drizzle-team/drizzle-orm), both of which can be run in Cloudflare D1, +so if you want to change them in the future, you can do so in either case. However, the TCP +connection that was announced this time is available using a library called pg, so we will choose +Kysely to connect using this library. (For the sake of accuracy, Kysely is not an ORM, but a query +builder.) + +### Database Migration + +https://www.prisma.io + +Database is a very sensitive subject, so we want to use a stable database. So for migration, it uses +Prisma. There is another reason to use Prisma. If using Prisma, you can use +[prisma-kysely](https://github.com/valtyr/prisma-kysely) to automatically generate types for Kysely. + +## Build a GraphQL Server + +From here, start writing the actual code. By the way, the finished product is in this +[repository](https://github.com/chimame/graphql-yoga-worker-with-pothos). If you want to see only +the finished product, please refer to the repository. + +### Create Cloudflare Workers + +Create them using [c3](https://developers.cloudflare.com/pages/get-started/c3), which was recently +announced. + +```bash +npm create cloudflare +``` + +You can choose a template, but currently, no matter what you choose to create GraphQL, there will be +something in the way, so choose something as close to a blank state as possible to build it. + +![](./select-application-template.png) + +This will create Cloudflare Workers that output "Hello World". + +### Create a GraphQL API + +Build a GraphQL API using GraphQL Yoga. First, install the necessary libraries. + +```bash +npm install graphql graphql-yoga @pothos/core +``` + +Create a schema with a Query that will be used as a sample. + +```ts filename="src/builder.ts" +import SchemaBuilder from '@pothos/core' + +export const builder = new SchemaBuilder({}) +``` + +```ts filename="src/schema.ts" +import { builder } from './builder' + +builder.queryType({ + fields: t => ({ + hello: t.string({ + resolve: () => 'world' + }) + }) +}) + +export const schema = builder.toSchema() +``` + +Load the created schema and create a GraphQL endpoint with GraphQL Yoga. + +```ts filename="src/workers.ts" +import { createYoga } from 'graphql-yoga' +import { schema } from './schema' + +// Create a Yoga instance with a GraphQL schema. +const yoga = createYoga({ schema }) + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { + return yoga(request, ctx) + } +} +``` + +To verify that the created GraphQL server is working, start the server with the following command. + +```bash +npm run start +``` + +If you access `http://localhost:8787/graphql` in a browser, you can open the Playground where you +can run GraphQL. + +```graphql +query { + hello +} +``` + +![](./execute-sample-query.png) + +### Create Database Schema + +Create a schema for the application to read into the database. Install Prisma and the necessary +libraries around it, since the migration is supposed to use Prisma. + +```bash +npm install -D prisma prisma-kysely +npm install kysely +``` + +Create a schema for the database with a `User` model by using Prisma. + +```prisma filename="prisma/schema.prisma" +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator kysely { + provider = "prisma-kysely" + output = "../src" + fileName = "types.ts" +} + +model User { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + name String? + email String +} +``` + +The `User` model is important, but what is important this time is the `generator kysely` part. This +is where prisma-kysely outputs the database type file for kysely when database migration is +performed. In this case, the definition is written in the file path `src/types.ts`. Execute the +following command to output the database type file for database migration and Kysely. + +```bash +npx prisma migrate dev +``` + +### Create Connections to the Database + +Next, writing the code to connect to the database created. The database is assumed to be PostgreSQL, +and further libraries are required to connect to it, so we will install them. + +```bash +npm install -D @types/pg +npm install pg +``` + +Writing the code for the connection using the installed `pg`. + +```ts filename="src/context.ts" +import { Kysely, PostgresDialect } from 'kysely' +import { Pool } from 'pg' +import type { DB } from './types' + +export const connection = () => + new Kysely({ + dialect: new PostgresDialect({ + pool: new Pool({ + host: 'localhost', + database: 'postgres', + user: 'postgres', + password: 'password' + }) + }) + }) +``` + +There is one important piece of code here. It is slightly different from what is written in the +Kysely [README](https://github.com/kysely-org/kysely#minimal-example). + +```diff +- export const db = new Kysely({ ++ export const connection = () => new Kysely({ +``` + +My code is defined in a function and each call is an instantiation of Kysely. This is the intention. +The reason is that when connecting over TCP from Cloudflare Workers, TCP disconnects on a +per-request basis, so I need to make a connection to the database on a per-request basis. Kysely +uses something called `Pool` in the `pg` it uses for connections, which does connection pooling. +Kysely also only accepts `PostgresPool` connections, so this is what it does. connection pooling is +simply explained as defining multiple connections from one application to a database, and using +those connections within an application. However, this connection pooling is incompatible with the +Cloudflare Workers specification that TCP connections are broken after each request. In other words, +when a second request is received, the connection object is already connected, but because +Cloudflare Workers' TCP connection is broken, it cannot connect to the database and an error occurs. +So the code dares to instantiate Kysely in order to reconnect. The following configuration is +required for `pg` to work on Cloudflare Workers. + +```toml filename="wrangler.toml" +node_compat = true +``` + +If you do not enable this option, pg will not work. + +https://developers.cloudflare.com/workers/wrangler/configuration/#node-compatibility + +### Create GraphQL Schema + +Finally, write the GraphQL code to fetch data from the database and return it to the client. Some of +the code was [suggested](https://github.com/chimame/graphql-yoga-worker-with-pothos/pull/1) by +[hayes](https://github.com/hayes), the author of `Pothos`, while I was working on this article. + +```ts filename="src/models/index.ts" +export * from './User' +``` + +```ts filename="src/models/User.ts" +import { AllSelection } from 'kysely/dist/cjs/parser/select-parser' +import { builder } from '../builder' +import { DB } from '../types' + +export const UserType = builder.objectRef>('User') + +UserType.implement({ + fields: t => ({ + id: t.exposeID('id'), + name: t.exposeString('name', { nullable: true }), + email: t.exposeString('email'), + initial: t.string({ + nullable: true, + resolve: user => user.name?.slice(0, 1) + }) + }) +}) +``` + +```ts filename="src/resolvers/index.ts" +export * from './User' +``` + +```ts filename="src/resolvers/Users/index.ts" +export * from './query' +``` + +```ts filename="src/resolvers/User/query.ts" +import { builder } from '../../builder' +import { connection } from '../../context' +import { UserType } from '../../models/User' + +builder.queryFields(t => ({ + User: t.field({ + type: UserType, + nullable: true, + async resolve() { + const db = connection() + await db.selectFrom('User').selectAll().executeTakeFirst() + } + }) +})) +``` + +```diff filename="src/schema.ts" +import { builder } from './builder' + +builder.queryType({ + fields: (t) => ({ + hello: t.string({ + resolve: () => 'world', + }), + }), +}) + ++ export * from './models' ++ export * from './resolvers' + +export const schema = builder.toSchema() +``` + +As you can see from the `connection()` of the resolver, this is where the connection to the database +is established. In a real application, it is better to establish a connection in the GraphQL Yoga +`Context` and use it for each resolver, instead of establishing a connection for each resolver. +(Creating countless connections puts a load on the database.) + +However, it is better to be creative in terms of database connections. Even if you establish one +connection per request, the number of connections may grow if you use serverless autoscaling like +Cloudflare Workers. If this happens, the database will be overloaded, and in the worst case, the +database may go down. Therefore, it is recommended to use a connection pooling tool such as +PgBouncer for PostgreSQL. + +### Verify That It Works + +Finally, run the application, execute the next Query, and if it returns the value successfully, you +are done. + +```graphql +query getUser { + User { + id + } +} +``` + +![](./execute-user-query.png) + +## Conclusion + +Although it took some time to identify the connection error by connection pool, building a GraphQL +API on Cloudflare Workers is now within feasible limits. Thanks to the help of various libraries, +including GraphQL Yoga, I was able to build this API. I think Cloudflare Workers, together with +Yoga, can be used to build a fast, production ready GraphQL servers. diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/select-application-template.png b/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/select-application-template.png new file mode 100644 index 00000000000..20fe57c1f27 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/select-application-template.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/thumbnail.jpg b/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/thumbnail.jpg new file mode 100644 index 00000000000..9eb2fb1e475 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphql-yoga-worker/thumbnail.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/2.jpg b/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/2.jpg new file mode 100644 index 00000000000..766af9e0b8c Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/2.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/7.jpg b/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/7.jpg new file mode 100644 index 00000000000..1a51558779b Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/7.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/backend.jpeg b/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/backend.jpeg new file mode 100644 index 00000000000..871202d77aa Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/backend.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/booth.jpg b/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/booth.jpg new file mode 100644 index 00000000000..b407ee23202 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/booth.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/guild.jpg b/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/guild.jpg new file mode 100644 index 00000000000..95046d81e18 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/guild.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/lee.jpeg b/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/lee.jpeg new file mode 100644 index 00000000000..57781907650 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/lee.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/page.mdx new file mode 100644 index 00000000000..e097ad9a230 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphqlconf-2023-recap/page.mdx @@ -0,0 +1,192 @@ +--- +title: GraphQLConf 2023 Recap +authors: [laurin, uri] +tags: [graphql, graphql-federation] +date: 2023-09-25 +description: + Explore the highlights of GraphQLConf 2023. Learn about the latest trends in GraphQL, from gateway + solutions to composite schemas and innovative projects. +--- + +Wow, what a ride! +We've just came back from GraphQL Conf and we have a lot to share! + +The community gathered together in San Francisco for +[**the first-ever GraphQL Conference**](https://graphql.org/conf), almost 10 years after GraphQL was +invented. + +It was an extraordinary warm, welcoming, community-centric event that brought together developers, +enthusiasts, and experts from all over the world. Over the course of three action-packed days, +filled with amazing workshops, exciting talks, and interesting sponsor booths (Hasura, The Guild, +Postman, The Graph, solo.io, Inigo, Stellate, StepZen(IBM), Neo4J, WunderGraph, HyGraph and +Graphabase), we had the incredible opportunity to immerse ourselves in the world of GraphQL. The +overwhelming volume of high-quality presentations by speakers from companies such as Netflix, +Intuit, Meta, AWS, Coinbase Salesforce, Atlassian, Apollo GraphQL, Meetup, IBM, Booking.com, +Wix.com, Okta, Miro and others clearly highlighted the immense need for a community-driven +conference. + +Several participants mentioned it was one of their favorite confs ever (it certainly was for us!) + +The Linux Foundation events team, lead by Emily Ruf and the GraphQL Foundation Conference committee, +lead by Keith Babo, had made a remarkable job with the first ever GraphQL open conference! + +![The Guild at GraphQLConf 2023 - photo by graphql.org/conf](./guild.jpg) + +One of the highlights of the conference was the chance to meet and shake hands with +[Lee Byron](https://twitter.com/leeb), the visionary behind GraphQL. We expressed our heartfelt +gratitude for his groundbreaking invention and the positive impact it has had on the developer +community. From its inception to the present day, Lee has been tirelessly championing GraphQL - from +working groups, board meetings, to the first official GraphQL Conf! Our community is truly fortunate +to have such a dedicated leader. It was truly inspiring to see the faces of the people we had only +known through outdated avatars on GitHub or Twitter, who are the driving force behind the vibrant +GraphQL community. + +![Lee Byron at GraphQLConf 2023 - photo by Taz Singh](./lee.jpeg) + +> Thanks to [Taz Singh](https://twitter.com/tazsingh) for the image, check out his +> [full GraphQL Conf 2023 album](https://photos.google.com/share/AF1QipOu3QoVIrMnJsVk2Y0Vsu2aw3Vo6z1px-VYmOkX6S91FARgcdDfMu3rzpU6H8fqNQ?key=UmR5VXNEbWhYYndHa3VQVk9DTzBPUkJQRE45SFdB) + +Its hard to summarize everything that happened, but the main messages we got from the conference +were: + +1. GraphQL is being used by the largest companies in the world, with clear and solid best practices. + Adoption is growing and no company in going back +2. Loads of innovation is happening in the open source GraphQL Ecosystem, at any part of the stack +3. Innovation has paved the way for numerous superior open-source solutions across every layer of + the stack. This empowers developers with choices, allowing for clear separation of concerns + between the gateway, subgraph, and schema registry. With this flexibility, there's no need for + vendor lock-in; developers can select the best tool for each specific layer +4. That innovation leads to new ways people use GraphQL, beyond the classic Client-Server gateway + solutions +5. With GraphQL Conf, the first open GraphQL Conference, the community can find all the available + tools and innovations in one place + +> There are too many great talks to cover, lucky you can find the recordings of all the workshops +> and talks [on the website's schedule (click on each talk)](https://graphql.org/conf/schedule/) + +The first day of the conference was workshops day, with many great practical workshops across +everything GraphQL. You can find all the workshop recordings in the schedule +[here](https://graphql.org/conf/schedule/) + +The second day started with welcoming remarks by Lee, followed with great keynotes by Marc-Andre, +Keith Babo, Theo Browne (would couldn't believe he was invited to give a keynote ;) ) , Bryan +Robinson, and our very own Uri Goldshtein. + +[Uri's keynote on the state of GraphQL]() +focused on highlighting the biggest open source innovations from the last year, including those +unveiled at the conference and how we all can use them to improve our GraphQL developer experience. +This talk also served as a roadmap to other talks at the conference. He covered important releases +for GraphQL Clients and GraphQL Gateways. One of the key takeaways was the emergence of GraphQL +gateways as a common pattern. + +For a long time, developers had limited options when it came to choosing a GraphQL gateway. However, +now we are **witnessing a flourishing and diverse range of gateways being built in various +languages** like Apollo Router, Hot Chocolate, Cosmos, and our own GraphQL Mesh. The future promises +friendly competition between the teams and companies behind these projects. Uri highlighted the +available choices and alternatives out there and also talked about how you should go about choosing +the right gateway solution for you. + +He then announced **the most important release of the conference - the new GraphQL Fusion spec** by +ChilliCream and The Guild, Hasura, IBM, AWS AppSync, WunderGraph and many more. An open spec, under +the Foundation, for +[remote execution and federation of services](https://the-guild.dev/graphql/hive/federation) that +challenges the existing patterns of specification-less Federation protocols with a fresh approach. + +He ended with the message - We are just getting started! + +**The conference was a testament to the growing popularity and importance of GraphQL.** Whether +attendees were just starting to explore GraphQL, had already adopted it, or were active contributors +in the GraphQL ecosystem, everyone plays a vital role in the success and future of GraphQL. The +energy and enthusiasm in the conference halls were palpable, and it was evident that we are just +scratching the surface of what GraphQL can achieve. + +![Discussion Panel at GraphQLConf 2023 - photo by graphql.org/conf](./7.jpg) + +It also showed that in an open conference you can find deep and nuanced perspectives on +architectural choices. For example, on the GraphQL Gateways subject, even with talks and +announcements on great toolings for that type of architecture, it's important to note that schema +composition, GraphQL gateways, and other tools should be adopted based on their practical benefits +in specific workflows. The conference emphasized that these solutions should be embraced only if +they genuinely enhance the efficiency and productivity of your company's engineering team. GraphQL +Fusion +([Conf Talk](https://graphql.org/conf/schedule/4a4e842d1cd0c06083f484d31225abd1/?name=GraphQL%20Fusion:%20Rethinking%20Distributed%20GraphQL)), +for example, offers adaptability to any GraphQL setup, making it easier to migrate to a more +distributed setup in the future and even support for federating APIs that are not GraphQL (OpenAPI, +SOAP, gRPC, etc.). On the other hand, other +[Federation API](https://the-guild.dev/graphql/hive/federation) protocols requires a specific +subgraph structure. Additionally, it was interesting to learn that even Facebook, with its vast +resources, finds value in a monolithic approach for their GraphQL implementation. + +![The Guild cap](./backend.jpeg) + +Another significant aspect highlighted during the conference was the **need for continuous education +and knowledge sharing within the GraphQL community**. While there are numerous tutorials and guides +available for specific libraries and frameworks, there is a growing demand for more generic best +practices that can be applied across different GraphQL solutions. Topics like security through +persisted queries or HTTP and response caching sparked engaging discussions among developers, +showcasing the collective effort needed to improve GraphQL implementation within companies. + +![Uri at GraphQLConf 2023 - photo by graphql.org/conf](./2.jpg) + +Lastly, the conference showcased several innovative projects that challenge the way we currently use +GraphQL. +[Grafast](https://graphql.org/conf/schedule/275443caa2eda5df06699b724efa533c/?name=The%20Future%20of%20Efficiency%20Is%20Here:%20Add%20Planning%20to%20Your%20Schema!), +for instance, introduces a more efficient approach to implementing GraphQL schemas, while +[Houdini](https://graphql.org/conf/schedule/ce430c038efa9a9c19743d1ccc702de9/?name=Houdini:%20A%20GraphQL-First%20Meta-Framework) +and +[Isograph](https://graphql.org/conf/schedule/2517f7a1d13ad3c0652e1b3cc5b65714/?name=Isograph%20%E2%80%94%20Rethink%20GraphQL-Powered%20React%20Apps) +are exciting new GraphQL clients inspired by Relay, taking the GraphQL experience to new heights. +These projects sparked lively discussions and left us with a sense of anticipation and plenty of new +ideas to explore and improve the GraphQL community as a whole. And if we look ahead to the future, +[Aleksandra's talk about AI, GraphQL, and the Rise of Malleable Applications](https://graphql.org/conf/schedule/4feef977ceb883c69c91ccd2dd607aec/?name=AI,%20GraphQL,%20and%20the%20Rise%20of%20Malleable%20Applications) +gave us a glimpse at what's possible! + +We extend our heartfelt **thanks to everyone who attended GraphQL Conf 2023**. For those who +couldn't make it, don't worry! Its impossible to cover the span of amazing talks that happened +there, but the recordings of all the workshops and talks are already +[available on the website's schedule (click on each talk)](https://graphql.org/conf/schedule/), +ensuring that the knowledge and insights shared during the conference can reach a wider audience. We +also invite you to join the discussions on the official +[GraphQL discord](https://discord.graphql.org), where the vibrant community continues to thrive. +Also, new community Meetup groups have been announced at the conference, so make sure to +[signup here](https://linuxfoundation.surveymonkey.com/r/KWNWSM5) to attend, speak or organize your +local Meetup. + +![The Guild's booth](./booth.jpg) + +**The overwhelming participation and turnout at the conference have made clear that GraphQL Conf +will return in 2024.** So, if you missed out this year, rest assured there will be more +opportunities to meet and connect with the GraphQL community. Let us know how can we make GraphQL +Conf 2024 even better! + +**See you next year!** + +### The Guild at GraphQL Conf + +The Guild is a group of open source developers who strive to create a vibrant and open ecosystem in +GraphQL and APIs in general. We chose to be the largest sponsor of the conference (diamond sponsor + +workshop day sponsor) and do whatever we can to make the official event a success. We are proud at +the success of the event and humbled by the community and everyone involved. + +At the conference we demonstrated our solutions for GraphQL Gateway, Server and Schema Registry. +**Our individual tools are fully open source, work separately and integrate with any other tool in +the ecosystem**, including our competitors - **the opposite of a vendor lock-in**, so you can choose +the best tool that works for you. + +- GraphQL Server - [Yoga Server](https://the-guild.dev/graphql/yoga-server) (see + [comparison to other servers](https://the-guild.dev/graphql/yoga-server/docs/comparison)) +- GraphQL Gateway - [GraphQL Mesh](https://the-guild.dev/graphql/mesh) (Supports Federation v1, v2, + Stitching and upcoming support for Fusion and subgraphs from any API Protocol like OpenAPI, gRPC, + etc.) + ([Conf talk](https://graphql.org/conf/schedule/7a87fe1cfc351a993ed40e01d384e3c6/?name=The%20Graph%20of%20Everything%20-%20Federated%20Architecture%20for%20Any%20API%20Service), + [Conf Workshop](https://graphql.org/conf/schedule/de9b490bff0d1e234ec4e19bc03392b5/?name=GraphQL%20Mesh%20-%20A%20Federated%20Gateway%20for%20Any%20API%20Protocol%20-%20GraphQL,%20OpenAPI,%20gRPC%20and%20More)) +- Schema Registry - [GraphQL Hive](https://the-guild.dev/graphql/hive) - Offers hosted and open + source, self hosted solutions + ([Conf Workshop]()) +- Tools - We build [many other open source tools](https://the-guild.dev/#platform) to support your + stack across the different layers, here are more talks from the conference about our tools: + - [Using GraphQL Tools with teams and APIs that are not GraphQL](https://graphql.org/conf/schedule/b9e35d673e7b541421d45ce2043dc05e/?name=GraphQL%20Everywhere%20-%20How%20GraphQL%20is%20Being%20Used%20in%20Places%20You%20Never%20Thought%20Were%20Possible) + - [GraphQL Over Internet](https://graphql.org/conf/schedule/6c2eefe955e288e974a9182dac06f8fa/?name=GraphQL%20Over%20Internet) + - [The Evolution of GraphQL Code Generation](https://graphql.org/conf/schedule/675c416b16ad2b0c519b1ec894353fc5/?name=The%20Evolution%20of%20GraphQL%20Code%20Generation) + - [Shared Schema Policies and Automatic Standards Across Your Company's Teams](https://graphql.org/conf/schedule/ff6a2ae37d87e74c9f7739a1331804a1/?name=Shared%20Schema%20Policies%20and%20Automatic%20Standards%20Across%20Your%20Company%E2%80%99s%20Teams) + - [How to Choose a GraphQL Gateway?](https://graphql.org/conf/schedule/70f9e59dc60cf417aa38eb890b2a8abe/?name=How%20to%20Choose%20a%20GraphQL%20Gateway?) diff --git a/packages/web/docs/src/app/blog/(posts)/graphqxl-language/page.mdx b/packages/web/docs/src/app/blog/(posts)/graphqxl-language/page.mdx new file mode 100644 index 00000000000..3c2666c9cf7 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/graphqxl-language/page.mdx @@ -0,0 +1,179 @@ +--- +title: GraphQXL - The Missing GraphQL Language Extension? +tags: [graphql] +authors: gabotechs +date: 2022-12-12 +description: GraphQXL, a new language for building big and scalable GraphQL server-side schemas +--- + +import { Comparison } from '#components/comparison' + +When building GraphQL APIs following a schema-first approach and the project goes big, people +usually find themselves repeating almost identical structures across the schema and copy-pasting +common fields across the different types and inputs. + +## The Need of a New Language + +The GraphQL schema definition language (commonly referred to as SDL) is really nice for writing API +schemas, but it tends to fall short on features as it is not a programming language (and it is not +meant to be). + +With a code-first approach it is very easy to autogenerate repetitive graphql code as it is the +programming language of choice the one that does all the hard work, but with a schema-first approach +the limits are set by the capabilities of vanilla GraphQL. + +There are points in favor of the schema-first approach: + +- Non-programmers can contribute to new features by just touching the GraphQL schema. +- The GraphQL schema itself serves like a blueprint for developers who will implement the API. +- Frontend developers can start the work as long as the schema definition is complete, even before + the backend team has finished implementing it. +- For customer facing APIs, it makes sense to give more importance to the schema definition, as it's + what the customers will directly interact with. + +GraphQXL is directed for people that want to keep using a schema-first approach but also want to +benefit from the code re-usability, consistency and maintainability that a programming language +would provide. + +## Some Interesting Features + +It is very common to reuse a subset of fields across many types, for example: + +```graphql +type Product { + id: ID! + name: String! + createdAt: Date + modifiedAt: Date + # ... +} + +type User { + id: ID! + name: String! + createdAt: Date + modifiedAt: Date + # ... +} +``` + +This pattern can be repeated in a lot of types in the same schema, leading to code duplication and +potential inconsistencies. + +In GraphQXL this would look something like: + + +```graphql filename="Source GraphQXL" copy=false +type _CommonFields { + id: ID! + name: String! + createdAt: Date + modifiedAt: Date +} + +type Product { ...\_CommonFields } + +type User { ...\_CommonFields } + +```` +```graphql filename="Compiled GraphQL" copy=false +type Product { + id: ID! + name: String! + createdAt: Date + modifiedAt: Date +} + +type User { + id: ID! + name: String! + createdAt: Date + modifiedAt: Date +} +```` + + + +The idea is not only to reuse code instead of copy-pasting, but also to enforce consistency across +resources in the schema. + +## Added Syntax for Enforcing Reusability + +[Pagination based on cursors and connections](https://relay.dev/graphql/connections.htm) is one of +the most common and standard ways for providing pagination. This, implemented in a good number of +types, would lead to a bunch of verbose types with pretty much the same structure. + +For example, this vanilla GraphQL code: + +```graphql +"The Product's Edge object for pagination" +type ProductEdge { + "The Product's cursor that refers to the current node" + cursor: String! + "The Product itself" + node: Product! +} + +"The Connection object for paginating across the ProductEdges" +type ProductConnection { + "Metadata with the current page info" + pageInfo: PageInfo! + "List of ProductEdges for the current page" + edges: [ProductEdge!]! +} + +"The User's Edge object for pagination" +type UserEdge { + "The User's cursor that refers to the current node" + cursor: String! + "The User itself" + node: User! +} + +"The Connection object for paginating across the UserEdges" +type UserConnection { + "Metadata with the current page info" + pageInfo: PageInfo! + "List of UserEdges for the current page" + edges: [UserEdge!]! +} +``` + +Verbose right? and this is just for a couple of resources, there is usually a lot more in a GraphQL +schema. + +How could this be written using GraphQXL? + +```graphql +"The ${{ variables.T }}'s Edge object for pagination" +type Edge { + "The ${{ variables.T }}'s cursor that refers to the current node" + cursor: String! + "The ${{ variables.T }} itself" + node: T! +} + +"The Connection object for paginating across the ${{ variables.T }}s" +type Connection { + "Metadata with the current page info" + pageInfo: PageInfo! + "List of ${{ variables.T }}s for the current page" + edges: [T!]! +} + +type ProductEdge = Edge +type ProductConnection = Connection +type UserEdge = Edge +type UserConnection = Connection +``` + +## Is There Something Else? + +Of course! There is a lot more things that you can check in the +[GitHub repository](https://github.com/gabotechs/graphqxl), or in the +[GraphQXL Book](https://gabotechs.github.io/graphqxl). + +You can also play around with it in the [GraphQXL explorer](https://graphqxl-explorer.vercel.app). + +It would be awesome to hear opinions from the community and new feature requests that could solve +other challenges when defining GraphQL schemas. diff --git a/packages/web/docs/src/app/blog/(posts)/hive-introducing-schema-policy/page.mdx b/packages/web/docs/src/app/blog/(posts)/hive-introducing-schema-policy/page.mdx new file mode 100644 index 00000000000..d1f8a4f4a92 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/hive-introducing-schema-policy/page.mdx @@ -0,0 +1,94 @@ +--- +title: Introducing Schema Policy in Hive +tags: [graphql, graphql-hive] +authors: dotan +date: 2023-05-16 +description: New GraphQL-Hive feature for enfocring best-practices and schema-design styles. +--- + +import NextImage from 'next/image' +import { Callout } from '@theguild/components' + +**We are thrilled to announce that Hive got a new, shiny feature: Schema Policy** 🎉 + +As your GraphQL API and schema grow, tasks such as enforcing best practices, deciding on schema +design, and collaborating with other teams become more complicated. + +We have learned from our experience of building and maintaining many GraphQL APIs over the years +that the ability to scale and evolve your API is an important goal. Therefore, we have introduced a +new feature in Hive that enables developers to easily lint, verify, and enforce best practices +across the entire GraphQL API. + +![](https://the-guild.dev/graphql/hive/_next/static/media/policy-overview.e560205a.png 'Choose a custom policy and enfore best practices') + +With a simple dashboard configuration, you can enforce the following: + +1. **Naming conventions** (e.g `type User` instead `type user`), **order of appearance** (e.g field + `name` should come after field `age`), and enum value style (e.g `VALUE` instead of `Value`). +2. **Require descriptions** for specific elements in your GraphQL schemas (e.g a type or a field), + and even require a specific style for your descriptions (inline using `" ... "`, block + `""" ... """` and even forbid `# ...` ). +3. **Deprecation rules** for enforcing a reason for every deprecation, and even a deprecation date. +4. A set of rules to enforce **Relay-style GraphQL schema:** pagination inputs, `Connection` types, + `PageInfo` and `Node` interfaces. +5. Enforce `@oneOf` structure the right way (by ensuring response structure and directives usage) +6. **And +20 more rules you can customize!** + +![](https://the-guild.dev/graphql/hive/_next/static/media/policy-rules-config.ff7acd8f.png 'Every rule has a broad range of configuration') + + + **Reminder:** Hive does not have feature-gates or any limitation on features. All features are + available to all users and organization (including the Free plan!) 😍 + + +### Gradual Adoption + +During the GraphQL schema `check` process, policies are executed and evaluated. + +You can use this mechanism to ensure that your schema is fully compatible and adhere the schema +policies, before running the `publish` flow and update the schema in the registry. + +Each rule can be configured to trigger either a **warning** or an **error**. + +![](https://the-guild.dev/graphql/hive/_next/static/media/policy-cli-warning.4937f501.png) + +A warning will be printed as CLI output, while an error will allow you to reject and fail the schema +check. This way, you can introduce new policy rules and gradually without breaking your existing +pipelines. + +![](https://the-guild.dev/graphql/hive/_next/static/media/policy-cli-error.12678074.png) + +### Organization/project Level + +The GraphQL schema policies are defined at the organization level, and applied to all projects and +targets. + +You can enable Policy override for projects, and either disable or modify specific policy rules +defined at the organization level. + +![](https://the-guild.dev/graphql/hive/_next/static/media/policy-override-config.c08c0bba.png) + +![](https://the-guild.dev/graphql/hive/_next/static/media/policy-override-severity.a35154f9.png) + +### Open-Source under the Hood + +The schema policies feature is powered by [GraphQL-ESLint](https://the-guild.dev/graphql/eslint), +which is another project built and maintained by The Guild. We are using a subset of this feature, +specifically designed for linting GraphQL schemas. + +With GraphQL-ESLint, you can easily integrate GraphQL into ESLint, and lint GraphQL schemas, +operations, and the combination of the two in a full-stack project. You can also build custom rules +to enforce specific GraphQL schema designs. + +We invite all members of Hive and the GraphQL community to collaborate and improve GraphQL-ESLint by +sharing new use-cases and best practices. Rules and customizations added to GraphQL-ESLint will be +available quickly as part of Hive. + +### How to Use Schema Policy? + +You can use Policy today with your existing [GraphQL-Hive](https://app.graphql-hive.com/) projects. +[Documentation is also available](https://the-guild.dev/graphql/hive/docs/features/schema-policy). + +After enabling Policy and rules for your organization or project, try +[performing a GraphQL schema `check`](https://the-guild.dev/graphql/hive/docs/features/schema-registry#check-a-schema) +to execute the policy check. diff --git a/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/docs-search.png b/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/docs-search.png new file mode 100644 index 00000000000..bfef50da04c Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/docs-search.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/filter-by-client.png b/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/filter-by-client.png new file mode 100644 index 00000000000..e6d4a123899 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/filter-by-client.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/page.mdx b/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/page.mdx new file mode 100644 index 00000000000..e82bb98dca3 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/page.mdx @@ -0,0 +1,86 @@ +--- +title: Hive Summer Update 2023 +authors: [laurin, kamil, dimitri, dotan] +tags: [graphql, graphql-hive, graphql-federation] +date: 2023-07-20 +description: + Learn what is new on GraphQL Hive, we have shipped a lot of new exciting features and + improvements. +--- + +import { Callout } from '@theguild/components' + +After shipping [Schema Policy](/blog/hive-introducing-schema-policy) last month we are happy to +share with you some of the new features we worked on. + +## Persisted Schema Checks + +We now persist the schema checks and display them on the Hive Target page. When using the GitHub +integration we now also automatically link the GitHub check run to the schema check. You can see a +schema diff, policy errors, and the supergraph in this new view. + +![Schema Checks Overview](./schema-checks.png) + +## Manually Approve a Schema Check + +It is now possible to manually approve a schema check. Doing so will transform the schema check into +a successful schema check and also update the GitHub check run on the linked repository from failed +to successful. + +## Schema Coordinate Client Usage on Explorer + +On the Explorer page, we now show which clients are using a specific schema coordinate. This is +useful to understand which clients are affected by a schema change and which teams you need to +contact for communicating an upcoming schema change. + + + In the following example, you can see that the `Mutation.schemaPublish` schema coordinate is used + by `Hive Client`, `Hive CLI`, `Hive Ruby Client` and other unknown clients. + + +![Schema Coordinate Client Usage on Explorer Page](./schema-coordinate-client-usage.png) + +## Subgraph Information on Explorer + +For [GraphQL Federation](https://the-guild.dev/graphql/hive/federation) projects, we now show the +subgraph information on the Explorer page. It is now easier to understand which types and fields are +referenced from which subgraphs, which helps you do strategic decisions. + +![Subgraph Information on Explorer](./subgraph.png) + +## Filter GraphQL Operations by Client + +It is now possible to filter the GraphQL operations by clients on the Operations view. This allows +you to quickly find all operations that are used by a specific client. + +![Filter Operations by Client](./filter-by-client.png) + +## GraphQL Operation Collections + +You can now save and organize GraphQL Operations in collections in the Laboratory. Share the +collections with your team and make collaboration easier. + +## A New Fresh Look + +It is no secret that we at The Guild are developers and not designers. However, we tried our best to +improve the UI and UX of Hive. We hope you like it. + +## Support Google Cloud Storage for Self-Hosting + +We now support Google Cloud Storage as a storage backend for self-hosted Hive instances. + +## Improved Documentation Search + +We improved the search on the documentation page for your convenience. It might seem like a small +change but it makes a big difference. + +![New documentation search](./docs-search.png) + +## What Do You Think Is Still Missing? + +The development of Hive is driven by community members needs and contributions. We appreciate any +feedback on the existing and new features we just shipped. + +If there is anything you are missing or you have any questions, please reach out to us or open a +GitHub issue (or pull request) on the +[GraphQL Hive repository](https://github.com/kamilkisiela/graphql-hive). diff --git a/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/schema-checks.png b/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/schema-checks.png new file mode 100644 index 00000000000..2f166d0590e Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/schema-checks.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/schema-coordinate-client-usage.png b/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/schema-coordinate-client-usage.png new file mode 100644 index 00000000000..026d695d431 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/schema-coordinate-client-usage.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/subgraph.png b/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/subgraph.png new file mode 100644 index 00000000000..409b399b954 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/hive-summer-update-2023/subgraph.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/houdini-and-kitql/2022 05 25 - KitQL & Houdini.gif b/packages/web/docs/src/app/blog/(posts)/houdini-and-kitql/2022 05 25 - KitQL & Houdini.gif new file mode 100644 index 00000000000..32bcbbdc732 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/houdini-and-kitql/2022 05 25 - KitQL & Houdini.gif differ diff --git a/packages/web/docs/src/app/blog/(posts)/houdini-and-kitql/page.mdx b/packages/web/docs/src/app/blog/(posts)/houdini-and-kitql/page.mdx new file mode 100644 index 00000000000..ccd7f439094 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/houdini-and-kitql/page.mdx @@ -0,0 +1,218 @@ +--- +title: Bringing the best GraphQL experience to Svelte +tags: [graphql, svelte] +authors: [jycouet, AlecAivazis] +date: 2022-07-04 +description: + It's time to unveil a massive effort by the KitQL and Houdini maintainers to join forces! +--- + +## A Tale of Two Projects + +[SvelteKit](https://kit.svelte.dev) is taking the web dev community by storm by providing an +excellent solution for server-side rendered web applications and simplifying the creation of +"WebApp"s ... whatever that means 😅. While most of the common libraries for GraphQL apps do have +svelte bindings, none of them really _spark joy_. It was hard to avoid this feeling that people were +trying to smash a React-shaped wedge into a Svelte-shaped hole. Independently, +[Alec](https://github.com/AlecAivazis) and [Jean-Yves](https://twitter.com/jycouet) set out to give +the Svelte community something better and took very different approaches to the problem. Eventually +we connected on GitHub started to discuss the pros and cons of each approach. During this team, one +question kept popping up: + +> What would the best experience for using #Svelte and #GraphQL look like? + +It was very clear we both wanted to provide "the best" solution for using GraphQL and Svelte. It +seemed silly to work independently since we shared this common goal so all we needed to do was +figure out how to structure our joint efforts. + +## The Best of Both Worlds + +While at first we thought that Houdini and KitQL were solving the same problem, during our +conversation it became clear that KitQL's scope was significantly larger than Houdini's. KitQL +strives to provide the best possible, "batteries included" solution for fullstack applications built +with GraphQL and SvelteKit. It achieves this by integrating powerful tools in the GraphQL community +such as [Yoga](https://graphql-yoga.com), [Envelop](https://envelop.dev), +[Modules](https://graphql-modules.com), [Scalars](https://graphql-scalars.dev), +[GraphQL-ESLint](https://github.com/dimaMachina/graphql-eslint), and +[CodeGen](https://graphql-code-generator.com) with its own custom client that ran in the browser. On +the other hand, Houdini is entirely focused on the client-side of the GraphQL picture and fills a +similar role to libraries like Relay, urql, and Apollo. With this realization, the path forward was +clear: bring the best parts of the KitQL client into Houdini and integrate it with the rest of the +tools in the KitQL Stack. + +Now users no longer have to choose between KitQL or Houdini. If you are looking for: + +- a GraphQL client, we recommend to use Houdini +- building a full-stack app, we recommend KitQL _(including Houdini of course 😉)_ + +> And it's available Today 🎉🎉🎉 + +Houdini & KitQL + +## How Does It Looks like to Use GraphQL in SvelteKit? + +You have now two ways that you can mix and match in your application: + +### External Documents + +You write your GraphQL query in an external file `AllItems.gql` like: + +```graphql +query AllItems { + items { + id + name + } +} +``` + +And you can use the generated store everywhere in your app like: + +```svelte + + +{#each $GQL_AllItems.data.items as item} + {item.name} +{/each} +``` + +Pretty simple, right? You can put this code in a component or in a route. Here, you are doing `CSR` +(Client Side Rendering). The browser is doing a network call to get data. + +If you want to enjoy `SSR` (Server Side Rendering), you have to add a small bit of code to your +route: + +```svelte + +``` + +### Inline Documents + +With inline documents, the approach is a bit different but relies on the same store API under the +hood. Here is the only thing you write and you automagically get `CSR` and `SSR` depending on your +situation: + +```svelte + + +{#each $data.items as item} +
    {item.name}
    +{/each} +``` + +### Pros and Cons? + +While the two APIs are equivalent, there are pros and cons to both approaches. What you gain in +simplicity with the preprocessor, you lose in flexibility. The preprocessor can only run on +`.svelte` files so if you want to do things inside of an endpoint or any random file, you'll need to +use the store API. If you want to send custom headers for just a single request, you'll need the +store API. + +## Some Examples + +Just to give you a feel for what we have built, here are a few common situations you have probably +seen. + +### Pagination Example + +For example, you want to do an infinite scroll pagination? And load more items? Simply do 👇 + +```svelte + + + + +{#each $GQL_AllItems.data.items as item} + {item.title} +{/each} +``` + +And that's it! + +### Mutation Example to Append the List + +Want to take the result of a mutation and add it to a list? + +First, you need to inform graphql that this is a "special" list with a directive: + +```graphql +query AllItems { + items @list(name: "All_Items") { + id + name + } +} +``` + +After the generate, a fragment is created for you: `All_Items_insert` (to be used in the mutation). + +And that's it! Call your mutation and your data will apprend the list automagically! + +```svelte + + + + +{#each $GQL_AllItems.data.items as item} + {item.title} +{/each} +``` + +- No need to retrigger a query to get the new data in the list. +- No need to append the list manually. +- Don't even need remember exactly what fields need to ask for the item. + +> Houdini takes care of everything for you 🎉. + +## Join Us in Building the Future + +We showed here two examples... but a lot more is available in the two projects. + +Please come on our GitHub repos to ⭐ star, 🗣️ discuss, 🎉 ask for new features, and more: + +- [Houdini](https://github.com/HoudiniGraphql/houdini) +- [KitQL](https://github.com/jycouet/kitql) + +_If you were already using these projects, please follow +[Houdini migration to 0.16.0](https://houdinigraphql.com/guides/migrating-to-016) or +[KitQL migration to 0.7.0](https://kitql.dev/docs/migrating-to-0.7.0) to be up-to-date._ + +Speak to you soon! 🤟 diff --git a/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/3834264b711c4451c2468b290bae23b0.jpeg b/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/3834264b711c4451c2468b290bae23b0.jpeg new file mode 100644 index 00000000000..57d13140c90 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/3834264b711c4451c2468b290bae23b0.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/6186d4c6a0df059772b2f7f909933f30.jpeg b/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/6186d4c6a0df059772b2f7f909933f30.jpeg new file mode 100644 index 00000000000..ca816dbb4ac Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/6186d4c6a0df059772b2f7f909933f30.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/77cab98d7eb6c4c2bc19fc1feaf1bdfc.jpeg b/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/77cab98d7eb6c4c2bc19fc1feaf1bdfc.jpeg new file mode 100644 index 00000000000..58e6d155221 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/77cab98d7eb6c4c2bc19fc1feaf1bdfc.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/81347d812b696786953f22baa07b1acd.jpeg b/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/81347d812b696786953f22baa07b1acd.jpeg new file mode 100644 index 00000000000..d26a22a4f44 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/81347d812b696786953f22baa07b1acd.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/c181a2ba3ee15dca2a7297136a0c5996.jpeg b/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/c181a2ba3ee15dca2a7297136a0c5996.jpeg new file mode 100644 index 00000000000..54a4a5054dd Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/c181a2ba3ee15dca2a7297136a0c5996.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/cb13e8fa5ea78c7b0159da981f34bfc7.jpeg b/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/cb13e8fa5ea78c7b0159da981f34bfc7.jpeg new file mode 100644 index 00000000000..e03d4dd7d8a Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/cb13e8fa5ea78c7b0159da981f34bfc7.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/efaa174a086d487ba51b1da7edabcadb.jpeg b/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/efaa174a086d487ba51b1da7edabcadb.jpeg new file mode 100644 index 00000000000..72df721a3d8 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/efaa174a086d487ba51b1da7edabcadb.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/f446050521c10447f45bc89e466b3370.webm b/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/f446050521c10447f45bc89e466b3370.webm new file mode 100644 index 00000000000..82f8b3b9c1b Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-brain-detects-shapes/f446050521c10447f45bc89e466b3370.webm differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-i-build-babel-plugins/44878b4c39410ab4dd64d38230625752.png b/packages/web/docs/src/app/blog/(posts)/how-i-build-babel-plugins/44878b4c39410ab4dd64d38230625752.png new file mode 100644 index 00000000000..6ca98b7808c Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-i-build-babel-plugins/44878b4c39410ab4dd64d38230625752.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-i-build-babel-plugins/7dcdb894fc6fc51790d358c69c98aefe.png b/packages/web/docs/src/app/blog/(posts)/how-i-build-babel-plugins/7dcdb894fc6fc51790d358c69c98aefe.png new file mode 100644 index 00000000000..a02a51bbb2d Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-i-build-babel-plugins/7dcdb894fc6fc51790d358c69c98aefe.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-i-build-babel-plugins/b00e12c482b6ba2f40860ff3471bc55c.png b/packages/web/docs/src/app/blog/(posts)/how-i-build-babel-plugins/b00e12c482b6ba2f40860ff3471bc55c.png new file mode 100644 index 00000000000..be4af0a08df Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-i-build-babel-plugins/b00e12c482b6ba2f40860ff3471bc55c.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-i-build-babel-plugins/f842356c49c8c281d1e2072723f92010.png b/packages/web/docs/src/app/blog/(posts)/how-i-build-babel-plugins/f842356c49c8c281d1e2072723f92010.png new file mode 100644 index 00000000000..960a379310a Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-i-build-babel-plugins/f842356c49c8c281d1e2072723f92010.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/how-not-to-learn-graphql-1.jpg b/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/how-not-to-learn-graphql-1.jpg new file mode 100644 index 00000000000..7250fbb4984 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/how-not-to-learn-graphql-1.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/how-not-to-learn-graphql-2.png b/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/how-not-to-learn-graphql-2.png new file mode 100644 index 00000000000..ab20b063f99 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/how-not-to-learn-graphql-2.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/how-not-to-learn-graphql-3.png b/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/how-not-to-learn-graphql-3.png new file mode 100644 index 00000000000..d10a42e9f26 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/how-not-to-learn-graphql-3.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/how-not-to-learn-graphql-4.png b/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/how-not-to-learn-graphql-4.png new file mode 100644 index 00000000000..9e06aa1875a Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/how-not-to-learn-graphql-4.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/page.mdx b/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/page.mdx new file mode 100644 index 00000000000..a4105a58c3e --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/page.mdx @@ -0,0 +1,817 @@ +--- +title: How not to learn GraphQL +authors: charlypoly +tags: [graphql, graphql-federation] +date: 2022-02-14 +description: A guide to avoid common learning biases and misconceptions around GraphQL. +--- + +"_GraphQL is a front-end technology_", "_[Federation](https://the-guild.dev/graphql/hive/federation) +is the only solution for unified graph and, services orchestration_", "_GraphQL is a replacement of +REST"_ are common misconceptions that are most likely linked to biased learning of GraphQL. + +Because of its rapid ecosystem evolution in the past years, learning GraphQL in 2022 is more +challenging than it used to be. + +For this reason, this article, greatly inspired by +"[How not to learn TypeScript](https://fettblog.eu/how-not-to-learn-typescript)" and +"[How not to learn Rust](https://dystroy.org/blog/how-not-to-learn-rust/)", will guide you to avoid +common learning biases and misconceptions around GraphQL. + +## Mistake #1: GraphQL Is a Front-End Technology + +Yes, is it true that GraphQL was first popularized as a technology solving issues on the client-side +(mobile/web apps) by: + +- reducing the number of roundtrips with the API(s) +- reducing the size of fetched data by requesting only the necessary fields +- removing the overhead of fetching and crunching data from multiple endpoints + +![](./how-not-to-learn-graphql-1.jpg) + +In short, GraphQL gave the control back to the data consumers (mobile apps, web apps). + +### GraphQL as an Innovative Solution to Microservices Orchestration + +However, starting in 2018, the GraphQL ecosystem started to grow, especially on the back-end side, +where new horizons of GraphQL were developed. + +Many open-source actors started to propose solutions for unified schema/services orchestration, to +name the major ones: [GraphQL Tools](https://graphql-tools.com/docs/introduction), +[Apollo Federation](https://apollographql.com/docs/federation), [Hasura](https://hasura.io), +[GraphQL Mesh](https://graphql-mesh.com/docs/getting-started/introduction). + +![graphql gateway.png](./how-not-to-learn-graphql-2.png) + +Rapidly, some companies started to offer enterprise solutions such as SaaS/PaaS to manage GraphQL +schema at scale: [Apollo Studio](https://apollographql.com/docs/studio), +[AWS AppSync](https://aws.amazon.com/fr/appsync), [Hasura Cloud](https://hasura.io/cloud). + +### GraphQL's Path on Back-End Use-Cases + +Not limited to services orchestration, GraphQL continued its goal to become the go-to language for +data. + +Recent projects such as [GraphQL Mesh](https://graphql-mesh.com) or products such as +[Hasura Cloud](https://hasura.io/cloud) proved that GraphQL has a purpose beyond the simple +front-end/mobile apps fetching challenges. + +Other actors, such as Neo4J, a historical graph database actor, +[announced support for GraphQL](https://neo4j.com/developer/graphql/#_the_neo4j_graphql_library) as +a data query language. + +## Mistake #2: Design GraphQL APIs like REST APIs + +When designing a GraphQL Schema, many projects decide to keep a 1-1 relationship with the underlying +data schema (database or other microservices). + +Marc-André Giroux covered this subject in the +"_[GraphQL Mutation Design: Anemic Mutations](https://productionreadygraphql.com/blog/2018-02-18-graphql-mutation-design-anemic-mutations)_". +The term _Anemic_ refers to a design where a Mutation (or Query) only contains data, not behaviors. + +A good GraphQL Schema design should: + +- simplify the usage by providing the proper abstractions (ex: aggregated fields, atomic mutation + across multiple microservices) +- provide specialized mutations that represent specific behaviors instead of CRUD mutations directly + linked to an underlying data-schema + +For example, for a schema exposing a query to update a `User` type as follows: + +```graphql +type User { + id: ID! + name: String! + email: String! + firstName: String + lastName: String + plan: UserPlan! + premiumPreferences: UserPremiumPreferences +} + +enum UserPlan { + DEFAULT + PREMIUM +} + +type UserPremiumPreferences { + alerts: Boolean + alertsFrequency: Boolean + # ... +} +``` + +A “data-driven” GraphQL Mutation for `updateUser` would be the following: + +```graphql +input UpdateUserPremiumPreferences { + alerts: Boolean! + alertsFrequency: String +} + +input UpdateUser { + name: String + email: String + firstName: String + lastName: String + premiumPreferences: UserPremiumPreferences +} + +type Mutation { + updateUser(id: ID!, input: UpdateUser!): User +} +``` + +As explained in Marc-André Giroux's article, the above mutations expose a lot of optional fields, +making the typing of the mutation obsolete (and also the self-documentation). + +> By removing any nullability constraints on the input fields, we've pretty much deferred all this +> validation to the runtime, instead of using the schema to guide the API user towards proper usage. + +_[GraphQL Mutation Design: Anemic Mutations](https://productionreadygraphql.com/blog/2018-02-18-graphql-mutation-design-anemic-mutations)_. + +A better, behavior-driven, design would be the following: + +```graphql +input UpdateUserPremiumPreferences { + alerts: Boolean! + alertsFrequency: String +} + +input UpdateUser { + name: String + email: String + firstName: String + lastName: String +} + +type Mutation { + updateUser(id: ID!, input: UpdateUser!): User + updateUserPreferences(id: ID!, input: UpdateUserPremiumPreferences!): User +} +``` + +We now offer specialized mutations where the end-user developer won't have to guess which fields are +required and will get better static validations of the mutations arguments. + +Remember that a GraphQL Schema is neither a back-end nor front-end owned technology; it is a +technical part of the stack that both parts must shape. + +Whether you build a product API or gateway of microservices, the goal of a GraphQL API is to provide +a simplified abstraction of a set of business logic. + +## Mistake #3: Learning GraphQL Only through Apollo + +The Apollo company has been one of the leading early contributors of GraphQL and still is a +prominent actor of the ecosystem. + +However, while most Apollo projects still keep a good share of usage and overall SEO presence, it is +worth mentioning that new equivalent projects and alternatives rose in the past years. + +### Browser GraphQL Clients + +Regarding web-based client GraphQL Client, `apollo-client` is no longer the only viable solution. + +[urql](https://formidable.com/open-source/urql) came as a solid and different vision of a GraphQL +client design, not just as an alternative, with: + +- Flexible cache system +- Extensible design (easing adding new capabilities on top of it) +- Lightweight bundle (~5x lighter than Apollo Client) +- Support for file uploads and offline mode + +In the React ecosystem, [React Query](https://react-query.tanstack.com/graphql) (especially when +used with GraphQL Code Generator) brings a lightweight and agnostic solution for GraphQL. + +React Query is a great candidate if you are looking for an easy-to-use and lightweight multipurpose +client (GraphQL-capable) that provides essential features such as: + +- Powerful cache (background refresh, window-focus refreshing) +- Advanced querying pattern (parallel queries, dependent queries, prefetching) +- UX patterns: optimistic updates, scroll restoration +- Great dev tools + +Finally, when it comes to building simple applications that might not need caching or optimistic UIs +capabilities, the famous [`graphql-request` library](https://github.com/prisma-labs/graphql-request) +is a perfect companion! + +This lightweight library comes with the essential features to query a GraphQL API: + +- Mutations validation +- Support for File upload +- Batching +- Promise-based API +- TypeScript support + +### Server GraphQL Libraries + +The same trend applies on the server-side, where many libraries are now widely used. + +Due to its historical presence and leading solutions for Apollo-powered technologies such as +Federation, Apollo server still has a significant usage share. + +However, many new companies and projects prefer more opinionated or lightweight alternatives. + +Nest.js is an excellent alternative for GraphQL APIs building for Angular design and +[DDD](https://en.wikipedia.org/wiki/Domain-driven_design) lovers. +[Nest.js](https://docs.nestjs.com/graphql/quick-start) comes with: + +- Fully-typed resolvers building experience +- Strong structuring patterns such as repositories, services, etc +- Logging system +- Support for Apollo Federation + +Nest.js is a great choice if you look into an opinionated and structured way to build your GraphQL +APIs. + +On the other hand, other non-opinionated libraries such as +[GraphQL Yoga](https://graphql-yoga.com/docs/quick-start) focus on bringing the best developer +experience in building extensible GraphQL servers. + +GraphQL Yoga comes with a performant HTTP server that provides: + +- subscriptions over HTTP (SEE) +- support for [`@defer` and `@stream`](https://github.com/graphql/graphql-js/pull/2839) +- out of the box uploads and CORS capabilities +- Extensible design with + [the Envelop plugins system](https://graphql-yoga.com/docs/features/envelop-plugins) +- Apollo Federation support +- Easy integration with Next.js, Svelte Kit, Cloudflare workers, and more! + +### A New Learning Path + +As we can see, GraphQL offers +[multiple solutions from front-end, back-end, databases to SaaS](https://landscape.graphql.org). + +If you start learning GraphQL, an excellent place to start is +[howtographql.com](https://howtographql.com), where you'll be able to test multiple frameworks and +find the ones that best fit your stack and product/project. + +## Mistake #4: Throwing Errors from Resolvers + +For most new adopters (especially coming from REST APIs), having GraphQL APIs returning `200 OK` on +errors seems like a flaw. + +However, while this design choice is entirely legit (_GraphQL is bringing back the real purpose of +HTTP status code that REST hijacked_), another aspect of error handling in GraphQL is not. + +Most GraphQL resolvers implementation handle errors as follows: + +```tsx +const resolvers = { + Query: { + user(parent, args, context) { + // ... + throw new Error('User not found') + } + } +} +``` + +Doing so returns the following response: + +```json +{ + "errors": [ + { + "message": "User not found", + "locations": [{ "line": 6, "column": 7 }], + "path": ["user", 1] + } + ] +} +``` + +As explained by [Laurin](https://twitter.com/n1rual) from The Guild in +[this article](https://blog.logrocket.com/handling-graphql-errors-like-a-champ-with-unions-and-interfaces), +using this error pattern is a bad habit since: + +- Errors need to be parsed on the client-side (we "guess" the error by parsing the error message) +- Errors are not colocated while Queries are designed to shape data on the UI + +### Elegant GraphQL Errors with Union Types + +A more elegant way to emit error in GraphQL is to use Union types for expressing expected errors as +follows: + +```graphql +type User { + id: ID! + login: String! +} + +type UserNotFoundError { + message: String! +} + +union UserResult = User | UserNotFoundError + +type Query { + user(id: ID!): UserResult! +} +``` + +Allowing us to write the following query: + +```graphql +query { + user(id: 1) { + ... on User { + id + login + } + + ... on UserNotFoundError { + message + } + } +} +``` + +Now, the fetched response **reflects the correct errors in a type-safe and colocated** way: + +```json +{ + "data": { + "user": { + "message": "User not found", + "__typename": "UserNotFoundError" + } + } +} +``` + +More details are available in the +[Handling GraphQL errors like a champ with unions and interfaces](https://blog.logrocket.com/handling-graphql-errors-like-a-champ-with-unions-and-interfaces) +article. + +Note: _Error masking_ is also an excellent approach that does not require updating the schema: +[https://graphql-yoga.com/docs/features/error-masking](https://graphql-yoga.com/docs/features/error-masking). + +## Mistake #5: "Federation Is the Only Viable Way to Compose Schemas" + +### Federation Is Opinionated Stitching + +Apollo's Federation is commonly brought as **the** solution to compose schemas or build a +microservices' architecture with GraphQL. + +While [Federation](https://the-guild.dev/graphql/hive/federation) has been +[brought under a new name as a distinct solution to Stitching](https://principledgraphql.com/integrity#2-federated-implementation), +the reality is actually quite different. + +Stitching is a design that consists of a single GraphQL gateway schema that composes multiple +underlying GraphQL services. + +It can be achieved with 3 approaches: Federation, Modern Stitching and, Schema Extensions. + +Taking the following subschemas: + +```graphql +# Posts subschema +type Post { + id: ID! + text: String + userId: ID! +} + +type Query { + postById(id: ID!): Post +} + +# Users subschema +type User { + id: ID! + email: String +} + +type Query { + userById(id: ID!): User +} +``` + +Let's see 3 distinct Stitching approaches to add the `User.posts` sub-query: + +### Federation + +**Federation architecture is actually stitching.** It stitches subschemas (via an Apollo Gateway) by +providing a set of Type Merging directives (`@requires`, `@key`, `@external`). + +Each "entity type" must originate by a single service and can be extended by using a mix of schema +extensions (`extend type`) and the Federation's SDL directives. + +To add the `User.posts` sub-query, the following schemas would be required: + +```graphql +# Posts subschema +type Post { + id: ID! + text: String + userId: ID! +} + +extend type User @key(fields: "id") { + id: ID! @external + posts: [Post!] @requires("id") +} + +extend type Query { + postById(id: ID!): Post +} + +# Users subschema +type User @key(fields: "id") { + id: ID! + email: String +} + +extend type Query { + userById(id: ID!): User +} +``` + +### Modern Stitching + +Modern Stitching +([GraphQL Tools](https://graphql-tools.com/docs/schema-stitching/stitch-type-merging) v7+) is +relying on a programmatic approach where **each subschema is totally independent**. + +Only the gateway must provide type merging information to indicate who must resolve fields shared +across multiple services. + +After simply adding the `posts: [Post!]!` field to `User`, in the "posts" subschema: + +```graphql +# Posts subschema +type Post { + id: ID! + text: String + userId: ID! +} + +type User { + id: ID! + posts: [Post!]! +} + +type Query { + postById(id: ID!): Post + postsByUserId(userId: ID!): User +} + +# Users subschema +type User { + id: ID! + email: String +} + +type Query { + userById(id: ID!): User +} +``` + +and adding the following gateway configuration will be required: + +```tsx +import { stitchSchemas } from '@graphql-tools/stitch' + +const gatewaySchema = stitchSchemas({ + subschemas: [ + { + schema: postsSchema, + merge: { + User: { + fieldName: 'postUserById', + selectionSet: '{ id }', + args: originalObject => ({ id: originalObject.id }) + } + } + }, + { + schema: usersSchema, + merge: { + User: { + fieldName: 'userById', + selectionSet: '{ id }', + args: originalObject => ({ id: originalObject.id }) + } + } + } + ] +}) +``` + +The final stitched schema could also be built similarly to Federation by using type merging +directives (`@merge`, `@canonical`): + +```graphql +# Posts subschema +type Post { + id: ID! + text: String + userId: ID! +} + +type User { + id: ID! + posts: [Post!]! +} + +type Query { + postById(id: ID!): Post + postsByUserId(userId: ID!): User @merge(keyField: "id") +} + +# Users subschema +type User { + id: ID! + email: String +} + +type Query { + userById(id: ID!): User @merge(keyField: "id") +} +``` + +### Schema Extensions + +Schema extensions, **similar to the initial Stitching proposals**, is a different approach where +individual subschemas are only exposing their own types. + +The gateway becomes responsible for merging and enriching the final schema by using Schema +Extensions: + +```ts filename="gateway.ts" +// ... +export const schema = stitchSchemas({ + subschemas: [postsSubschema, usersSubschema], + typeDefs: /* GraphQL */ ` + extend type Post { + user: User! + } + extend type User { + posts: [Post!]! + } + `, + resolvers: { + User: { + posts: { + selectionSet: `{ id }`, + resolve(user, args, context, info) { + return delegateToSchema({ + schema: postsSubschema, + operation: 'query', + fieldName: 'postsByUserId', + args: { userId: user.id }, + context, + info + }) + } + } + }, + Post: { + user: { + selectionSet: `{ userId }`, + resolve(post, args, context, info) { + return delegateToSchema({ + schema: usersSubschema, + operation: 'query', + fieldName: 'userById', + args: { id: post.userId }, + context, + info + }) + } + } + } + } +}) +``` + +## Modern Stitching Architecture + +> [Schema Stitching](https://graphql-tools.com/docs/stitch-combining-schemas) (a component +> of [GraphQL Tools](https://graphql-tools.com)[*v6 and under*]) got a bad rap when it was famously +> abandoned by Apollo in favor of +> their [Federation](https://apollographql.com/docs/federation) architecture some years back. +> However, Stitching came under the stewardship of [The Guild](/) and friends in 2020, and they've +> since overhauled it with numerous automation and performance enhancements. Seemingly out of +> nowhere, Schema Stitching has reemerged as something of a nimble hummingbird racing alongside the +> stallion that is Apollo Federation. + +[https://product.voxmedia.com/2020/11/2/21494865/to-federate-or-stitch-a-graphql-gateway-revisited](https://product.voxmedia.com/2020/11/2/21494865/to-federate-or-stitch-a-graphql-gateway-revisited) + +While initial Schema Stitching implementation (similar to Schema Extensions) had verbose +configuration and performance issues (lack of batching), the Modern Stitching +([GraphQL Tools](https://graphql-tools.com/docs/schema-stitching/stitch-type-merging) v7+) relies on +Type Merging that provides great performance and developer experience. + +Let's review the main features of Modern Stitching and how it differentiates from Federation. + +### Explicit Fields Resolutions via Queries + +Modern Stitching **does not require types to have an origin** (like Federation entities). + +All subschemas, being **independent**, can define types that might exist in other sibling services. + +The only requirement to allow GraphQL Tools to merge types together is that each **subschema must +expose a query to resolve a type**, as follows: + +```graphql +# Posts subschema +type Post { + id: ID! + text: String + userId: ID! +} + +type User { + id: ID! + posts: [Post!]! +} + +type Query { + postById(id: ID!): Post + postsByUserId(userId: ID!): User +} + +# Users subschema +type User { + id: ID! + email: String +} + +type Query { + userById(id: ID!): User +} +``` + +Here, our "Posts" subschema adds a field to the `User` type (`User.posts`). + +To be able to resolve the `User` type on the following query: + +```graphql +query UserPosts { + userById(id: "1") { + email + posts { + id + } + } +} +``` + +We need to add the `postsByUserId(userId: ID!)` query to resolve the `User` type on the "Posts" +service. + +As explained in the +[GraphQL Tools v7+ documentation](https://graphql-tools.com/docs/schema-stitching/stitch-type-merging#merging-flow), +here is the merging flow performed when processing the `UserPosts` operation: + +![Untitled](./how-not-to-learn-graphql-3.png) + +Modern Stitching's Type Merging approach follows the initial design of GraphQL resolvers but for +types sharing fields across multiple subschemas. + +Such design leaves your subschemas independent (working on a schema only requires local knowledge) +and built using vanilla-GraphQL (without a framework-specific mental model) while the stitching +configuration is done at the gateway level. + +### Improved Performance with Automatic Batching + +The main reproach made to the old Stitching approach was around performance. + +Modern Stitching brings fast Type Merging with 2 features: + +**Batching** + +Let's say that we execute the following operation on a new `users` query added to the Users service: + +```graphql +query UsersPosts { + users { + email + posts { + id + } + } +} +``` + +In order to resolve each user's posts, Modern Stitching will have to call `postsByUserId` Query on +the Posts service many times. + +Fortunately, if we transform the existing `postsByUserId` by the following: + +```graphql +postUsersByIds(ids: [ID!]!): [User]! +``` + +and properly configure our gateway, Modern Stitching will group the `User.posts` resolutions into a +single `postUsersByIds` query to the Posts service. + +**Query batching** + +The same logic is applied by Modern Stitching at a higher level. + +If an operation needs to call a service multiple times, all those operations will be merged as one. +For example, on a hypothetical Products schema, the following queries: + +```graphql +query { + products(ids: [1, 2, 5]) { + name + } +} +query { + seller(id: 7) { + name + } +} +query { + seller(id: 8) { + name + } +} +query { + buyer(id: 9) { + name + } +} +``` + +Will be transformed to the following unique operation that will be sent to the Products service: + +```graphql +query { + products_0: products(ids: [1, 2, 5]) { + name + } + seller_0: seller(id: 7) { + name + } + seller_1: seller(id: 8) { + name + } + buyer_0: buyer(id: 9) { + name + } +} +``` + +Those two features +([Batching](https://graphql-tools.com/docs/schema-stitching/stitch-type-merging#batching) and +[Query batching](https://graphql-tools.com/docs/batch-execution)) significantly improve the +performance of a Modern Stitching architecture at scale. + +### The Flexibility of Server Implementations: No Subscriptions Limitation + +When used with the Modern Stitching programmatic API (without the `@merge` and `@canonical` +directives), building subschemas is achieved with vanilla GraphQL. + +It means that each subschema can be built with any technologies, framework, and libraries and +require no compatibility work (while Federation requires subschemas to use a Federation-compatible +GraphQL server). + +It is also true for the gateway server. + +Modern Stitching is a set of utilities that let you choose your own GraphQL-compatible HTTP +server/library (JavaScript): + +- GraphQL Yoga +- Nest.js +- GraphQL Helix + Express +- Apollo Server + +Finally, Modern Stitching allows you +[to **support GraphQL Subscriptions in a composed schema**](https://graphql-tools.com/docs/server-setup#adding-subscriptions-support) +(over WS or SEE). + +## You Might Not Need Stitching + +Today, the most popular solutions for composing a unified GraphQL layer are done over the network +(stitching with remote schemas or via Apollo Federation gateway). + +The main reason for doing that is the separation in development and deployment workflows and +allowing teams to have ownership over the schema. + +However, such a setup creates performance issues due to the network overhead. + +While Apollo +[works on a software solution to gap the gateway latency](https://apollographql.com/blog/announcement/backend/apollo-router-our-graphql-federation-runtime-in-rust), +a solution might reside at the organization level. + +Another popular trend in the JavaScript ecosystem is monorepo. + +Thanks to the evolutions of tools ([lerna](https://github.com/lerna/lerna), +[yarn workspaces](https://classic.yarnpkg.com/lang/en/docs/workspaces)) and from the GitHub platform +(code owners system), monorepo is a viable and frequent pattern for teams to work together on a +complex stack. + +The separation in development and deployment workflows and allowing teams to have ownership over the +schema can also be achieved by merging the subschemas at build time and deploying it as a gateway +that runs local modules, with no network overhead. + +![gateway.png](./how-not-to-learn-graphql-4.png) + +Using a monorepo with schema merging at build time solves the incompressible performance issue while +preserving the ownership of the subschemas and guaranteeing that deployed subschemas are compatible. diff --git a/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/preview.png b/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/preview.png new file mode 100644 index 00000000000..5dc7085e524 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-not-to-learn-graphql/preview.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-should-you-pin-dependencies-and-why/list.png b/packages/web/docs/src/app/blog/(posts)/how-should-you-pin-dependencies-and-why/list.png new file mode 100644 index 00000000000..e99efed7698 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-should-you-pin-dependencies-and-why/list.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-to-write-graphql-resolvers-effectively/page.mdx b/packages/web/docs/src/app/blog/(posts)/how-to-write-graphql-resolvers-effectively/page.mdx new file mode 100644 index 00000000000..99733598470 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/how-to-write-graphql-resolvers-effectively/page.mdx @@ -0,0 +1,464 @@ +--- +title: How to write GraphQL resolvers effectively +authors: eddeee888 +tags: [graphql, codegen, node, server, typescript] +date: 2024-11-04 +description: + Learn GraphQL concepts like resolver map, resolver chain, mappers, defer resolve and use GraphQL + Code Generator and Server Preset to write resolvers. +--- + +import { Callout } from '@theguild/components' + +Resolvers are the fundamental building blocks of a GraphQL server. To build a robust and scalable +GraphQL server, we must understand how to write GraphQL resolvers effectively. In this blog post, we +will explore: + +- how resolvers work +- concepts such as resolver map, resolver chain, defer resolve and mappers +- tools and best practices + +## Glossary + +- **Resolver map**: An object containing resolvers that match the types and fields in the GraphQL + schema. +- **Resolver chain**: The order of resolvers execution when the GraphQL server handles a request. +- **Mapper**: The shape of the data returned by a resolver to become the `parent` parameter of the + next resolver in the resolver chain. +- **Defer resolve**: A technique to avoid unnecessary resolver execution of a field early in the + resolver chain. + +## What Are Resolvers? + +In a GraphQL server, a resolver is a function that "resolves" a value which means doing arbitrary +combination of logic to return a value. For example: + +- returning a value statically +- fetching data from a database or an external API to return a value +- executing a complex business logic to return a value + +Each field in a GraphQL schema has an optional corresponding resolver function. When a client +queries a field, the server executes the resolver function to resolve the field. + +Given this example schema: + +```graphql filename="src/graphql/schema.graphql" +type Query { + movie(id: ID!): Movie +} + +type Movie { + id: ID! + name: String! + actors: [Actor!]! +} + +type Actor { + id: ID! + stageName: String! +} +``` + +We can write a **resolver map** like this: + +```ts filename="src/graphql/resolvers.ts" +const resolvers = { + Query: { + movie: () => {} // `Query.movie` resolver + }, + Movie: { + id: () => {}, // `Movie.id` resolver + name: () => {}, // `Movie.name` resolver + actors: () => {} // `Movie.actors` resolver + }, + Actor: { + id: () => {}, // `Actor.id` resolver + stageName: () => {} // `Actor.stageName` resolver + } +} +``` + +We will discuss how the code flows through resolvers when the server handles a request in the next +section. + +## Code Flow and Resolver Chain + +Using the same schema, we may send a query like this: + +```graphql +query Movie { + movie(id: "1") { + id + name + actors { + id + stageName + } + } +} +``` + +Once the server receives this query, it starts at `Query.movie` resolver, and since it returns a +nullable `Movie` object type, two scenarios can happen: + +- If `Query.movie` resolver returns `null` or `undefined`, the code flow stops here, and the server + returns `movie: null` to the client. +- If `Query.movie` resolver returns anything else (e.g. objects, class instances, number, non-null + falsy values, etc.), the code flow continues. Whatever being returned - usually called + **mapper** - will be the first argument of the Movie resolvers i.e. `Movie.id` and `Movie.name` + resolvers. + +This process repeats itself until a GraphQL scalar field needs to be resolved. The order of the +resolvers execution is called the **resolver chain**. For the example request, the resolver chain +may look like this: + +```mermaid +flowchart LR + A[Query.movie] --> B(Movie.id) + A[Query.movie] --> C(Movie.name) + A[Query.movie] --> D(Movie.actors) + D --> E(Actor.id) + D --> F(Actor.stageName) +``` + + + There are four positonal arguments of a resolver function: + - `parent`: the value returned by the parent resolver. + - For root-level resolvers like `Query.movie`, `parent` is always `undefined`. + - For other object-type resolvers like `Movie.id` and `Movie.name`, `parent` is the value returned by parent resolvers like `Query.movie` + - `args`: this is the arguments passed by client operations. In our example query, `Query.movie` resolver would receive `{ id: "1" }` as `args` + - `context`: An object passed through the resolver chain. It is useful for passing information between resolvers, such as authentication information, database connection, etc. + - `info`: An object containing information about the operation, such as operation AST, path to the resolver, etc. + + +We must return a value that can be handled by the scalars. In our example: + +- `Movie.id` and `Actor.id` resolvers must return a non-nullable value that can be coerced into the + `ID` scalar i.e. `string` or `number` values. +- `Movie.name` and `Actor.stageName` resolver must return a non-nullable value that can be coerced + into the `String` scalar i.e. `string`, `boolean` or `number` values. + + + You can learn about GraphQL Scalar, including native Scalar and coercion concept, in this guide + [here](https://the-guild.dev/blog/the-complete-graphql-scalar-guide) + + +It is important to remember that we will encounter runtime errors if the resolver returns unexpected +values. Some common scenarios are: + +- a resolver returns `null` to a non-nullable field +- a resolver returns a value that cannot be coerced into the expected scalar type +- a resolver returns a non-array value to an array field, such as `Movie.actors` + +## How to Implement Resolvers + +### Implementing Resolvers - Or Not + +If an empty resolver map is provided to the GraphQL server, the server still tries to handle +incoming requests. However, it will return `null` for every root-level field in the schema. This +means if a root-level field like `Query.movie`'s return type is non-nullable, we will encounter +runtime error. + +If object type resolvers are omitted, the server will try to return the property of the same name +from `parent`. Here's what `Movie` resolvers may look like if they are omitted: + +```ts +const resolvers = { + Movie: {}, // 👈 Empty `Movie` resolvers object, + Movie: undefined, // 👈 or undefined/omitted `Movie` resolvers object... + + // 👇 ...are the equivalent of the following `Movie` resolvers: + Movie: { + id: parent => parent.id, + name: parent => parent.name, + actors: parent => parent.actors + } +} +``` + +This means if `Query.movie` resolver returns an object with `id`, `name`, and `actors` properties, +we can omit the `Movie` and `Actor` resolvers: + +```ts +const resolvers = { + Query: { + movie: () => { + return { + id: '1', + name: 'Harry Potter and the Half-Blood Prince', + actors: [ + { id: '1', stageName: 'Daniel Radcliffe' }, + { id: '2', stageName: 'Emma Watson' }, + { id: '3', stageName: 'Rupert Grint' } + ] + } + } + } + // 💡 `Movie` and `Actor` resolvers are omitted here but it still works, + // Thanks to default resolvers! +} +``` + +In this very simple example where we are returning static values, this will work. However, in a +real-world scenario where we may fetch data from a database or an external API, we must consider the +response data shape, and the app performance. We will try to simlulate a real-world scenario in the +next section. + +### Implementing Resolvers for Real-World Scenarios + +Below is an object this can be used as an in-memory database: + +```ts +const database = { + movies: { + // Movies table + '1': { + id: '1', + movieName: 'Harry Potter and the Half-Blood Prince' + } + }, + actors: { + // Actors table + '1': { + id: '1', + stageName: 'Daniel Radcliffe' + }, + '2': { + id: '2', + stageName: 'Emma Watson' + }, + '3': { + id: '3', + stageName: 'Rupert Grint' + } + }, + movies_actors: { + // Table containing movie-actor relationship + '1': { + // Movie ID + actorIds: ['1', '2', '3'] + } + } +} +``` + +We usually pass database connection through context, so we might update `Query.movie` to look like +this: + +```ts +const resolvers = { + Query: { + movie: (_, { id }, { database }) => { + const movie = database.movies[id] // 🔄 Database access counter: (1) + + if (!movie) { + return null + } + + return { + id: movie.id, + name: movie.movieName, + actors: (database.movies_actors[id] || []).actorIds // 🔄 (2) + .map(actorId => { + return database.actors[actorId] // 🔄 (3), 🔄 (4), 🔄 (5) + }) + } + } + } +} +``` + +This works, however, there are a few issues: + +- We are accessing the database 5 times every time `Query.movie` runs (Counted using 🔄 emoji in the + previous code snippet). Even if the client may not request for `actors` field, this still happens. + So, we are making unnecessary database calls. This is called **eager resolve**. +- We are mapping `movie.movieName` in the return statement. This is fine here, but our schema may + scale to have multiple fields returning `Movie`, and we may have to repeat the same mapping logic + in multiple resolvers. + +To improve this implementation, we can use **mappers** and **defer resolve** to avoid unnecessary +database calls and reduce code duplication: + +```ts +type MovieMapper = { + // 1. + id: string + movieName: string +} +type ActorMapper = string // 2. + +const resolvers = { + Query: { + movie: (_, { id }, { database }): MovieMapper => { + const movie = database.movies[id] + + if (!movie) { + return null + } + + return movie // 3. + } + }, + Movie: { + name: (parent: MovieMapper) => parent.movieName, // 4. + actors: (parent: MovieMapper, _, { database }): ActorMapper[] => + database.movies_actors[parent.id].actorIds // 5. + // 6. + }, + Actor: { + id: (parent: ActorMapper) => parent, // 7. + stageName: (parent: ActorMapper, _, { database }) => database.actors[parent].stageName // 8. + } +} +``` + +1. We define a `MovieMapper` type to represent the shape of the object returned by `Query.movie` + resolver. +2. Similarly, we define `ActorMapper` to be returned wherever `Actor` type is expected. Note that in + this example, `ActorMapper` is a `string` representing the ID of an actor in this case, instead + of an object. +3. The `MovieMapper` is returned by `Query.movie` resolver, and it becomes the `parent` of `Movie` + resolvers in (4) and (5). +4. Since `MovieMapper` has `movieName` property, but the GraphQL type expects String scalar for + `name` instead. So, we need to return the mapper's `movieName` to the schema's `Movie.name` + field. +5. `MovieMapper` doesn't have `actors` property, so we must find all the related actors from the + database. Here, we expect an array of `ActorMapper` (i.e. an array of `string`) to be returned, + which is the `actorIds` array in the database. This just means the `parent` of each `Actor` + resolver is a string in (7) and (8) +6. We can skip implementing `Movie.id` resolver because by default it passes `MovieMapper.id` + property safely. +7. Because each `ActorMapper` is the ID of an actor, we return the `parent` instead of `parent.id` +8. Similarly, we use `parent` as the ID of the actor to fetch the actor's `stageName` from the + database. + + + By using **mappers** and **defer resolve** techniques, we avoid unnecessary database calls and + reduce code duplication. The next time we need to write a resolver that returns `Movie`, `Actor` + objects, we can simply return the corresponding mapper objects. + + +### Implementing Resolvers with Mappers and Defer Resolve Best Practices + +We saw the benefits of using mappers and defer resolve in the previous section. Here are some +TypeScript best practices to keep in mind when implementing these techniques, failing to enforce +them may result in runtime errors: + +- Mappers MUST be correctly typed as the return type of a resolver and the parent type of the next + resolver. +- Object resolvers MUST be implemented if the expected schema field does not exist in the mapper or, + the mapper's field cannot be coerced into the expected schema type of the same name. + +At a small scale, it is easy to keep track of the types and mappers. However, as our schema grows, +it becomes harder to maintain the typing and remembering which resolvers to implement. This is where +tools like [GraphQL Code Generator](https://the-guild.dev/graphql/codegen) and +[Server Preset](https://www.npmjs.com/package/@eddeee888/gcg-typescript-resolver-files) can be used: + +- GraphQL Code Generator: generates TypeScript types for the resolvers, focusing on correct + nullability, array and types. +- Server Preset: generates and wires up resolvers conveniently, and runs analysis to suggests + resolvers that require implementation, reducing the risk of runtime errors. + +To get started, install the required packages: + +```sh npm2yarn +npm i -D @graphql-codegen/cli @eddeee888/gcg-typescript-resolver-files +``` + +Next, create a `codegen.ts` file at the root of your project: + +```ts filename="codegen.ts" +import { defineConfig } from '@eddeee888/gcg-typescript-resolver-files' +import type { CodegenConfig } from '@graphql-codegen/cli' + +const config: CodegenConfig = { + schema: 'src/graphql/schema.graphql', + generates: { + 'src/graphql': defineConfig({ + resolverGeneration: 'minimal' + }) + } +} +export default config +``` + +Then, add mappers into `schema.mappers.ts` file, in the same directory as `schema.graphql`: + +```ts filename="src/graphql/schema.mappers.ts" +type MovieMapper = { + id: string + movieName: string +} +type ActorMapper = string +``` + + +Server Preset automatically detects and wires up mappers if they follow the convention: +1. The mappers are declared in a file in the same directory as the schema source file, and has the file name ending with `.mappers.ts` segment instead of the schema source file's extension. For example: + - If your schema file is `schema.graphql`, the mappers file is `schema.mappers.ts`. + - If your schema file is `schema.graphql.ts`, the mappers file is `schema.graphql.mappers.ts`. +2. The mapper type names are in the format of `Mapper` where `` is the GraphQL schema name. For example: + - If the schema type is `Movie`, then the mapper type is `MovieMapper`. + + +Finally, run codegen to generate resolvers: + +```sh npm2yarn +npm run graphql-codegen +``` + +We will see generated resolver files in `src/graphql` directory: + +```ts filename="src/graphql/resolvers/Query/movie.ts" +import type { QueryResolvers } from './../../types.generated' + +export const movie: NonNullable = async (_parent, _arg, _ctx) => { + /* Implement Query.movie resolver logic here */ +} +``` + +```ts filename="src/graphql/resolvers/Movie.ts" +import type { MovieResolvers } from './../types.generated' + +export const Movie: MovieResolvers = { + /* Implement Movie resolver logic here */ + actors: async (_parent, _arg, _ctx) => { + /* Movie.actors resolver is required because Movie.actors exists but MovieMapper.actors does not */ + }, + name: async (_parent, _arg, _ctx) => { + /* Movie.name resolver is required because Movie.name exists but MovieMapper.name does not */ + } +} +``` + +```ts filename="src/graphql/resolvers/Actor.ts" +import type { ActorResolvers } from './../types.generated' + +export const Actor: ActorResolvers = { + /* Implement Actor resolver logic here */ + id: async (_parent, _arg, _ctx) => { + /* Actor.id resolver is required because Actor.id exists but ActorMapper.id does not */ + }, + stageName: async (_parent, _arg, _ctx) => { + /* Actor.stageName resolver is required because Actor.stageName exists but ActorMapper.stageName does not */ + } +} +``` + +By providing mappers, codegen is smart enough to understand that we want to defer resolve, and we +need to write logic for `Movie.actors`, `Movie.name`, `Actor.id` and `Actor.stageName` resolvers to +ensure we don't encounter runtime errors. + + + Learn how to set up GraphQL Code Generator and Server Preset for GraphQL Yoga and Apollo Server in + this guide + [here](https://the-guild.dev/graphql/codegen/docs/guides/graphql-server-apollo-yoga-with-server-preset). + + +## Summary + +In this article, we have explored how resolvers work in a GraphQL server resolver code flow and +**resolver chain**, and how to write resolvers effectively using **mappers** and **defer resolve** +techniques. Finally, we add GraphQL Code Generator and Server Preset to automatically generate +resolvers and their types to ensure strong type-safety and reduce runtime errors. diff --git a/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/page.mdx b/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/page.mdx new file mode 100644 index 00000000000..3d4d89c46f8 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/page.mdx @@ -0,0 +1,435 @@ +--- +title: How we shipped CDN access tokens with Cloudflare Workers and R2 +tags: [graphql-hive, javascript, cloudflare, whatwg] +authors: laurin +date: 2023-02-02 +description: + Learn how we are using Cloudflare Workers to provide highly available access tokens for the Hive + schema artifacts CDN. +--- + +As we are building the GraphQL schema registry Hive we are constantly facing interesting problems +and challenges that we need to solve. + +In this post we will talk about how we are using Cloudflare Workers to provide authenticated highly +available access to GraphQL schema artifacts, e.g. SDL (Schema Definition Language), the service +lists or the Supergraph. + +## The Problem + +The availability of the GraphQL schema artifacts is mission-critical for the success of the Hive +clients to work flawless, even if there is an incident on the GraphQL Hive side. + +The schema artifacts might be consumed by GraphQL Gateways, and any outage could prevent them from +shipping to production. + +That is why from the beginning we decided to use Cloudflare Workers as a CDN for the GraphQL schema +artifacts. + +```mermaid +sequenceDiagram; + participant Client + participant CDN (CF Worker) + participant CDN Storage (R2) + + critical Validate Access Token + Client->>+CDN (CF Worker): Request GET /v1/artifact/c7ce447c-f5e6-4f13-87b8-d3051ba3fc45/sdl + CDN (CF Worker)-->>-Client: Responds with 302 presigned URL + end + Client->>CDN Storage (R2): GET: presigned URL + CDN Storage (R2)->>Client: Sends Artifact +``` + +However, in the first iteration of the token was generated based on the target id (think of it as +project id) and an application secret and, thus, static. + +That means if this key would accidentally leak to the outside world, it would be impossible to +invalidate it. Some of our customers were justifiably concerned about this. + +We needed a new solution that allows generating and deleting many different access keys in parallel, +without drastically impacting the performance of the CDN. + +On top of that, we needed the old "legacy" CDN access tokens still to be usable (and also be able to +invalidate those!) alongside the new ones, as we did not want to break consumers that already access +the CDN. + +## The Solution + +First we needed to decide upon a store in which we can store the hashes of the new CDN access +tokens. + +We were already using the Cloudflare R2 (S3 Buckets) for storing the schema artifacts. As these +artifacts are as close to the Cloudflare Workers as possible we decided that this might be the best +solution, compared to e.g. a remote Redis or PostgreSQL instance with increased latency. + +Cloudflare Workers KV was not an option, as GraphQL Hive supports both hosted (cloud) and +self-hosted (community) deployments, where the latter should be able to run without the need for ANY +external (paid) cloud services. We did not have a KV replacement available, on the other site there +are many S3 compatible storage solutions such as MinIO available. + +The whole flow for accessing the CDN artifacts looks like the following. + +```mermaid +sequenceDiagram; + participant Client + participant CDN (CF Worker) + participant CDN Storage (R2) + + critical Validate Access Token + Client->>+CDN (CF Worker): Request GET /v1/artifact/c7ce447c-f5e6-4f13-87b8-d3051ba3fc45/sdl + CDN (CF Worker)-->>-Client: Responds with 302 presigned URL + end + Client->>CDN Storage (R2): GET: presigned URL + CDN Storage (R2)->>Client: Sends Artifact +``` + +E.g. a request for fetching the access token would look like the following with `cURL`. + +```bash filename="cUrl command for fetching an CDN artifact" +curl -v \ + -H 'X-Hive-CDN-Key: kAZlQN8U7q8pMsAMVpA0fBeRvvbaKt1h9JniZbi3+5k=' \ + https://cdn.graphql-hive.com/v1/c7ce447c-f5e6-4f13-87b8-d3051ba3fc45/sdl +``` + +```bash filename="cUrl output" +/artifacts/v1/c7ce447c-f5e6-4f13-87b8-d3051ba3fc45/sdl > GET +302 < Found +location < Header: https://6d5bc18cd8d13babe7ed321adba3d8ae.r2.cloudflarestorage.com/artifacts/c7ce447c-f5e6-4f13-87b8-d3051ba3fc45/sdl +Found. +``` + +In order to get this new feature shipped flawlessly, we needed to roll it out incrementally. After +some conceptualizing we came up with the following phases: + +- Phase 1: Automatically write new "generated" legacy access tokens to the R2 buckets +- Phase 2: Run a migration to import all "legacy" access tokens in the R2 buckets +- Phase 3: Roll out the new CDN access token validation for legacy tokens +- Phase 4: Roll out the new CDN access token format and management UI in the web application + +## Phase 1 + +At a first glance Phase 1 and Phase 2 almost seem identical, however to distinguish between these +two, and the need to roll out these changes separately is crucial for ensuring integrity of the +application. + +In our web application we have a button "Connect to CDN", that automatically generates the old +"static" legacy access token for a target/project. + +If we would not alter this implementation to also write this access token to the R2 bucket, there +might be a timing issue as the CDN access key of a target/project created shortly/during the +database migration or after it might not be available on the R2 bucket and furthermore be no longer +valid once we roll out Phase 3. + +In addition to the R2 bucket, we also wanted to write a reference to the access token to our +PostgreSQL database, as we want to store some meta information such as an access token alias for the +web app later on. + +We ended up with a database schema like this. + +```sql +CREATE TABLE "public"."cdn_access_tokens" ( + "id" UUID PRIMARY KEY DEFAULT uuid_generate_v4 (), + "target_id" UUID NOT NULL REFERENCES "targets" ("id") ON DELETE CASCADE, + "s3_key" TEXT UNIQUE NOT NULL, + "first_characters" TEXT NOT NULL, + "last_characters" TEXT NOT NULL, + "alias" TEXT NOT NULL, + "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() +); +``` + +## Phase 2 + +After Phase 1 was rolled, we could continue with writing a database migration for seeding/importing +all the legacy access tokens into both the R2 bucket and our PostgreSQL database. + +For this we just used a cursor paginated SQL query for reading the targets (to which a CDN access +token is related to) and then run some logic for writing the data to the designated locations. + +```ts filename="Slonik and Zod for safely reading paginated results from PostgreSQL" +import { CommonQueryMethods } from 'slonik' +import * as zod from 'zod' + +const TargetsModel = zod.array( + zod.object({ + id: zod.string(), + created_at_cursor: zod.string() + }) +) + +async function getPaginationTargets( + connection: CommonQueryMethods, + cursor: null | Cursor +): Promise> { + const query = sql` + SELECT + "id" + , to_json("created_at") as "created_at_cursor" + FROM + "targets" + ${ + cursor + ? sql` + WHERE + ("created_at" = ${cursor.lastCreatedAt} AND "id" > ${cursor.lastId}) + OR "created_at" > ${cursor.lastCreatedAt} + ` + : sql`` + } + ORDER BY + "created_at" ASC + , "id" ASC + LIMIT + 200 + ` + + const items = await connection.query(query) + return TargetsModel.parse(items.rows) +} +``` + +The R2/S3 key for the legacy access token would be `/cdn-legacy-keys//`. In +that key we store the bcrypt hash of the static legacy CDN access token. + +After we successfully ran this database migration, we could check the PostgreSQL database and R2 in +order to see an equal amount of entries. + +## Phase 3 + +This was the scariest phase. After we seeded the new access token storage, it was now time to switch +the token validation method. + +Instead of verifying the CDN access token using an encryption secret environment variable, we would +now instead load the R2 key based on the artifact target id being accessed. + +E.g. for the URL `/v1/artifact/c7ce447c-f5e6-4f13-87b8-d3051ba3fc45/sdl`, we would load the R2 key +`/cdn-legacy-keys/c7ce447c-f5e6-4f13-87b8-d3051ba3fc45/` and then check the hash stored +in that key, against the provided access key (the `X-Hive-CDN-Key` header value). + +In case the key does not exist, or the hash would not match the access token, that means it is no +longer valid/invalid, which is later on our mechanism for allowing users to invalidate the legacy +token. + +On top of that, to prevent reading from R2 for every request and speed up the token validation for +subsequent requests, we utilized the +[Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/) for storing the token +validation result for 5 minutes. + +That means if an access token is invalidated (the R2 key is deleted), it might take up to 5 minutes +until the change is propagated. However, we felt like this is a great trade-off for by-passing +reading from R2 and running bcrypt for verifying the access token against the hash. + +```ts filename="Using the Cache API for caching access token verification" +const validateLegacyCDNAccessToken = async (args: { + targetId: string + accessToken: string + s3: S3Config + requestCache: Cache + waitUntil: FetchEvent['waitUntil'] +}): Promise => { + const cacheKey = new Request( + [ + 'https://key-cache.graphql-hive.com', + 'legacy', + args.targetId, + encodeURIComponent(args.accessToken) + ].join('/'), + { + method: 'GET' + } + ) + + const response = await args.requestCache.match(cacheKey) + + if (response) { + const responseValue = await response.text() + + const isValid = responseValue === '1' + return isValid + } + + const withCache = async (isValid: boolean) => { + const promise = args.requestCache.put( + cacheKey, + new Response(isValid ? '1' : '0', { + status: 200, + headers: { + // cache for 5 minutes + 'Cache-Control': `s-maxage=${60 * 5}` + } + }) + ) + + // don't block the request by the cache + args.waitUntil(promise) + + return isValid + } + + const key = await args.s3.client.fetch( + [args.s3.endpoint, args.s3.bucketName, 'cdn-legacy-keys', args.targetId].join('/'), + { + method: 'GET' + } + ) + + if (key.status !== 200) { + return withCache(false) + } + + const isValid = await bcrypt.compare(args.accessToken, await key.text()) + return withCache(isValid) +} +``` + +After everything was implemented and properly tested via our integration test suite, we finally +rolled it out to production. And... everything was on fire. + +![Fire Fire Fire](./phase-3-rollout-errors.png) + +We quickly responded with rolling back the latest changes and then it was time for debugging... + +![Revert](./phase-3-revert.png) + +## Phase 3.1 + +Now we needed to figure out what exactly was going wrong that let the new validation method fail. +This was especially hard, as debugging Cloudflare Worker code is fairly cumbersome. + +We were already using [Cloudflare Analytics](https://www.cloudflare.com/analytics/) for exporting +metrics in our Grafana Dashboard. So the first idea was to add the new validation method with +excessive logging and run it along-side the old validation method. + +After, this change was deployed, we quickly saw that reading the access tokens from R2 went fine, +however, validating the access tokens failed. + +After way much more time than we would like to have spent on debugging this, we finally realized +that we had used the wrong secret for generating the legacy access tokens before they were hashed +and written to R3. + +That basically meant we would have to prune all `cdn_access_token` database records and re-run our +migration with the correct secret. + +After a bit of careful and manual tinkering with the production database and a deployment later, the +database migration from Phase 2 was now run with the correct secret. + +On the dashboard, we could now finally see the new validation method to succeed. + +![Rollout](./phase-3-1-rollout.png) + +That mean it was now safe to re-apply the changes from Phase 3 and roll them out to production. + +This time everything went smooth and as expected. + +The analytics now showed that most requests hit the cached access key validation and we had no +clients complaining. + +![All good](./phase-3-1-all-good-in-da-hood.png) + +## Phase 4 + +Now the legacy tokens being handled properly, we could now focus on deploying the new access tokens +and building the UI for creating/deleting both the existing legacy access tokens and new access +tokens. + +The new access keys should be stored at `/cdn-keys//`. That +allows having an infinite amount of access keys. The value written to +`/cdn-keys//` is the bcrypt hash of the private key that is +part of the access token issued to the user. + +```text +accessToken = "hv2" + base64(accessKeyId + ":" + privateKey) +``` + +The `privateAccessKeyId` is never stored anywhere on our side, only the hash. + +The `"hv2"` prefix is a convenience for us for quickly identifying whether an access key sent to us +is a legacy or new access token. + +E.g. for the URL `/v1/artifact/c7ce447c-f5e6-4f13-87b8-d3051ba3fc45/sdl`, we would load the R2 key +`/cdn-keys/c7ce447c-f5e6-4f13-87b8-d3051ba3fc45/`. + +We can read the `accessKeyId` from the `X-Hive-CDN-Key` header value. + +```ts filename="Logic for decoding the incoming CDN access token" +export interface CDNToken { + keyId: string + privateKey: string +} + +/** + * We prefix the token so we can check fast wether a token is a new one or a legacy one. + */ +const keyPrefix = 'hv2' + +export function isCDNAccessToken(token: string) { + return token.startsWith(keyPrefix) === true +} + +const decodeError = { type: 'failure', reason: 'Invalid access token.' } as const + +/** + * Safely decode a CDN token that got serialized as a string. + */ +export function decodeCdnAccessTokenSafe(token: string) { + if (isCDNAccessToken(token) === false) { + return decodeError + } + + token = token.slice(keyPrefix.length) + + const str = globalThis.atob(token) + const [keyId, privateKey] = str.split(':') + if (keyId && privateKey) { + return { type: 'success', token: { keyId, privateKey } } as const + } + return decodeError +} +``` + +Once we decoded the incoming access token we can then read the R2 key value e.g. +`/cdn-keys/c7ce447c-f5e6-4f13-87b8-d3051ba3fc45/c7de111c-f5g9-4f13-87b8-d1267ba3ge95` +and then check the user-sent `privateKey` against the hash stored there. + +For subsequent requests, the same cache logic as for the legacy tokens is reused. + +The UI part was pretty straight-forward and less challenging to build, however it was still part of +this project. + +The new token overview: + +![All good](./phase-4-cdn-access-token-overview.png) + +Creating a new token: + +![All good](./phase-4-token-create-form.png) + +We successfully deployed this to production and then informed all our clients that are waiting for +this feature. 🎉 + +In addition, this is of course now also available for the self-hosted Hive users. + +## Conclusion + +This was an exciting and challenging project to solve and Cloudflare provides useful tools for +solving these kinds of problems. + +On the other hand debugging Cloudflare tooling is often frustrating and cumbersome, documentation is +also often scarce or non-existing for more advanced use-cases. + +Nevertheless, we are happy to finish this project successfully and are looking forward to all the +future challenges! + +In case you did not know, Hive is fully open-source and self-hostable! You can find all the code, +steps and pull requests on GitHub! + +- https://github.com/kamilkisiela/graphql-hive/pull/1003 +- https://github.com/kamilkisiela/graphql-hive/pull/1043 +- https://github.com/kamilkisiela/graphql-hive/pull/1005 +- https://github.com/kamilkisiela/graphql-hive/pull/1114 +- https://github.com/kamilkisiela/graphql-hive/pull/1120 +- https://github.com/kamilkisiela/graphql-hive/pull/1127 +- https://github.com/kamilkisiela/graphql-hive/pull/1130 +- https://github.com/kamilkisiela/graphql-hive/pull/1142 +- https://github.com/kamilkisiela/graphql-hive/pull/1143 +- https://github.com/kamilkisiela/graphql-hive/pull/1061 diff --git a/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-3-1-all-good-in-da-hood.png b/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-3-1-all-good-in-da-hood.png new file mode 100644 index 00000000000..9b47500f0b5 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-3-1-all-good-in-da-hood.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-3-1-rollout.png b/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-3-1-rollout.png new file mode 100644 index 00000000000..bf784d410cc Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-3-1-rollout.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-3-revert.png b/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-3-revert.png new file mode 100644 index 00000000000..93621ccb920 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-3-revert.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-3-rollout-errors.png b/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-3-rollout-errors.png new file mode 100644 index 00000000000..c70b87b0538 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-3-rollout-errors.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-4-cdn-access-token-overview.png b/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-4-cdn-access-token-overview.png new file mode 100644 index 00000000000..db2e4fa3afb Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-4-cdn-access-token-overview.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-4-token-create-form.png b/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-4-token-create-form.png new file mode 100644 index 00000000000..678a3b9fe1e Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/how-we-shipped-cdn-access-tokens-with-cloudflare-workers-and-r2/phase-4-token-create-form.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/improved-angular-console/78d0f670476d24762cc05eefd42a0321.png b/packages/web/docs/src/app/blog/(posts)/improved-angular-console/78d0f670476d24762cc05eefd42a0321.png new file mode 100644 index 00000000000..0c76e511676 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/improved-angular-console/78d0f670476d24762cc05eefd42a0321.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/improved-angular-console/f55951c146120a5fc45fa19066b8b6dd.webm b/packages/web/docs/src/app/blog/(posts)/improved-angular-console/f55951c146120a5fc45fa19066b8b6dd.webm new file mode 100644 index 00000000000..ddd4013d5f2 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/improved-angular-console/f55951c146120a5fc45fa19066b8b6dd.webm differ diff --git a/packages/web/docs/src/app/blog/(posts)/improved-security-with-graphql-armor-support-for-yoga-server-2/page.mdx b/packages/web/docs/src/app/blog/(posts)/improved-security-with-graphql-armor-support-for-yoga-server-2/page.mdx new file mode 100644 index 00000000000..802a3902aec --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/improved-security-with-graphql-armor-support-for-yoga-server-2/page.mdx @@ -0,0 +1,109 @@ +--- +title: Improved Security with GraphQL Armor support for Yoga Server 2 +authors: laurin +tags: [graphql, graphql-tools, security] +date: 2022-08-24 +description: Bringing security by default to GraphQL and Yoga Server. +--- + +We are utterly excited to introduce +[GraphQL Armor](https://github.com/Escape-Technologies/graphql-armor) compatibility with Yoga 2. + +## When the GraphQL Ecosystem Encounters Security + +A few weeks ago, the GraphQL Security company [Escape](https://escape.tech) released GraphQL Armor, +an open-source middleware to add a security layer on top of GraphQL endpoints and mitigate common +attacks. + +GraphQL Armor blocks abusive requests by putting reasonable and clever limits to Queries. To us, +this represents the go-to solution when using +[persisted operations](https://envelop.dev/plugins/use-persisted-operations) is not possible, like +when building a public GraphQL API. Also, even if you are building an internal API, these tools can +be handy for preventing too heavy GraphQL queries. This technical approach is also complementary to +the existing set of plugins for hardening endpoints: + +- [GraphQL Authz](/blog/graphql-authz) and + [use-operation-field-permissions](https://envelop.dev/plugins/use-operation-field-permissions) for + Access Control & Business logic +- [use-generic-auth](https://envelop.dev/plugins/use-generic-auth) and + [use-auth0](https://envelop.dev/plugins/use-auth0) for Authorization + +Thus, we decided to work together with Escape's team to continuously improve security standards and +defaults for the Yoga and GraphQL community. + +> Why couldn't you have production security best practices in Yoga by default? + +## What Do You Get by Using GraphQL Armor? + +Armor comes out of the box with a set of plugins that applies security best practices to any +production GraphQL Server: + +- Aliases Limit +- Character Limit +- Cost Limit +- Depth Limit +- Directives Limit +- Disabled Field Suggestion + +More rules are added weekly. And we are more than open to feedback and contributions! + +Note that the default configuration has been designed with conservation in mind: Adding Armor to a +production project should not interfere with legitimate requests out of the box. + +## How Does It Look like to Use GraphQL Armor with Yoga? + +GraphQL Armor relies on Envelop plugins for its security rules. + +Getting started is dead-simple: `npm install -S @escape.tech/graphql-armor` (or +`yarn add @escape.tech/graphql-armor`) + +Then let's take a minimalistic Yoga server: + +```ts +import { createServer } from '@graphql-yoga/node' +import { schema } from './schema' + +export function initServer() { + const server = createServer({ + schema + }) + return server +} +``` + +Adding GraphQL armor is just a matter of adding a few envelop plugins: + +```ts +import { EnvelopArmor } from '@escape.tech/graphql-armor' +import { createServer } from '@graphql-yoga/node' +import { schema } from './schema' + +const armor = new EnvelopArmor() +const enhancements = armor.protect() + +export function initServer() { + const server = createServer({ + schema, + plugins: [...enhancements.plugins] + }) + return server +} +``` + +This example can be found in our example repository +[github.com/dotansimha/graphql-yoga](https://github.com/dotansimha/graphql-yoga/tree/master/examples) + +import { YoutubeIframe } from '#components/youtube-iframe' + + + +## Join Us in Building the Future of GraphQL Security + +Escape's team is actively working on improving Armor and its support for Yoga Server This is just +the start of a great collaboration between our teams to ensure better security for the whole GraphQL +ecosystem. There is much more to come! Feel free to come on Armor's GitHub to ⭐ star, 🗣️ discuss, +🎉 ask them for new features, and more: + +[github.com/Escape-Technologies/graphql-armor](https://github.com/Escape-Technologies/graphql-armor) + +Talk to you soon! 🤟 diff --git a/packages/web/docs/src/app/blog/(posts)/injectable-services-in-react/59ec8df34fe4fd4bb20960e2270a12df.jpeg b/packages/web/docs/src/app/blog/(posts)/injectable-services-in-react/59ec8df34fe4fd4bb20960e2270a12df.jpeg new file mode 100644 index 00000000000..a7c8cd6bb0e Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/injectable-services-in-react/59ec8df34fe4fd4bb20960e2270a12df.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/injectable-services-in-react/734ee8644a65dfb39e62734e0a448ca7.jpeg b/packages/web/docs/src/app/blog/(posts)/injectable-services-in-react/734ee8644a65dfb39e62734e0a448ca7.jpeg new file mode 100644 index 00000000000..d5642bea46f Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/injectable-services-in-react/734ee8644a65dfb39e62734e0a448ca7.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/injectable-services-in-react/ee3c78c0569b4f8d0e0e30d18fe47416.png b/packages/web/docs/src/app/blog/(posts)/injectable-services-in-react/ee3c78c0569b4f8d0e0e30d18fe47416.png new file mode 100644 index 00000000000..8aa414099bb Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/injectable-services-in-react/ee3c78c0569b4f8d0e0e30d18fe47416.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/introducing-envelop/definition.png b/packages/web/docs/src/app/blog/(posts)/introducing-envelop/definition.png new file mode 100644 index 00000000000..bd18911b12b Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/introducing-envelop/definition.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/introducing-envelop/envelop.png b/packages/web/docs/src/app/blog/(posts)/introducing-envelop/envelop.png new file mode 100644 index 00000000000..c007ebcc3cc Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/introducing-envelop/envelop.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/introducing-envelop/page.mdx b/packages/web/docs/src/app/blog/(posts)/introducing-envelop/page.mdx new file mode 100644 index 00000000000..19f13ec8d25 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/introducing-envelop/page.mdx @@ -0,0 +1,412 @@ +--- +title: Introducing Envelop - The GraphQL Plugin System +authors: dotan +tags: [graphql, server, node, plugins, envelop] +date: 2021-07-22 +description: + 'A new tool by The Guild: A powerful new way for creating fast, modern and secure GraphQL servers.' +--- + +**Today we are super excited to share with you a new open-source library we've been working on for +the past few months!** + +![Envelop](./definition.png) + +## TL;DR + +- Envelop aims to be **The GraphQL Plugin system** ([envelop.dev](https://envelop.dev)) +- Envelop is not a GraphQL server, it's just a wrapper on top of the GraphQL engine. +- Make “hard” **GraphQL capabilities easy** by installing **powerful plugins** + ([Caching](https://github.com/dotansimha/envelop/pull/311), Tracing with + [Prometheus](https://envelop.dev/plugins/use-prometheus)/DataDog/[NewRelic](https://envelop.dev/plugins/use-newrelic)/[Sentry](https://envelop.dev/plugins/use-sentry)/[OpenTelemetry](https://envelop.dev/plugins/use-open-telemetry)/[ApolloTracing](https://envelop.dev/plugins/use-apollo-tracing), + [Loggers](https://envelop.dev/plugins/use-logger), + [GraphQL-Jit](https://envelop.dev/plugins/use-graphql-jit), + [Persisted Operations](https://envelop.dev/plugins/use-persisted-operations), Security with + [rate-limit](https://envelop.dev/plugins/use-rate-limiter)/[depth-limit](https://envelop.dev/plugins/use-depth-limit)/[Auth0](https://envelop.dev/plugins/use-auth0) + and many others from the [Plugins Hub](https://envelop.dev/plugins)) +- **Solve once and share across the ecosystem** - Each plugin works with any HTTP server or + deployment (Express/Fastify/Netlify/Vercel/AWS Lambda/Azure Functions/Cloudflare Workers/Google + Cloud Functions) and any schema builder (SDL, Apollo Federation, Nexus, TypeGraphQL and others) +- **Framework for Frameworks** - Envelop will become the new basis for GraphQL Frameworks. + [It's already available if you are using RedwoodJS](https://community.redwoodjs.com/t/using-graphql-envelop-helix-in-redwood-v0-35/2276), + and we have PRs open for Loopback, NestJS, Parse and others. +- **"Babel for GraphQL"** - Envelop also aims to be the "enrichment layer" for GraphQL. You can use + any new GraphQL Features today (`@defer`/`@stream`, `@live` queries, `@oneOf` and any open RFC + already today, even if graphql-js has not yet implemented or released it) +- `envelop` is also available on [ProductHunt](https://producthunt.com/posts/envelop-3)! + +## Overview + +**Envelop** is a lightweight _library_ that allows developers to create plugins that enriches the +GraphQL execution layer with new features. **It's the plugin system for your GraphQL layer.** + +Envelop's core is based on hooks and plugins - we believe that developers should share and +open-source small pieces of implementation and logics that can help others, while still keeping +their codebase customized to their needs with full control and power. + +**Envelop** is **schema-agnostic and HTTP-server agnostic**, meaning that it can be integrated with +any kind of setup. We do not aim to provide a complete, vendor-locking suite, since we believe that +the developer should be able to adjust any part of their application, at any time, without major +implications. + +As with any open-source created and maintained by The Guild - we created Envelop based on real-life +use-cases, coming from our clients (startups, enterprises and our own products) and from the GraphQL +community. We strive to keep our open-source modern, well maintained and always up-to-date, and +support the community around it. + +## Background + +While working with many clients on GraphQL projects, we noticed a major gap in collaboration across +projects, and a gap in knowledge sharing. + +Things were overcomplicated, and GraphQL servers just kept reinventing the wheel. + +We believe these gaps were created because many GraphQL frameworks are focused on creating a “whole” +experience, sometimes to promote their stack/product, rather than introducing real flexibility for +developers. + +Also, as GraphQL keeps evolving with new capabilities and solutions, it seems like the GraphQL +frameworks are making it hard or even impossible to use these new features like `@defer` / +`@stream`, `@live` queries, `@oneOf` and other new GraphQL features. + +We tried to locate the core of that issue, and from our point-of-view, it seemed like GraphQL was +missing a robust, simple and flexible plugin system. That's why we created **Envelop**. + +While most existing implementations of GraphQL servers/frameworks introduce feature-rich +environments, Envelop aims to introduce only hooks on top of the original GraphQL functions, without +modifying the signature, and allow you to choose the features that you need, by adding **Envelop +plugins**. + +Most existing GraphQL servers are implemented in a way that implements schema building and HTTP +server integration, meaning the features that are only relevant to the GraphQL layer “leaks” and +creates a very opinionated product. + +We believe that the `Network Transport <> GraphQL Engine <> GraphQL Schema` coupling should be +separated, and each part should take care of it's role, without mixing these features. Each layer +has its own responsibility. + +That's why we decided to create an agnostic library where you can choose your transport (HTTP / +WebSocket / anything else), choose your schema (any schema builder works with Envelop), and Envelop +will take care of the extra features. + +We also felt that for too long things haven't been moving on the server area when it comes to +GraphQL - most servers are in maintenance/support mode and don't bring anything new. + +Many extra features of GraphQL are straightforward, but not available for developers since it's not +open-source (or, bundled into specific frameworks/servers), or not transparent enough (like, +tracing, metrics, auditing, fine-grained permissions and more). We aim to change that. + +## The `envelop` Approach + +One of the goals of Envelop is to allow developers to modify/enrich their GraphQL execution layer. + +In most implementations, running a GraphQL operation consists of the following actions: + +- `parse` - takes raw GraphQL operation string and converts it into an executable DocumentNode. +- `validate` - AST based validations, that checks the DocumentNode against the GraphQL schema. +- `contextBuilding` - builds a GraphQL execution context, based on the incoming request, and + prepares for the execution. +- `variables` - parses the input variables and builds the variables object. +- `execute` - takes a GraphQL schema, operation `DocumentNode`, variables and context and runs your + resolvers. + +> There are more phases, and more workflows - It's dropped only for brevity ;) + +Envelop allows developers to create plugins that hook into any phase, and change the behaviour of +it, based on the feature it implements. The output of `envelop` are the GraphQL functions, with the +injected behaviour based on the plugins you use. + +![Envelop](./envelop.png) + +> Very initial draft of what Envelop is. + +By creating these plugins, you can create custom behaviour in a very easy way. + +Let's try to break a few plugins and understand how it works: + +- `useLogger` - hooks into the “before” of all phases, and just does `console.log`. +- `useTiming` - hooks into “before” and “after” of all phases, measures times, and then prints it. +- `useParserCache` - hooks into before and after the `parse` phase and implements caching based on + the operation string. +- `useGraphQLJit` - hooks into `execute` phase and replaces the `execute` function with + GraphQL-Jit's executor. +- `usePersistedOperations` - hooks into `parse` and replaces the `parse` function with a function + that maps a hash into a DocumentNode. +- `useGenericAuth` - hooks into context building and resolves the current user from the GraphQL + request, then hooks into the `execute` phase to verify the user authentication. +- `useOpenTelemetry` - hooks into all phases, execution and resolvers, and creates Spans for + OpenTelemetry tracing. + +**Makes sense, right?** Because if you have control of all the execution pipeline, you can easily +create very sophisticated plugins that implement things that were missing before with GraphQL, +without changing/forking GraphQL. + +![Envelop](https://envelop.dev/assets/home-communication.png) + +## Getting Started + +To get started with **Envelop**, make sure you understand the other requirements that you need: + +- You need a GraphQL schema - it doesn't matter how you created it (either with GraphQL core + library, makeExecutableSchema, or any code-first / schema-first frameworks) +- You need a HTTP server - like express, Fastify, Koa AWS Lambda or others +- You need a request normalization and GraphQL request pipeline - we recommend + [`graphql-helix`](https://github.com/contrawork/graphql-helix) for that. + +[You can also find more in-depth article and technical documentation here](https://envelop.dev/docs/getting-started) + +To get started quickly, start by installing only `@envelop/core` package in your project: + +```sh +yarn add @envelop/core +``` + +Now, take a look at the following code snippet - it creates a `/graphql` endpoint, normalizes the +incoming request with `graphql-helix`, creates the GraphQL functions with Envelop and runs the +operation: + +```ts +import fastify from 'fastify' +import { getGraphQLParameters, processRequest } from 'graphql-helix' +import { envelop, useLogger, useSchema } from '@envelop/core' + +// This creates the `getEnveloped` function for us. Behind the scene the wrapped functions are created once, here. +const getEnveloped = envelop({ + plugins: [useSchema(schema), useLogger()] +}) +const app = fastify() + +app.route({ + method: ['POST'], + url: '/graphql', + async handler(req, res) { + // Here we can pass the request and make available as part of the "context". + // The return value is the GraphQL-proxy that exposes all the functions. + const { parse, validate, contextFactory, execute, schema } = getEnveloped({ + req + }) + const request = { + body: req.body, + headers: req.headers, + method: req.method, + query: req.query + } + const { operationName, query, variables } = getGraphQLParameters(request) + + // Here, we pass our custom functions to Helix, and it will take care of the rest. + const result = await processRequest({ + operationName, + query, + variables, + request, + schema, + parse, + validate, + execute, + contextFactory + }) + + if (result.type === 'RESPONSE') { + res.status(result.status) + res.send(result.payload) + } else { + // You can find a complete example with Subscriptions and stream/defer here: + // https://github.com/contrawork/graphql-helix/blob/master/examples/fastify/server.ts + res.send({ errors: [{ message: 'Not Supported in this demo' }] }) + } + } +}) + +app.listen(3000, () => { + console.log(`GraphQL server is running...`) +}) +``` + +With that example, we only used `useLogger`, so while executing GraphQL operations, you should see +that everything you do should be printed to the log. + +## Use Plugins + +![Plugins](https://envelop.dev/assets/features-pluggable.png) + +But logging is not everything possible with Envelop. By adding more plugins, you can add more +features to your GraphQL execution, based on your app needs. + +For example, here's a cool snippet for boosting things in your execution layer: + +```ts +const getEnveloped = envelop({ + plugins: [useSchema(schema), useParserCache(), useValidationCache(), useGraphQLJit()] +}) +``` + +> By using `useParserCache` we make sure to parse every unique operation only once. By using +> `useValidationCache` we make sure to validate every unique operation only once. By using +> `useGraphQLJit` we replace the default execute function with a +> [just-in-time implementation](https://github.com/zalando-incubator/graphql-jit). + +While working with our clients, we saw that many pieces of code can be moved into an Envelop plugin, +and shared with the community. That created tons of plugins that you can now use quickly, without +implementing it on your own for that specific project! + +We also created [Envelop Plugins Hub](https://envelop.dev/plugins) : a place where you can find all +the plugins that are available for Envelop, with their documentation, versions, and some stats. +Plugin Hub is open and available for the community to add their own. + +## Write Your Own Plugins + +![Plugins](https://envelop.dev/assets/features-modern.png) + +Writing plugins for Envelop is super simple. We allow you to write code that connects to the phases +that you need, and we'll make sure to run your functions at the right time. + +Plugins can either live as internal plugins that are relevant only to your project, or you can share +it with the community as NPM package. + +To get started with a custom plugin, choose what phases you need, and create functions that handle +what you need. Envelop will provide a low-level, flexible api in each phase, so you can communicate +with the core pipeline. + +```ts +import { Plugin } from '@envelop/types' + +const myPlugin: Plugin = { + onParse({ params }) { + console.log('Parse started!', { args }) + + return result => { + console.log('Parse done!', { result }) + } + }, + onExecute({ args }) { + console.log('Execution started!', { args }) + + return { + onExecuteDone: ({ result }) => { + console.log('Execution done!', { result }) + } + } + } +} + +const getEnveloped = envelop({ + plugins: [ + /// ... other plugins ..., + myPlugin + ] +}) +``` + +**[You can find here the complete plugins documentation](https://envelop.dev/docs/plugins)** + +## Sharing `envelop`s {/* eslint-disable-line mdx/remark */} + +![Share](https://envelop.dev/assets/features-performant.png) + +In many cases, developers are looking for a way to reuse their server setup, as a +boilerplate/template. Envelop allows you to create Envelops instances and later share it with +others. + +```ts +import { envelop, useEnvelop, useSchema } from '@envelop/core' + +// Somewhere where you wish to create the basics of what you wish to share +// This defined the base plugins you wish to use as base. +const myBaseEnvelop = envelop({ + plugins: [useOrgAuth(), useOrgTracing(), useOrgLogsCollector()] +}) + +// Later, when you create your own Envelop, you can extend that and add custom plugins. +// You can also specify the schema only at this point +const myEnvelop = envelop({ + plugins: [useEnvelop(myBaseEnvelop), useSchema(myServerSchema), useMyCustomPlugin()] +}) +``` + +So if you are working in a microservices' environment, or in an enterprise that has many servers - +you can now share the entire base GraphQL setup in a single variable, and extend it based on your +needs. + +**[You can read more about sharing/composing envelops here](https://envelop.dev/docs/composing-envelop)** + +## "Babel for GraphQL" - New Features for the GraphQL Engine + +Since we allow developers to take part in any phase of the execution, it means that you can easily +add new features for the GraphQL engine, and not just features that come on top of GraphQL. + +For example, one of the Envelop plugins +([`useExtendedValidation`](https://envelop.dev/plugins/use-extended-validation)) allows developers +now to write and run GraphQL validations, with access to the operation variables. That means you can +write simple validations now without making it part of your schema. + +One of the things that is also possible now is `@oneOf` - a spec suggestion that is still in +discussion for adding input unions, but already available for you if you use Envelop, because +extended validations can access variables and can do additional things that was difficult to do +before. + +Here are some additional examples for cool new plugins: + +- `perEventContextResolver`: suggested in + [this PR](https://github.com/graphql/graphql-js/pull/2485), and almost + [available in envelop](https://github.com/dotansimha/envelop/pull/183). +- `@oneOf`: suggested in [this PR](https://github.com/graphql/graphql-spec/pull/825), and now + [available in envelop](https://github.com/dotansimha/envelop/blob/main/packages/plugins/extended-validation/README.md#union-inputs-oneof). +- [Migrate operations](https://github.com/dotansimha/envelop/pull/94) - a new suggestion for + migration GraphQL operation during `parse`, which allows simpler flow for introducing breaking + changes. +- [Public schema filter](https://github.com/n1ru4l/graphql-public-schema-filter) - for creating a + simple GraphQL schema that can be used for public APIs based on existing GraphQL schema. +- [`useOperationFieldPermissions`](https://envelop.dev/plugins/use-operation-field-permissions) - a + plugin that allows you to check if the fields queried in an operation are allowed for a user + before execution starts. + +## Adoption and Migration Path / Framework for Frameworks + +If you are already using GraphQL, you are probably using a server that comes with all the features +built-in. This is great in some cases, but if you wish to have that extra flexibility, you can +migrate to Envelop. You can even use Envelop with other server frameworks without migrating the +entire pipeline (see examples section below). + +GraphQL is also widely adopted in the JAMStack world - and libraries that offer GraphQL out of the +box migrate to Envelop to simplify parts of the code, and to allow their users to extend the GraphQL +layer in a simple way. + +[Redwood](https://redwoodjs.com) is a great example. We start with a small suggestion PR, and the +Redwood team was open for new ideas - so +[now you can use envelop if you are a Redwood user](https://community.redwoodjs.com/t/migrate-your-redwood-graphql-api-with-graphql-helix-and-envelop/2264) +! + +[Here is a thread about why Redwood now gives you the option](https://community.redwoodjs.com/t/using-graphql-envelop-helix-in-redwood-v0-35/2276) +to replace Apollo Server with [GraphQL-Helix](https://github.com/contrawork/graphql-helix) + +`Envelop`. + +During that process, we also start to work with other frameworks and support them with that: +[Loopback](https://github.com/loopbackio/loopback-next/pull/7634), +[NestJS](https://github.com/PabloSzx/nestjs-graphql/pull/1), +[Parse](https://github.com/parse-community/parse-server/pull/7394), +[Apollo Server](https://github.com/apollographql/apollo-server/discussions/5541) and others. + +We are also helping with that, so if you are migrating to Envelop and not sure what it +includes/means for your project - feel free to reach out to us (through +[GitHub](https://github.com/the-guild-org), [email](https://the-guild.dev/contact) or the +[chat box in our website](/)) and we would love to help you with that. + +## Examples + +Since we understand that Envelop doesn't come as a whole server, we create tons of examples you can +use for reference. We added examples for using several HTTP servers (express/fastify), running +different Functions/Lambda cloud providers, different schema providers (Type-GraphQL, Nexus) +subscriptions transports (SSE / GraphQL-WS), new GraphQL features like `@stream` / `@defer` and +more. + +[You can find all examples and demos here](https://envelop.dev/docs/integrations) + +## What's Next? + +We are constantly working on improving the low-level API of Envelop, so if something is missing, you +can always reach out and report an issue. We are also adding more plugins based on our use-cases. + +Like with any other open-source maintained by **The Guild**, we always welcome you to share your +thoughts, ideas, feedback, questions and issues. We also encourage developers to take an active part +in the development of the products/libraries they are using - so if you think something you wrote +can benefit others - we can help with making it a reality! diff --git a/packages/web/docs/src/app/blog/(posts)/introducing-graphql-eslint/1.png b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-eslint/1.png new file mode 100644 index 00000000000..4853f1f7543 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-eslint/1.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/introducing-graphql-eslint/page.mdx b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-eslint/page.mdx new file mode 100644 index 00000000000..513cd1c273e --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-eslint/page.mdx @@ -0,0 +1,125 @@ +--- +title: Introducing GraphQL-ESLint! +authors: dotan +tags: [graphql, graphql-eslint, eslint] +date: 2020-10-03 +description: + A new tool by The Guild, for making your GraphQL schema and GraphQL operations more standard and + robust! +--- + +**We are super excited to introduce a new development tool by The Guild!** + +While working on very large codebases (hundreds of developers on a single codebase), we've noticed a +gap in the current state-of-the-art development flow for GraphQL developers: it's very hard to +maintain standards in GraphQL schemas and GraphQL operations. + +Those could be standards that are specific for a single codebase or shared in the community. As the +GraphQL ecosystem is growing quickly, we decided it's important to create a modern integration +between GraphQL and ESLint, that supports all the modern needs of a GraphQL developer: +[`graphql-eslint`](https://github.com/dimaMachina/graphql-eslint). + +> Special thanks to [ilyavolodin](https://github.com/ilyavolodin) for his work on a similar project. +> We've joined forces to bring a new exiting tool for you! + +## TL;DR + +``` +yarn add -D @graphql-eslint/eslint-plugin +``` + +- [`graphql-eslint` in GitHub](https://github.com/dimaMachina/graphql-eslint) +- [List of available rules](https://the-guild.dev/graphql/eslint/rules) (and + [planned rules](https://github.com/dimaMachina/graphql-eslint/issues?q=is%3Aissue+is%3Aopen+label%3Anew-rule)) + +* 🚀 Validates, lints, prettifies and checks for best practices across GraphQL schema and GraphQL + operations. +* 🚀 Integrates with ESLint core (as a ESTree parser). +* 🚀 Works on `.graphql` files, `gql` usages in code files, and `/* GraphQL */` magic comments. +* 🚀 Lints both GraphQL schema and GraphQL operations. +* 🚀 Extended type-info for more advanced use-cases and sophisticated validations +* 🚀 Supports ESLint directives (for example: `# disable-next-line`) +* 🚀 Easily extendable - supports custom rules based on GraphQL's AST and ESLint API. +* 🚀 Integrates with `graphql-config` +* 🚀 Integrates and visualizes lint issues in popular IDEs (VSCode / WebStorm) + +## What It Does? + +With `graphql-eslint` you can easily validate and lint your GraphQL schema and your GraphQL +operations, enforce coding-style, best practices, and avoid runtime issues. + +![GraphQL-ESLint](./1.png 'GraphQL ESLint in action') + +It can run as a syntax validation tool (without a GraphQL schema), or as a complete validation +solution for your GraphQL operations (with a schema loaded into it). + +`graphql-eslint` works on your `.graphql` files, but also on your code-files that uses `gql` +(graphql template literal tag) or `/* GraphQL */` magic comments. + +It is fully integrated with VSCode for visualizing the reported issues, and allows you to customize +the rules, and easily add new custom ones. + +## How Does It Work? + +This package acts as a ESLint parser, and a plugin with rules. + +The parser we wrote transforms the [GraphQL AST](https://graphql.org/graphql-js/language) into +[ESTree structure](https://github.com/estree/estree), so it allows you to travel the GraphQL AST +tree easily. + +If you wish to read more about +[how it works, please refer to the docs' directory in the repo](https://github.com/dimaMachina/graphql-eslint/blob/master/docs/README.md). + +## Writing Your Own Rules + +Since it's running as a real ESLint parser, +[you can easily customize it and write your own rules](https://github.com/dimaMachina/graphql-eslint/blob/master/docs/custom-rules.md), +based on your project's needs and preferences. + +If you are familiar with the GraphQL AST structure, you can write custom rules very quickly, since +our solution comes with a wrapper that makes it easier to write and test new rules, and have full +TypeScript support. + +Need a new custom rule? Just create a file in your project, and travel the AST to make sure it +matches your needs. + +Think that rule might be valuable for others? +[Create a PR!](https://github.com/dimaMachina/graphql-eslint#contributing) + +```ts +import { GraphQLESLintRule } from '@graphql-eslint/eslint-plugin' + +const rule: GraphQLESLintRule = { + create(context) { + return { + OperationDefinition(node) { + if (!node.name || node.name.value === '') { + context.report({ + node: node, + message: `Oops, name is required on GraphQL operations!` + }) + } + } + } + } +} +``` + +## Compared to Similar Tools + +There are other solutions that offer GraphQL linting. We decided to write a modern library that fits +our clients' needs, integrates with IDEs and is easily customizable by anyone. + +- `apollographql/eslint-plugin-graphql` - offers a similar solution, but is designed in a different + architecture and unfortunately, it's not customizable and extendable, and doesn't work on some + modern solution. +- `cjoudrey/graphql-schema-linter` - works as a standalone tool (without ESLint) and offers rules + for linting GraphQL schemas only. + +## Summary + +We hope GraphQL-ESLint becomes a central place for the community to collaborate on best practices +and bring them into your team in an automated and easy way. + +We already use it in companies like Microsoft on a very large codebases, and it helped us share our +practices across thousands of developers! diff --git a/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/53cb02616ee279f3774e7ccf2b2c00ae.jpeg b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/53cb02616ee279f3774e7ccf2b2c00ae.jpeg new file mode 100644 index 00000000000..9f653f62b4d Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/53cb02616ee279f3774e7ccf2b2c00ae.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/97a878e053408c9aac2989f102fae5b2.png b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/97a878e053408c9aac2989f102fae5b2.png new file mode 100644 index 00000000000..2d05eb19aff Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/97a878e053408c9aac2989f102fae5b2.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/c7f0f0866d7bb5684a766eff7108fe1f.jpeg b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/c7f0f0866d7bb5684a766eff7108fe1f.jpeg new file mode 100644 index 00000000000..51fb4fcca48 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/c7f0f0866d7bb5684a766eff7108fe1f.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/ca013c71cd929edd8b85fd8d5aaf473c.jpeg b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/ca013c71cd929edd8b85fd8d5aaf473c.jpeg new file mode 100644 index 00000000000..dd8499636d7 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/ca013c71cd929edd8b85fd8d5aaf473c.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/db3ebe4655f21fadd3d68d71bb0342ef.jpeg b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/db3ebe4655f21fadd3d68d71bb0342ef.jpeg new file mode 100644 index 00000000000..632b357ac32 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/db3ebe4655f21fadd3d68d71bb0342ef.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/e448298f70d03901ed6d7f913684fff6.jpeg b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/e448298f70d03901ed6d7f913684fff6.jpeg new file mode 100644 index 00000000000..9138e901882 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/e448298f70d03901ed6d7f913684fff6.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/f2f4b5ebdecf4640a2cbc79f7560ec88.png b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/f2f4b5ebdecf4640a2cbc79f7560ec88.png new file mode 100644 index 00000000000..414a19bad6a Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/f2f4b5ebdecf4640a2cbc79f7560ec88.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/ff95930cdd17aff41491ea78c724bcf5.jpeg b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/ff95930cdd17aff41491ea78c724bcf5.jpeg new file mode 100644 index 00000000000..93a4656beab Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/ff95930cdd17aff41491ea78c724bcf5.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/page.mdx b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/page.mdx new file mode 100644 index 00000000000..d3e5a733e37 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/introducing-graphql-inspector/page.mdx @@ -0,0 +1,207 @@ +--- +title: 'Introducing: GraphQL Inspector' +authors: kamil +tags: [graphql, graphql-inspector] +date: 2019-01-18 +description: + Prevent breaking changes. Find broken operations. Get Schema Coverage. Check deprecated usage and + type duplicates. All as part of your CI process. +--- + +Prevent breaking changes. Find broken operations. Get Schema Coverage. Check deprecated usage and +type duplicates. All as part of your CI process. + +Throughout almost three years of working with GraphQL, me and [The Guild](/) introduced solutions +that changed the way we write our projects today. + +In order to use GraphQL in our Angular applications, we created +[Angular Apollo](https://github.com/apollographql/apollo-angular). To automate and increase +type-safety, we open-sourced +[GraphQL Code Generator](https://github.com/dotansimha/graphql-code-generator). Most recent thing +was [GraphQL Modules](https://github.com/Urigo/graphql-modules) that helped us to separate a server +into smaller, reusable, feature based parts. All of that was developed based on the experience and +used with huge success by our clients. + +Today, I'm happy to introduce another piece of our tech stack, we call it + +> GraphQL Inspector! + +**GraphQL inspector** is a tool that's main purpose is to make sure your GraphQL API and all its +clients are well-developed. + +import { VideoEmbed } from '#components/video-embed' + + + +--- + +## Key Features + +- [**Finds breaking, dangerous and safe changes**](https://graphql-inspector.com/docs/essentials/diff) + when modifying a GraphQL API +- [**GitHub Application**](https://graphql-inspector.com/install) + + [GitHub Action](https://graphql-inspector.com/docs/recipies/github) _(Bitbucket integration soon)_ +- **Command Line Interface** +- **Completely free and open-source** — host your own GraphQL Inspector +- [**Schema coverage**](https://graphql-inspector.com/docs/essentials/coverage) **—**see unused + parts of Schema based on all your clients' fragments and operations +- [**Validates operations**](https://graphql-inspector.com/docs/essentials/validate) against a + GraphQL Schema — you notice errors before run-time! +- [**Find duplicates or similar types**](https://graphql-inspector.com/docs/essentials/similar) + +--- + +## Use It in GitHub + +We offer GraphQL Inspector as a [GitHub Application](https://graphql-inspector.com/install) that you +can install in any of your repositories within a single click. That's the easiest way possible to +start using Inspector. + +![](./e448298f70d03901ed6d7f913684fff6.jpeg 'GraphQL Inspector annotates every change in your GraphQL Schema') + +## Inspector as Part of Your Workflow + +In order to use GraphQL Inspector, you need to make sure you write an entire GraphQL Schema to a +file so Inspector could see it. Git hooks fits well here. Whenever there's a new commit, the file +will be updated. + +Next, you configure GraphQL Inspector in `package.json` : + +```json +{ + "name": "my-app", + "scripts": { + "graphql:write": "graphql-inspector introspect src/schema.js --write schema.graphql" + }, + "graphql-inspector": { + "diff": true, + "schema": { + "ref": "head/master", + "path": "schema.graphql" + } + } +} +``` + +> You can read more about that in +> [“Github Application” chapter](https://graphql-inspector.com/docs/recipies/github#usage) on +> [our website](https://graphql-inspector.com). + +Now, whenever someone submits a Pull Request the GraphQL Inspector will compare schemas and fail if +there are breaking changes. + +![](./97a878e053408c9aac2989f102fae5b2.png 'Prevents from merging the Pull Request') + +## GitHub Actions + +I know GitHub Actions are not yet publicly available but if you're lucky enough, you can use them to +host your own GraphQL Inspector that is deployed and live per each commit or a pull request. Because +an Action is temporarily executed with Docker, you won't use any Cloud service or any other paid +resources. + +![](./ca013c71cd929edd8b85fd8d5aaf473c.jpeg 'An example workflow. Check your API on every pull request or commit.') + +Right now, Actions are not the most enjoyable thing but because it's free and super easy to setup we +highly recommend to take advantage of it if you don't want to use hosted by us GraphQL Inspector. + +We don't store any data, the code is open-sourced and deployed with Netlify Functions. + +> Because we strongly believe in open-source, **you can also have your own instance of GraphQL +> Inspector up and running.** + +## Bitbucket Integration + +GraphQL Inspector doesn't support Bitbucket yet, but it's on top of our roadmap, and we're starting +working on it. If you need it, please reach out or comment on +[the open issue](https://github.com/kamilkisiela/graphql-inspector/issues/54). + +## Prevent Breaking Changes + +GraphQL Inspector compares an old and a new GraphQL Schema in order to alert you about breaking +changes. It also tracks other changes, dangerous to implement and those that are entirely safe. + +This way you have a clear vision of how your Schema is developed. + +![](./db3ebe4655f21fadd3d68d71bb0342ef.jpeg 'Schema Comparison in GraphQL Inspector CLI') + +## Schema Coverage + +The idea behind it is to see which part of your schema is used, how many times and by which +operation. It only applies to documents that can be statically analyzed. + +Inspector is able to extract every fragment or operation from your TypeScript and JavaScript files. +It supports any kind of template literal tag but also `.graphql` files. + +![](./c7f0f0866d7bb5684a766eff7108fe1f.jpeg 'Shows you how many times each field is used by all fragments and operations') + +## Validate Documents and Find Deprecated Usage + +In order to find out that your operation or fragment is broken you need to run an app and execute a +query or mutation. Thanks to GraphQL Inspector, you can easily check for errors or deprecated usage +at any point of time. + +![](./53cb02616ee279f3774e7ccf2b2c00ae.jpeg) + +> I recommend to make it as part of your CI or even git hooks. + +## Find Duplicates + +Another interesting feature of Inspector allows you to find similar types and maybe even duplicates. +It's not something everyone might want to use but we found it really helpful when we were migrating +few separate GraphQL APIs to a monorepo and merging them into a single server at +[**Air France — KLM**](https://medium.com/airfrance-klm). The person who could say more about that +would be Mart Ganzevles, who's the father of that particular feature, and it was his first ever +open-source contribution. Amazing work Mart! + +![](./ff95930cdd17aff41491ea78c724bcf5.jpeg) + +## Desktop Application + +We are going to work on a Desktop version of GraphQL Inspector to boost the whole experience even +further. Imagine exploring everything in a nicely done and interactive application. You could click +on things in order to see them in details, just amazing! + +## Other Features + +- Runs a GraphQL Server with faked data within a single command +- Writes an introspection result of GraphQL Schema to a file + +## How Does It Compare to Other Tools + +**Apollo Engine** GraphQL Inspector tries to solve a bit of a different use-case. The main idea +behind Apollo Engine is to get insights of how a GraphQL server behaves in production. + +It supports schema comparison too but the main difference is that we **don't store any of your +data** and in order to track changes, **we simply take advantage of Git**. Each change is available +in git history, so the workflow is straightforward, and it's something you're already familiar with. + +Because of that, Apollo's advantage is that it takes into account production data and not only your +code. But if that is important to you, you can gather this data from your GraphQL servers today, +store it wherever you want, and we can add a feature to add this data into one of our reports. + +**GraphQL Doctor** and our tool have in common only the Schema Comparison feature, but Inspector +tracks also changes that are safe to introduce. Doctor's main goal is to run within GitHub and +prevent from breaking an API. We would love to collaborate with them, make sure we truly covered all +their use cases and maybe merge their library into ours. + +## Open-Source Community + +In [The Guild](/), we love open-source, our whole careers were possible because of that. We also +found out each other through Open-Source :) + +That's why I encourage you to help us develop GraphQL Inspector and **let's build it together**. + +Smallest piece of code, bug fix, documentation improvement or even a simplest suggestion counts as +contribution! + +## Links + +- [Website](https://graphql-inspector.com) and [documentation](https://graphql-inspector.com/docs) +- Visit the [repository](https://github.com/kamilkisiela/graphql-inspector) on GitHub +- CLI available — `yarn global add @graphql-inspector/cli` +- Checkout the [GitHub Application](https://graphql-inspector.com/install) +- See an [example](https://github.com/kamilkisiela/graphql-inspector-example/pull/3) app that uses + GraphQL Inspector diff --git a/packages/web/docs/src/app/blog/(posts)/joining-graphql-foundation/page.mdx b/packages/web/docs/src/app/blog/(posts)/joining-graphql-foundation/page.mdx new file mode 100644 index 00000000000..c9d5436bc79 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/joining-graphql-foundation/page.mdx @@ -0,0 +1,117 @@ +--- +title: The Guild is joining the GraphQL Foundation +authors: uri +tags: [graphql, foundation] +date: 2020-10-30 +description: Our plans to contribute to the GraphQL Foundation. +--- + +We are happy to announce that The Guild has joined the GraphQL Foundation as an official member! + +We are happy to join companies like Facebook, Twitter, AWS, Shopify, Apollo and +[others](https://foundation.graphql.org/members) in order to help shape the future of GraphQL and +the community. + +It is not cheap to be a foundation member, but we feel there is a big need and that we have a lot to +contribute for that important goal. + +We feel that because of our [unique structure](https://the-guild.dev/about-us), we can contribute in +unique ways to the foundation. + +We want to open the core foundation work for the community, making it easier for all of you to +contribute and influence GraphQL's future. + +Here are the first things we are going to focus on, and we would love your help with them: + +### 1. [GraphQL.org](https://graphql.org) + +graphql.org is a very important website. It is important because it's the first thing beginners +would get to when starting with GraphQL. Making that first experience smooth and easy is crucial. + +The graphql.org website is very good, but it hasn't been updated for a couple of years now since the +original creators finished with their current iteration. + +There are no active maintainers and contributions are being completely blocked. + +That leads to people getting stuck and following the resources and links into unmaintained tutorials +and libraries. + +We've decided to invest time and improve the website as much as we can! + +[Dipesh Wagle](https://github.com/Dipeshwagle) lead and [@ardatan](https://github.com/ardatan) +joined him and both have did some awesome work! Check out their PRs on the website, follow the +discussions, let us know for any feedback or improvements you have and we would love your help if +you want to contribute! + +1. [Upgrading graphql.org to Gatsby](https://github.com/graphql/graphql.github.io/pull/938) +2. [Improving graphql.org code's page](https://github.com/graphql/graphql.github.io/pull/936) + +### 2. [GraphQL.js](https://github.com/graphql/graphql-js) + +graphql-js is a powerful library that is engine behind many community libraries today. + +Ever since it was originally created by Lee Byron, which has done an amazing work creating a library +that is still so powerful and relevant, not a lot of major changes has happened. + +That is a good thing, but with time came more needs for updates and improvements. + +The current biggest improvement that is waiting is the TypeScript migration. It has been planned for +a couple of years now but not a lot of progress has been made. + +We are going to change that now. Please follow the issue, read throughout the discussion to see what +was planned and what happened, and help us unlock the work and make real progress towards a full +migration. + +### [Roadmap to TypeScript Issue](https://github.com/graphql/graphql-js/issues/2104) + +This also holds back many other things like the great work that has been done on +[stream and defer](https://github.com/graphql/graphql-js/pull/2319) (the current plan is to merge +that into master only after that TS migration has been done). + +We are actively working on it and now is the perfect time to join and help us with that effort. +Reach out and let us know if you can help and how we could support you with anything you need. + +Also, please join the new +[graphql-js working group sessions](https://github.com/graphql/graphql-js-wg). + +### 3. Community Forum + +Today the GraphQL community is scattered across many different forums. That's ok, but having a +central place to talk about common issues can be good, because it means you won't have to lock +yourself into specific solutions in order to solve common problems. We believe there is importance +of talking about shared best practices, no matter what tools and languages you use. + +We also are to blame for that as we've created our own Discord channel. The reason is that the +official GraphQL Slack wasn't being promoted and that the history of the messages there was being +deleted. + +We want to merge our own popular forum into the official GraphQL forum and revive the discussions +there and hope others would follow. For that we need to decide on the right solution. Please +[follow the relevant issue](https://github.com/graphql/foundation/issues/21) and share your +thgouhts. + +### 4. GraphQL's General Growth in the Community + +Everyone here is working on GraphQL because we think it's a good idea :) + +So we would love to see GraphQL grow and spread. + +Currently, the foundation hasn't been doing a lot of promotion and using it's assets like that +website, social accounts and things like that. One thing we believe could be valuable is a monthly +newsletter from the foundation. But instead of inventing a new one, we thought it was a better idea +to collaborate with the existing ones. + +So we've reached out to Prisma, which owns the GraphQL Weekly newsletter and has been maintaining it +for years and decided to partner with them to spread the fondation's messages! + +# Summary + +Our goals are to help open the doors for anyone who wants to be part of the community, learn and +contribute! + +Everything we wrote above is just the start, and we want to hear from you how we could improve the +GraphQL community as a whole. + +We hope that our work would help you acheive that, but we mostly want to hear from you - what are +the things that stop you from contributing and influencing the community? Let us know and let's +change that together! diff --git a/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/buy-instagram-likes.png b/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/buy-instagram-likes.png new file mode 100644 index 00000000000..79d7f948e78 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/buy-instagram-likes.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/cheap-github-stars-march.png b/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/cheap-github-stars-march.png new file mode 100644 index 00000000000..03a0447448f Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/cheap-github-stars-march.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/cheap-github-stars-may.png b/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/cheap-github-stars-may.png new file mode 100644 index 00000000000..dba28c2ab28 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/cheap-github-stars-may.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/cheap-stars-reciept.png b/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/cheap-stars-reciept.png new file mode 100644 index 00000000000..c1668f10dca Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/cheap-stars-reciept.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/premium-stargazers-on-github.png b/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/premium-stargazers-on-github.png new file mode 100644 index 00000000000..2d43d45f48a Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/premium-stargazers-on-github.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/premium-stars-reciept.png b/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/premium-stars-reciept.png new file mode 100644 index 00000000000..1c51b5eb155 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/judging-open-source-by-github-stars/premium-stars-reciept.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/layout.tsx b/packages/web/docs/src/app/blog/(posts)/layout.tsx index 5f2fa8b6977..ff4c4e49795 100644 --- a/packages/web/docs/src/app/blog/(posts)/layout.tsx +++ b/packages/web/docs/src/app/blog/(posts)/layout.tsx @@ -10,7 +10,12 @@ export default function BlogPostLayout({ children }: { children: React.ReactNode -
    p:first-of-type]:text-2xl/8')}> +
    div>:first-child]:hidden [&_main>p:first-of-type]:text-2xl/8', + )} + > {children}
    diff --git a/packages/web/docs/src/app/blog/(posts)/magical-babel-plugin-for-react-performance-boost/5f2a07bb7a73727cca4707c0e85f75e8.jpeg b/packages/web/docs/src/app/blog/(posts)/magical-babel-plugin-for-react-performance-boost/5f2a07bb7a73727cca4707c0e85f75e8.jpeg new file mode 100644 index 00000000000..ee70c101c7c Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/magical-babel-plugin-for-react-performance-boost/5f2a07bb7a73727cca4707c0e85f75e8.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/magical-babel-plugin-for-react-performance-boost/893729d5d857bd8f7ebcdfc388a150e8.jpeg b/packages/web/docs/src/app/blog/(posts)/magical-babel-plugin-for-react-performance-boost/893729d5d857bd8f7ebcdfc388a150e8.jpeg new file mode 100644 index 00000000000..b404583b2fb Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/magical-babel-plugin-for-react-performance-boost/893729d5d857bd8f7ebcdfc388a150e8.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/manage-circular-imports-hell-in-graphql-modules/page.mdx b/packages/web/docs/src/app/blog/(posts)/manage-circular-imports-hell-in-graphql-modules/page.mdx new file mode 100644 index 00000000000..eee9b440c53 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/manage-circular-imports-hell-in-graphql-modules/page.mdx @@ -0,0 +1,152 @@ +--- +title: Manage Circular Imports Hell in GraphQL-Modules +authors: arda +tags: [graphql, graphql-modules, di] +date: 2019-03-18 +description: + Designing and building modular GraphQL API may not look straight-forward. It is hard to keep a + perfect modularity with standalone and encapsulated modules. +--- + +Designing and building modular GraphQL API may not look straight-forward the first time you start. +It is hard to keep a perfect modularity with standalone and encapsulated modules. + +It is really easy to appeal to the circular imports, but that's exactly what you shouldn't do in any +case. You might say while reading this; **_I DON'T HAVE ANY WAY OF NOT CREATING CIRCULAR IMPORTS_**! + +In a previous versions of GraphQL-Modules, we used to allow users to have circular imports in their +GraphQL-Modules applications. However, it created a lot of extra logic which slows down the initial +schema generation speed, because we always need to check if there is circular imports between +modules. + +Then, if GraphQL-Modules found some, it would **merge** all the members of this **circular import** +into a one **LARGE MODULE** which was **breaking almost every principle of encapsulation and +modularity** we mentioned in previous blog-posts of our GraphQL-Modules series. + +Finally, we decided to **remove this** support; then **force** people to have **strict modularity** +in their projects. + +_Read more about [modular encapsulation](/blog/modular-encapsulation-graphql-modules)._ + +Forcing people out of a way of developing is always hard, and we've got +[questions from you](https://github.com/Urigo/graphql-modules/issues/317) about how to solve some +specific issues — so in this blog post and new doc section we will help you understand why this was +a bad practice and how to migrate from it with different use cases. + +## The Problem + +Let's assume we have 3 different entities in our database; + +- User +- Post +- Comment + +Then, if we create three different modules for these three entities; + +```graphql +type User { + id: ID + name: String + username: String + email: String + ## Some other fields + posts: [Post] + comments: [Comment] +} +``` + +```graphql +type Post { + id: ID + title: String + content: String + user: User + comments: [Comment] +} +``` + +```graphql +type Comment { + id: ID + content: String + user: User + post: Post +} +``` + +As you can see above, every module imports other modules; and this creates a circular dependency. + +You might ask if this is the only way to implement modules for these entities; because it looks like +there is no point to have different modules for those schemas. Having circular dependency is the +same situtation with having a single large module. + +import { VideoEmbed } from '#components/video-embed' + + + +## How to Solve + +Let's see what we have in terms of relationship; - `User` doesn't depend on `Post` and `Comment` - +`Post` doesn't depend on `Comment`. + +- `Comment` depends on `User` and `Post`, because it has `userId` and `postId` fields - `Post` also + depends on `User` because it has `userId` field + +So let's create modules in that way, + +```graphql +type User { + id: ID + name: String + username: String + email: String + ## Some other fields +} +``` + +```graphql +type Post { + id: ID + title: String + content: String + user: User +} +extend type User { + posts: [Post] +} +``` + +```graphql +type Comment { + id: ID + content: String + user: User + post: Post +} +extend type Post { + comments: [Comment] +} +extend type User { + comments: [Comment] +} +``` + +> Using this approach, you will have standalone modules; otherwise will create a big module which +> contains all of them like we used to handle circular deps in this way (**merging all circular +> imports**). + +Also `extend` says that it needs a main definition from imported modules which makes the connection +more readable in terms of entity relations. + +--- + +## All Posts about GraphQL Modules + +- [GraphQL Modules — Feature based GraphQL Modules at scale](/blog/graphql-modules) +- [Why is True Modular Encapsulation So Important in Large-Scale GraphQL Projects?](/blog/modular-encapsulation-graphql-modules) +- [Why did we implement our own Dependency Injection library for GraphQL-Modules?](/blog/dependency-injection-library-in-graphql-modules) +- [Scoped Providers in GraphQL-Modules Dependency Injection](/blog/graphql-modules-scoped-providers) +- [Writing a GraphQL TypeScript project w/ GraphQL-Modules and GraphQL-Code-Generator](/blog/graphql-typescript-modules-codegen) +- [Authentication and Authorization in GraphQL (and how GraphQL-Modules can help)](/blog/graphql-modules-auth) +- [Authentication with AccountsJS & GraphQL Modules](/blog/accountsjs-graphql-modules) +- [Manage Circular Imports Hell with GraphQL-Modules](/blog/manage-circular-imports-hell-in-graphql-modules) diff --git a/packages/web/docs/src/app/blog/(posts)/meteor-with-webpack-in-2018/274cf118280e652e8e0f63c442c59d9b.png b/packages/web/docs/src/app/blog/(posts)/meteor-with-webpack-in-2018/274cf118280e652e8e0f63c442c59d9b.png new file mode 100644 index 00000000000..2509717a1ca Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/meteor-with-webpack-in-2018/274cf118280e652e8e0f63c442c59d9b.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/meteor-with-webpack-in-2018/771a9c467ce313c5ab1fffb433a6686e.png b/packages/web/docs/src/app/blog/(posts)/meteor-with-webpack-in-2018/771a9c467ce313c5ab1fffb433a6686e.png new file mode 100644 index 00000000000..b6ef6a18cd8 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/meteor-with-webpack-in-2018/771a9c467ce313c5ab1fffb433a6686e.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/migrating-from-rest/page.mdx b/packages/web/docs/src/app/blog/(posts)/migrating-from-rest/page.mdx new file mode 100644 index 00000000000..ff8d9b8ae7a --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/migrating-from-rest/page.mdx @@ -0,0 +1,152 @@ +--- +title: Migrating from Schemaless REST API to GraphQL without writing any code +tags: [graphql, rest, graphql-mesh] +authors: arda +date: 2020-05-31 +description: + Migrating to GraphQL from a REST API has never been easier. With tools like GraphQL Mesh you can + do it automatically. +--- + +GraphQL was originally created in order to easily provide a powerful API on top of existing code. +The current approach people are using today while migrating from REST API to GraphQL is to create a +new schema and use GraphQL as a proxy. That has a lot of benefits because it gives us an opportunity +to rethink the API and improve it, without changing the underlining services. + +Let's first start with looking at that approach: + +## Implementing a Basic Proxy GraphQL Backend + +Let's say you have `/user` endpoint that does all CRUD operations for `User` entity with different +HTTP methods, and you would need a GraphQL schema like below; + +```graphql +type Query { + user(id: ID): User +} + +type Mutation { + createUser(input: UserInput): User + updateUser(id: ID, input: UserInput): User + deleteUser(id: ID): ID +} + +type User { + id: ID + name: String + age: Int +} + +input UserInput { + name: String + age: Int +} +``` + +And you would also need a thin business logic that proxies upcoming GraphQL requests to the REST API +using GraphQL resolvers like below; + +```js +module.exports = { + Query: { + user: (root, args) => fetch('https://myrest.com/user/' + args.id).then(res => res.json()) + }, + Mutation: { + createUser: (root, args) => + fetch('https://myrest.com/user', { + method: 'PUT', + body: JSON.stringify(args.input) + }).then(res => res.json()), + updateUser: (root, args) => + fetch('https://myrest.com/user' + args.id, { + method: 'POST', + body: JSON.stringify(args.input) + }).then(res => res.json()), + deleteUser: (root, args) => + fetch('https://myrest.com/user' + args.id, { + method: 'DELETE' + }).then(res => res.json()) + } +} +``` + +This example assumes that you have `/user/:id` endpoint that gets a `User` entity with `HTTP GET`, +deletes user with `HTTP DELETE` and updates a `User` that has the given id with the given input. +Also `/user` endpoint creates a new `User` with the given input. + +But this implementation will be hard to maintain when the REST API is updated and become bigger. + +## Using GraphQL Mesh Instead without Any Code + +GraphQL Mesh is a tool that handles multiple non-GraphQL data sources and generates an executable +GraphQL schema on top of them with a simple configuration file. +[You can check the announcement blog post to learn more](/blog/graphql-mesh) + +On top of having handlers that automatically take care of sources with schema like - +OpenAPI/Swagger, gRPC, SOAP, and others, it also has JSON Schema handler that generates a GraphQL +Schema based on the given JSON schema files. This handler can also generate JSON Schema on runtime +based on the given sample request and response data. + +First you need to create a project using `yarn` on an empty directory: + +```sh +yarn init +``` + +After that we need to install some dependencies of Mesh: + +```sh +yarn add @graphql-mesh/cli @graphql-mesh/json-schema graphql +``` + +Create a `.meshrc.yml` which is a configuration file for GraphQL Mesh on our new project: + +```yaml filename=".meshrc.yml" +sources: + - name: MyRest + handler: + jsonSchema: + baseUrl: https://myrest.com/ + operations: + - type: Query + field: user + path: /user/{args.id} + method: GET + responseSample: ./getUserResponse.json + - type: Mutation + field: createUser + path: /user + method: PUT + requestSample: ./createUserRequest.json + responseSample: ./createUserResponse.json + - type: Mutation + field: updateUser + path: /user/{args.id} + method: POST + requestSample: ./updateUserRequest.json + responseSample: ./updateUserResponse.json + - type: Mutation + field: deleteUser + path: /user/{args.id} + method: DELETE + responseSample: ./deleteUserResponse.json +``` + +As you can see in the configuration, we defined our endpoints without a single line of code. After +creating this configuration file. We need to get sample request and response files by calling those +endpoints on our local. + +With a single command, our new GraphQL endpoint is ready to serve; + +```sh +yarn mesh serve +``` + +## Not Only as a Gateway but Also a Completely Type-Safe SDK + +Mesh is able to generate a type-safe SDK from generated GraphQL API because the generated GraphQL +schema is a local `GraphQLSchema` that can be executed without binding an HTTP server. + +That means you can use GraphQL Mesh inside your existing services or clients, +[as an SDK](https://graphql-mesh.com/docs/recipes/as-sdk), just as a simple dependency, without +adding another box in your architecture. diff --git a/packages/web/docs/src/app/blog/(posts)/modular-encapsulation-graphql-modules/c806f0ae70dca7a4d1e434736f241ed5.png b/packages/web/docs/src/app/blog/(posts)/modular-encapsulation-graphql-modules/c806f0ae70dca7a4d1e434736f241ed5.png new file mode 100644 index 00000000000..47378c68850 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/modular-encapsulation-graphql-modules/c806f0ae70dca7a4d1e434736f241ed5.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/modular-encapsulation-graphql-modules/page.mdx b/packages/web/docs/src/app/blog/(posts)/modular-encapsulation-graphql-modules/page.mdx new file mode 100644 index 00000000000..7f35f72ce95 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/modular-encapsulation-graphql-modules/page.mdx @@ -0,0 +1,135 @@ +--- +title: Modular Encapsulation in Large-Scale GraphQL Projects +authors: arda +tags: [graphql, graphql-modules] +date: 2019-01-07 +description: + Why is True Modular Encapsulation So Important in Large-Scale GraphQL Projects? — GraphQL Modules + is your savior! +--- + +**TL;DR**: If you are writing a large-scale project, it's necessary to understand the relations +between your pieces of code, and how they effect each other. Otherwise, it might be difficult to +deal with changes later. + +While developing a **large-scale project**, removing, changing and modifying parts of the project +can become a very time-consuming and risky task, because understanding the full side-effects of +those changes is very hard. + +In [GraphQL-Modules](https://graphql-modules.com), you can declare your modules in a feature-based +structure, with clear enforced boundaries and avoid unexpected side-effects during development. + +Each module can declare its own scope of the complete schema: + +![](./c806f0ae70dca7a4d1e434736f241ed5.png 'Every module has its own schema, context and DI +container. Here you can see AuthModule is appended to UserModule. But, other modules don't import +AuthModule. So, they cannot access directly AuthModule DI container or context.') + +Each module has its own **_schema_**, **_context_**, **_typeDefs_**, **_resolvers,_** and +business-logic and each module is encapsulated, which limits the module's access to only its own +parts of the schema. + +**So, what's the real benefit of this?** + +Let's assume you have an authentication module; + +After a while, you decided to use **_AccountsJS_** in your **_GraphQL_** project which has +completely different implementation than the existing one. + +This can be a risky change, because you're afraid of breaking a lot of things, and you can't be sure +about the all the places in your code that uses values affected by the authentication module, for +example the global context. + +With GraphQL-Modules' approach of encapsulation, even the context is completely encapsulated, so +every module that uses your existing **_AuthenticationModule_**'s **_context_** in defined on the +`imports` of the dependent modules, and its interface is already extended by +**_AuthenticationModule_**'s **_context_** if you're using _TypeScript_. When it is removed, you +will notice that change on compile-time immediately. + +Let's take a look at some code, to show how GraphQL Modules makes you create those dependencies +explicitly. + +`AppModule` is our top Application Module; + +```typescript +import { GraphQLModule } from '@graphql-modules/core' +import { AuthenticationModule } from './modules/auth.module' + +const AppModule = new GraphQLModule({ + imports: [ + AuthenticationModule, //this module has `user` in its context + SomeImportantModuleThatUsesAuthenticatedContext + // ... + ] +}) +``` + +And there is another module that tries to use `AuthenticationModule`'s context, but it doesn't +import `AuthenticationModule`. In this case, it is not possible to get anything from +`AuthenticationModule` in the resolvers, because it is not imported. The following module doesn't +know anything about `AuthenticationModule`. + +```ts +const SomeImportantModuleThatUsesAuthenticatedContext = new GraphQLModule({ + typeDefs: SOME_TYPE_DEFS_HERE, + imports: [ + // ... + // This module doesn't import AuthenticationModule + // ... + ], + resolvers: { + // ... + Query: { + getUser(root, args, context) { + return context.user // Oops! Undefined! + } + } + // ... + } +}) +``` + +To fix this, we need to import `AuthenticationModule` into that 'important' module to make it able +to access `AuthenticationModule`'s context like below; + +```ts +const SomeImportantModuleThatUsesAuthenticatedContext = new GraphQLModule({ + typeDefs: SOME_TYPE_DEFS_HERE, + imports: [ + // ... + // This module imports AuthenticationModule + AuthenticationModule + // ... + ], + resolvers: { + // ... + Query: { + getUser(root, args, context) { + return context.user // Yes, we have that user HERE! + } + } + // ... + } +}) +``` + +So, these examples above show us that the encapsulation can be very important in the long term of +our project development. + +**We think modular approach is not just merging schemas and concatenate context factory functions to +each other.** + +Having a tool that knows how to **encapsulate modules** and **force this policy**, makes it much +easier to write modular schema, and later, even reuse existing modules and share them across +multiple projects. + +# All Posts about GraphQL Modules + +- [GraphQL Modules — Feature based GraphQL Modules at scale](/blog/graphql-modules) +- [Why is True Modular Encapsulation So Important in Large-Scale GraphQL Projects?](/blog/modular-encapsulation-graphql-modules) +- [Why did we implement our own Dependency Injection library for GraphQL-Modules?](/blog/dependency-injection-library-in-graphql-modules) +- [Scoped Providers in GraphQL-Modules Dependency Injection](/blog/graphql-modules-scoped-providers) +- [Writing a GraphQL TypeScript project w/ GraphQL-Modules and GraphQL-Code-Generator](/blog/graphql-typescript-modules-codegen) +- [Authentication and Authorization in GraphQL (and how GraphQL-Modules can help)](/blog/graphql-modules-auth) +- [Authentication with AccountsJS & GraphQL Modules](/blog/accountsjs-graphql-modules) +- [Manage Circular Imports Hell with GraphQL-Modules](/blog/manage-circular-imports-hell-in-graphql-modules) diff --git a/packages/web/docs/src/app/blog/(posts)/multiple-environments-endpoints-graphql-inspector/graph.png b/packages/web/docs/src/app/blog/(posts)/multiple-environments-endpoints-graphql-inspector/graph.png new file mode 100644 index 00000000000..2b065c9568e Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/multiple-environments-endpoints-graphql-inspector/graph.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/multiple-environments-endpoints-graphql-inspector/page.mdx b/packages/web/docs/src/app/blog/(posts)/multiple-environments-endpoints-graphql-inspector/page.mdx new file mode 100644 index 00000000000..45fe8288997 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/multiple-environments-endpoints-graphql-inspector/page.mdx @@ -0,0 +1,75 @@ +--- +title: Multiple environments and endpoints in GraphQL Inspector +authors: kamil +tags: [graphql, graphql-inspector] +date: 2020-05-02 +description: + Use different variants of GraphQL schema. Live and running GraphQL API as the source of truth of + a schema. +--- + +One of the features introduced with the new version of GraphQL Inspector is a support for **multiple +variants of GraphQL schema** and ability to use **live and running GraphQL endpoints**. + +We recently released a lot more interesting features. You can read about them in +["New GraphQL Inspector and upcoming features"](/blog/new-graphql-inspector) article. + +Here, we're going to focus on environments and endpoints only. + +## Using Environments + +The previous iteration of GraphQL Inspector worked with only a single target branch. Every Pull +Request was checked against the default branch of Git repository. We can agree, it's not ideal :) + +**The new version allows to define as many environments as you wish**. We called them "environments" +but those are just variants of GraphQL schema. You may have production environment as well as +staging and QA. + +![](../multiple-environments-endpoints-graphql-inspector/graph.png 'Using environments') + +**Using multiple environments means:** + +- notifications narrowed down to each variant of schema +- ability to define different settings per environment + +Follow the [_"Using Environments"_](https://graphql-inspector.com/docs/recipes/environments) chapter +in our docs. + +## Using Endpoints + +We highly recommend to use branches to identify environments but GraphQL Inspector is here to help +and not force any opinionated patterns. + +That's why the new version introduces a way to **use live and running GraphQL endpoints as the +source of truth of schema**. + +This approach is very useful when you don't deploy an API on every Push and your default branch is +fine with temporary breaking changes. To learn more, read +[_"Using Endpoints"_ chapter](https://graphql-inspector.com/docs/recipes/endpoints). + +--- + +## Future Plans + +We're working on **Azure and Bitbucket integrations** plus a **monitoring feature**. Talk to us to +try it out. + +We want to make **Azure** and **Bitbucket** a first-class citizens in GraphQL Inspector and give you +the same experience as you get right now with **GitHub**. + +**Monitoring** will enable you to analyze the traffic of your GraphQL APIs and provide details +needed to improve performance. Collecting information about the usage will let you safely remove +deprecated pieces of GraphQL Schema. + +> If you're interested, please reach out to us! + +--- + +## Enjoy GraphQL Inspector! + +We have big plans for Inspector, and you're very welcome to join us in that journey. + +GraphQL Inspector is a tool created by developers, for developers and that's why we'd love to get +your feedback and shape GraphQL Inspector together! + +Oh... and it's [Open Sourced](https://github.com/kamilkisiela/graphql-inspector)! diff --git a/packages/web/docs/src/app/blog/(posts)/new-graphql-inspector/page.mdx b/packages/web/docs/src/app/blog/(posts)/new-graphql-inspector/page.mdx new file mode 100644 index 00000000000..cebb219170f --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/new-graphql-inspector/page.mdx @@ -0,0 +1,106 @@ +--- +title: New GraphQL Inspector and upcoming features +authors: kamil +tags: [graphql, graphql-inspector] +date: 2020-05-05 +description: + See what's new and shape the future of GraphQL Inspector together! It's entirely open-sourced! +--- + +## Hi + +We want to tell you about the new version of GraphQL Inspector. It's been a while, but it's finally +out, and we have BIG plans for it. + +## What Is GraphQL Inspector? + +It's an entirely [open-sourced](https://github.com/kamilkisiela/graphql-inspector) and **community +driven** tool to help you improve and maintain your GraphQL stack. It comes with a **CLI, GitHub +Application and GitHub Action**. You can read more on [our website](https://graphql-inspector.com). + +## Why Should I Read the Announcement? + +First, **Inspector is free** and has **a bunch of new exciting features**. Plus, the announcement is +short, so you can quickly read it and move on to watch Tiger King or whatever... + +--- + +## What's New? + +Short overview of new features, each of them has a dedicated article with explanations and details. + +**Links to those articles:** + +- [Schema Change Notifications](/blog/schema-change-notifications) +- [Enable Remote Control in GraphQL Inspector](/blog/remote-control-graphql-inspector) +- [Validate GraphQL Schema in any Continuous Integration and Delivery Pipeline](/blog/ci-ci-graphql-inspector) +- [Multiple environments and endpoints](/blog/multiple-environments-endpoints-graphql-inspector) + +--- + +## Schema Change Notifications + +Stay up to date with changes in your GraphQL Schema. Receive notifications on Slack, Discord or even +via WebHooks. + +![Notifications on Slack](../schema-change-notifications/slack.png 'Notifications on Slack') + +## Remote Control of Pull Requests via HTTP Endpoint + +Intercept schema changes via HTTP and decide which changes are acceptable which are not and all of +this through a serverless function. + +![](../remote-control-graphql-inspector/remote-graph.png 'What is Remote Control?') + +## Multiple Environments + +The new version allows to define as many environments as you wish. We called them "environments" but +those are just variants of GraphQL schema. You may have production environment as well as staging +and QA. + +![](../multiple-environments-endpoints-graphql-inspector/graph.png 'Using environments') + +## GraphQL Endpoints + +Use live and running GraphQL endpoints as the source of schema. This approach is very useful when +you don't deploy an API on every Push and your default branch can live with temporary breaking +changes. + +## CLI for CI/CD + +GraphQL Inspector since day one supported any kind of Continuous Integration and Delivery Pipeline. +Nothing new here, except now it's more flexible and super lightweight. + +The new version of GraphQL Inspector comes with a **CLI crafter specifically for CI/CD**. + +## And a Lot More... + +We highly recommend to visit [graphql-inspector.com](https://graphql-inspector.com) and explore all +the possibilities. + +--- + +# Future Plans + +We're working on **Azure and Bitbucket integrations** plus a **monitoring feature**. Talk to us to +try it out. + +We want to make **Azure** and **Bitbucket** a first-class citizens in GraphQL Inspector and give you +the same experience as you get right now with **GitHub**. + +**Monitoring** will enable you to analyze the traffic of your GraphQL APIs and provide details +needed to improve performance. Collecting information about the usage will let you safely remove +deprecated pieces of GraphQL Schema. + +> If you're interested, please reach out to us! + +--- + +## Enjoy GraphQL Inspector! + +We have big plans for Inspector, and you're very welcome to join us in that journey. + +GraphQL Inspector is a tool created by developers, for developers and that's why we'd love to get +your feedback and shape GraphQL Inspector together! + +Oh... and it's [Open Sourced](https://github.com/kamilkisiela/graphql-inspector)! diff --git a/packages/web/docs/src/app/blog/(posts)/nextra-2/contrast-mode.png b/packages/web/docs/src/app/blog/(posts)/nextra-2/contrast-mode.png new file mode 100644 index 00000000000..176e8af7b05 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/nextra-2/contrast-mode.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/nextra-2/page.png b/packages/web/docs/src/app/blog/(posts)/nextra-2/page.png new file mode 100644 index 00000000000..41d1e4c7653 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/nextra-2/page.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/nextra-2/rtl.png b/packages/web/docs/src/app/blog/(posts)/nextra-2/rtl.png new file mode 100644 index 00000000000..7b2a21f28be Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/nextra-2/rtl.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/nextra-4/opengraph-image.png b/packages/web/docs/src/app/blog/(posts)/nextra-4/opengraph-image.png new file mode 100644 index 00000000000..7b8ace6b5ee Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/nextra-4/opengraph-image.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/nextra-4/subpages-example.png b/packages/web/docs/src/app/blog/(posts)/nextra-4/subpages-example.png new file mode 100644 index 00000000000..c9ea6d0f2f9 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/nextra-4/subpages-example.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/nodejs-esm/nodejs_logo.png b/packages/web/docs/src/app/blog/(posts)/nodejs-esm/nodejs_logo.png new file mode 100644 index 00000000000..530e7cfb98c Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/nodejs-esm/nodejs_logo.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/nodes-child-process/8e3e63cb7dfad3159760903440eb32fd.png b/packages/web/docs/src/app/blog/(posts)/nodes-child-process/8e3e63cb7dfad3159760903440eb32fd.png new file mode 100644 index 00000000000..0c19fd33891 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/nodes-child-process/8e3e63cb7dfad3159760903440eb32fd.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/offline-graphql-the-easy-parts/page.mdx b/packages/web/docs/src/app/blog/(posts)/offline-graphql-the-easy-parts/page.mdx new file mode 100644 index 00000000000..3373851ffee --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/offline-graphql-the-easy-parts/page.mdx @@ -0,0 +1,216 @@ +--- +title: Flutter GraphQL | The Guild +description: Brick is an open source package that provides offline support for GraphQL in Flutter +date: 2023-01-25 +authors: [tshedor, josiassejod1] +tags: [graphql, deepdive, flutter] +--- + +import { Callout } from '@theguild/components' + +## Offline GraphQL: The Easy Parts + +Your app is perfect. It is fast. It is pretty. It overwhelms the GraphQL server with requests. The +UX is really very good. Your CI is perfect. When there's no internet it's unusable. It has 4.9 stars +on the App Store. Your app is almost perfect. + +Make your app perfect and cache your data on the client. Tear-free. + +## Reintroducing Brick + +Brick +[runs your data](https://medium.com/flutter-community/brick-your-app-five-compelling-reasons-and-a-pizza-analogy-to-make-your-data-accessible-8d802e1e526e). +The easiest way to think of Brick is in terms of pizza, with data as your ingredients. + +- You're in a rush, and you just need a slice. You buy one from under the heat lamp. This is + memory-cached data. +- You're cooking for a small group and a frozen pie is good enough. This is disk-cached data. +- You want to sit down for a nice meal with the freshest ingredients. This is remote-fetched data. + +[Brick](https://getdutchie.github.io/brick/#/README?id=learn), +[can](https://www.youtube.com/watch?v=2noLcro9iIw) do +[a lot](https://www.youtube.com/watch?v=jm5i7e_BQq0). But we're here to tell you the good news about +fetching the same data from GraphQL, memory, and SQLite. + +Let's start with the basics. + +## Querying in Brick + +```dart +final repository = OfflineFirstWithGraphqlRepository() +// Queries can be general: +final users = await repository.get(); + +// Or specific: +final query = Query.where('email', 'user@example.com', limit1: true); +final user = await repository.get(query: query); + +// Or associated: +final query = Query.where('customer', Query.where('email', 'user@example.com')); +final user = await repository.get(query: query); +``` + +That's it. That's how you request data from every pizza source. No switching of memory/remote/disk: +it's a single entrypoint. + +## Runtime Document Generation + +While most packages utilize code generation to create GraphQL documents, Brick takes advantage of +Dart's strong typing. Your source of truth is your Dart class (which ultimately creates a +[serialize/deserialize adapter](https://github.com/GetDutchie/brick/blob/main/packages/brick_offline_first_with_rest/example/lib/brick/adapters/kitchen_sink_adapter.g.dart)). + +This permits the return type to be consistent when being used instead of a new class for every +return. By leaving the declaration in Brick, the same source of truth is used to generate and manage +SQLite columns. + +```dart +class User extends OfflineFirstWithGraphqlModel { + final String firstName; + + // Request this field as a different name + @Graphql(name: 'last_name') + final String lastName; + + // Don't request this field but send it when creating/updating + @Graphql(ignoreFrom: true) + String get name => [firstName, lastName].join(' '); + + // Don't request or send this field + @Graphql(ignore: true) + final String? email; + + // Customize parsing of GraphQL; useful for differences in language conventions + @Graphql(fromGenerator: 'UserType.values.byName(%DATA_PROPERTY%.toLowerCase())') + final UserType type; + + // Document generation can be controller in rare instances where Brick cannot + // detect the subfields of a field type + @Graphql(subfields: {'zipCode': {}}) + final Map address; + + User({ + required this.firstName, + required this.lastName, + this.email, + this.type, + }); +} + +enum UserType { customer, admin } +``` + +Brick's approach is not without pitfalls. Queries are generally unoptimized - you're always +requesting the same data as you would with REST. We found that managing separate-but-very-similar +return types was not worth the minimal optimization versus making the same request. + +That said, if your requests vary significantly for large models, consider creating two models (e.g. +`CustomerLight` and a `Customer`) to use the same structure but with smaller requests. + + + Some `upsert` requests (or mutation operations) are much simpler than sending an entire model. + These require variables to be declared, and Brick does not presently support generating + strongly-typed input classes. Instead, a basic, JS-like `Map` can be sent with + `Query`. + + +## Decipherable Requests + +Operations chill alongside business logic. When a GraphQL request is made, Brick will intelligently +create the document and use the declared operation from your configuration: + +```dart +class UserQueryOperationTransformer extends GraphqlQueryOperationTransformer { + GraphqlOperation get get => GraphqlOperation( + document: r'''query Users { + getUsers {} + }''' + ); + + const UserQueryOperationTransformer(super.query, super.instance); +} + +@ConnectOfflineFirstWithGraphql( + graphqlConfig: GraphqlSerializable( + queryOperationTransformer: UserQueryOperationTransformer.new, + ) +) +class User extends OfflineFirstWithGraphqlModel {} + +// in your state or UI: +final users = await repository.get(); +``` + +These can also be overriden per request but subfields don't need to be declared because they're +generated based on the model: + +```dart +await repository.get(query: Query( + providerArgs: { + 'document': r''' + query Users { + getActiveUsers {} + } + ''' + } +)); +``` + +## Streams! + +GraphQL's subscriptions work, youbetcha. Why wait for your data when you could render it +immediately? + +```dart +final subscription = repository.subscribe().listen((users) {}); + +// In Flutter: +StreamBuilder>( + stream: repository.subscribe(), + builder: (_, snap) => Column(children: snap.data.map(Text.new).toList()), +) +``` + +Even if your GraphQL server doesn't utilize susbcriptions, you can still use `subscribe`. Events +will be triggered every time data is inserted into the local SQLite database. + + + Always dispose [your streams](https://dart.dev/articles/libraries/creating-streams)! + + +## Now Make It Offline + +Brick follows an optimistic offline-first practice: data is inserted locally before it reaches the +server. If the data doesn't exist, Brick requests it from your GraphQL server: + +![image](https://user-images.githubusercontent.com/865897/72176226-cdd8ca00-3392-11ea-867d-42f5f4620153.jpg) + +> It says REST, but it's the same architecture for GraphQL + +Remember, Brick does all of this for you via the single entrypoint. There's no toggling between +different data sources in your implementation code. + +## Retry Queue + +If the app is offline when a mutation operation is made, the operation is added to a queue. The +queue will reattempt until it receives a successful response from the server. And it will try. And +try. And try. We've seen queues retry for 30 days. + +![image](https://user-images.githubusercontent.com/865897/72175823-f44a3580-3391-11ea-8961-bbeccd74fe7b.jpg) + +> Not your jam? Synchronous execution can be requested (circumventing the queue) by applying the +> `OfflineFirstUpsertPolicy.awaitRemote` policy. + + + You can still [use the retry + queue](https://github.com/GetDutchie/brick/blob/main/packages/brick_offline_first_with_graphql/lib/src/graphql_offline_queue_link.dart) + as an independent link in your `gql` client. You can also grab [requests about to be + retried](https://pub.dev/documentation/brick_offline_first/latest/offline_queue/RequestSqliteCacheManager/unprocessedRequests.html) + and [delete + them](https://pub.dev/documentation/brick_offline_first/latest/offline_queue/RequestSqliteCacheManager/deleteUnprocessedRequest.html) + if they're perpetually blocking the queue. + + +--- + +Lost? Inspired? Hungry? [Open an issue with us](https://github.com/GetDutchie/brick/issues). We'd +love to hear from you. diff --git a/packages/web/docs/src/app/blog/(posts)/on-demand-shared-graphql-subscriptions-with-rxjs/page.mdx b/packages/web/docs/src/app/blog/(posts)/on-demand-shared-graphql-subscriptions-with-rxjs/page.mdx new file mode 100644 index 00000000000..ee85d75f053 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/on-demand-shared-graphql-subscriptions-with-rxjs/page.mdx @@ -0,0 +1,560 @@ +--- +title: On-Demand Shared GraphQL Subscriptions with RxJS +authors: gthau +tags: [graphql, subscriptions, typescript, rxjs] +date: 2023-01-27 +description: + Trigger on-demand expensive subscriptions and share results between multiple subscribers +--- + +import { Callout } from '@theguild/components' + +GraphQL has native support for subscriptions as schema operations. GraphQL subscriptions allow to +receive continuous updates, usually implemented using a persistent WebSocket connection or +Server-Sent Events. + +Ideally, we want subscriptions to be stateless and independent for every subscriber: when a +subscriber subscribes to an entity, this subscription is independent of any other subscription and +can be handled by any instance of our GraphQL server. This is the case when the entity subscribed to +is "simple" and processing updates is trivial: for example, we receive events from a pubsub +provider/message queue (e.g. Redis, Nats.io, etc.) in the form `{ type: update, entityId: xyz }`. In +this case, our subscription handler can simply subscribe to these events, filter on the requested +`entityId`, and forward those events (maybe a bit processed) to the subscriber. + +However, this ideal scenario is not always possible. We might have "complex" entities we want to +subscribe to, e.g. views that are aggregates of data over several data sources. The view might be +expensive to fetch and subscribing to the view's content updates might involve subscribing to +multiple events and performing expensive processing and/or additional fetching in order to compute +the updates and send them to the subscriber. + +In this case, if multiple subscribers subscribe to the same view, the events' processing, additional +fetching, and computing would have to be done for each subscriber, which will lead to performance +issues as the number of subscribers increases. + +In this article, we'll show how we can improve this use case by sharing the subscriptions' sources +and also perform their side-effect processing only when there are subscribers. + + + The solution uses [RxJS](https://rxjs.dev/), a library for [Reactive Functional + Programming](https://en.wikipedia.org/wiki/Functional_reactive_programming), it is based on the + [Observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) and provides [a wide range of + functions](https://rxjs.dev/api?type=function) to deal with streams of events in a declarative + way. + + +## Mutualizing the Subscription's Work + +If the subscription is independent of the subscriber (e.g. the logged-in user) and only dependent +upon the view, then we can mutualize the events' listening, buffering, processing, additional +fetching, and computing required to update the view. This work can be done once and the result +delivered to each subscriber. + +## A Solution + +### A Simple Example to Illustrate the Solution + +In the following lines, we'll use a very simplified example in order to present the solution: a +GraphQL subscription that produces sequential integers. Then we'll apply the solution to a more +complex real-life example. + +The solution to the simple example is implemented in this +[repository](https://github.com/gthau/graphql-shared-subscriptions-with-rxjs). + +### Using RxJS Multicasting Capabilities + +By default observables are lazy and cold, meaning their side-effect are run only when a subscriber +subscribes to the observable. Furthermore, for every subscriber, the observable pipeline is +executed. + +In the following example, the http query will be executed twice: `query$` is a cold observable, no +side-effect happens until a subscriber subscribes to it. When a second subscriber subscribes, the +observable side-effect is re-executed. + +```typescript +const query$ = httpClient.get('/path/to/some/resource') +query$.subscribe(console.log) +query$.subscribe(console.log) +``` + +This is in opposition to Promises which are eager and hot: in this example the http query is +executed only once. + +```typescript +const queryPromise = fetch('/path/to/some/resource') +queryPromise.then(console.log) +queryPromise.then(console.log) +``` + +However, RxJS has multicasting capabilities to turn cold observables into hot observables. Using +RxJS's multicasting operators, we can create observable pipelines which are shared: their +side-effect is triggered when the first subscriber subscribes to it and when more than one +subscriber subscribes, the pipeline is executed only once and the emitted value is passed to all +subscribers. + +Let's see an example: + +```typescript +import { interval } from 'rxjs' + +const obs$ = interval(1000) // observable emitting 0, 1, 2, ... every second +obs$.subscribe(console.log) // prints 0, 1, 2, ... + +setTimeout(() => obs$.subscribe(console.log), 3000) // after 3 seconds, prints 0, 1, 2, ... +``` + +However, using the [`share`](https://rxjs.dev/api/operators/share) operator, the observable pipeline +is executed once and the second subscriber (even subscribing later), gets the same (shared) result +as the first subscriber. + +```typescript +const obs$ = interval(1000).pipe(share()) // observable emitting 0, 1, 2, ... every second +obs$.subscribe(console.log) // prints 0, 1, 2, ... + +setTimeout(() => obs$.subscribe(console.log), 3000) // after 3 seconds, prints 3, 4, 5 ... +``` + +## Sharing the Observables + +In the simple example above, we have multiple subscribers subscribing to a shared observable, in the +same context of execution. However, in GraphQL subscriptions, the 2nd (or nth) subscriber will have +its own context of execution. If we want to reuse a shared observable, we have to store it. + +Let's create a module to manage the subscriptions' shared observables: + +```typescript +// util function to get or create a publisher +// takes a "cache" of publisher and a factory function and returns a higher-order function to get or create from this cache +export const getOrCreatePublisher = (publishersMap, makePublisherFn) => entityId => { + if (publishersMap.has(entityId)) { + return publishersMap.get(entityId)! + } + + const newPublisher = makePublisherFn(entityId) + publishersMap.set(entityId, newPublisher) + return newPublisher +} +``` + +```typescript +// assuming that the entityId is a number, and the shared observable emits numbers +const publishersMap = new Map>() + +const makePublisher = (entityId: number): Observable => { + return interval(1000).pipe(share()) +} + +export const getPublisher = getOrCreatePublisher(publishersMap, makePublisher) +``` + +Now from our GraphQL's subscription resolver, we can use it like the following: + +```typescript +import { createSchema } from 'graphql-yoga' +import { eachValueFrom } from 'rxjs-for-await' + +export const schema = createSchema({ + typeDefs: /* GraphQL */ ` + type Subscription { + giveMeInts: Int! + } + `, + resolvers: { + Subscription: { + giveMeInts: { + subscribe(root, args, context) { + return eachValueFrom(getPublisher(args.entityId)) + }, + resolve(root, args, context) { + return root + } + } + } + } +}) +``` + +With this code, when a subscriber subscribes, it will create the shared observable (publisher), +store it in a cache so that it can be reused by subsequent subscribers, and subscribe to it. When a +second subscriber subscribes, it will get the existing publisher from the cache and subscribe to it, +and it will get the same value as the first subscriber gets. When all subscribers have unsubscribed, +the source observable is unsubscribed. If later, a new subscriber subscribes, the source observable +will be resubscribed and its pipeline reexecuted (in our case, it will start emitting from 0 again). + + + The graphql-js engine expects the `subscribe` handler to return an AsyncIterable, so we use the + [`rxjs-for-await` package from Ben Lesh](https://github.com/benlesh/rxjs-for-await) which contains + different functions from transforming an Observable into an AsyncIterable (there are different + functions to deal with backpressure and lossiness/losslessness). + + +So far, so good. But we have a memory leak: after all subscribers have unsubscribed, we keep the +publishers in the cache. We can clean those up using the +[`finalize`](https://rxjs.dev/api/operators/finalize) operator: this operator allows running a +callback on observable termination (either the observable is complete or has no more subscribers). + +Let's modify our publisher factory function: + +```typescript +import { interval } from 'rxjs' +import { finalize, share } from 'rxjs/operators' + +const makePublisher = (entityId: number): Observable => { + return interval(1000).pipe( + // clean up publisher cache when no more subscribers to this publisher + finalize(() => publishersMap.delete(entityId)), + share() + ) +} +``` + +### Delay the Shared Observable Cleanup + +Because the view might be expensive to fetch and update, we only do so when users are subscribing to +it. In the previous example, as soon as all users have unsubscribed, the shared observable is +unsubscribed and deleted from the cache. This might not be the desired behavior, e.g.: + +- we can have one user subscribing to the view, this user refreshes his browser's page, effectively + ending the subscription, and triggering a cache cleanup, but then the view is resubscribed almost + immediately, triggering a new fresh fetch +- we can have one user stopping the subscription (e.g. navigating away from this view to another + part of our app), but another user subscribing soon (a few milliseconds, seconds or minutes) after + +To optimize for those use cases, we can delay the unsubscription of the shared observable when no +more subscribers are subscribing to it, using the `resetOnRefCountZero` config of the `share` +operator. This is available in RxJS v7. + +```typescript +const makePublisher = (entityId: number): Observable => { + return interval(1000).pipe( + // clean up publisher cache when no more subscribers to this publisher + finalize(() => publishersMap.delete(entityId)), + share({ + // when no more subscribers, wait 10 seconds to unsubscribe from the source and reset the underlying subject + resetOnRefCountZero: () => timer(10_000) + }) + ) +} +``` + +In the example above, after all subscribers have unsubscribed (`refcount` is 0) the connector (the +subject that subscribes to the source observable) now waits for 10 seconds (10000 ms) before it +resets the shared observable. During this period of time, the observable pipeline continues to +execute. If a new subscriber subscribes before the `timer` has emitted, the `refcount` is again +greater than 0 and the connector will keep connecting to the source observable. + + + We use `timer` in this example but we can use any observable (e.g. receiving a specific event from our message queue) that emits at some point. Be careful though to make sure that the observable you use will actually emit otherwise you'll have a memory leak. To remedy this issue, you might want to add safety, e.g. using [`raceWith`](https://rxjs.dev/api/operators/raceWith) with `timer` to make sure you don't wait indefinitely: + +```typescript +share({ + // when no more subscribers, wait for 'my_event' up to 1 minute then unsubscribe from the source and reset the underlying subject + resetOnRefCountZero: () => pubsub.events('my_event').pipe( + raceWith(timer(60_000)), + ), +}), +``` + + + +### Reuse Shared Observable Sources across Multiple Subscriptions + +With this solution, we can even go further: if a subscription depends on the processing of common +events or on the source of another subscription, we can reuse the shared observable created for +those. + +Let's make a new subscription that returns a sentence with the current number emitted by the +`giveMeInts` subscription's source: + +```typescript +export const schema = createSchema({ + typeDefs: /* GraphQL */ ` + type Subscription { + giveMeInts: Int! + giveMeStringfiedInts: String! + } + `, + resolvers: { + Subscription: { + /* giveMeInts subscription's resolver unchanged */ + giveMeStringfiedInts: { + subscribe(root, args, context) { + return eachValueFrom(getStringifiedPublisher(args.entityId)) + }, + resolve(root, args, context) { + return root + } + } + } + } +}) +``` + +And the new publisher reusing the other subscription's publisher: + +```typescript +import { map } from 'rxjs/operators' + +// create a new cache for those publishers +const stringifiedPublishersMap = new Map>() + +export const getStringifiedPublisher = getOrCreatePublisher( + stringifiedPublishersMap, + makeStringifiedPublisher +) + +function makeStringifiedPublisher(entityId: number): Observable { + // this new publisher gets or creates the source publisher from the other subscription + return getPublisher(id).pipe( + map(val => `The current number is ${val}`), + // clean up publisher cache when no more subscribers to this publisher + finalize(() => stringifiedPublishersMap.delete(entityId)), + share({ + // when no more subscribers, wait 10 seconds to unsubscribe from the source and reset the underlying subject + resetOnRefCountZero: () => timer(10_000) + }) + ) +} +``` + +Now, whether a subscription to `giveMeStringfiedInts` or `giveMeInts` starts first, both will be +shared and in sync. This further mutualizes the processing of expensive computations. In the simple +example showcased above, the advantage might be small, but in a real-world scenario, it could make a +big difference: imagine one subscription sending the full structure of a expensive-to-compute view +while a second subscription sends only a diff of the current structure with the previous one, the +second subscription's source depends on the source of the first. Mutualizing the view's computations +makes a difference regardless of which subscription has been triggered first. + +### Run Server-Side Logic When a User's Subscription Is Closed + +In GraphQL we implement subscriptions by writing two functions: + +- a `subscribe` function which is run one time only and must return an AsyncIterable +- a `resolve` function which is run every time a value is emitted by the AsyncIterable returned by + the `subscribe` function and transforms this value into the Subscription output type. + +In order to execute logic, we have 2 places: in `subscribe` when the subscription starts and in +`resolve` on each emitted value. What if we want to execute logic when the subscription ends +(whether it was completed or was stopped by the client)? + +The answer is: it is not contemplated in the spec. But it doesn't mean we can't do it. In fact, it +is even very simple! If we use the solution described above, we already have all the pieces in +place. We only need to add one line: in the `subscribe` function where we set up the AsyncIterable +(by transforming the Observable into an AsyncIterable), we only have to use RxJS's `finalize` +operator. + +```ts +import { createSchema } from 'graphql-yoga' +import { eachValueFrom } from 'rxjs-for-await' +import { finalize } from 'rxjs/operators' + +export const schema = createSchema({ + typeDefs: /* GraphQL */ ` + type Subscription { + giveMeInts: Int! + } + `, + resolvers: { + Subscription: { + giveMeInts: { + subscribe(root, args, context) { + return eachValueFrom( + getPublisher(args.entityId).pipe( + finalize(() => { + // run logic here when subscription ends + console.log(`Subscription giveMeInts has ended for user ${context.userId}`) + }) + ) + ) + }, + resolve(root, args, context) { + return root + } + } + } + } +}) +``` + +In the previous examples, we used the `finalize` operator on the shared observable pipeline in order +to clean up after all subscriptions to a specific entity had ended. But we can also use it per +subscription by piping it onto the shared observable: this way it is only run for when this specific +subscription ends. And of course, like we just did, you can combine both! + +## Apply to a Real-Life Example + +Let's consider a view such as a complex "playlist": this playlist is a list of items organized in +blocks, the items themselves are a complex aggregate of several multimedia (video, audio, graphics) +objects, those multimedia objects themselves might be aggregate (bundles) or have of some children +objects (e.g. sub-clips, graphics on a video, subtitles). In effect, this complex playlist is +organized in the form of a tree of objects. + +The system emits different events: + +- some are related directly to the playlist itself (e.g. new item in the playlist, item removed from + the playlist, item changed position inside playlist) +- others are related to the parent multimedia objects (object deleted, object's metadata changed, + object has new children objects) +- others are related to the children objects of the top level objects (object deleted, object's + metadata changed) + +This playlist's data is expensive to fetch, can be large, and is cached only when users subscribe to +it: because it is expensive to compute updates, we want to ensure we don't process events for all +playlists in the system, only the ones some user is currently interested in. + +Let's reuse the techniques explained above. + +### Buffer Events + +First, we'll buffer the events, dedupe them and share the observable because those events will be +checked against every playlist subscribed to, so we can share the buffering and deduplication: + +```typescript +// utils +export const isNotEmpty = ({ length }: ArrayLike) => length > 0 +``` + +```typescript +import { from } from 'rxjs' +import { buffer, filter, share } from 'rxjs/operators' + +const playlistEventNames = ['playlist_event_1', 'playlist_event_2', 'playlist_event_n'] as const +const objectsEventNames = ['objects_event_1', 'objects_event_2', 'objects_event_n'] as const + +function bufferEvents(eventNames, bufferTime = 500) { + return from(pubsub.events(eventNames)).pipe(buffer(bufferTime), filter(isNotEmpty), share()) +} + +const allPlaylistEvents$ = bufferEvents(playlistEventNames) +const allObjectsEvents$ = bufferEvents(objectsEventNames) +``` + +### Create Shared Publishers for the Playlists' Structure Changes + +In this example, we'll assume that the datasource is the source of truth, we'll listen to events to +detect potential changes in the playlist and we'll force refetch (invalidate cache and fetch latest +version) from the datasource (assuming calls are deduped using a Dataloader backed by a cache). + +```typescript +import { merge } from 'rxjs' +import { bufferTime, filter, finalize, map, share, switchMap } from 'rxjs/operators' + +const playlistStructureMap = new Map>() +const playlistStructureDiffMap = new Map>() + +export const getPlaylistStructurePublisher = getOrCreatePublisher( + playlistStructureMap, + makePlaylistStructurePublisher +) + +export const getPlaylistStructureDiffPublisher = getOrCreatePublisher( + playlistStructureDiffMap, + makePlaylistStructureDiffPublisher +) + +function makePlaylistStructurePublisher(playlistId: number): Observable { + const playlistEvents$ = allPlaylistEvents$.pipe( + map(events => events.filter(ev => ev.playlistId === playlistId)), + filter(isNotEmpty) + ) + + const playlistObjectsEvents$ = allObjectsEvents.pipe( + // this function filter the events that can have an impact on the structure of the playlist + // e.g. "object has a new child object" + // and discard other events that have no impact on the structure + // e.g. "object name has changed" + map(events => filterObjectEventsThatAffectPlaylistStructure(events)), + // only keeps events where the objectId refers to an object that belongs to this playlist's structure + map(events => filterObjectsThatBelongToPlaylist(events, playlistId)), + filter(isNotEmpty) + ) + + return merge(playlistEvents$, playlistObjectsEvents$).pipe( + // buffer because both source observables might emit shortly one after another + bufferTime(500), + filter(isNotEmpty), + // fetchPlaylistStructure is a function backed by a dataloader with cache + // passing `true` as 2nd param to force fetch from datasource instead of cache + switchMap(_ => fetchPlaylistStructure(playlistId, true)), + // remove publisher from cache after it's been unsubscribed + finalize(() => { + console.debug( + `Shared playlistStructurePublisher[playlist=${playlistId}] has finalized, removing it from cache` + ) + playlistStructureMap.delete(playlistId) + }), + share({ + // keep subscribing for 1 minute after last subscriber unsubscribes + resetOnRefCountZero: () => timer(60_000) + }) + ) +} + +function makePlaylistStructureDiffPublisher(playlistId: number): Observable { + // reuse the playlist shared observable + return getPlaylistStructurePublisher(playlistId).pipe( + startWith([]), // pairwise needs 2 values to compare, initialize observable with a default value + pairwise(), // groups emission into a tuple [previousValue, currentValue] + map(([previousPlaylist, currentPlaylist]) => makeDiff(previousPlaylist, currentPlaylist)), + filter(isNotEmpty), + // remove publisher from cache after it's been unsubscribed + finalize(() => { + console.debug( + `Shared playlistStructureDiffPublisher[playlist=${playlistId}] has finalized, removing it from cache` + ) + playlistStructureDiffMap.delete(playlistId) + }), + share({ + // keep subscribing for 1 minute after last subscriber unsubscribes + resetOnRefCountZero: () => timer(60_000) + }) + ) +} +``` + +### Writing the Subscription's Resolver + +Now that we have all the subscription's required logic encapsulated and data processing shared, +let's use it from the subscription's resolver: + +```typescript +import { createSchema } from 'graphql-yoga' +import { eachValueFrom } from 'rxjs-for-await' + +export const schema = createSchema({ + typeDefs: /* GraphQL */ ` + type Subscription { + playlistStructureUpdates(playlistId: Int): PlaylistStructure! + playlistStructureDiffs(playlistId: Int): PlaylistStructureDiff! + } + `, + resolvers: { + Subscription: { + playlistStructureUpdates: { + subscribe(root, args, context) { + return eachValueFrom(getPlaylistStructurePublisher(args.playlistId)) + }, + resolve(root, args, context) { + return root + } + }, + playlistStructureDiffs: { + subscribe(root, args, context) { + return eachValueFrom(getPlaylistStructureDiffPublisher(args.playlistId)) + }, + resolve(root, args, context) { + return root + } + } + } + } +}) +``` + +## Conclusion + +In this article, we have seen how to use RxJS (or the reactive programming library of your choice) +in order to mutualize the generation of expensive-to-produce values for a GraphQL subscription but +also to trigger this processing on-demand whenever a consumer is interested in subscribing to a +specific entity updates and stop it (with or without delay) when there are no more subscribers. Note +that this technique is not exclusive to GraphQL subscriptions and can be used for any kind of +subscription. diff --git a/packages/web/docs/src/app/blog/(posts)/open-source-apollo-federation/page.mdx b/packages/web/docs/src/app/blog/(posts)/open-source-apollo-federation/page.mdx new file mode 100644 index 00000000000..19d0d4581eb --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/open-source-apollo-federation/page.mdx @@ -0,0 +1,171 @@ +--- +title: Open Source composition and validation library for Apollo Federation +tags: [graphql, graphql-federation] +authors: [kamil] +date: 2023-10-05 +description: + Introducing MIT licensed drop-in replacement for the Apollo Federation composition library. +--- + +The Guild is proud to unveil its latest contribution to the GraphQL community: **an MIT-licensed +drop-in replacement for `@apollo/composition` library**. + +We recognized the importance of the existing ecosystem of tools and existing knowledge base. That is +why `@theguild/federation-composition` library shares the same API, produces the exact same +supergraph SDL, and has an identical set of validation rules (including error messages and codes). + +- [GitHub repository](https://github.com/the-guild-org/federation) +- [NPM package](https://www.npmjs.com/package/@theguild/federation-composition) + +## What Is Composition and Validation? + +[**GraphQL Federation**](https://the-guild.dev/graphql/hive/federation) is an architectural approach +for building and managing GraphQL APIs at scale. It consists of several key components: + +- **Subgraphs**: These are individual GraphQL services that represent specific domains or + capabilities. Each subgraph serves a portion of the overall data and functionality that the API + provides. +- **Supergraph**: The supergraph is the unified GraphQL schema that contains all the subgraphs. It's + essentially the merged view of all the individual subgraphs, creating a single API. +- **Schema registry**: This is a central repository that stores the schemas of the subgraphs. It's + where the gateway retrieves information about the available subgraphs and their schemas. +- **Gateway**: The gateway is responsible for handling incoming GraphQL requests from clients. It + requires a supergraph to know how to route these requests across the appropriate subgraphs. The + gateway retrieves this supergraph from the Schema Registry. +- **Validation and composition**: The process of composing the supergraph involves merging the + schemas of the individual subgraphs. During this step, the system also checks for any potential + errors that might arise from the composition. This is crucial to ensure that the resulting + supergraph is both correct and compatible. + +As you can see, the **"Validation and Composition"** step is critical to ensure reliability and +integrity of the API within the schema registry. + +## Why Did We Create an Open Source Alternative? + +The restrictions enforced by Elastic License 2.0 blocks vendors interested in offering a schema +registry as a service to use the `@apollo/composition` library. This library is crucial for +composing and validating federated subgraphs, as it's the only available solution. + +This situation creates a monopoly that prevents or at least slows down innovation in this space. + +We wanted to change that and introduced alternatives: + +- **[GraphQL Hive](https://the-guild.dev/graphql/hive)** - replacement for Apollo Studio +- [**`@theguild/federation-composition`**](https://www.npmjs.com/package/@theguild/federation-composition) - + Open source alternative to `@apollo/composition` + +--- + +> In parallel to this, we're working together with [ChilliCream](https://chillicream.com/) on an +> **Open source alternative for Apollo Federation**, called +> [**GraphQL Fusion**](https://chillicream.com/blog/2023/08/15/graphql-fusion). +> +> You can watch a talk from [GraphQLConf 2023](/blog/graphqlconf-2023-recap) about GraphQL Fustion +> [here](https://graphql.org/conf/schedule/4a4e842d1cd0c06083f484d31225abd1/?name=GraphQL%20Fusion:%20Rethinking%20Distributed%20GraphQL). + +--- + +## A Familiar API + +One of the core features of our open source replacement is its seamless compatibility with the +`@apollo/composition` API. This means that transitioning to our solution is not only effortless but +also ensures that developers can continue working with the familiar interface they've come to rely +on. + +```diff +- import { composeServices } from '@apollo/composition'; ++ import { composeServices } from '@theguild/federation-composition'; +import { services } from './services'; + +const compositionResult = composeServices(services); +``` + +## Identical Validation and Composition Rules + +Our composition library maintains absolute fidelity to the original library. It has the same set of +validation rules and provides identical error messages. **This ensures that existing projects and +workflows remain uninterrupted, while still benefiting from the dramatic performance enhancements +and no vendor locking.** + +Maintaining consistent validation rules, error codes, and error messages with the +`@apollo/composition` library is crucial as it streamlines the process of troubleshooting +composition errors using the official Apollo Federation documentation, GitHub issues, and +StackOverflow questions. + +```diff filename="Example validation rule" +- import { composeServices } from '@apollo/composition'; ++ import { composeServices } from '@theguild/federation-composition'; +import { services } from './services'; + +const compositionResult = composeServices(services); + +console.log(compositionResult); + +// Outputs + +{ + "errors": [{ + "message": " + [users] On field "User.profile", for @provides(fields: "name"): field "Profile.name" should not be part of a @provides since it is already provided by this subgraph (it is not marked @external)", + "extensions": { + "code": "PROVIDES_FIELDS_MISSING_EXTERNAL" + } + }] +} +``` + +Because both libraries yields the same error code and message, you can look up +`PROVIDES_FIELDS_MISSING_EXTERNAL` on the +[Apollo Federation error documentation](https://www.apollographql.com/docs/federation/errors) or ask +other Apollo Federation users for help. + +## Identical Supergraph SDL + +Our composition library is capable of producing supergraph SDL. This means that it can seamlessly +integrate with [Apollo Router](https://www.apollographql.com/docs/router/), +[Apollo Server](https://www.apollographql.com/docs/apollo-server/), Apollo Gateway and any other +tool that supports supergraph SDL. + +> It allows other vendors to introduce new schema registries, +> [GraphQL Hive](https://the-guild.dev/graphql/hive) is a good example of that. + +## 20x Times Faster + +In head-to-head comparisons, our implementation outperforms the original by a significant margin. +When working with simple schemas, our solution is a staggering **5 times faster**. + +However, when it comes to large-scale graphs, the difference is even more pronounced, with our +implementation boasting an astounding **20 times speed improvement** (231ms instead of 4.48s). + +```text +cpu: Apple M1 Max +runtime: node v20.8.0 (arm64-darwin) + +benchmark time (avg) (min … max) p75 p99 p995 +------------------------------------------------- ----------------------------- +• small supergraph (4 subgraphs) +------------------------------------------------- ----------------------------- +apollo 4.05 ms/iter (2.98 ms … 9.33 ms) 4.38 ms 7.85 ms 9.33 ms +guild 771.93 µs/iter (573.46 µs … 2.45 ms) 763.42 µs 2.04 ms 2.16 ms + +summary for small supergraph + guild + 5.25x faster than apollo + +• huge supergraph (100+ subgraphs) +------------------------------------------------- ----------------------------- +apollo 4.48 s/iter (4.41 s … 4.53 s) 4.51 s 4.53 s 4.53 s +guild 231.71 ms/iter (217.79 ms … 265.92 ms) 232.53 ms 265.92 ms 265.92 ms + +summary for huge supergraph + guild + 19.32x faster than apollo +``` + +--- + +## Join the Collaboration + +We're inviting developers, GraphQL enthusiasts from all corners of the world to join us in this +exciting venture. Whether it's through contributing code, reporting issues, or providing feedback, +your involvement will play a pivotal role in shaping the future of this project. diff --git a/packages/web/docs/src/app/blog/(posts)/open-source-graphql-security/page.mdx b/packages/web/docs/src/app/blog/(posts)/open-source-graphql-security/page.mdx new file mode 100644 index 00000000000..6682209b6ce --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/open-source-graphql-security/page.mdx @@ -0,0 +1,249 @@ +--- +title: Building Open Source GraphQL Security +authors: nohehf +tags: [graphql] +date: 2023-12-08 +description: + Learn how open-source boosts GraphQL security and explore defensive and offensive tools, + resources, and best practices to protect your GraphQL APIs. +--- + +# Open-Source GraphQL Security, Importance & Tools + +Open-source principles have forged GraphQL as a standard, it's ecosystem and community. Bringing +value via transparency and collective knowledge as well as empowering the community with tools and +practices to build better, bigger & more efficient APIs from day to day. Now that GraphQL APIs are +well-established and with a lots of queries on a daily basis, ensuring their security becomes +crucial. + +In this exploration, we'll dive into how these open-source practices are important and benefits for +the security of GraphQL and which community tools you can leverage today to secure your APIs. + +## GraphQL Is Open-Source at Its Core + +The inception and evolution of GraphQL are intrinsically tied to the principles of open-source, +ensuring it flourished not just as a query language but as a community-driven initiative progressing +toward broader, unified API development standards. From its public +[specifiction release](https://github.com/graphql/graphql-spec) in 2015 by Facebook to its +transition to the [GraphQL Foundation](https://graphql.org/foundation/) in 2018—hosted by the Linux +Foundation—its journey has been transparent and collaborative. + +While the GraphQL specification paved the way for consistent and efficient API queries, the +open-source community enhanced this original draft further. Take the +[Relay Pagination Specification](https://relay.dev/graphql/connections.htm), for instance, which +streamlines how pagination can be handled in GraphQL (among other React specific features), +providing a standard methodology that developers can rely on. Similarly, the introduction of custom +directives allowed GraphQL to become more flexible and adaptable to specific use-cases, like the +[@rest directive](https://the-guild.dev/graphql/tools/docs/schema-directives#fetching-data-from-a-rest-api) +or +[@live queries](https://the-guild.dev/blog/subscriptions-and-live-queries-real-time-with-graphql). + +Moreover, the vast majority of widespread GraphQL tools have and are being built under open-source +licences. Servers implementations, frontend clients, utilities like schema management, code +generation or even fully fledged API platforms are legion. You can find a comphrensive list of such +tools on the [graphql.org website](https://graphql.org/code/). [The Guild](https://the-guild.dev/) +are also masters when it comes to GrahpQL & Open-source and their tools come highly recommended. + +For instance, as GraphQL is transport agnostic, it can be used with any protocol, a tremendous +amount of open-source tools have been built to leverage this flexibility, especially by +[@enisdenjo](https://github.com/enisdenjo): [graphql-http](https://github.com/graphql/graphql-http) +[graphql-ws](https://github.com/enisdenjo/graphql-ws) +[graphql-sse](https://github.com/enisdenjo/graphql-sse) + +I can also mention the work on unified APIs via +[GraphQL federation](https://the-guild.dev/graphql/hive/federation) that is also under active +open-source development, with the +[Composite Schema Working Group](https://github.com/graphql/composite-schemas-wg) initiative, and +upcoming +[GraphQL Fusion](https://graphql.org/conf/sessions/4a4e842d1cd0c06083f484d31225abd1/?name=GraphQL%20Fusion:%20Rethinking%20Distributed%20GraphQL) +specification. + +And last but not least, the GraphQL community is also very community centered, wether via the +[GraphQL working group](https://graphql.org/community/developers/#working-groups) or the various +events and meetups such as the [GraphQL Conf](https://graphql.org/conf/). + +In essence, GraphQL isn't simply open-source in its availability but embodies open-source in its +ongoing development, enhancements, and community engagement, perpetually enriching its ecosystem +with diverse inputs, insights, and innovations. + +## The Importance of Open Source and Public Resources in Security + +Keeping the internet safe is a big challenge. It's like a tightly connected place with lots of +potential threats. Open source and public tools have been doing a great job at protecting it for +years. + +Cybersecurity thrives on collaboration, exemplified by the Common Vulnerabilities and Exposures +([CVE](https://cve.mitre.org/)) system. This public database acts as a central repository for known +cybersecurity threats, allowing for quick dissemination and response. By making vulnerabilities +public, the CVE system ensures timely and widespread implementation of security measures. For +instance, the identification and patching of the Heartbleed bug in OpenSSL was significantly aided +by the CVE system, showcasing its effectiveness in promoting rapid response. + +Open source plays a crucial role in cybersecurity by fostering an environment of transparency and +collaboration. It allows for the open identification and resolution of vulnerabilities, benefiting +the entire digital ecosystem. For example, the Linux Kernel, known for its security, continually +improves through community contributions. Similarly, tools like Kali Linux offer insights into +offensive cybersecurity strategies, helping developers strengthen their defenses. + +This combination of open source and CVE is especially vital in areas like GraphQL and API security, +providing a foundation for robust, adaptable cybersecurity strategies. Through shared knowledge and +tools, open source and CVE create a proactive defense against evolving cyber threats. + +In a field like cybersecurity, where dangers morph quickly, having shared knowledge, united +alertness, and available-to-all tools become an essential shield against potential attacks. The +marriage of open source and cybersecurity offers a lively platform for ongoing learning, changing, +and sharing attack and defense mechanisms, crafting a global, united front against cyber threats. + +Moving forward, we'll dive into how this perfect pairing of open source and cybersecurity is +crucial, not just relevant, for GraphQL and API security. We'll highlight practical tools and +strategies you can use today to protect your applications. + +## Open Source GraphQL Security Ressources & Tools + +There are many GraphQL open-source tools available to help developers and businesses defend against +possible cybersecurity threats. From defensive measures that shield sensitive data to offensive +tools aimed at identifying vulnerabilities, the open-source community has build invaluable resources +to cover a wide variedy of cyber-security needs. + +### Learning Tools & Resources: Armoring with Knowledge + +In the sphere of cybersecurity, especially concerning GraphQL, the adage 'knowledge is power' is +paramount. Continual learning, embracing best practices, and leveraging insights from the community +is an essential shield to secure APIs against vulnerabilities. + +1. **Best Practices** + + - [Persisted/Trusted queries](https://benjie.dev/graphql/trusted-documents): This feature allows + you to save bandwidth and improve performance by sending a hash of the query instead of the + full query. It also has a huge impact on security, as it prevents attackers from sending + arbitrary queries to your server. Check GraphQL Yoga + [persisted operations](https://the-guild.dev/graphql/yoga-server/docs/features/persisted-operations) + - [9 GraphQL Security Best Practices](https://escape.tech/blog/9-graphql-security-best-practices/): + Dive into Escape's comprehensive guide which unveils nine pivotal security best practices, + presenting a blend of actionable insights and theoretical knowledge to fortify GraphQL + implementations against potential threats. + - [The Guild's best practices article](https://the-guild.dev/blog/unleash-the-power-of-fragments-with-graphql-codegen): + While this resource by The Guild isn't strictly security-focused, it provides invaluable best + practices on GraphQL clients that, when adeptly applied, augment the robustness and efficiency + of GraphQL APIs, subsequently enhancing their inherent security. + - [Official authorization docs](https://graphql.org/learn/authorization/) The official GraphQL + documentation provides a comprehensive guide to authorization, which is a crucial aspect of + security in your API. Generally speaking, knowing the specification and documentation is a key + to understanding how your application works and therefore how to secure it. + +2. **[API Security Academy](https://escape.tech/blog/api-security-academy/)** + + The API Security Academy, an open-source platform developed by Escape, navigates through the + multifaceted world of GraphQL security. A wellspring of knowledge, it offers structured learning + paths, exploring vulnerabilities, attack vectors, and preventive strategies, thereby forging a + security-savvy developer who can intuitively construct and validate secure APIs. + +3. **Blogs and More** + + Explore the many insights and experiences shared by experts through different channels: + + - **Blogs**: Immerse yourself in rich content through blogs from + [Escape](https://escape.tech/blog/) and [The Guild](https://the-guild.dev/blog/), offering a + spectrum of perspectives, learnings, and strategies around GraphQL and cybersecurity. + - **Videos**: Discover visual insights through a collection of videos curated by + [GraphQL WTF](https://graphql.wtf/). Although not strictly centered around security, + understanding various facets of GraphQL enhances your capability to architect, implement, and + secure GraphQL APIs more effectively. + +Online security is always changing, often at a rapid pace. By adhering to best practices, engaging +with learning platforms and tapping into the collective knowledge shared through blogs and videos, +we arm ourselves and our APIs against the multifaceted cybersecurity threats that persist in the +digital realm. + +This path of continuous learning and adaptation ensures that as developers and cybersecurity +professionals, we remain up to date to secure our GraphQL APIs against both prevalent and emerging +threats. + +### Defensive Tools + +1. **[GraphQL Armor](https://github.com/Escape-Technologies/graphql-armor)** + + Developed by our tech team at Escape, GraphQL Armor is a middleware plugin designed to be an + immediate security upgrade for your GraphQL server. Acting like a personal bodyguard for your + data, it integrates seamlessly with [Envelop](https://the-guild.dev/graphql/envelop) — a + flexible plugin system introduced by The Guild for crafting high-quality, performant GraphQL + extensions. GraphQL Armor adheres to the ethos of providing accessible, user-friendly security + solutions that can be efficiently integrated into your GraphQL setup, safeguarding it from + potential vulnerabilities and threats. + +2. **[GraphQL Shield](https://github.com/dimatill/graphql-shield)** + + GraphQL Shield empowers developers with a permission layer for applications, securing APIs by + utilizing an intuitive rule-API that activates the Shield engine on every request. Moreover, it + smartly caches data to keep your application sprightly and ensures internal data remains under + wraps, enhancing both performance and security. + +With open-source defensive tools like GraphQL Armor and GraphQL Shield, businesses and developers +can reinforce the security of their GraphQL APIs, protecting data and operations from unauthorized +access and potential malicious activities. Navigating through the extensive open-source ecosystem +and leveraging these security tools not only fortifies your GraphQL APIs but also enriches the +collective knowledge and defense mechanisms of the community, aligning with the spirit of +collaborative development and security. + +In the upcoming section, we'll delve into offensive tools and resources for learning to equip you +with a holistic arsenal for safeguarding your GraphQL APIs. + +### Offensive Tools: A Proactive Step towards Fortified Defense + +When it comes to ensuring an unbreakable security perimeter, adopting an offensive approach is +pivotal. By putting our defenses to the test and simulating possible attacks, we preemptively expose +potential vulnerabilities and weaknesses before they can be exploited maliciously. This strategy is +basically following the idea: the best defense is a good offense. Let's uncover two powerful +open-source tools that allow us to proactively secure our GraphQL APIs by adopting an offensive +stance. + +1. **[Goctopus](https://escape.tech/blog/introducing-goctopus/)** + + Say hello to Goctopus, our open-source GraphQL endpoint discovery and fingerprinting tool. This + tentacled marvel meticulously hunts down GraphQL endpoints, fingerprinting their authentication + methods and schema availability, ensuring not a single detail slips through its grasp. Built by + the Escape team, Goctopus lends developers and businesses a watchful eye, enabling them to + identify and comprehend the specifics of GraphQL endpoints with precision, ensuring that they + can reinforce any possible points of vulnerability within their organization before they become + a target. + +2. **[Clairvoyance](https://github.com/nikitastupin/clairvoyance)** + + Meet Clairvoyance, a savvy tool designed to peek into GraphQL APIs even when introspection is + disabled, which is a common security measure (e.g., + [Apollo Server automatically disables introspection in production environments](https://www.apollographql.com/docs/tutorial/schema/#explore-your-schema)). + Clairvoyance subtly and skillfully retrieves schema information from these APIs, providing + developers with insights into the API structure and potential vulnerability points. This becomes + particularly vital when dealing with APIs that have sought to shield their schema details as a + security measure. + +Both Goctopus and Clairvoyance offer distinctive capabilities, allowing developers and cybersecurity +professionals to proactively and meticulously assess and bolster their GraphQL API defenses. By +utilizing these offensive tools, we position ourselves a step ahead, ensuring that our APIs are not +just constructed with security in mind but are continually assessed and enhanced to defend against +evolving cybersecurity threats. + +As we move forward, our journey will venture into open-source learning resources and best practices +in GraphQL security, ensuring that your armory is not just stocked with tools but also with +knowledge and strategies to implement them effectively. + +## Wrapping up and Joining Forces + +Huge shoutout to [The Guild](https://the-guild.dev/) for hosting this dive into the depths of +GraphQL and open-source magic. Their work, and the work of countless others in our robust community, +propels GraphQL to new heights daily. Your code, knowledge, and keen eyes are the gears moving this +ecosystem forward. + +But hey, let's not stop there! **Contribute, Engage, Elevate.** That's the open-source mantra. Your +expertise could well be the next big leap forward for GraphQL tools and platforms. Check out these +awesome GraphQL projects on [graphql.org/code](https://graphql.org/code/) and the +[Awesome GraphQL security list](https://github.com/Escape-Technologies/awesome-graphql-security)! +Here at [Escape](https://escape.tech/), our open-source initiatives and SaaS solutions are all about +injecting the GraphQL space with robust, unshakeable security defenses. But it's a team sport, and +every contribution counts. + +Let's keep the momentum, elevate our game in cybersecurity, and shape a future where GraphQL isn't +just powerful and efficient but an unassailable fortress in API development. + +Thanks for tagging along on this adventure. Together, let's build a safer, and better GraphQL +ecosystem. diff --git a/packages/web/docs/src/app/blog/(posts)/optimize-bundle-size-with-swc-and-graphql-codegen/page.mdx b/packages/web/docs/src/app/blog/(posts)/optimize-bundle-size-with-swc-and-graphql-codegen/page.mdx new file mode 100644 index 00000000000..206bb268601 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/optimize-bundle-size-with-swc-and-graphql-codegen/page.mdx @@ -0,0 +1,202 @@ +--- +title: Optimize your Bundle Size with SWC and GraphQL Codegen +authors: jessevelden +tags: [client-preset, codegen] +date: 2023-04-18 +description: + 'Optimize your Bundle Size with the SWC plugin for client-preset for GraphQL Code Generator' +--- + +import { Callout } from '@theguild/components' + +With the new +[`client-preset` for GraphQL Code Generator](https://the-guild.dev/blog/unleash-the-power-of-fragments-with-graphql-codegen) +it is possible to write your GraphQL queries inlined, next to your component in a type-safe way. +This can greatly improve the Developer Experience (DX). + +Instead of writing your operations in a separate file, you can write them inline in your component, +and use the `graphql` function to parse the operation and get the correct types. + +{/* prettier-ignore */} +```tsx +// Instead of operations in a separate .graphql file and importing generated hooks: +import { useGetCartByIdQuery } from './graphql/generated-hooks' +const { data } = useGetCartByIdQuery({ variables: { id: '1' } }) + +// You can write your operations directly next to your component and get the correct types: +import { graphql } from './graphql' +const GetCartByIdDocument = graphql(/** GraphQL */ ` + query GetCartById($id: ID!) { + id + items { + id + ...CartItem + } + } +`) +// Awesome! We have the correct types for this operation even when we specify it next to our component! +``` + +However, this approach can come at a cost - the bundle size of your pages can increase +significantly, especially as the number of queries and mutation grows. In this post, we will show +you what the problem is and how to optimize your bundle size with the new +[`@graphql-codegen/client-preset-swc-plugin`](https://www.npmjs.com/package/@graphql-codegen/client-preset-swc-plugin) +plugin for your applications supported by the SWC compiler such as projects using +[Next.js](https://nextjs.org) or [Vite](https://vitejs.dev/). + +## The Generated Code When Using the `client-preset` + +When using the `client-preset` without any additional optimization, your site's bundle can quickly +become bloated with unnecessary imports on every page/ component where you use GraphQL. This is +because by default `client-preset`'s `graphql` function that enables a great TypeScript DX, has to +have all the GraphQL operations available to it to parse the operation and have the correct types. + +The generated code output for the `graphql` function is a function that looks similar to the +following code, where code-splitting and tree-shaking is not easily possible. + +```ts +const documents = { + 'fragment CartItems on CartItem {\n name\n quantity \n }\n ': types.CartItemsFragmentDoc, + 'query GetCartById($id: ID!) {\n cart(id: $id) {\n id \n items { \n id \n ...CartItem\n }\n totalItems\n}\n }\n': + types.GetCartByIdDocument +} +export function graphql( + source: 'fragment CartItems on CartItem {\n name\n quantity\n }\n ' +): (typeof documents)['\n fragment CartItem on CartItem {\n name\n quantity\n }\n '] +export function graphql( + source: 'query GetCartById($id: ID!) {\n cart(id: $id) {\nid\nitems {\nid\n...CartItem\n}\ntotalItems\n }\n }\n' +): (typeof documents)['\n query GetCartById($id: ID!) {\n cart(id: $id) {\n id\n items {\n id \n ...CartItem \n } \n totalItems \n } \n } n'] +export function graphql(source: string): unknown +export function graphql(source: string) { + // Here we access the documents object, so the bundler cannot make assumption over what is + // actually used at runtime + return (documents as any)[source] ?? {} +} +``` + +Every time you use the `graphql` function, all the operations in the project are imported, +regardless of wether they are used or not. This can be a problem if you have a lot of documents +(query, mutation, and fragments) in your project. + + + For more information/ a history of this functionality, you can read more about it in this blog + post from Laurin Quast about this new client-preset feature: [Unleash the Power of Fragments with + GraphQL Code + Generator](https://the-guild.dev/blog/unleash-the-power-of-fragments-with-graphql-codegen). It was + based on the former + [`gql-tag-operations-preset`](https://the-guild.dev/graphql/codegen/plugins/presets/gql-tag-operations-preset) + that is now replaced by the `client-preset`, which was originally explored by [Maël + Nison](https://github.com/arcanis) in [this Github + project](https://github.com/arcanis/graphql-typescript-integration). + + +## The Solution: A Compiler Plugin + +This is where a compiler can make a big difference. Instead of using the map which contains all +GraphQL operations in the project, we can use the generated document types for that specific +operation. So the example above would compile to: + +```ts +import { CartItemFragmentDoc } from './graphql' +import { GetCartByIdDocument } from './graphql' +const GetCartByIdDocument = GetCartByIdDocument +const CartItemFragment = CartItemFragmentDoc +``` + +There was already a +[Babel plugin](https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#babel-plugin) for +the `client-preset` for projects using [Babel](https://babeljs.io) in the `client-preset` package. +Now, as SWC itself, and Next.js is becoming more popular and uses [SWC](https://swc.rs) as its +default compiler, there was a need for a SWC plugin as well. SWC is a fast and modern +JavaScript/TypeScript compiler written in Rust, so the Babel plugin couldn't be used. + +This is where the new +[@graphql-codegen/client-preset-swc-plugin](https://www.npmjs.com/package/@graphql-codegen/client-preset-swc-plugin) +comes in. This plugin uses the [SWC](https://swc.rs) compiler to optimize the generated code, +reducing the bundle size for all your SWC projects! + +## How to Use the SWC Plugin + +To use the SWC plugin, you need to install the plugin: + +```bash +npm install -D @graphql-codegen/client-preset-swc-plugin +yarn add -D @graphql-codegen/client-preset-swc-plugin +``` + +To use the SWC plugin with Next.js, update your `next.config.js` to add the following: + +```js filename="next.config.js" +const nextConfig = { + // ... + experimental: { + swcPlugins: [ + [ + '@graphql-codegen/client-preset-swc-plugin', + { artifactDirectory: './src/gql', gqlTagName: 'graphql' } + ] + ] + } +} +``` + +or with Vite React, update your `vite.config.ts` to add the following: + +```ts filename="vite.config.ts" +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react-swc' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + react({ + plugins: [ + [ + '@graphql-codegen/client-preset-swc-plugin', + { artifactDirectory: './src/gql', gqlTagName: 'graphql' } + ] + ] + }) + ] +}) +``` + +or for other SWC projects add this to your `.swcrc` file: + +```json5 filename=".swcrc" +{ + // ... + jsc: { + // ... + experimental: { + plugins: [ + [ + '@graphql-codegen/client-preset-swc-plugin', + { artifactDirectory: './src/gql', gqlTagName: 'graphql' } + ] + ] + } + } +} +``` + +Then you're ready to go! The plugin will automatically optimize your generated code when SWC +compiles your files. + +In conclusion, using the +[`client-preset`](https://graphql-code-generator.com/plugins/presets/client-preset) for GraphQL Code +Generator is a powerful way to improve the DX of your project. However, without proper optimization, +the bundle size can quickly become bloated. By using the +[@graphql-codegen/client-preset-swc-plugin](https://www.npmjs.com/package/@graphql-codegen/client-preset-swc-plugin), +(or the +[Babel plugin](https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#babel-plugin)) +you can optimize the generated code and reduce the bundle size, and in the end improve the loading +time of your application. + +Like every open source project we maintain, we always use it ourselves in production - in our own +products and with our clients. In +[Hive, our open source GraphQL Schema Registry](https://the-guild.dev/graphql/hive) we also use this +new plugin and [on this PR](https://github.com/kamilkisiela/graphql-hive/pull/2063) you can see the +changes we've made to introduce it and the benefits we got from it. Feel free to explore +[Hive's source code](https://github.com/kamilkisiela/graphql-hive) to find how +[The Guild is using GraphQL](https://www.youtube.com/watch?v=78ufgsWfgVc&list=PLhCf3AUOg4PgQoY_A6xWDQ70yaNtPYtZd&index=3&t=1081) diff --git a/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/31360e1c8ab84c672e68d7faa4af0846.jpg b/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/31360e1c8ab84c672e68d7faa4af0846.jpg new file mode 100644 index 00000000000..daf5d8e742c Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/31360e1c8ab84c672e68d7faa4af0846.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/4133a89b94c1797a726c64732a6acb4a.jpg b/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/4133a89b94c1797a726c64732a6acb4a.jpg new file mode 100644 index 00000000000..56ea8cb615f Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/4133a89b94c1797a726c64732a6acb4a.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/50ea860594889aa4f1b96a509c886b9c.jpg b/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/50ea860594889aa4f1b96a509c886b9c.jpg new file mode 100644 index 00000000000..bfb2f0d9de5 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/50ea860594889aa4f1b96a509c886b9c.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/55d6dee0027f11ee6061a0e3e140c028.webm b/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/55d6dee0027f11ee6061a0e3e140c028.webm new file mode 100644 index 00000000000..07f29929d1b Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/55d6dee0027f11ee6061a0e3e140c028.webm differ diff --git a/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/9cf1c0081093630fe6dfeeab6055e0d3.jpg b/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/9cf1c0081093630fe6dfeeab6055e0d3.jpg new file mode 100644 index 00000000000..d10709d18f2 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/9cf1c0081093630fe6dfeeab6055e0d3.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/da0137e3b38b30be41a5c32479e3c4cd.jpg b/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/da0137e3b38b30be41a5c32479e3c4cd.jpg new file mode 100644 index 00000000000..408b630b944 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/react-app-out-of-a-webflow/da0137e3b38b30be41a5c32479e3c4cd.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/react-dom-event-handling-system/bb65d66b9bfbfdb35da621677cd25dd5.jpg b/packages/web/docs/src/app/blog/(posts)/react-dom-event-handling-system/bb65d66b9bfbfdb35da621677cd25dd5.jpg new file mode 100644 index 00000000000..3cc5dd49102 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/react-dom-event-handling-system/bb65d66b9bfbfdb35da621677cd25dd5.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/react-dom-event-handling-system/c7455683b79da1438932e5b04bb9cc36.png b/packages/web/docs/src/app/blog/(posts)/react-dom-event-handling-system/c7455683b79da1438932e5b04bb9cc36.png new file mode 100644 index 00000000000..df0b0d47e59 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/react-dom-event-handling-system/c7455683b79da1438932e5b04bb9cc36.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/react-dom-event-handling-system/dff8edec78c39a832292c7435ee1213b.png b/packages/web/docs/src/app/blog/(posts)/react-dom-event-handling-system/dff8edec78c39a832292c7435ee1213b.png new file mode 100644 index 00000000000..288cbf95553 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/react-dom-event-handling-system/dff8edec78c39a832292c7435ee1213b.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/react-e2e-tests-with-hooks/a95ef5c7a9025fdca2be4b9bc63903ee.png b/packages/web/docs/src/app/blog/(posts)/react-e2e-tests-with-hooks/a95ef5c7a9025fdca2be4b9bc63903ee.png new file mode 100644 index 00000000000..c04a8df36e9 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/react-e2e-tests-with-hooks/a95ef5c7a9025fdca2be4b9bc63903ee.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/react-e2e-tests-with-hooks/aa57af3e7221a72b87255d47a28c595c.png b/packages/web/docs/src/app/blog/(posts)/react-e2e-tests-with-hooks/aa57af3e7221a72b87255d47a28c595c.png new file mode 100644 index 00000000000..86e5be849ed Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/react-e2e-tests-with-hooks/aa57af3e7221a72b87255d47a28c595c.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/react-hooks-system/923d5b18cf82df80927ee5278f7048ff.jpeg b/packages/web/docs/src/app/blog/(posts)/react-hooks-system/923d5b18cf82df80927ee5278f7048ff.jpeg new file mode 100644 index 00000000000..1c3b459db3c Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/react-hooks-system/923d5b18cf82df80927ee5278f7048ff.jpeg differ diff --git a/packages/web/docs/src/app/blog/(posts)/react-hooks-system/bca747b6dd5992e26b78942e8ba4f071.png b/packages/web/docs/src/app/blog/(posts)/react-hooks-system/bca747b6dd5992e26b78942e8ba4f071.png new file mode 100644 index 00000000000..fa36a174879 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/react-hooks-system/bca747b6dd5992e26b78942e8ba4f071.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/recursive-react-tree-component-implementation-made-easy/97a3475a06a03454f868d7b3f894c1d3.png b/packages/web/docs/src/app/blog/(posts)/recursive-react-tree-component-implementation-made-easy/97a3475a06a03454f868d7b3f894c1d3.png new file mode 100644 index 00000000000..314bea0f04a Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/recursive-react-tree-component-implementation-made-easy/97a3475a06a03454f868d7b3f894c1d3.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/recursive-react-tree-component-implementation-made-easy/c8c1a6838b625929134696ac2cc64f8a.png b/packages/web/docs/src/app/blog/(posts)/recursive-react-tree-component-implementation-made-easy/c8c1a6838b625929134696ac2cc64f8a.png new file mode 100644 index 00000000000..9ba905094cd Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/recursive-react-tree-component-implementation-made-easy/c8c1a6838b625929134696ac2cc64f8a.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/recursive-react-tree-component-implementation-made-easy/db6c7817bbaf73790065c3e1f8ebb835.png b/packages/web/docs/src/app/blog/(posts)/recursive-react-tree-component-implementation-made-easy/db6c7817bbaf73790065c3e1f8ebb835.png new file mode 100644 index 00000000000..dd7f4b4d26f Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/recursive-react-tree-component-implementation-made-easy/db6c7817bbaf73790065c3e1f8ebb835.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/recursive-react-tree-component-implementation-made-easy/e02fcb8858f8f24b40d4259001c474f0.png b/packages/web/docs/src/app/blog/(posts)/recursive-react-tree-component-implementation-made-easy/e02fcb8858f8f24b40d4259001c474f0.png new file mode 100644 index 00000000000..10dbca4c72d Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/recursive-react-tree-component-implementation-made-easy/e02fcb8858f8f24b40d4259001c474f0.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/remote-control-graphql-inspector/page.mdx b/packages/web/docs/src/app/blog/(posts)/remote-control-graphql-inspector/page.mdx new file mode 100644 index 00000000000..b89ed2007b6 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/remote-control-graphql-inspector/page.mdx @@ -0,0 +1,78 @@ +--- +title: Enable Remote Control in GraphQL Inspector +authors: kamil +tags: [graphql, graphql-inspector] +date: 2020-05-01 +description: + Intercept schema changes via HTTP and decide which changes are acceptable which are not and all of + this through a serverless function. +--- + +The new version of GraphQL Inspector comes with **remote control of Pull Requests and Pushes via +HTTP endpoint**. You're in charge of accepting or rejecting changes, even if they break the GraphQL +API. + +We recently released a lot more interesting features. You can read about them in +["New GraphQL Inspector and upcoming features"](/blog/new-graphql-inspector) article. + +## What Is Remote Control? + +Think of it as a way to intercept Schema Checks done by GraphQL Inspector and report back a +conclusion: + +> to fail or not to fail + +Every Push or opened Pull Request is checked with GraphQL Inspector and based on the changes in a +schema, it decides whether to reject those changes. We don't want to stop you there so Inspector +lets you review changes and decide what to do. + +![](./remote-graph.png 'What is Remote Control?') + +The idea is simple. You create an HTTP endpoint, like serverless function or whatever and on every +Push or Pull Request we send you a list of changes with all the details (mainly criticality level) +and Inspector expects to receive back a conclusion and changes. + +```javascript +export default (req, res) => { + const changes = req.body.changes + const conclusion = decide(changes) + + res.json({ + changes, + conclusion + }) +} +``` + +The HTTP endpoint will also receive **details about a Pull Request or a Commit** that triggered the +schema check, so possibilities are endless here. + +You're able to reject all Pull Requests done by your manager, even when there are no breaking +changes. + +--- + +## Future Plans + +We're working on **Azure and Bitbucket integrations** plus a **monitoring feature**. Talk to us to +try it out. + +We want to make **Azure** and **Bitbucket** a first-class citizens in GraphQL Inspector and give you +the same experience as you get right now with **GitHub**. + +**Monitoring** will enable you to analyze the traffic of your GraphQL APIs and provide details +needed to improve performance. Collecting information about the usage will let you safely remove +deprecated pieces of GraphQL Schema. + +> If you're interested, please reach out to us! + +--- + +## Enjoy GraphQL Inspector! + +We have big plans for Inspector, and you're very welcome to join us in that journey. + +GraphQL Inspector is a tool created by developers, for developers and that's why we'd love to get +your feedback and shape GraphQL Inspector together! + +Oh... and it's [Open Sourced](https://github.com/kamilkisiela/graphql-inspector)! diff --git a/packages/web/docs/src/app/blog/(posts)/remote-control-graphql-inspector/remote-graph.png b/packages/web/docs/src/app/blog/(posts)/remote-control-graphql-inspector/remote-graph.png new file mode 100644 index 00000000000..3964276d382 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/remote-control-graphql-inspector/remote-graph.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/runtime-jsx/55cc9640bbaec3a7b9a5948d71adfd3a.png b/packages/web/docs/src/app/blog/(posts)/runtime-jsx/55cc9640bbaec3a7b9a5948d71adfd3a.png new file mode 100644 index 00000000000..bb4b1f80538 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/runtime-jsx/55cc9640bbaec3a7b9a5948d71adfd3a.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/runtime-jsx/7ba5847a0000df3ef06a6913a79346f2.png b/packages/web/docs/src/app/blog/(posts)/runtime-jsx/7ba5847a0000df3ef06a6913a79346f2.png new file mode 100644 index 00000000000..d13815de820 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/runtime-jsx/7ba5847a0000df3ef06a6913a79346f2.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/runtime-jsx/7c7f8978be94efed4206593bbf067568.png b/packages/web/docs/src/app/blog/(posts)/runtime-jsx/7c7f8978be94efed4206593bbf067568.png new file mode 100644 index 00000000000..4c9f65a84b4 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/runtime-jsx/7c7f8978be94efed4206593bbf067568.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/runtime-jsx/fadbe31be89eb8b0be9fc5f035c9617d.png b/packages/web/docs/src/app/blog/(posts)/runtime-jsx/fadbe31be89eb8b0be9fc5f035c9617d.png new file mode 100644 index 00000000000..d56160eef0b Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/runtime-jsx/fadbe31be89eb8b0be9fc5f035c9617d.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/scalable-apis-with-graphql-server-codegen-preset/filesystem-resolvers.png b/packages/web/docs/src/app/blog/(posts)/scalable-apis-with-graphql-server-codegen-preset/filesystem-resolvers.png new file mode 100644 index 00000000000..cb4e50efbc8 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/scalable-apis-with-graphql-server-codegen-preset/filesystem-resolvers.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/scalable-apis-with-graphql-server-codegen-preset/merge-resolvers.gif b/packages/web/docs/src/app/blog/(posts)/scalable-apis-with-graphql-server-codegen-preset/merge-resolvers.gif new file mode 100644 index 00000000000..99173964527 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/scalable-apis-with-graphql-server-codegen-preset/merge-resolvers.gif differ diff --git a/packages/web/docs/src/app/blog/(posts)/scalable-apis-with-graphql-server-codegen-preset/page.mdx b/packages/web/docs/src/app/blog/(posts)/scalable-apis-with-graphql-server-codegen-preset/page.mdx new file mode 100644 index 00000000000..f2e88cbd9f7 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/scalable-apis-with-graphql-server-codegen-preset/page.mdx @@ -0,0 +1,563 @@ +--- +title: Scalable APIs with GraphQL Server Codegen Preset +authors: eddeee888 +tags: [graphql, codegen, node, server, typescript] +date: 2023-01-24 +description: + Structuring GraphQL server the right way enables many teams to work in harmony while minimising + runtime risks. +--- + +A GraphQL server is usually a central system where teams need to be able to develop features whilst +not blocking other teams. Each team can have varied standards and practices. So, if the GraphQL +server is not set up in a structure to allow concurrent contribution, development slows down, and +more time is spent on admin tasks rather than delivering new features. + +This blog post explores some of the common problems GraphQL servers encounter at scale and +recommends how to solve them. + +## The Easy Problem: Code Ownership + +### Problem: How to Manage Code Ownership? + +Sharing a single codebase across many teams without clear structure and guidelines is a recipe for +disaster. The first team in the codebase usually establishes a structure that works for them. When a +second team joins, they most likely follow the structure already there. The same story happens for +every team thereafter. After a few more rounds, development slows down. One may review the structure +and find themselves staring down a codebase structured by one team, for one team. + +Teams want their members to be notified of changes related to their domain, but not every Pull +Request (PR). If you were ever notified of a change unrelated to your team, chances are the codebase +needs to be set up to support many teams working on it. + +In the example below, Team A is the first to set up the server. They manage `User` and `Auth` +domains, so they create `datasources` and `resolvers` folders for these files: + +```text +├── src/ +│ ├── schema/ +│ │ ├── datasources/ +│ │ │ ├── UserDatasource.ts +│ │ │ ├── AuthDatasource.ts +│ │ ├── resolvers/ +│ │ │ ├── userResolvers.ts +│ │ │ ├── authResolvers.ts +│ │ ├── userSchema.graphql +│ │ ├── authSchema.graphql +│ ├── server.ts +│ ├── codegen.yml +``` + +Then, Team B comes into the codebase. They manage `Book` domain, so they add their files following +the same structure: + +```text +├── src/ +│ ├── schema/ +│ │ ├── datasources/ +│ │ │ ├── UserDatasource.ts +│ │ │ ├── AuthDatasource.ts +│ │ │ ├── BookDatasource.ts +│ │ ├── resolvers/ +│ │ │ ├── userResolvers.ts +│ │ │ ├── authResolvers.ts +│ │ │ ├── bookResolvers.ts +│ │ ├── userSchema.graphql +│ │ ├── authSchema.graphql +│ │ ├── bookSchema.graphql +│ ├── server.ts +│ ├── codegen.yml +``` + +While this works at a small scale with low communication overhead, it cannot scale. When there are +tens or hundreds of datasources and resolvers, it would be hard to know who owns what. A simple +solution is to assign files to owners using GitHub's CODEOWNERS or similar features. But it has to +be done on every file because files are split into category folders, e.g. `resolvers`, +`datasources`, etc. + +It is tempting to split this structure up into folders, each named after the team that manages it: + +```text +├── src/ +│ ├── schema/ +│ │ ├── TeamA/ # Team A notified (by CODEOWNERS) if changes happen in this folder +│ │ │ ├── datasources/ +│ │ │ │ ├── UserDatasource.ts +│ │ │ │ ├── AuthDatasource.ts +│ │ │ ├── resolvers/ +│ │ │ │ ├── userResolvers.ts +│ │ │ │ ├── authResolvers.ts +│ │ │ ├── userSchema.graphql +│ │ │ ├── authSchema.graphql +│ │ ├── TeamB/ # Team B notified (by CODEOWNERS) if changes happen in this folder +│ │ │ ├── datasources/ +│ │ │ │ ├── BookDatasource.ts +│ │ │ ├── resolvers/ +│ │ │ │ ├── bookResolvers.ts +│ │ │ ├── bookSchema.graphql +│ ├── server.ts +│ ├── codegen.yml +``` + +This is already a significant improvement as ownership is defined, but organisational problems +remain. For example, what happens when Team A changes what they own, splits into smaller teams or +changes its name? All these scenarios need admin work: renaming the folder in the best case and +moving files around in the worst. Admin work like this creates no value for end users. + +On top of that, all datasources and resolvers must be put together into the GraphQL server. It is +the `server.ts` file in this example. Who owns this file and other server maintenance such as +package updates, security patches, `codegen.yml` etc.? + +### Solution: Split into Modules + +It is common to see teams change their name, divide as teams scale, or combine as structure and +priorities change. Yet, the thing that generally remains stable over long periods is the business +domain. If we split our schema based on the business domain and assign teams accordingly, it becomes +much more scalable. If a team needs to hand over a domain to another team, they only need to update +the CODEOWNERS file. + +It is best to have a small group of dedicated maintainers for server maintenance. This could consist +of one member from each team in the codebase, and its membership can rotate. Alternatively, some +companies may choose to assign this responsibility to a dedicated team. Having a dedicated group - +let's call them the Maintainers - helps reduce noise and cognitive load for the rest of the teams, +allowing them to focus on delivering features. + +Using the same example before, we can identify 3 main domains: `User`, `Auth` and `Book`, each +having CODEOWNERS set up to notify appropriate teams of changes. The Maintainers own `server.ts` and +other configs like `codegen.yml` (We all use +[GraphQL Codegen](https://the-guild.dev/graphql/codegen), right? 😉). + +```text +├── src/ +│ ├── schema/ +│ │ ├── user/ # Team A notified if changed +│ │ │ ├── datasources.ts +│ │ │ ├── resolvers.ts +│ │ │ ├── schema.graphql +│ │ ├── auth/ # Team A notified if changed +│ │ │ ├── datasources.ts +│ │ │ ├── resolvers.ts +│ │ │ ├── schema.graphql +│ │ ├── book/ # Team B notified if changed +│ │ │ ├── datasources.ts +│ │ │ ├── resolvers.ts +│ │ │ ├── schema.graphql +│ ├── server.ts # Maintainers notified if changed +│ ├── codegen.yml # Maintainers notified if changed +``` + +## The Hard Problem: Best Practice Alignment at Scale + +Splitting schema into modules is usually easy to get teams to agree on. Then we start to see the +hard problems: how to enforce best practices to the teams while reducing the time Maintainers need +to spend on server maintenance. + +### 1. How to Enforce Best Practices for All Teams + +Bad practices and conventions spread like bushfire on a hot summer day in Australia; it is the +worst! I used to be part of a Maintainers team. We had guidelines on various topics, one being +resolver naming convention. On one occasion, a developer incorrectly used pascal case instead of +camel case. The following day I woke up with more than half of the resolvers in pascal case. 😱 + +OK, I am exaggerating here, but the experience was very traumatising. + +Guidelines are only good if people follow them. Standards start to slip without explicit and +automatic enforcement, and bad practices begin to spread. + +We need automated tools to enforce guidelines effectively. Luckily, these days we have an extensive +range of tools to help with GraphQL server best practices: + +- [GraphQL Codegen](https://the-guild.dev/graphql/codegen) with + [typescript](https://the-guild.dev/graphql/codegen/plugins/typescript/typescript) and + [typescript-resolvers](https://the-guild.dev/graphql/codegen/plugins/typescript/typescript-resolvers) + plugins for type-safe GraphQL server development in TypeScript +- [GraphQL ESLint](https://github.com/dimaMachina/graphql-eslint) for validation, linting, and + checking for best practices and conventions (like all resolvers must be camel case!!! 💀). + +However, there are areas to improve. + +For example, the guideline for one of the GraphQL servers I am working on is to use generated types +from GraphQL Codegen. Some team members, particularly those new to the codebase, may have yet to be +aware of this. Luckily, other team members or the Maintainers catch these issues at PR review time. +But it would save everyone time and effort if we had tools to enforce this guideline automatically. + +### 2. How to Minimise Noise to the Maintainers + +Maintainers usually need to manage these aspects of a GraphQL server: + +- Core server logic: resolver map, schemas, CI/CD, etc. +- Config files: `.graphqlrc`, `codegen.yml`, etc. + +Changing anything in the Maintainers' domain should notify them. Unfortunately, this happens +regularly in routine workflows. Maintainers also have their team's work and general package and +security updates to worry about. So, being notified of unrelated PRs quickly leads to burnout. + +For example, if a new resolver is added, it must be manually added to the resolver map. So, +Maintainers are notified of every new resolver. It may look like this if you are using +[GraphQL Yoga](https://the-guild.dev/graphql/yoga-server): + +```ts +// server.ts (managed by the Maintainers) +import { createServer } from 'node:http' +import { createSchema, createYoga } from 'graphql-yoga' +import { Auth } from "./schema/auth/resolvers"; +import { book, Book } from "./schema/auth/resolvers"; +import { user, User } from "./schema/user/resolvers"; + +const schema = createSchema({ + typeDefs: `...`, + resolvers: { // This is the resolver map + Query: { + user, + book, + }, + Auth, + Book + User, + } +}); + +const yoga = createYoga({ schema }); +const server = createServer(yoga) +server.listen(4000, () => { + console.info('Server is running on http://localhost:4000/graphql') +}) +``` + +There is a way to mitigate this issue: each schema module exports an object of resolvers, and they +are passed into `resolvers` as an array. Internally, the resolvers are merged using +[mergeResolvers from @graphql-tools/merge](https://the-guild.dev/graphql/tools/docs/schema-merging#merging-resolvers). +This means the Maintainers are notified on every new module instead of every new resolver: + +```ts +// server.ts +import { createServer } from 'node:http' +import { createSchema, createYoga } from 'graphql-yoga' +import * as authResolvers from './schema/auth/resolvers' +import * as bookResolvers from './schema/book/resolvers' +import * as userResolvers from './schema/user/resolvers' + +const schema = createSchema({ + typeDefs: `...`, + resolvers: [ + // mergeResolvers are called internally + authResolvers, + bookResolvers, + userResolvers + ] +}) + +// rest of server config +``` + +However, `mergeResolvers` has a caveat: it is simply merging plain JavaScript objects at runtime. +Thus, there is a risk of someone accidentally overriding others' resolvers. This issue is hard to +find in large codebases with hundreds of resolvers and modules. + +mergeResolvers can accidentally override resolvers if there are conflicts in resolver names + +Another commonly used feature is +[mappers](https://the-guild.dev/graphql/codegen/plugins/typescript/typescript-resolvers#mappers). +This feature allows resolvers to return custom mapper objects instead of GraphQL output types. + +The problem with mappers is that we need to update `codegen.yml` every time we need to create one: + +```yaml +# codegen.yml +generates: + src/schema/types.generated.ts: + plugins: + - typescript + - typescript-resolvers + mappers: + User: './mappers#UserMapper' + Profile: './mappers#ProfileMapper' + # Add another line for each mapper +``` + +This is a problem for the Maintainers because these changes are based on team requirements. Whether +a team uses mappers or not is the team's choice and should not concern the Maintainers. Yet, the +Maintainers are notified because they own the `codegen.yml` file. + +### Solution: Use GraphQL Server Codegen Preset + +To solve the above problems, I am working on a codegen preset for GraphQL server: +[@eddeee888/gcg-typescript-resolver-files](https://www.npmjs.com/package/@eddeee888/gcg-typescript-resolver-files). +The aim is to move from guidelines/config to conventions. All changes happen inside teams' modules, +so feature work does not notify the Maintainer. + +This works for any GraphQL server implementation, such as GraphQL Yoga, Apollo Server, etc. + +Here's how to get started: + +```bash +yarn add -D @graphql-codegen/cli @eddeee888/gcg-typescript-resolver-files +``` + +Then, you can add the following config: + +```yaml +# codegen.yml +schema: 'src/**/*.graphql' +generates: + src/schema: + preset: '@eddeee888/gcg-typescript-resolver-files' +``` + +Note that this preset includes `@graphql-codegen/typescript` and +`@graphql-codegen/typescript-resolvers` under the hood, so you do not have to set that up manually! + +Now, all we have to do is to set up schema modules like this: + +```text +├── src/ +│ ├── schema/ +│ │ ├── base/ +│ │ │ ├── schema.graphql +│ │ ├── user/ +│ │ │ ├── schema.graphql +│ │ ├── book/ +│ │ │ ├── schema.graphql +``` + +Given the following content of schema files: + +```graphql +# src/schema/base.graphql +type Query +type Mutation + +# src/schema/user.graphql +extend type Query { + user(id: ID!): User +} +type User { + id: ID! + fullName: String! +} + +# src/schema/book.graphql +extend type Query { + book(id: ID!): Book +} +extend type Mutation { + markBookAsRead(id: ID!): Book! +} +type Book { + id: ID! + isbn: String! +} +``` + +When we run codegen: + +```bash +yarn codegen +``` + +We will see the following files: + +```text +├── src/ +│ ├── schema/ +│ │ ├── base/ +│ │ │ ├── schema.graphql +│ │ ├── user/ +│ │ │ ├── resolvers/ +│ │ │ │ ├── Query/ +│ │ │ │ │ ├── user.ts # Generated, changes not overwritten by codegen +│ │ │ │ ├── User.ts # Generated, changes not overwritten by codegen +│ │ │ ├── schema.graphql +│ │ ├── book/ +│ │ │ ├── resolvers/ +│ │ │ │ ├── Query/ +│ │ │ │ │ ├── book.ts # Generated, changes not overwritten by codegen +│ │ │ │ ├── Mutation/ +│ │ │ │ │ ├── markBookAsRead.ts # Generated, changes not overwritten by codegen +│ │ │ │ ├── Book.ts # Generated, changes not overwritten by codegen +│ │ │ ├── schema.graphql +│ │ ├── types.generated.ts # Entirely generated by codegen +│ │ ├── resolvers.generated.ts # Entirely generated by codegen +``` + +#### Generated Files + +- **Shared schema and resolver TypeScript types**: `types.generated.ts`. This is generated by + `@graphql-codegen/typescript` and `@graphql-codegen/typescript-resolvers` plugins. This can be + ignored in Git or removed from CODEOWNERS because it is entirely generated. + +- **Resolver map**: `resolvers.generated.ts`. This puts all other resolvers together statically, + ready to be used by the GraphQL server. This can be ignored in Git or removed from CODEOWNERS + because it is entirely generated. + +{/* prettier-ignore */} +```ts filename="src/schema/resolvers.generated.ts" +/* This file was automatically generated. DO NOT UPDATE MANUALLY. */ +import type { Resolvers } from './types.generated' +import { book as Query_book } from './book/resolvers/Query/book' +import { markBookAsRead as Mutation_markBookAsRead } from './book/resolvers/Mutation/markBookAsRead' +import { Book } from './book/resolvers/Book' +import { user as Query_user } from './user/resolvers/Query/user' +import { User } from './user/resolvers/User' +export const resolvers: Resolvers = { + Query: { + book: Query_book, + user: Query_user + }, + Mutation: { + markBookAsRead: Mutation_markBookAsRead + }, + + Book: Book, + User: User +} +``` + +- **Operation resolvers**: + - `src/schema/user/resolvers/Query/user.ts` + - `src/schema/book/resolvers/Query/book.ts` + - `src/schema/book/resolvers/Mutation/book.ts` + +```ts +// Example: src/schema/user/resolvers/Query/user.ts +import type { QueryResolvers } from './../../../types.generated' + +export const user: NonNullable = async (_parent, _arg, _ctx) => { + /* Implement Query.user resolver logic here */ +} +``` + +- **Object type resolvers**: + - `src/schema/user/resolvers/User.ts` + - `src/schema/book/resolvers/Book.ts` + +```ts +// Example: src/schema/user/resolvers/User.ts +import type { UserResolvers } from './../../types.generated' + +export const User: UserResolvers = { + /* Implement User resolver logic here */ +} +``` + +Resolvers are generated with developer experience in mind: + +- Automatically typed: you can go straight into implementing resolver logic. +- Location in the schema matches the generated location on the filesystem: you can easily jump to a + resolver file. For example, `user` Query logic can be found in `Query/user.ts`. + +Search for resolver files easily because the filesystem location matches schema location + +The resolver files are not overwritten when codegen runs again. However, there are some smarts +built-in to ensure resolvers are correctly exported. For example, if we rename `User` resolver to +`WrongUser` in `src/schema/user/resolvers/User.ts`, and then run codegen, the file will be updated +with a warning: + +```ts +// Example: src/schema/user/resolvers/User.ts +import type { UserResolvers } from './../../types.generated' + +export const WrongUser: UserResolvers = { + /* Implement User resolver logic here */ +} +/* WARNING: The following resolver was missing from this file. Make sure it is properly implemented or there could be runtime errors. */ +export const User: UserResolvers = { + /* Implement User resolver logic here */ +} +``` + +Some of these features are inspired by [gqlgen](https://gqlgen.com/) so check it out if you need a +Golang GraphQL server implementation. + +#### Other GraphQL Types + +These other types are also supported by the preset: + +- Union: A file is generated for every Union type. +- Scalars: + - If the Scalar name matches one in [graphql-scalars](https://the-guild.dev/graphql/scalars), it + is automatically imported from `graphql-scalars` into the resolver map. Make sure to install it: + ```bash + yarn add graphql-scalars + ``` + - If the Scalar name does not exist in `graphql-scalars`, a file is generated for every Scalar + type. + +For other currently non-supported types, we can declare them using the `externalResolvers` preset +config. + +#### Mappers Convention + +Mappers can be added by exporting types or interfaces with `Mapper` suffixes from `.mappers.ts` +files in each module. For example, `UserMapper` will be used as `User`'s mapper type. + +```ts +// src/schema/user/schema.mappers.ts + +// This works! This will be used as mapper for `User` object type +export { User as UserMapper } from 'external-module' + +// This 1 works! For `User1` object type +export interface User1Mapper { + id: string +} + +// This works 2! For `User2` object type +export type User2Mapper = { id: string } + +// This works 3! For `User3` object type +interface User3Mapper { + id: string +} +export { User3Mapper } +``` + +#### Gradual Migration Supported + +If you have an existing codebase in a modularised structure but cannot migrate all at once, the +preset has `whitelistedModules` and `blacklistedModules` options to support gradual migration. + +#### Customisable Conventions + +All mentioned conventions are customisable! Check out the documentation for +[more options](https://github.com/eddeee888/graphql-code-generator-plugins/tree/master/packages/typescript-resolver-files). + +### Differences between Server Preset and `graphql-modules` + +So far, the preset may sound similar to [graphql-modules](https://the-guild.dev/graphql/modules) as +they both split schemas into modules. Yet, they solve different problems. + +`graphql-modules` is a modularisation utility library that allows each module to maintain schema +definitions and resolvers separately, whilst serving a unified schema at runtime. + +The preset focuses on conventions (such as file structures, types, integrations with other +libraries, etc.). However, it does not force schema modularisation upon the user. In fact, the +default `modules` mode is recommended for its scalablility and simplicity. The preset also has a +`merged` mode to generate files in a monolithic way that may suit some teams and organisations. + +This also means there could be a mode in the preset to support `graphql-modules` in the future. + +## Summary + +In this blog post, we have explored the problems likely to occur if your GraphQL server is not +prepared for scale: + +- unclear ownership +- ignored best practices +- noisy maintenance + +We can solve these problems by following the outlined recommendations: + +- modularise the codebase based on business domains +- use the GraphQL server codegen preset + [@eddeee888/gcg-typescript-resolver-files](https://www.npmjs.com/package/@eddeee888/gcg-typescript-resolver-files) + +At [SEEK](https://www.seek.com.au/), we are experimenting with this preset and getting positive +feedback. [Let me know on Twitter](https://twitter.com/eddeee888) if it works for you too! diff --git a/packages/web/docs/src/app/blog/(posts)/schema-change-notifications/discord.png b/packages/web/docs/src/app/blog/(posts)/schema-change-notifications/discord.png new file mode 100644 index 00000000000..53021b3e685 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/schema-change-notifications/discord.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/schema-change-notifications/page.mdx b/packages/web/docs/src/app/blog/(posts)/schema-change-notifications/page.mdx new file mode 100644 index 00000000000..cd28c8ef974 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/schema-change-notifications/page.mdx @@ -0,0 +1,106 @@ +--- +title: Schema Change Notifications in GraphQL Inspector +authors: kamil +tags: [graphql, graphql-inspector] +date: 2020-05-04 +description: + Stay up to date with changes in your GraphQL Schema. Receive notifications on Slack, Discord or + even via WebHooks. +--- + +The new version of GraphQL Inspector comes with **schema change notifications**. We had it in plans +for [almost a year](https://github.com/kamilkisiela/graphql-inspector/issues/214) now and we're very +excited to finally roll it out! + +We recently released a lot more interesting features. You can read about them in +["New GraphQL Inspector and upcoming features"](/blog/new-graphql-inspector) article. + +## Schema Change Notifications + +If you wish to stay up to date with changes happening in your GraphQL API the Notifications are +perfect for you. + +We know how hard it is to track and keep up with the development of a GraphQL Schema. Following +every merged Pull Request or every pushed commit is impossible at scale. You don't want to analyze +the code in order to find and understand changes in your GraphQL API. + +> We decided to automate and reverse this process so every new change will come to you, not the +> other way around. + +GraphQL Inspector tracks every Push in your Git repository and depending on a branch, sends you a +message on Slack or Discord (or custom Webhook) with a list of changes introduced to a GraphQL +schema. + +Changes are grouped by their criticality level: Breaking, Dangerous and Safe. + +Schema Change Notifications feature works well with any setup, +[even with multiple environments](/blog/multiple-environments-endpoints-graphql-inspector). You're +able to track changes in all variants of your GraphQL API, on production, staging and other +environments. + +### Notifications on Slack + +To start receiving notifications on Slack you simply provide a url to Slack's Webhook. + +```yaml +notifications: + slack: webhook-url-here +``` + +That's it. + +![Notifications on Slack](./slack.png 'Notifications on Slack') + +### Notifications on Discord + +The configuration process is exactly the same as with Slack. Provide a url to a webhook created in +your Discord workspace. + +```yaml +notifications: + discord: webhook-url-here +``` + +Enjoy! + +![Notifications on Discord](./discord.png 'Notifications on Discord') + +## Webhook + +GraphQL Inspector should not limit you but help and that's why we decided to support not only Slack +and Discord but rather something more universal, like Webhooks. + +```yaml +notifications: + webhook: webhook-url-here +``` + +You can setup an HTTP endpoint, like serverless function and receive an event, every time there's a +change in GraphQL Schema. + +--- + +## Future Plans + +We're working on **Azure** and **Bitbucket** integrations plus a **monitoring** feature. Talk to us +to try it out. + +We want to make **Azure** and **Bitbucket** a first-class citizens in GraphQL Inspector and give you +the same experience as you get right now with **GitHub**. + +**Monitoring** will enable you to analyze the traffic of your GraphQL APIs and provide details +needed to improve performance. Collecting information about the usage will let you safely remove +deprecated pieces of GraphQL Schema. + +> If you're interested, please reach out to us! + +--- + +## Enjoy GraphQL Inspector! + +We have big plans for Inspector, and you're very welcome to join us in that journey. + +GraphQL Inspector is a tool created by developers, for developers and that's why we'd love to get +your feedback and shape GraphQL Inspector together! + +**Oh… and it's [Open-Sourced](https://github.com/kamilkisiela/graphql-inspector)!** diff --git a/packages/web/docs/src/app/blog/(posts)/schema-change-notifications/slack.png b/packages/web/docs/src/app/blog/(posts)/schema-change-notifications/slack.png new file mode 100644 index 00000000000..4d54b08a886 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/schema-change-notifications/slack.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/slack-bot-with-cloudflare/page.mdx b/packages/web/docs/src/app/blog/(posts)/slack-bot-with-cloudflare/page.mdx new file mode 100644 index 00000000000..eb69d8354bd --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/slack-bot-with-cloudflare/page.mdx @@ -0,0 +1,165 @@ +--- +title: Building Slack Bot with Cloudflare Workers +authors: tuvalSimha +tags: [graphql, codegen] +date: 2022-10-12 +description: The Guild Internal toolL - GitHub Slack bot reminder in Cloudflare Workers +--- + +## Introduction + +Big or small companies are complex systems; every company must find the right way to work +efficiently. There are a lot of applications that help us to manage our daily job efficiently. +Lately, we found some non-optimized in The Guild employees-system process. + +So, we thought about some solutions and decided to create an internal tool that helps us and might +also be valuable for others. + +One of those solutions was the **GitHub Slack bot reminder in Cloudflare Workers**. + +### What Are Cloudflare Workers? + +Cloudflare Workers is a platform that provides a serverless execution environment. This environment +allows you to create entire applications. This platform runs on cloud networks around the world. + +### What We Can Do with Cloudflare Workers? + +The Workers platform comes with a new set of tools and nice APIs to integrate directly into the +deployment flow you want to design. At a high level, we can create a lot of cool features: + +- Build an entire Slack bot +- Return small HTML page +- JSON return +- Fetch HTML / JSON +- A/B Testing +- Auth with headers +- Schedule processing +- And a lot more examples… + ([You can find a lot of examples in Cloudflare docs](https://developers.Cloudflare.com/workers/examples)) + +### Getting Started - Installation Setup + +Cloudflare provides some easy and quick installation - +[You can find it here](https://developers.Cloudflare.com/workers/get-started/guide). + +After you install and set up all requirements, let's talk about what we did to create the Slack bot. + +After you installed all Cloudflare packages and run `wrangler init`, you got the following +`index.ts` file: + +```ts +export default { + async fetch(request) { + return new Response('Hello World!') + } +} +``` + +This function will fetch any change in the URL of the worker. + +#### What The Guild Is Building with Cloudflare Workers? + +Cloudflare also provides the option to schedule some functions. You can add this function to the +index file: + +```ts filename="index.ts" +async function scheduled( + controller: ScheduledController, + env: Env, + ctx: ExecutionContext +): Promise { + await run(env) +} +``` + +The `run` function is our main function that gets the `ENV` environment variable secret and executes +the code we create. + +Here you can see the `run` function, we can write any code we want to schedule in the Cloudflare +Workers. We create a Slack bot that sends a daily message about all open pull requests. + +```ts filename="index.ts" +export async function run(env: Env) { + const slack = createSlackClient(env.SLACK_TOKEN) + // We use Slack Client to send messages + const octokit = new Octokit({ auth: env.GH_BOT_TOKEN }) + // We use Octokit to use GitHub API + // ... WRITE ANY CODE YOU WANT HERE +} +``` + +#### How Can I Schedule My Function in the Cloudflare Worker? + +After you finish the installation, Cloudflare also creates `toml` file, this file contains the +`name`, `main`, `compatibility_date`, and `triggers`. To use the triggers, Cloudflare supports crons +and looks like that: + +```toml +[triggers] +crons = ["0 10 * * *"] + // This trigger will execute the function scheduled in the index + // file every day at 10:00 UTC +``` + +#### How Can I Fetch the Data from GitHub? + +To get the data from GitHub, we create a GraphQL query. GitHub provides some easy and nice explorer +tools - You can use it here with the following example: + +```graphql +{ + organization(login: "the-guild-org") { + membersWithRole(first: 30) { + totalCount + edges { + node { + login + name + } + } + } + } +} +``` + +After we create the query, we need to use our data. For that, we use +[GraphQL Code Generator](https://the-guild.dev/graphql/codegen) to generate the types based on the +query. + +```yaml +schema: + - https://api.github.com/graphql: + headers: + Authorization: Bearer ${GH_BOT_TOKEN} +documents: 'src/**/*.ts' +generates: + types.ts: + config: + scalars: + URI: string + plugins: + - typescript + - typescript-operations +``` + +Like the example above, we define the schema path, add Authorization to the header and after that, +we generate the schema and create types to use in `run` function. + +##### Why Did I Use GraphQL Code Generator? + +- GraphQL Code Generator generates types-based schema - The types are fully type-safe. +- The process to add GraphQL Code Generator to the project is easy, fast and reduces type error. + +### What Else You Can Do with Cloudflare Workers? (Hint? Yoga) + +[**GraphQL Yoga**](https://the-guild.dev/graphql/yoga-server), one of the greatest tools in The +Guild, provides a GraphQL server. + +Our focus is on easy setup, performance, and great developer experience. + +Thanks to its platform-agnostic design, Yoga makes your GraphQL Server run everywhere, even on +Cloudflare Workers, with no additional packages required! + +You can find more information and tutorials on how to integrate **Cloudflare Workers** with +**GraphQL Yoga** - +[link](https://graphql-yoga.com/docs/integrations/integration-with-cloudflare-workers). diff --git a/packages/web/docs/src/app/blog/(posts)/sofa/cover.jpg b/packages/web/docs/src/app/blog/(posts)/sofa/cover.jpg new file mode 100644 index 00000000000..c8592088951 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/sofa/cover.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/sofa/page.mdx b/packages/web/docs/src/app/blog/(posts)/sofa/page.mdx new file mode 100644 index 00000000000..31d0c4ffb9b --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/sofa/page.mdx @@ -0,0 +1,261 @@ +--- +title: SOFA — The best way to REST (is GraphQL) +authors: uri +tags: [graphql, rest, sofa] +date: 2019-01-25 +description: Ending the REST vs. GraphQL debate once and for all +--- + +## Ending the REST vs. GraphQL Debate Once and for All + +## TL;DR + +- Don't choose between REST and GraphQL — **create a fully RESTful API automatically** from your + GraphQL implementation (with a library and a single line of code) +- Get most of **the benefits of GraphQL** on the backend and frontend, while using and **exposing + REST** +- **Support all your existing clients** with REST while improving your backend stack with GraphQL +- Create custom, perfectly client-aligned REST endpoints for your frontend simply by naming a route + and attaching a query +- Stop arguing about REST vs GraphQL. Use GraphQL, generate REST and get the best from both +- In the other way around (REST to GraphQL) you won't get the best of both world but less powerful, + harder to maintain server implementation with some benefits of GraphQL. It is a good and fast + start for a migration though. + +## Wait, What!? + +Many articles have been written about the pros and cons of GraphQL and REST APIs and how to decide +which one to use. I'm not going to repeat those here.. + +A lot of time and energy spent by smart consultants to write those articles, read those articles, +while most of them are finished with the “it depends on your use case” summary, without actually +specifying those use cases! + +I've been working with REST, GraphQL and SOAP APIs for many years. So I thought, why not come up +with a list of those use cases and for each one of those to check — what can't you do in GraphQL +that you can do with REST and what you wouldn't want to do with GraphQL and you would prefer REST. + +After creating that list, I suddenly had a thought — what if there was another option — what if my +powerful GraphQL server could just generate a REST API for me? + +> Then I could get the best of both worlds! + +The more I dived into the idea and implementation then more I realized it's not only that we can +have both types of APIs created for us, but even if we just want to expose REST APIs, and none of +our clients use GraphQL, **GraphQL is the best way the create REST APIs!** + +## How Does the above Sentence Even Make Sense?! + +Usually when we ([The Guild](/)) help companies and organizations to modernize their APIs, the first +to understand the benefits of GraphQL are the frontend developers, for obvious reasons. But as soon +as the backend developers “Get it”, they become the biggest advocates of the technology. But they +still need to support existing clients and 3rd party partners. + +That's why those newly generated REST APIs get a lot of the features and benefits from the internal +GraphQL implementation that make backend developers happy: + +- Fully **generated documentation** that is always up-to-date (Swagger, OpenAPI and GraphiQL) +- Truly **RESTful API** out of the box +- **GraphQL Subscriptions as Webhooks** +- **Runtime validation of data** — be 100% sure that fetched data matches schema's and query's + structure. You send exactly what I want to send, string is a string, an object has exactly the + same properties. +- Creating a custom endpoint is now a matter of choose a route name and attaching a query to it. + done. **No more manual work of creating and maintaining client specific endpoints!** +- Use GraphQL's philosophy of evolving APIs through schemas — **no more painful V1 — V2 API + migrations.** +- Use modern technology that is easier to hire people to. Companies like Facebook, Airbnb and + [others](https://graphql.org/users) have moved to GraphQL. None of them has gone back. +- **The power of GraphQL resolvers** to create your API implementation, instead of manually written + controllers from MVC + +**What I get from having resolvers?** + +- Easier to transform data, so it matches the response (GraphQL Schema). That's because every entity + has its own resolvers, so the mapping is moved into smaller pieces and reused across an entire + app. +- GraphQL allows you to easily share data across every resolver, we call it Context. +- Forces you to define and resolve data in an opinionated way that actually helps to build an API. + It runs functions in parallel (functions that are nested at the same level), handles async and at + the end, it is responsible for merging all of that into a single object, so you don't have to + think about it. + +## SOFA — Use GraphQL to Create RESTful API + +So we created [SOFA](https://github.com/Urigo/SOFA) (pun intended), an open source library you +install on your GraphQL server to create a fully RESTful and configurable API gateway. Use GraphQL +to REST. + +## "How to" Tutorial + +Let's create a short step-by-step tutorial on how to create a RESTful API. + +Step 1: npm install the [`sofa-api`](https://npmjs.com/package/sofa-api) package and add the +following line of code: + +```ts +import express from 'express' +import sofa from 'sofa-api' + +const app = express() + +app.use(sofa({ schema })) +``` + +Step 2: Go REST on a Sofa, you're done. + +Kamil Kisiela added Sofa to the +[SpaceX GraphQL API](https://medium.com/open-graphql/migrating-spacex-api-to-graphql-e1fe69a3a8e7) +implementation by Carlos Rufo, **in a single +[commit](https://github.com/spacexland/api/pull/7/commits/9b50393ad33c27693d89f271ec1247715c6dcc53#diff-f41e9d04a45c83f3b6f6e630f10117fe)**. + +Check out the fully generated [REST endpoints](https://api.spacex.land/rest/capsules), the +[Swagger live documentation](https://api.spacex.land/rest), +[GraphiQL editor](https://api.spacex.land/graphql) and the +[GraphiQL-Explorer](https://api.spacex.land)! + +By the way, what you see here is a REST API, generated on top of a GraphQL API, created on top of +another REST API…. + +> Why did you do that for!?!? + +## Gradually Migrating from Old REST Implementations + +This is actually a good direction to go. In many of the companies we work with, they've created REST +API layers using old technology on top of their original web-services. + +But those REST implementations are problematic (for all the obvious reasons people choose to move to +GraphQL). + +So our way to go is to create GraphQL implementations on top of those REST layers, migrate the +clients to those implementations and then gradually remove the old RESTful layer and call the +services directly. + +Using Sofa made those transitions much faster because we can offer all the existing clients to +migrate to our GraphQL implementation without actually using GraphQL themselves. We simply expose +the same REST endpoints on top of GraphQL and they are moving to our layer happily because we can +accommodate all of their requests and custom REST endpoints much faster than the original, old REST +implementations. + +--- + +## Give Me More Details + +Sofa uses Express by default, but you can use any other server framework. Sofa is also GraphQL +server implementation agnostic. + +Head over to the [Sofa website](https://sofa-api.com) for documentation and to the +[Github repository](https://github.com/Urigo/sofa) for reporting issues and helping out. + +## How SOFA Works? + +Under the hood, Sofa turns each field of Query and Mutation types into routes. First group of routes +is available only through GET method, mutations on the other hand get POST. + +Sofa uses GraphQL's AST to create an operation with all possible variables (even those deeply +nested) and knows exactly what to fetch. Later on it converts the request's body into operation's +variables and execute it against the Schema. It happens locally, but it's also possible to use an +external GraphQL Server or even Apollo Link. + +Right now Sofa has a built-in support for [Express](https://expressjs.com) but it's totally possible +to use a different framework. The main concept stays exactly the same so only the way we handle the +request differs across different server implementations. + +## GraphQL Subscriptions as Webhooks? + +The way it works is simply, you start a subscription by calling a special route and you get a unique +ID that later on might be used to update or even stop the subscription. Subscriptions are Webhooks. +Sofa knows exactly when there's an even happening on your API and notifies you through the endpoint +you've assigned a subscription to. + +## Models / Resources? + +In some cases you don't want to expose an entire object but just its id. How you're able to do that +with Sofa? You need to have two queries. First one has to return a single entity based just on its +id (which would be an argument) and the second one should resolve a list of those. Also the names +should match, for example a resource called User should have two queries: `user(id: ID): User` and +`users: [User]`. Pretty much the same thing you would do with REST. + +```graphql +type Query { + user(id: ID!): User + users: [User] +} +``` + +Before Sofa creates the routes, it looks for those Models and registers them so when the operations +are built you don't fetch everything but only an id. + +But what if you want to fetch an entire object but only in few places? + +There's an option called `ignore` that allows you to do that. You simply pass a path in which you +want to overwrite the default behavior. + +Given the schema below, you would get just author's id. + +```graphql +type Book { + id: ID + title: String! + author: User! +} + +extend type Query { + book(id: ID!): Book + books: [Book] +} +``` + +With `ignore: ['Book.author']`you end up with an entire User object. + +```ts +sofa({ + // ... + ignore: ['Book.author'] +}) +``` + +## Swagger and OpenAPI + +Thanks to GraphQL's type system Sofa is able to generate always up-to-date documentation for your +REST API. Right now we support Swagger and its OpenAPI specification but it's really easy to adopt +different specs. + +```ts +import sofa, { OpenAPI } from 'sofa-api' + +const openApi = OpenAPI({ + schema, + info: { + title: 'Example API', + version: '3.0.0' + } +}) + +app.use( + sofa({ + schema, + onRoute(info) { + openApi.addRoute(info, { + basePath: '' + }) + } + }) +) + +openApi.save('./swagger.json') +``` + +## Summary + +[sofa-api](https://github.com/Urigo/sofa) makes it extremely easy to create a RESTful API with all +the best practices of REST from a GraphQL server using all its power. + +Stop wasting your life arguing about REST vs GraphQL — Be productive, get the benefits of both +worlds and move into the future of API development. + +I hope this would become the last REST vs. GraphQL article out there…. if you think it won't, +comment with a use case and let's try it out! + +Thanks to [Kamil Kisiela](https://twitter.com/kamilkisiela) for working with me on this and making +this library a reality! diff --git a/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/fed-v1-constant-vus-over-time.svg b/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/fed-v1-constant-vus-over-time.svg new file mode 100644 index 00000000000..020da3d424f --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/fed-v1-constant-vus-over-time.svg @@ -0,0 +1,623 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + apollo-router + + + wundergraph + + + mesh-supergraph-bun + + + mesh-bun + + + mesh + + + mesh-supergraph + + + apollo-server + + + apollo-server-node16 + + + mercurius + + + + + + + + gateway-setup + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 20 + + + 40 + + + 60 + + + 80 + + + 100 + + + 120 + + + 140 + + + 160 + + + 180 + + + + + + + + rps + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/fed-v1-constants-vus-subgraphs-delay-resources.svg b/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/fed-v1-constants-vus-subgraphs-delay-resources.svg new file mode 100644 index 00000000000..60164ee924a --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/fed-v1-constants-vus-subgraphs-delay-resources.svg @@ -0,0 +1,624 @@ + + + + + + + + + + + + + + + + + + + + + + + + + wundergraph + + + apollo-router + + + mesh-supergraph-bun + + + mesh-bun + + + mesh-supergraph + + + mesh + + + apollo-server + + + mercurius + + + + + + + + gateway-setup + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 20 + + + 40 + + + 60 + + + 80 + + + 100 + + + 120 + + + 140 + + + 160 + + + 180 + + + 200 + + + + + + + + rps + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/fed-v1-constants-vus-subgraphs-delay.svg b/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/fed-v1-constants-vus-subgraphs-delay.svg new file mode 100644 index 00000000000..d79ef802d67 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/fed-v1-constants-vus-subgraphs-delay.svg @@ -0,0 +1,624 @@ + + + + + + + + + + + + + + + + + + + + + + + + + wundergraph + + + apollo-router + + + mesh-supergraph-bun + + + mesh-bun + + + mesh-supergraph + + + mesh + + + apollo-server + + + mercurius + + + + + + + + gateway-setup + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 20 + + + 40 + + + 60 + + + 80 + + + 100 + + + 120 + + + 140 + + + 160 + + + 180 + + + 200 + + + + + + + + rps + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/fed-v1-ramping-vus.svg b/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/fed-v1-ramping-vus.svg new file mode 100644 index 00000000000..a5fd11b8ff5 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/fed-v1-ramping-vus.svg @@ -0,0 +1,651 @@ + + + + + + + + + + + + + + + + + + + + + + + + apollo-server + + + mesh + + + mesh-bun + + + mesh-supergraph + + + mesh-supergraph-bun + + + apollo-router + + + wundergraph + + + + + + + + gateway-setup + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 5,000 + + + 10,000 + + + 15,000 + + + 20,000 + + + 25,000 + + + 30,000 + + + 35,000 + + + 40,000 + + + 45,000 + + + 50,000 + + + 55,000 + + + 60,000 + + + + + + + + duration (p95) + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/gw.png b/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/gw.png new file mode 100644 index 00000000000..07c9e294183 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/gw.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/page.mdx b/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/page.mdx new file mode 100644 index 00000000000..788e3a95f54 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/state-of-graphql-gateways-in-2023/page.mdx @@ -0,0 +1,353 @@ +--- +title: State of GraphQL Gateways in 2023 +tags: [graphql, gateway, benchmark, performance, report, graphql-federation] +authors: dotan +date: 2023-09-14 +description: + A six-month journey of researching, benchmarking, exploring and comparing GraphQL gateways and the + Federation spec in 2023. +--- + +import { Callout } from '@theguild/components' + +The GraphQL ecosystem is growing rapidly, and solutions to complex problems are more accessible than +ever before. + +One of the most common questions about GraphQL is how to extend or enrich it. This can be achieved +by adding custom flows, securing your `/graphql` endpoint, improving performance through caching, +orchestrating upstream calls for distributed GraphQL, or monitoring and tracing it. + +Either a specific ad-hoc solution or a gateway designed specifically for GraphQL can solve all of +the following problems. + +**In this blog post, we'll cover The Guild's (and other companies) six-month journey of researching, +exploring, and creating a report on the state of GraphQL gateways in 2023. We compared, tested, and +benchmark various open-source solutions, and we'd like to share our process thus far.** + + + We would like to thank our partners and customers who took part in that journey! + + +## But First, What's a GraphQL Gateway? + +A GraphQL gateway follows a workflow that adds functionality and acts as a proxy between the +consumer and the actual GraphQL server(s) running the GraphQL schema(s). An architecture diagram of +this setup will look like the following: + + + <>![](./gw.png) + + +In the architecture chart above, the GraphQL gateway responsible for exposing your GraphQL server +externally and adding capabilities to it. The GraphQL server, on the other hand, implements the +actual GraphQL schema and runs resolvers, dataloaders, and any other custom code required for +fetching and connecting your data or entities. + +Incorporating a GraphQL gateway into your architecture can help to offload some of the common +features and capabilities from your GraphQL server. Here's a (partial) list of things that a GraphQL +gateway can do: + +1. **Caching** in GraphQL can be implemented in various ways, but it doesn't need to be tightly + coupled to your schema implementation. A GraphQL gateway can handle caching for the GraphQL + server(s) to reduce network traffic to the actual server. This approach also results in a more + lightweight and easier-to-maintain GraphQL server implementation. +2. **Authentication** and **authorization** can be managed by the GraphQL gateway. If you are using + JWT or any other standard, you can verify the token, permissions, and scopes at the gateway + level. Then, you can pass the authentication metadata (such as user ID) to the GraphQL server. + This way, your GraphQL server does not need to handle authentication and can focus solely on + implementing your application's business logic. +3. When it comes to GraphQL servers, **security** is also a concern. By placing a GraphQL gateway in + front of your GraphQL server, you can easily protect your GraphQL API from malicious queries and + guard against [common attack vectors](https://escape.tech/blog/tag/graphql-vulnerability/). +4. A GraphQL gateway can also help **harden** your API and prevent the leakage of sensitive + information, error details, or even PII. +5. **Policy** validations are also a popular feature in GraphQL. Things like rate-limiting, + depth-limit, or complexity limit can all be implemented in your gateway instead of the server. +6. A GraphQL gateway can also address the lack in **modern features like real-time or + subscriptions** in your GraphQL server. The gateway can expose a real-time network transport, + such as WebSocket or SSE, to the consumers while sending a regular HTTP request to the upstream + GraphQL server. +7. **Schema filtering** can also be implemented at the gateway level, allowing for different sets of + features, fields, and types to be exposed on each endpoint. +8. And it can also do easy things, like rendering the latest version of **GraphiQL** for you, with + the all the enabled transports configured (WebSocket/SSE/HTTP). + +## Benchmarking and Comparing GraphQL Gateways + +To understand and compare the GraphQL gateway landscape, we began by collecting information about +existing solutions. This included: + +1. Which distributed GraphQL specifications are supported. +2. How the product/library is used (as code or as a product). +3. What features are supported. + +For our current benchmarks setup, we made the following decisions to ensure a fair comparison +between GraphQL gateways: + +1. We used a distributed GraphQL specification to check the performance of a complex setup that + requires the gateway to take a significant part of the execution flow. We chose the + [Federation](https://the-guild.dev/graphql/hive/federation) (v1) specification since it is widely + used and supported by many gateways. +2. We disabled response caching to ensure a complete request flow is executed. +3. We compared responses to ensure that all gateways respond in the same way. +4. We tracked all available vitals and metrics, such as network traffic, CPU, and RAM. +5. We tested different use cases to simulate real-life scenarios with different flows, such as peak + times or upstream server delays. +6. We are running all scenarios for every change, and have a complete overview of the results and + the stats of every gateway. +7. We are running Rust-based servers for the subgraphs implementation, to ensure it's never becoming + a bottleneck. + +For the benchmark, we picked the following gateways: + +1. [**Apollo-Server**](https://www.apollographql.com/docs/apollo-server/): a JS/TS library for + implementing GraphQL servers or gateways. +2. [**Apollo-Router**](https://www.apollographql.com/docs/router/): the Rust-based product for + running Apollo Federation. +3. [**Wundergraph**](https://wundergraph.com/): a Go-based GraphQL gateway and platform. +4. [**GraphQL-Mesh**](https://the-guild.dev/graphql/mesh): a JS/TS GraphQL gateway based on + [**GraphQL-Yoga**](https://the-guild.dev/graphql/yoga-server), that also supports + anything-to-GraphQL, also supports any other API protocol (REST, OpenAPI, gRPC, SOAP, etc) as + subgraphs + +In addition to that, we used various runtimes for the JavaScript/TypeScript solutions: +[Node.js](https://nodejs.org/en) (18 and 20), and [Bun](https://bun.sh). + +Also, all scenarios are running within Docker container environment, on a stable runner (a dedicated +GitHub Actions runner, that runs 1 concurrent job, to avoid race conditions or missing resources), +and limitations for memory and CPU. + +**This decisions above are subject to change, mainly because we want to introduce more options and +more scenarios.** + +The [entire source code is open-source](https://github.com/the-guild-org/gateways-benchmark), and we +encourage developers to help us with the following: + +- Share more use-cases and realistic scenarios; either by changing the specification used, gateways + tested, plugins, execution flow or parameters. +- Improve the code of the actual gateways and help to improve the GraphQL ecosystem. + +## The Numbers + +### `constant-vus-over-time` + +> [You can find here the latest results, report and statistics](https://github.com/the-guild-org/gateways-benchmark/blob/main/federation-v1/scenarios/constant-vus-over-time/README.md#overview-for-constant-vus-over-time) + +This is the naive, simplest setup with no additional adjustments; the gateway is running the +Federation v1 specification, with a large query (2 top level fields, and 4~7 nested levels, +different entities across multiple subgraphs, and some fragments spreads). + +- **VUs:** 300 +- **Time:** 10 minutes +- **CPU limit:** 2 +- **Memory limit:** 4GB + +| Gateway | RPS ⬇️ | Requests | Duration | Notes | +| :------------------- | :----: | :---------------------: | :----------------------: | :--------------------------------------------------------------------------- | +| apollo-router | 175 | 105605 total, 0 failed | avg: 923ms, p95: 2598ms | ✅ | +| wundergraph | 168 | 100951 total, 0 failed | avg: 900ms, p95: 2583ms | ✅ | +| mesh-supergraph-bun | 120 | 72820 total, 0 failed | avg: 2400ms, p95: 4116ms | ✅ | +| mesh-bun | 111 | 67229 total, 0 failed | avg: 2606ms, p95: 4439ms | ✅ | +| mesh | 94 | 57124 total, 0 failed | avg: 3092ms, p95: 3723ms | ✅ | +| mesh-supergraph | 94 | 56839 total, 0 failed | avg: 3109ms, p95: 3815ms | ✅ | +| apollo-server | 69 | 41971 total, 980 failed | avg: 4282ms, p95: 3174ms | ❌ 980 failed requests, 980 non-200 responses, 980 unexpected GraphQL errors | +| apollo-server-node16 | 67 | 40578 total, 0 failed | avg: 4432ms, p95: 6252ms | ✅ | +| mercurius | 50 | 30432 total, 50 failed | avg: 5912ms, p95: 6121ms | ❌ 50 failed requests | + +export const Card = ({ children }) => ( +
    + {children} +
    +) + +![](./fed-v1-constant-vus-over-time.svg) + +Here are some insights worth mentioning: + +✅ Almost gateways tested were able to process all requests without any failures (side note: when +CPU limit is lower, some JS gateways are failing to respond to requests, and the requests are +failing with a timeout or connecting drop, as you can see with `apollo-server` on Node.js 18). + +🏆 **Apollo-Router** and **Wundergraph** are the fastest gateways, with an average response time of +900ms and a p95 of 2500ms, while under pressure of incoming requests. + +🚅 The fastest JS-based gateway is GraphQL-Mesh (almost x2 than other JS-based), and when running on +Bun runtime, it's even faster (x1.5). + +📈 The peak memory usage of **Apollo-Router** was around 400MB, while **Wundergraph** needed more +resources (x3, 1.3GB) to handle the same amount of requests. Memory consumption of **GraphQL-Mesh** +in the test above was 550MB on Bun, and 1.4GB on Node.js 18. + +📊 `mercurius` (Fastify-based) failed to handle ~50 requests. + +### `constant-vus-subgraphs-delay` + +> [You can find here the latest results, report and statistics](https://github.com/the-guild-org/gateways-benchmark/tree/main/federation-v1/scenarios/constant-vus-subgraphs-delay#overview-for-constant-vus-subgraphs-delay) + +Same as **`constant-vus-over-time`** but with a random delay (20~150ms) for all upstream HTTP calls. +This scenario forces the gateway to keep more in-flights requests in-memory, and creates a more +realistic scenario. + +- **VUs:** 300 +- **Time:** 10 minutes +- **CPU limit:** 2 +- **Memory limit:** 4GB + +| Gateway | RPS ⬇️ | Requests | Duration | Notes | +| :------------------ | :----: | :--------------------: | :------------------------: | :------------------------------------------------------------------------ | +| wundergraph | 192 | 115808 total, 0 failed | avg: 1311ms, p95: 2017ms | ✅ | +| apollo-router | 186 | 112402 total, 0 failed | avg: 1123ms, p95: 2335ms | ✅ | +| mesh-supergraph-bun | 109 | 65876 total, 0 failed | avg: 2664ms, p95: 4510ms | ✅ | +| mesh-bun | 103 | 62060 total, 0 failed | avg: 2832ms, p95: 4791ms | ✅ | +| mesh-supergraph | 101 | 61344 total, 0 failed | avg: 2875ms, p95: 3437ms | ✅ | +| mesh | 93 | 56112 total, 0 failed | avg: 3154ms, p95: 3838ms | ✅ | +| apollo-server | 64 | 38683 total, 92 failed | avg: 4652ms, p95: 6050ms | ❌ 92 failed requests, 92 non-200 responses, 92 unexpected GraphQL errors | +| mercurius | 12 | 7838 total, 0 failed | avg: 23393ms, p95: 24457ms | ✅ | + +![](./fed-v1-constants-vus-subgraphs-delay.svg) + +Here are some insights worth mentioning: + +🏆 Similar to the previous test, **Apollo-Router** and **Wundergraph** are the fastest gateways, +with an average response time of 1100ms and a p95 of 2300ms, while under pressure of incoming +requests. + +🚅 The fastest JS-based gateway is **GraphQL-Mesh** (almost x2 than other JS-based), and when +running on Bun runtime, it's even faster (x1.5). + +📈 The increased time of in-flight requests and the combination of Go runtime, led **Wundergraph** +use 2.6GB of RAM, while **Apollo-Router** was able to handle the same amount of requests with 600MB +of RAM. + +📈 **GraphQL-Mesh** running on Bun was also able to handle a significant amount of requests with +just 600MB of RAM. + +### `constant-vus-subgraphs-delay-resources` + +> [You can find here the latest results, report and statistics](https://github.com/the-guild-org/gateways-benchmark/blob/main/federation-v1/scenarios/constant-vus-subgraphs-delay-resources/README.md#overview-for-constant-vus-subgraphs-delay-resources) + +Same as **`constant-vus-subgraphs-delay-resources`** but with additional resources (CPU and RAM) and +more concurrent VUs. + +- **VUs:** 500 +- **Time:** 10 minutes +- **CPU limit:** 4 +- **Memory limit:** 8GB + +| Gateway | RPS ⬇️ | Requests | Duration | Notes | +| :------------------ | :----: | :----------------------: | :------------------------: | :------------------------------------------------------------------------------ | +| wundergraph | 192 | 115980 total, 0 failed | avg: 1808ms, p95: 3277ms | ✅ | +| apollo-router | 185 | 111874 total, 0 failed | avg: 1674ms, p95: 3630ms | ✅ | +| mesh-supergraph-bun | 109 | 65985 total, 0 failed | avg: 4451ms, p95: 7530ms | ✅ | +| mesh-bun | 102 | 61716 total, 0 failed | avg: 4756ms, p95: 7949ms | ✅ | +| mesh-supergraph | 102 | 61806 total, 0 failed | avg: 4746ms, p95: 5971ms | ✅ | +| mesh | 95 | 57722 total, 0 failed | avg: 5109ms, p95: 5981ms | ✅ | +| apollo-server | 67 | 40608 total, 2610 failed | avg: 7387ms, p95: 59998ms | ❌ 2610 failed requests, 2610 non-200 responses, 2610 unexpected GraphQL errors | +| mercurius | 12 | 7941 total, 0 failed | avg: 38437ms, p95: 41293ms | ✅ | + +![](./fed-v1-constants-vus-subgraphs-delay-resources.svg) + +Here are some insights worth mentioning: + +📊 More CPU and more RAM were able to push the gateways tested to better results. **Apollo-Router** +didn't seem to use most of the provided RAM (only needed 700MB) while **Wundergraph** has high +memory consumption (almost 3GB). + +📈 The ability of **GraphQL-Mesh** to use Node.js' `cluster` feature pays off when more CPUs are +available to use. + +📊 Under pressure, `apollo-server` (JS) was not able to some percentage of the requests. + +### `ramping-vus` + +> [You can find here the latest results, report and statistics](https://github.com/the-guild-org/gateways-benchmark/tree/main/federation-v1/scenarios/ramping-vus#overview-for-fed-v1-ramping-vus) + +Same as the previous setup, but this scenario aims to push the gateway to the limit by **running a +gradual increase of VUs**. Some gateways are lagging behind or breaks at such scale (with the +limitation of resources), and it's easier to spot the top capacity of requests it can handle while +under pressure. + +- **VUs:** 50 -> 2000 (ramping) +- **Time:** 10 minutes +- **CPU limit:** 4 +- **Memory limit:** 8GB + +The chart below measures the **p95** of the request duration (lower is better). + +| Gateway | duration(p95)⬇️ | RPS | Requests | Durations | Notes | +| :------------------ | :-------------: | :-: | :----------------------: | :----------------------------------------------------: | :------------------------------------------------------------------------------ | +| wundergraph | 6798ms | 168 | 102752 total, 0 failed | avg: 2665ms, p95: 6799ms, max: 18164ms, med: 2218ms | ✅ | +| apollo-router | 6826ms | 172 | 104975 total, 0 failed | avg: 2565ms, p95: 6827ms, max: 18279ms, med: 2098ms | ✅ | +| mesh-supergraph-bun | 16168ms | 120 | 74289 total, 0 failed | avg: 8354ms, p95: 16169ms, max: 40017ms, med: 7648ms | ✅ | +| mesh-supergraph | 18031ms | 105 | 64859 total, 0 failed | avg: 9598ms, p95: 18032ms, max: 25650ms, med: 9483ms | ✅ | +| mesh-bun | 18066ms | 109 | 67768 total, 0 failed | avg: 9245ms, p95: 18067ms, max: 44697ms, med: 8680ms | ✅ | +| mesh | 19777ms | 98 | 60981 total, 0 failed | avg: 10275ms, p95: 19777ms, max: 27667ms, med: 10122ms | ✅ | +| apollo-server | 60000ms | 75 | 48009 total, 7489 failed | avg: 13363ms, p95: 60001ms, max: 60717ms, med: 4289ms | ❌ 7489 failed requests, 7489 non-200 responses, 7489 unexpected GraphQL errors | + +![](./fed-v1-ramping-vus.svg) + +Here are some insights worth mentioning: + +🏆 **Apollo-Router** and **Wundergraph** are the fastest gateways and are able to process all +requests, with an average duration of 6500ms. + +❌ **Apollo-Server** and mercurius (JS-based) fails to handle the load, and both are failing to +respond to requests (with a timeout or connection drop). From the JS-based solution, +**GraphQL-Mesh** is the only one able to handle all requests. + +### The Fine Print + +Here are some important points to consider regarding the gateways tested in this benchmark: + +- **Federation specification compatibility:** + - Among the list of gateways tested, only `apollo-server`, `apollo-router`, and `graphql-mesh` + fully support the Federation v1 specification without any adjustments. Other servers, such as + `wundergraph`, do not have full support (Federation v2 specification is fully supported at the + moment only by `apollo-router`, `apollo-server` and `graphql-mesh`). + - We used the Federation Supergraph specification during the server runs, which is an artifact + produced from a successful composition of subgraphs. Some gateways, like `wundergraph` and + `mercurius`, do not support this specification, and a list of services was provided with a + real-time composition based on GraphQL introspection. + - We chose to use Federation v1 and not Federation v2 for this test because it is widely adopted + and more supported by available gateways. +- **Node.js runtimes:** + - For Node.js, we used version 18 (LTS) of the engine. + - In some of the scenarios mentioned above, we used Bun (recently released v1). +- **Fair comparison:** + - All gateways were running the same GraphQL schema, and the same GraphQL query was executed. + - All gateways were running as Docker containers, using the latest available version (and were + kept updated using Renovate). + - Resources were limited to 1 CPU and 1GB RAM for all gateways in all scenarios. +- **Subgraphs metrics:** + - In all the scenarios mentioned, all subgraphs consumed approximately 3% of the CPU and 10MB of + memory. We measured this to ensure that the subgraphs were not becoming the bottleneck and + affecting the results. +- **Not only Federation**: + - The expectation from a GraphQL gateway is to do more than query planner - while GraphQL-Mesh and + Wundergraph has broader scope and supports more use-cases, the Apollo tools are tailored to the + Apollo ecosystem, and built for a specific purpose. + +### What Did We Learn? + +- **Compatibility** is a big issue in the GraphQL ecosystem. The lack of a standard specification + for GraphQL gateways makes it hard to compare and benchmark different solutions. We hope that + future specifications will be more widely adopted and supported by the community. +- **Performance** is a big concern for GraphQL gateways. The ability to handle a large number of + requests and to scale easily is a must-have for any GraphQL gateway. Resource consumption is also + a big concern, and it's important to aim to keep the memory and CPU usage low, to avoid + bottlenecks and to keep the cost low. +- **Runtime environment matters:** Node.js is great, but we are keeping an eye on other runtimes, + such as Bun. We are also looking forward to seeing more solutions built with these runtimes. Rust + keeps RAM lower when written correctly, and Go is also a great option for building GraphQL + gateways, but memory consumption could be higher. + +### Keeping This Report Updated + +We welcome contributions from the community. If you notice any incorrect configuration, setup, or +any other issue that affects the results or the comparison, please +[reach out to us and report a GitHub issue](https://github.com/the-guild-org/gateways-benchmark/issues/new). +This will help us keep this benchmark up to date and ensure a fair comparison. + +We also encourage companies and developers to improve their stack. We would love to update this blog +post when significant changes in results occur (the results in the repository are always up to +date!). diff --git a/packages/web/docs/src/app/blog/(posts)/stellate-acquisition/page.mdx b/packages/web/docs/src/app/blog/(posts)/stellate-acquisition/page.mdx new file mode 100644 index 00000000000..dadf54c989c --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/stellate-acquisition/page.mdx @@ -0,0 +1,63 @@ +--- +title: The Guild acquires Stellate +authors: uri +tags: [graphql, company] +date: 2024-09-10 +description: + Today we are very pleased to announce that we have acquired Stellate GraphQL CDN - the leading + GraphQL Caching solution. +--- + +Today we are very pleased to announce that we have acquired +[Stellate](https://stellate.co/?utm_source=theguild&utm_medium=blog&utm_campaign=announcement) +GraphQL CDN - the leading GraphQL Caching solution. + +The Stellate team has created an innovative platform, industry leading solution for GraphQL Caching, +fulfilling GraphQL's full potential for caching, enabling its superiority over all REST based +caching solutions. + +The Guild's [goal](https://the-guild.dev/about-us) - providing an open platform with everything a +developer needs to take GraphQL into production, fits perfectly with Stellate's superb execution. + +That's why acquiring Stellate and integrating it into our +[GraphQL Hive Open Platform](https://the-guild.dev/graphql/hive) makes so much sense. + +We've always prided ourselves on being the provider the GraphQL community can count on for the long +term. +In the past, we've taken maintainership of a number of popular solutions, to give them new life and +improve them to the be industry leading solutions: + +- We've [taken ownership](https://the-guild.dev/blog/graphql-tools-v6) of the famous GraphQL Tools + library from Apollo - improving it for many years and recently integrating it into our + [Hive Gateway](https://the-guild.dev/graphql/hive/docs/gateway) - our open source gateway that + fully supports [Apollo Federation](https://the-guild.dev/graphql/hive/federation). +- We've [taken ownership](https://the-guild.dev/blog/announcing-graphql-yoga-v2) of GraphQL Yoga + from Prisma - making it the [leading](https://the-guild.dev/graphql/yoga-server/docs/comparison) + Subgraph and GraphQL server for the JS ecosystem. +- We've done the same with + [many other libraries](https://www.prisma.io/blog/the-guild-takes-over-oss-libraries-vvluy2i4uevs) + and continuously support official GraphQL Foundation projects like + [GraphiQL](https://graphql.org/conf/2023/sessions/e0985f6bdb4bbf07a5ca5ba72fbcc39c/), + [GraphQL-HTTP](https://github.com/graphql/graphql-http#graphql-http), + [GraphQL's official website](https://graphql.org/blog/2024-06-11-announcing-new-graphql-website/) + and many more. + +We offer GraphQL Hive to our users - a fully featured, complete 360˚ GraphQL platform that includes +a schema registry, analytics platform, Federated Gateway - available either as a hosted platform or +as a fully open-source, self-hosted solution under the +[MIT license](https://github.com/kamilkisiela/graphql-hive/blob/main/LICENSE). + +And now with an industry leading global GraphQL Edge Caching CDN! + +Like all of our other projects, this is just the start! We are looking to hear from you, where you +wish us to take Stellate and our platform in the future. + +Existing Stellate users, we are here for you. Reach out directly and schedule a call with us, we +want to learn about your current GraphQL experience and how the existing solutions can work better +for you. + +To everyone else, try Stellate today, +[try Hive today](https://the-guild.dev/graphql/hive/docs/get-started/first-steps) and let us know +what you wish the future of GraphQL to look like. + +See you all at [GraphQL Conf](https://graphql.org/conf/2024/)! diff --git a/packages/web/docs/src/app/blog/(posts)/styled-components-encapsulation/847a27410df9de57458f3736cf937b9b.png b/packages/web/docs/src/app/blog/(posts)/styled-components-encapsulation/847a27410df9de57458f3736cf937b9b.png new file mode 100644 index 00000000000..fcdf610e847 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/styled-components-encapsulation/847a27410df9de57458f3736cf937b9b.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/subscriptions-and-live-queries-real-time-with-graphql/page.mdx b/packages/web/docs/src/app/blog/(posts)/subscriptions-and-live-queries-real-time-with-graphql/page.mdx new file mode 100644 index 00000000000..8a18eea5ebe --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/subscriptions-and-live-queries-real-time-with-graphql/page.mdx @@ -0,0 +1,1386 @@ +--- +title: Subscriptions and Live Queries - Real Time with GraphQL +authors: laurin +tags: [graphql, subscription, real-time] +date: 2021-04-21 +description: + GraphQL subscriptions are used by many. Let's take a look at both methods for achieving real-time + communication with GraphQL. +--- + +Subscriptions are the go-to solution for adding real-time capabilities to a GraphQL-powered +application. At the same time the term GraphQL Live Query floats around and can often be found in +the context of subscriptions. + +While +[GraphQL Subscriptions have been a part of the GraphQL Specification](https://spec.graphql.org/draft/#sec-Subscription) +for some time, GraphQL Live Queries are not part of the specification and further there is no RFC +going on. + +However, +[discussion about GraphQL Live Queries started way back when GraphQL Subscriptions were designed](https://github.com/graphql/graphql-spec/issues/284). + +So let's take a recap of GraphQL Subscriptions, take a look at existing Live Query Solutions today +and compare the differences between the two solutions for real-time. + +**Note:** This blog post is also available as a YouTube video. + +import { YoutubeIframe } from '#components/youtube-iframe' + + + +## Subscription Recap + +The +[GraphQL Subscription RFC was merged back in March 2017](https://github.com/graphql/graphql-spec/pull/267). +The first major and wide-adopted transport implementation was (and probably is) +[`subscriptions-transport-ws`](https://npmjs.com/package/subscriptions-transport-ws). It was +developed by Apollo, but unfortunately they seem to have abandoned it since then. Fortunately, we +now have a successor [`graphql-ws`](https://github.com/enisdenjo/graphql-ws). + +A subscription operation looks similar to this. + +```graphql +subscription onPostAddedSubscription { + onPostAdded { + post { + id + author { + id + login + } + } + } +} +``` + +In contrast to a GraphQL query operation, a subscription operation is only allowed to select a +single field on the GraphQL Subscription root type. + +Furthermore, executing a subscription operation represents a stream of results where executing a +query operation only results in a single result. + +Let's take a quick look at the `graphql-js` reference implementation! + +Since promises cannot represent multiple values over time, the `graphql-js` reference +implementations uses [`AsyncIterator`](https://tc39.es/ecma262/#sec-asynciterable-interface), which +is a native structure similar to Observables (which one might already know by having dug a bit +deeper into the most-widely adopted GraphQL clients). + +Each subscription root field must provide a `subscribe` function that returns an `AsyncIterator` and +optionally has a `resolve` function for mapping the published events. + +When a subscription operation is executed, the `subscribe` function of that field resolver is called +and the `AsyncIterator` returned by it will be used as the source for the stream of events returned +to the client. + +Once the `AsyncIterator` publishes a value (the payload or event), the optional `resolve` function +on the selected subscription root field is called with the value. All subsequently executed +resolvers in the resolver/type tree behave like normal query resolvers. + +The most basic implementation for a counter would look similar to this: + +```ts +const sleep = (t = 1000) => new Promise(res => setTimeout(res, t)) + +const resolvers = { + Subscription: { + countToNumber: { + subscribe: async function* (_, args) { + for (let counter = 1; counter <= args.toNumber; counter++) { + yield { countToNumber: counter } + await sleep() + } + } + } + } +} +``` + +The above subscription will count up to the number provided via the `toNumber` argument (while +having a delay of one second between each message) and then complete. + +Of course, in a real-world application we would like to subscribe to other event sources instead of +some static, pre-defined events. + +The most common used (but not best maintained) library for such a PubSub engine in the GraphQL +context is [`graphql-subscriptions`](https://github.com/apollographql/graphql-subscriptions). There +are also adapters available for more distributed systems (where all GraphQL API replicas must be +notified about the event) e.g. over Redis. + +If there is not need for scaling horizontally the `graphql-subscriptions` package can be omitted and +be replaced with the Node.js native `events` module: + +```tsx +import { EventEmitter, on } from 'events' + +export const createPubSub = ( + eventEmitter: EventEmitter +) => { + return { + publish: >( + topic: TTopic, + payload: TTopicPayload[TTopic] + ) => void emitter.emit(topic as string, payload), + subscribe: async function* >( + topic: TTopic + ): AsyncIterableIterator { + const asyncIterator = on(emitter, topic) + for await (const [value] of asyncIterator) { + yield value + } + } + } +} + +const pubSub = createPubSub(new EventEmitter()) +``` + +A (type-safe) PubSub implementation in 21 lines of code. We will use this for the example below. + +In my opinion the different pub sub implementations should rather be based on EventEmitter instead +of `graphql-subscriptions`. A PubSub can but musn't be used together with GraphQL. By choosing the +name `graphql-subscriptions` it gives the impression that the logic is specific to GraphQL and +reduces other possible contributions from people that need a similar event abstraction. + +Therefore, I hope the next generation/iteration of Node.js PubSub implementations is less specific. + +Having said that, let's take a look at a more "real-world" like example of using subscriptions with +PubSub: + +```ts +const resolvers = { + Subscription: { + onPostAdded: { + subscribe: async function* (_, _, context) { + for await (const { id } of context.pubSub.subscribe('POST_ADDED')) { + const post = await context.postStore.load(id, context.viewer) + if (post === null) { + continue + } + yield { onPostAdded: loadedPost } + } + } + } + }, + Mutation: { + postAdd: (_, args, context) => { + const post = context.postStore.create(args) + // wo don't wanna publish the whole object via our event emitter, the id should be sufficient + context.pubSub.publish('POST_ADDED', { id: args.id }) + return post + } + } +} +``` + +Until now, we only took a look at the resolvers. Let's also quickly check the `subscribe` function +exported from `graphql-js`, which can be used for executing subscriptions. + +```ts +import { subscribe } from 'graphql' + +const subscribeResult = await subscribe({ + schema, + document +}) + +if (isAsyncIterable(subscribeResult)) { + for await (const executionResult of subscribeResult) { + sendToClient(executionResult) + } +} else { + sendToClient(subscribeResult) +} +``` + +The `subscribe` function returns either a `AsyncIterable` that publishes multiple `ExecutionResult`s +or a single `ExecutionResult` in case the setup of the subscription somehow fails. + +The interesting thing is that we can use any transport for delivering the results to client. The +most popular implementation (as mentioned before) is `subscriptions-transport-ws`. Unfortunately, +since it is poorly maintained, the GraphQL Working Group came up with a new implementation over +WebSockets, [`graphql-ws`](https://github.com/enisdenjo/graphql-ws). + +But we are not forced to use WebSockets at all. +[Server Side Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)) +might be a more lightweight solution for both our server and client. + +It is actually a shame that the default `express-graphql` reference HTTP transport implementation +does not come with a built-in subscription solution. + +Fortunately, we now have libraries like +[Graphql Helix](https://github.com/contrawork/graphql-helix), which, in my humble opinion, should +replace `express-graphql` as the reference HTTP implementation since GraphQL Helix is also not tied +to any web server framework. + +For public GraphQL APIs, I am convinced that Server Sent Events is the future as there is less work +required for implementing the protocol. + +I also built +[my own transport over Socket.io](https://github.com/n1ru4l/graphql-live-queries/tree/main/packages/socket-io-graphql-server), +which uses WebSockets by default and HTTP polling as a fallback. + +As you can see, with GraphQL subscriptions, we are free to choose the best transport for our +application requirements! + +--- + +Now that we took a look at how GraphQL Subscription resolvers are implemented on the server-side, +lets also check out how we can consume the GraphQL API on the client-side! + +Usually we will have a network interface that is called by our favorite GraphQL client. Every single +client has a different name and implementation. Apollo calls them links, Relay calls them fetcher +functions, and urql calls them exchanges. + +All of them have one thing in common. They are working with observable-like data structures, which +basically means for consuming subscriptions, all major GraphQL client-libraries decided to use +Observables, instead of AsyncIterators (which are still not part of the ECMA Spec as of October +2020). + +Like an AsyncIterator, an Observable can represent multiple values. As already mentioned, each +client library and transport have slightly different interfaces. I will use `graphql-ws` with +`relay-runtime` as an example. + +The example is taken straight from the `graphql-ws` section. + +```ts +import { createClient } from 'graphql-ws' +import { Network, Observable, RequestParameters, Variables } from 'relay-runtime' + +const subscriptionsClient = createClient({ + url: 'wss://i.love/graphql', + connectionParams: () => { + const session = getSession() + if (!session) { + return {} + } + return { + Authorization: `Bearer ${session.token}` + } + } +}) + +// yes, both fetch AND subscribe handled in one implementation +function fetchOrSubscribe(operation: RequestParameters, variables: Variables) { + return Observable.create(sink => { + if (!operation.text) { + return sink.error(new Error('Operation text cannot be empty')) + } + return subscriptionsClient.subscribe( + { + operationName: operation.name, + query: operation.text, + variables + }, + { + ...sink, + error: err => { + if (err instanceof Error) { + sink.error(err) + } else if (err instanceof CloseEvent) { + sink.error( + new Error( + `Socket closed with event ${err.code}` + err.reason + ? `: ${err.reason}` // reason will be available on clean closes + : '' + ) + ) + } else { + // GraphQLError[] + sink.error(new Error(err.map(({ message }) => message).join(', '))) + } + } + } + ) + }) +} + +const network = Network.create(fetchOrSubscribe, fetchOrSubscribe) +const store = new Store(new RecordSource()) +export const environment = new Environment({ network, store }) +``` + +With this configuration, Relay can now execute subscription operations. Because the graphql-ws +protocol is way more complex than the GraphQL over HTTP protocol, we use the client exported from +the graphql-ws package instead. This results in some additional bundle-size. As mentioned before SSE +might be a better, lightweight alternative. + +That aside, let's start with a basic subscription that should update one of our components. + +Our `PostRender` already shows some content. + +```ts +const PostQuery = graphql` + query PostQuery($postId: ID!) { + post(id: $postId) { + id + title + content { + text + } + totalLikeCount + } + } +` + +const PostRenderer = ({ postId }: { postId: string }) => { + const { props } = useQuery(PostQuery, /* variables */ { postId }) + + return +} +``` + +As a new feature requirement, the like count of the post should get updated once someone hits the +like button. + +We could choose different ways of implementing such a subscription. + +1. General Subscription for changed post + +```graphql +subscription PostChanged($postId: ID!) { + onPostChanged(postId: $postId) { + post { + id + totalLikeCount + } + } +} +``` + +2. Specific Subscription + +```graphql +subscription PostLikeCountChanged($postId: ID!) { + onPostLikeCountChanged(postId: $postId) { + totalLikeCount + } +} +``` + +Both solutions have different implications. + +1. General Subscription for changed post + +This approach is not limited to notifying whether the `totalLikeCount` of the post likes have +changed; in the future we could adjust the selection set on the post field as it also returns a +`Post` type similar to our already existing `Query.post` field. It will automatically update the +post record already in the cache as the Relay store (similar to other clients) can identify the post +object via the id field. The drawback is that we could potentially send too much data over the wire. +E.g. if we also wanted to subscribe to title changes all additional selected fields are sent to the +client each time the underlying event is emitted, even if only the `totalLikeCount` value has +changed. + +```tsx +const PostChangedSubscription = graphql` + subscription PostChanged($postId: ID!) { + onPostChanged(postId: $postId) { + post { + id + totalLikeCount + } + } + } +` + +const PostRenderer = ({ postId }: { postId: string }) => { + const { props } = useQuery(PostQuery, /* variables */ { postId }) + // thats all we gotta do + useSubscription(PostChangedSubscription, /* variables */ { postId }) + + return +} +``` + +2. Specific Subscription + +This subscription is specifically designed for only an update of the `totalCount`. However, the +subscription result returns no `Post` type. Therefore, we cannot make use of the automatic cache +updates via the `id`. We have to additionally define a handler for updating the post in the cache. + +```tsx +const PostLikeCountChangedSubscription = graphql` + subscription PostLikeCountChanged($postId: ID!) { + onPostLikeCountChanged(postId: $postId) { + totalCount + } + } +` + +const PostRenderer = ({ postId }: { postId: string }) => { + const { props } = useQuery(PostQuery, /* variables */ { postId }) + useSubscription( + PostLikeCountChangedSubscription, + /* variables */ { postId }, + { + // we need to manually update the cache :( + updater: (store, payload) => { + const record = store.get(postId) + if (record) { + record.setValue('totalLikeCount', payload.onPostLikeCountChanged.totalCount) + } + } + } + ) + + return +} +``` + +Obviously, for this example, no sane person would actually want to choose the second solution over +the first one. + +But as our business requirements might get more complex we might need to do manual cache updates. + +A very good example for this is lists. Imagine us having a set of data in which a single item +changes. The "easy to implement" solution would be to just refetch the complete list every time a +single item is added/removed/changed. However, For a list containing hundreds of items only sending +the changed item to the client might be the smarter and faster solution... + +This can be implemented via a union type. + +```graphql +type OnUserAdded { + user: User! +} +type OnUserRemoved { + removedUserId: ID! +} + +union OnUserListChange = OnUserAdded | OnUserRemoved + +type Subscription { + onUserListChange: OnUserListChange! +} +``` + +The corresponding code, including handling the cache updates: + +```tsx +const UserListQuery = graphql` + query UserListQuery { + users { + id + name + } + } +` + +const UserListChangeSubscription = graphql` + subscription UserListChangeSubscription { + onUserListChange { + __typename + ... on OnUserAdded { + user { + id + name + } + } + ... on OnUserRemoved { + removedUserId + } + } + } +` + +const UserListRenderer = ({ postId }: { postId: string }) => { + const { props } = useQuery(UserListQuery) + useSubscription( + UserListChangeSubscription, + /* variables */ { postId }, + { + // we need to manually update the cache :( + updater: (store, payload) => { + const rootField = store.getRootField() + const users = rootField.getLinkedRecords('users') + if (users) { + switch (payload.onUserListChange.__typename) { + case 'OnUserAdded': { + const newUser = store.create(payload.onUserListChange.user.id, 'User') + newUser.setValue('name', payload.onUserListChange.user.name) + rootField.setLinkedRecords('users', [...users, newUser]) + break + } + case 'onUserRemoved': { + rootField.setLinkedRecords( + users.map(user => user.getDataID() !== payload.onUserListChange.removedUserId) + ) + break + } + } + } + } + } + ) + + return +} +``` + +As our application grows the manual cache update code can become so complex and confusing that I +have considered switching back into simply refetching the queries in some applications. + +Fortunately, Relay contributors have +[worked on some nice query directives](https://github.com/facebook/relay/issues/3123) that allow +reducing such cache update code. It won't cover all cases though. + +In all of the above examples we responded (more or less implicitly) to data change events. + +Subscriptions can be used to apply data changes on the client. But they are probably not the best +tool for that. + +Before taking a look of what could be a better tool, let's look at another usage example for +subscriptions. + +```tsx +const OnPlaySound = graphql` + subscription OnPlayChatSound { + onPlayChatSound { + soundUrl + } + } +` + +const ChatRenderer = () => { + const chat = useQuery(ChatQuery) + useSubscription( + OnPlaySound, + useMemo({ + onNext: payload => { + playSound(payload.onPlayChatSound.soundUrl) + } + }) + ) + + return +} +``` + +The difference here is that we are not manipulating our existing data but rather executing a side +effect. + +Subscriptions can also be used for side effects that should not alter or touch any data in the +cache. + +## Live Queries + +What is a live query? Well, there is no specification for that so the term is ambiguous. Today, +there are several solutions one could describe as live queries. + +All those solutions have one thing in common: **Trying to keep the client state in sync with the +server**. + +Which can be paraphrased as **observing data**. + +Before we take a look at how all of those implementations, let's break down what we should or can +expect from a live query implementation. + +**Automatically update the clients** + +This is one is pretty obvious. The live query implementation should keep the client state as close +to the server state as possible. + +**Efficiently update clients** + +If only a part of our data has changed we don't necessarily need to send the complete execution +result over the wire. Only sending the changed parts might be more efficient. In general the network +overhead should be minimal. + +**Flexibility** + +Ideally a solution should not be tied to a specific database or coupled with some SaaS service that +won't be around next year. + +**Adoptability** + +In case we already have some kind of GraphQL schema or server implementation, the live query +solution should be adobtable without changing everything and starting new. + +## Polling a Query + +The easiest solution for implementing live queries would be to poll a query in intervals. Most +GraphQL clients have such a feature already implemented. + +```ts +const PostQuery = graphql` + query PostQuery($postId: ID!) { + post(id: $postId) { + id + title + content { + text + } + likes { + totalCount + } + } + } +` + +const PostRenderer = ({ postId }: { postId: string }) => { + const { props } = useQuery( + PostQuery, + /* variables */ { postId }, + { + pollIntervall: 5000 + } + ) + + return +} +``` + +1. Automatically updates clients. **No.** ❌ + +Depending upon the use-case, this could be a valid solution, but for true real-time applications +that require instant feedback, this is not precise enough due to the delay caused by the poll +interval. + +2. Efficiently updates clients. **No.** ❌ + +The whole execution result is sent over to the client for every single time the operation is +re-executed. A lot more data than necessary is transported over the wire to the client even if +nothing has changed from the last poll interval. + +3. Flexibility. **High.** ✅ + +Straight forward, as this does not rely on any changes on the server and only slight changes on our +frontend. + +4. Adoptability. **High.** ✅ + +Straight forward, again almost no changes required. + +## Live Queries over Subscriptions + +We already had a "live query over subscription"-like example above. + +```tsx +const PostChangedSubscription = graphql` + subscription PostChanged($postId: ID!) { + onPostChanged(postId: $postId) { + post { + id + title + likes { + totalCount + } + } + } + } +` + +const PostRenderer = ({ postId }: { postId: string }) => { + const { props } = useQuery(PostQuery, /* variables */ { postId }) + // thats all we gotta do + useSubscription(PostChangedSubscription, /* variables */ { postId }) + + return +} +``` + +Let's ditch the `PostQuery` completely and instead use a `PostSubscription` that always emits an +initial event. + +```tsx +const PostSubscription = graphql` + subscription PostSubscription($postId: ID!) { + post(id: $postId) { + id + title + likes { + totalCount + } + } + } +` + +const PostRenderer = ({ postId }: { postId: string }) => { + const { props } = useSubscription(PostChangedSubscription, /* variables */ { postId }) + + return +} +``` + +A server resolver implementation could look similar to this: + +```tsx +const resolvers = { + Subscription: { + post: async function* (_, { id }, context) { + let loadPost = () => context.postStore.load(id, context.viewer) + // publish first post + yield { post: await loadPost() } + // publish post again once change event is emitted + for await (const _ of context.pubSub.subscribe(`Post:${id}`)) { + yield { post: await loadPost() } + } + } + } +} +``` + +We replace two operations with a single one! + +A similar approach is used by +[Hasura](https://github.com/hasura/graphql-engine/blob/master/architecture/live-queries.md) and also +[PostGraphile](https://graphile.org/postgraphile/live-queries). + +The obvious drawback of both platforms is the lock-in into using a specific database. Of course that +might not be a problem for most people, but having a general solution that works with any data +source would be nice as more complex schema could fetch from different database types or other third +party APIs. + +Those implementations keep track of all the resources in the execution result and re-execute the +subscription operation once any of those resources changes. + +The resolver implementation above only responds to emitted post changes. In order keep track of all +the resources defined in an operation's selection set, we will have to come up with a smart +abstraction. + +Another drawback of subscriptions for live queries is the limitation of only selecting one root +subscription field, which is defined by the GraphQL subscription specification. Furthermore, we must +also redeclare our query type fields to the subscription type. + +There is a workaround we can apply for re-exporting the whole Query type via a sub-field in the +subscription type. + +```graphql +type Query { + user(id: ID!): User + post(id: ID!): Post +} + +type Subscription { + live: Query! +} +``` + +This approach would allow us to query everything on the query object type via the live field on the +subscription object type, without having the limit of only being able to query one resource or +having to redeclare every resource field resolver on the subscription type. Neat! + +```ts +const PostSubscription = graphql` + subscription PostSubscription($postId: ID!, $userId: ID!) { + live { + post(id: $postId) { + id + title + totalLikeCount + } + user(id: $userId) { + id + name + } + } + } +` +``` + +Okay, now we can select everything we could have selected with our query operation! + +1. Automatically updates clients. **Yes.** ✅ + +When using services like PostGraphile and Hasura that is the case. However, for any additional +resolvers that are added on top of the service schema, we cannot implement an invalidation +mechanism. In user-land we will have to come up with an implementation of resource tracking by +ourselves. + +2. Efficiently updates clients. **No.** ❌ + +The whole execution result is sent over to the client for every single time a live query is +invalidated. + +3. Flexibility. **Low.** ❌ + +Both Hasura and PostGraphile are tightly coupled to a SQL database. For any custom resolvers, we +have to come up with the mechanism for resource tracking and invalidation ourselves. + +4. Adoptability. **Low.** ❌ + +Switching to a server powered by PostGraphile or Hasura with an already existing GraphQL schema is +no easy task. + +## GraphQL Live Queries over Subscriptions with JSON Patch + +Ideally, we only want to send a patch to the client that provides instructions on how to get from +the previous execution result to the next. The lack of these instructions has been a big flaw in the +previous two implementations. + +The RFCs and implementations for the `@defer` and `@stream` introduced ways of sending (delayed) +partial results to clients. However, those "patch" operations are currently highly limited to a +"replace at path" and "append to list" operation. + +A format such as [JSON Patch](https://tools.ietf.org/html/rfc6902) might be a better alternative for +live queries. + +[`graphql-live-subscriptions`](https://github.com/D1plo1d/graphql-live-subscriptions) tries to solve +that with `Subscription.live` field that exposes both a `Query` and a JSON patch field. + +**Schema Types for `graphql-live-subscriptions`** + +```graphql +scalar JSON + +type RFC6902Operation { + op: String! + path: String! + from: String + value: JSON +} + +type LiveQuery { + patch: [RFC6902Operation] + query: Query +} + +type Subscription { + live: LiveQuery! +} +``` + +A live query operation can be declared similar to our `PostSubscription` document above. + +```ts +const PostSubscription = graphql` + subscription PostSubscription($postId: ID!, $userId: ID!) { + live { + query { + post(id: $postId) { + id + title + totalLikeCount + } + user(id: $userId) { + id + name + } + } + patch { + op + path + from + value + } + } + } +` +``` + +The difference is that the type returned by the `live` field has two fields instead of a single one. +The `query` field, which selects the selection set from `Query` type and a `patch` field which is a +JSON Patch operation. When executing the given operation against the server the initial result will +have the data selected by the `query` field selection set included. All following values will have +no `query` value (`null`), but instead an array of patch operations that describe the changes for +updating the last result to the next result. + +**Initial result** + +```json +{ + "data": { + "live": { + "query": { + "post": { + "id": "1", + "title": "foo", + "totalLikeCount": 10 + }, + "user": { + "id": "10", + "name": "Laurin" + } + }, + "patch": null + } + } +} +``` + +**Patch result (increase totalLikeCount)** + +```json +{ + "data": { + "live": { + "query": null + }, + "patch": [ + { + "op": "replace", + "path": "post/totalLikeCount", + "from": null, + "value": 11 + } + ] + } +} +``` + +The clients must then implement the logic for +[applying the patch operation to their client cache](https://github.com/n1ru4l/graphql-live-chat/blob/27a0b902e9098c7cc0f92ccd6f36f7d7e28a9677/packages/frontend/src/jsonpatch.ts#L10-L72) +or for applying the patches on the initial result in the network/fetcher/link layer. + +The server implementation uses an event emitter and an immutable state tree for detecting changes +that must be sent to clients. The patch is automatically generated from the next immutable state +that is compared against the last which got emitted via an `EventEmitter`. + +While the idea is quite nice, the implementation is obviously meant for backends that already use +reactive or immutable data structures. Having to rewrite our existing GraphQL layer to support live +queries is a big trade-off. Furthermore, the library is not maintained that well. I've made PRs to +make the library compatible with recent GraphQL versions, but these have yet to be merged. Using +unions and interfaces is not possible. Having to patch a library with `patch-package` before even +being usable is generally a bad sign. + +1. Automatically updates clients. **Yes.** ✅ + +When implementing our schema conform to the library, this library delivers precise results once the +immutable state has changed. + +2. Efficiently updates clients. **Yes.** ✅ + +Initially a result tree is sent to the client. Afterwards, only JSON patches that must be applied to +the initial result are sent to the client. + +3. Flexibility. **Kind of.** ✳️ + +We don't rely on any third party services, however, we forced into immutability to some extend. + +4. Adoptability. **It depends.** ✳️ + +Adding an immutable layer to our existing schema might be a pretty big change. Furthermore, the +library does lack support for some GraphQL features such as Interfaces and Unions. + +## GraphQL Live Queries via the `@live` Directive + +There are companies out there, like [Facebook](https://youtube.com/watch?v=BSw05rJaCpA), that are +already using this approach. There is also a GraphQL framework available in Go that supports live +queries out of the box! Check out thunder [here](https://github.com/samsarahq/thunder). + +The idea behind the `@live` directive is that it is used to mark that the client is interested in +keeping that query execution result as up to date as possible. The implementation, however, is up to +user-land. + +```graphql +query ($id: ID!) @live { + post(id: $id) { + id + title + totalLikeCount + } +} +``` + +The idea of just making any query without additional overhead a live query is very appealing from +the view of a frontend developer. From a backend perspective, however that raises new questions. +Just adding a directive on the operation on frontend won't make the whole backend reactive. + +After having built an example app with `graphql-live-subscriptions` from scratch, studying the flaws +of that library and being uncomfortable with the vendor lock-in of services such as PostGraphile and +Hasura, I decided to approach the problem of live queries in a more pluggable way, by using the +`@live` directive. + +### `@n1ru4l/graphql-live-query` A Common Definition and Set of Utilities for Determining of a Live Query + +This module provides two things. + +1. `GraphQLLiveDirective` that can be added to any schema. + +```ts +import { GraphQLSchema, specifiedDirectives } from 'graphql' +import { GraphQLLiveDirective } from '@n1ru4l/graphql-live-query' +import { mutation, query, subscription } from './schema' + +const schema = new GraphQLSchema({ + query, + mutation, + subscription, + directives: [GraphQLLiveDirective, /* Keep @defer/@stream/@if/@skip */ ...specifiedDirectives] +}) +``` + +2. `isLiveQueryOperationDefinitionNode` + +This is a simple function that takes a `OperationDefinitionNode` and returns `true` if it is a live +query document. + +[These utility functions can be found here on Github](https://github.com/n1ru4l/graphql-live-query/tree/main/packages/graphql-live-query) + +Those functions alone might not seem that helpful alone, but they are a contract live query +execution engines could built on. Such as the package we are talking about next 😇. + +### `@n1ru4l/in-memory-live-query-store` Keep Track and Invalidate Resources Selected by a Live Query in an Efficient, but Generic Way + +The `InMemoryLiveQueryStore.execute` function is a drop in replacement for the `execute` function +provided by the `graphql` package. + +When encountering a query operation that is marked with the `@live` directive it will return a +`AsyncIterator` instead of a `Promise` that can be used for sending multiple results to the client. +Similar to how `subscribe` (or `defer`/`stream`) works. + +Internally, the store keeps track of the resources selected in the live query operation selection +set. That means all root query field coordinates (e.g. `Query.post`) and global resource identifiers +(e.g. `Post:1`). The store can then be notified to re-execute live query operations that select a +given root query field or resource identifier via the `InMemoryLiveQueryStore.invalidate` method +with the corresponding resource identifier or field coordinates. A resource identifier is composed +out of the type name and the actual resolved id value separated by a colon, but this behavior can be +customized. For ensuring that the store keeps track of all our query resources we should always +select the id field on our object types. The store will only keep track of fields with the name id +and the type `ID!` (`GraphQLNonNull(GraphQLID)`). + +```ts +import { parse } from 'graphql' +import { InMemoryLiveQueryStore } from '@n1ru4l/in-memory-live-query-store' +import { schema } from './schema' + +const inMemoryLiveQueryStore = new InMemoryLiveQueryStore() + +const rootValue = { + todos: [ + { + id: '1', + content: 'Foo', + isComplete: false + } + ] +} + +inMemoryLiveQueryStore + .execute({ + schema, + operationDocument: parse(/* GraphQL */ ` + query todosQuery @live { + todos { + id + content + isComplete + } + } + `), + rootValue: rootValue, + contextValue: {}, + variableValues: null, + operationName: 'todosQuery' + }) + .then(async result => { + if (isAsyncIterable(result)) { + for (const value of result) { + // Send to client in real-world app :) + console.log(value) + } + } + }) + +// Invalidate by resource identifier +rootValue.todos[0].isComplete = true +inMemoryLiveQueryStore.invalidate(`Todo:1`) + +// Invalidate by root query field coordinate +rootValue.todos.push({ id: '2', content: 'Baz', isComplete: false }) +inMemoryLiveQueryStore.invalidate(`Query.todos`) +``` + +When using an ORM such as Prisma, we can simply add a middleware for automatically invalidating +resources. + + + +**Use Prisma middleware for resource invalidation** + +```ts +// Register middleware for automatic model invalidation +prisma.$use(async (params, next) => { + const resultPromise = next(params) + + if (params.action === 'update') { + resultPromise.then(res => { + if (res?.id) { + // invalidate `Post:1` on update + liveQueryStore.invalidate(`${params.model}:${res.id}`) + } + }) + } + + return resultPromise +}) +``` + +In case we have multiple server replicas some PubSub implementation can be used for distributing the +events. + +**PubSub with Redis** + +```ts +import Redis from 'ioredis' + +const redis = new Redis() +redis.subscribe('invalidations') +redis.on('message', (channel, message) => { + if (channel === 'invalidations') { + // message is "Post:1" + liveQueryStore.invalidate(message) + } +}) +``` + +The transports `graphql-ws` (GraphQL over WebSocket), `graphql-helix` (GraphQL over SEE) and +`@n1ru4l/socket-io-graphql-server` (GraphQL over Socket.io), support providing a custom `execute` +function that is allowed to return AsyncIterables (thanks to the recent changes required for +`@defer` and `@stream`). All we have to do is to pass the `InMemoryLiveQueryStore.execute` to our +server factory! + +**Example with graphql-ws** + +```tsx +import http from 'http' +import { parse, subscribe } from 'graphql' +import { useServer } from 'graphql-ws/lib/use/ws' +// yarn add ws +import ws from 'ws' +import { InMemoryLiveQueryStore } from '@n1ru4l/in-memory-live-query-store' +import { schema } from './schema' + +const inMemoryLiveQueryStore = new InMemoryLiveQueryStore() + +const server = http.createServer(function weServeSocketsOnly(_, res) { + res.writeHead(404) + res.end() +}) + +const wsServer = new ws.Server({ + server, + path: '/graphql' +}) + +useServer( + { + schema, + execute: inMemoryLiveQueryStore.execute, + subscribe + }, + wsServer +) + +server.listen(443) +``` + +The best thing is you can start playing around with it today! The fully functional implementation is +available as +[`@n1ru4l/in-memory-live-query-store` on Github](https://github.com/n1ru4l/graphql-live-query/tree/main/packages/in-memory-live-query-store). +Feel free to create any issues regarding missing features or documentation. Let's shape the future +for GraphQL live queries together! + +### `@n1ru4l/graphql-live-query-patch`: Optional JSON Patch Middleware for Smaller Payloads over the Wire + +GraphQL's execution result payloads can become quite huge. Sending those over the wire can be +expensive at some point. Especially when they are sent often for fast updating state. JSON patch is +a handy standard for only sending change instructions over the wire which can potentially reduce +such huge payloads. + +Instead of having JSON patches enabled by default, it is a totally optional module, that can be +applied on the client and the server for deflating (create patches on the server) and inflating +(apply patches on the client) the execution results. Smaller projects might even be better off not +using JSON patch at all, as the patch payload might be bigger than the whole query result. + +The patches are created by comparing the latest execution result with the previous execution result. +That means the server will always have to store the latest execution result as long as the live +query is active. + +Here are some example execution results after applying the patch generator + +**Initial result** + +```json +{ + "data": { + "post": { + "id": "1", + "title": "foo", + "totalLikeCount": 10 + }, + "user": { + "id": "10", + "name": "Laurin" + } + }, + "revision": 1 +} +``` + +**Patch result (increase totalLikeCount)** + +```json +{ + "patch": [ + { + "op": "replace", + "path": "post/totalLikeCount", + "value": 11 + } + ], + "revision": 2 +} +``` + +On the server adding the patch generation middleware is easy function composition: + +```ts +import { flow } from 'fp-ts/function' +import { createApplyLiveQueryPatchGenerator } from '@n1ru4l/graphql-live-query-patch' + +const applyLiveQueryPatchGenerator = createApplyLiveQueryPatchGenerator() + +const execute = flow(liveQueryStore.execute, applyLiveQueryPatchGenerator) +// same as +const execute0 = (...args) => applyLiveQueryPatchGenerator(liveQueryStore.execute(...args)) +``` + +On the client we now need to build an execution result out of the initial result and the patch +results, because our clients do not understand the graphql live query json patch protocol! + +Applying the middleware is pretty easy as well! + +```ts +import { flow } from 'fp-ts/function' +import { createApplyLiveQueryPatch } from '@n1ru4l/graphql-live-query-patch' + +const applyLiveQueryPatch = createApplyLiveQueryPatch() + +const execute = flow(networkInterface.execute, applyLiveQueryPatch) +// same as +const execute0 = (...args) => applyLiveQueryPatch(networkInterface.execute(...args)) +``` + +The library is optimized for network interfaces that return AsyncIterables. We can easily wrap out +favorite network interface (that uses observable style sinks) in an AsyncIterable with +[`@n1ru4l/push-pull-async-iterable-iterator`](https://github.com/n1ru4l/push-pull-async-iterable-iterator)! + +**Example with `graphql-ws`** + +```ts +import { pipe } from 'fp-ts/function' +import { createClient } from 'graphql-ws/lib/use/ws' +import { createApplyLiveQueryPatch } from '@n1ru4l/graphql-live-query-patch' +import { makeAsyncIterableIteratorFromSink } from '@n1ru4l/push-pull-async-iterable-iterator' + +const client = createClient({ + url: 'ws://localhost:3000/graphql' +}) + +const applyLiveQueryPatch = createApplyLiveQueryPatch() + +const asyncIterator = pipe( + makeAsyncIterableIteratorFromSink(sink => { + const dispose = client.subscribe( + { + query: 'query @live { hello }' + }, + { + next: sink.next, + error: sink.error, + complete: sink.complete + } + ) + return () => dispose() + }), + applyLiveQueryPatch +) + +// stop live query after 5 seconds. +setTimeout(() => { + asyncIterator.return() +}, 5000) + +for await (const executionResult of asyncIterator) { + console.log(executionResult) +} +``` + +The whole package can be found +[`@n1ru4l/graphql-live-query-patch` on GitHub](https://github.com/n1ru4l/graphql-live-query/tree/main/packages/graphql-live-query-patch). +It is also usable and feedback is highly appreciated. It currently has its flaws with list diffing, +so further thoughts and ideas are highly appreciated. + +**Note:** We now separated the `json-patch` package into a separate +`@n1ru4l/graphql-live-query-patch-json-patch` package. `@n1ru4l/graphql-live-query-patch` now only +includes common logic shared between patch payload implementations. The reason for this is that +there are more efficient patch formats available, such as `jsondiffpatch`, which generate smaller +patches for list changes. The latter is now available as the separate package +[`@n1ru4l/graphql-live-query-patch-jsondiffpatch`](https://github.com/n1ru4l/graphql-live-query/tree/main/packages/graphql-live-query-patch-json-diff). + +So let's take a look at this modular implementation approach regarding the aspects we used before. + +1. Automatically update the clients. **Yes.** ✅ + +The approach of pushing the invalidation responsibility to the server might at first seem like a +drawback, but a smart abstraction (such as a ORM middleware), can result in pretty responsive +applications. + +2. Efficiently updates clients. **Yes.** ✅ + +In case our execution result payloads are getting too big we can easily enable JSON patches by +adding a middleware. Furthermore, the middleware is totally live query implementation independent! +That means if our projects requires a different implementation of the live query engine, the +middleware can still be applied as long as the execution result is compatible with the live query +execution result format. + +3. Flexibility. **High.** ✅ + +Any database or third-party API can be used as long as we can somehow invalidate the resource (via +some PubSub system etc.). As all incorporated libraries are pretty pluggable only a few can be used +and others be replaced by something that might make more sense for a specific project. + +4. Adoptability. **High.** ✅ + +This library can be added to any existing GraphQL.js schema without any hassle if you use transport +that allows full control over the schema creation and execution phase, such as graphql-helix or +graphql-ws which you are hopefully already using! Resource invalidation code can be added to the +mutation resolvers over time gradually to reflect the needs of the frontend. The possibilities of +resource invalidation are endless and the logic for those can be added incrementally. E.g. via an +ORM middleware, in our mutation code or maybe even on our GraphQL gateway. + +## What's Next? + +Summed up this could be the start of a pluggable live query ecosystem that is flexible enough to be +compatible with a wide range of databases, graphql transports and schema builders instead of +focusing too much on a niche. In general, instead of having less flexible and bulky servers that try +to be too opinionated and restrictive in their schema -> execution -> transport flow, GraphQL +tooling should become more modular. At the same time this, however, should not imply that there is +no need for opinionated framework-like approaches (as long as they give enough flexibility, e.g. by +being composed out of modular packages). + +I hope that more people will start exploring the possibilities of GraphQL live queries and also +GraphQL in general! Eventually, we could even come up with an official live RFC for the spec! + +There is a lot more to think about such as + +- Partial query re-execution (e.g. by building ad-hoc queries that only select affected resources) +- Mechanisms for batching similar query operations (e.g. for preventing execution of same operation + multiple times after invalidation) +- ~~Better list diffing/format for live query patches (as JSON patch performs rather poorly on list + diffs)~~ (Solved by introducing + [`@n1ru4l/graphql-live-query-patch-jsondiffpatch`](https://github.com/n1ru4l/graphql-live-query/tree/main/packages/graphql-live-query-patch-json-diff)) +- What ever is bothering you while reading this article! 😄 + +If you are a enthusiastic tinkerer and plan to build something with the libraries above, share it +with me and everyone else, so we can get more insights into different use-cases and requirements for +all kind of applications! + +Let's continue the discussion on [Discord](https://the-guild.dev/discord), +[Slack](https://slack-invite.graphql.org) or on +[GitHub](https://github.com/n1ru4l/graphql-live-query/issues/14) + +Here are some more resource for you to thinker around with: + +[GraphQL Live Query libraries and example todo app that sync across all connected clients.](https://github.com/n1ru4l/graphql-live-query) + +[Experimental GraphQL Playground that implements RFC features (defer/stream) and live queries.](https://github.com/n1ru4l/graphql-bleeding-edge-playground) + +[More information on how the `InMemoryLiveQueryStore` keeps track of resources.](https://dev.to/n1ru4l/collecting-graphql-live-query-resource-identifier-with-graphql-tools-5fm5) + +[Further thoughts on how the Relay Spec could help backing a live query system.](https://dev.to/n1ru4l/graphql-live-queries-backed-by-the-relay-specification-3mlo) diff --git a/packages/web/docs/src/app/blog/(posts)/supercharge-your-subgraph-with-hive/architecture.png b/packages/web/docs/src/app/blog/(posts)/supercharge-your-subgraph-with-hive/architecture.png new file mode 100644 index 00000000000..4659ac0116a Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/supercharge-your-subgraph-with-hive/architecture.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/supercharge-your-subgraph-with-hive/features.png b/packages/web/docs/src/app/blog/(posts)/supercharge-your-subgraph-with-hive/features.png new file mode 100644 index 00000000000..0d9fec14549 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/supercharge-your-subgraph-with-hive/features.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/supercharge-your-subgraph-with-hive/page.mdx b/packages/web/docs/src/app/blog/(posts)/supercharge-your-subgraph-with-hive/page.mdx new file mode 100644 index 00000000000..1dd0e87bcfe --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/supercharge-your-subgraph-with-hive/page.mdx @@ -0,0 +1,135 @@ +--- +title: Supercharge Your Supergraph with Hive +tags: [graphql, graphql-hive, graphql-federation] +authors: saihaj +date: 2024-10-30 +description: + Explore API management for Federated GraphQL with Hive. Unlock tools for collaboration, + monitoring, and governance to simplify your API architecture. +featured: true +--- + +In the world of modern APIs, GraphQL has taken the spotlight for its flexibility, efficiency, and +developer-centric approach to querying data. But as applications grow, so do the complexities of +managing APIs across teams and services. That's where Supergraphs and federated GraphQL come into +play, taking GraphQL to the next level by enabling distributed teams to collaborate effectively on a +unified graph. The Hive GraphQL API platform empowers you with the tools to master the challenges of +Federated GraphQL + +In this article, we'll dive into the concept of Supergraphs with [Federation](/federation) and +explore how Hive supercharges your supergraph with essential tools for monitoring, collaboration, +and governance. + +## What's a Supergraph? + +A **supergraph** is a unified data graph created by combining several smaller, independently managed +subgraphs. It serves as the backbone of large-scale GraphQL implementations, where different teams +or microservices manage their own subgraphs but contribute to the overall system. This allows your +architecture to scale while maintaining the simplicity and power of a single, unified API. + +By [federating multiple subgraphs into a supergraph](/federation), organizations can delegate +responsibilities across teams while ensuring that consumers still interact with a single API. To see +an example in action, check out our +[documentation here](/docs/get-started/apollo-federation?utm_source=the_guild&utm_medium=blog&utm_campaign=supercharge-your-subgraph-with-hive). + +## Understanding Apollo Federation + +[**Apollo Federation**](https://www.apollographql.com/docs/federation/?utm_source=the_guild&utm_medium=blog&utm_campaign=supercharge-your-subgraph-with-hive) +is an implementation of Subschema Directive-based +[Type Merging](https://the-guild.dev/graphql/stitching/docs/approaches/type-merging?utm_source=the_guild&utm_medium=blog&utm_campaign=supercharge-your-subgraph-with-hive) that +is defined by the Apollo Federation Specification. This technique allows for multiple GraphQL +services to work together as a single, cohesive API. Instead of one massive schema, you have smaller +schemas (aka subgraphs) that represent different parts of your system. Federation makes these +subgraphs interoperable and composable, enabling teams to **independently** maintain services while +contributing to the supergraph. + +Here's a simplified view of the architecture: + +- **Subgraphs**: Services owned by individual teams with their GraphQL schemas. +- **Gateway**: A gateway that resolves queries by creating a query plan to direct them to the + relevant subgraphs, unifying all subgraphs into a single supergraph. + +This decoupled structure improves **scalability**, allows for +[microservice-like architecture](https://microservices.io/patterns/microservices.html?utm_source=the_guild&utm_medium=blog&utm_campaign=supercharge-your-subgraph-with-hive), +and enables more agile teams. + +![A diagram showing GraphQL Gateway (GraphQL Router) talking to GraphQL APIs known as subgraphs](./architecture.png) + +## Challenges of Managing a Supergraph + +Although a supergraph provides scalability and flexibility, its management involves several +complexities: + +- **Version Control and Schema Evolution**: Modifications in a single subgraph can ripple through + the entire supergraph, affecting its overall functionality. +- **Performance Tracking and Optimization**: With the integration of numerous services, monitoring + and maintaining performance across the whole API becomes increasingly complex. +- **Coordination and Oversight**: With multiple teams working on the same supergraph, ensuring + consistency and effective governance becomes crucial yet challenging. + +## Enters Hive + +**[GraphQL Hive (Hive)](/)** is a powerful, open source solution designed to address the challenges +of managing a supergraph. It's a GraphQL API management platform that provides observability, schema +registry, and collaboration tools to keep your supergraph organized and performant. + +## Key Features of Hive + +- **Schema Registry** Simplify schema publishing, federated service composition, and detecting + breaking changes. + - **Composition Error Prevention:** Avoid runtime errors by validating integrity of all your + subgraph schemas. + - **Schema Explorer:** Navigate through your schema and check ownership and usage of types. + - **Version Control System:** Track schema modifications across multiple environments from + development to production. + - **Schema Checks:** Identify any breaking changes before they reach production. Evolve your + schema with confidence. +- **Observability** Advanced tools to gain deep insights into API performance and user experience. + - **GraphQL consumers:** Track GraphQL requests to see how schema is utilized and by what + applications. + - **Overall performance:** Track and analyze overall GraphQL API performance. + - **Query performance:** Pinpoint slow operations to identify performance bottlenecks. +- **Schema Management** Evolve your GraphQL API with confidence. + - **Prevent breaking changes:** Integrated Schema Registry with GraphQL Monitoring for confident + API evolution. + - **Schema Policy:** Lint, verify, and enforce best practices across your federated GraphQL + architecture. + - **Detect unused fields:** Detect unused fields in your GraphQL schema for efficiency and + tidiness. +- **Security** Safeguard your GraphQL API through secure deployment practices. + - **Persisted operations**: pre-register GraphQL operations that will be executed against the + GraphQL API + +## Why Choose Hive for Your Supergraph? + +GraphQL Hive offers the **observability, control, and collaboration** tools you need to run a +successful supergraph architecture. While Hive works seamlessly with **any** Apollo Federation +gateway, we also offer +[Hive Gateway](/docs/gateway?utm_source=the_guild&utm_medium=blog&utm_campaign=supercharge-your-subgraph-with-hive), +the +[only 100% Apollo Federation-compatible gateway solution](/blog/federation-gateway-audit?utm_source=the_guild&utm_medium=blog&utm_campaign=supercharge-your-subgraph-with-hive). +Here's why Hive stands out: + +- **Enhanced Developer Experience**: Developers can focus on building features instead of worrying + about schema conflicts, performance issues, or breaking changes. +- **Streamlined Operations**: Hive's monitoring and validation tools reduce the operational burden + of maintaining a supergraph. +- **Governance**: Hive enables you to enforce schema policies, ensuring best practices and + consistency across all subgraphs. This governance framework helps maintain a unified and + high-quality API. +- **Enterprise-Grade Features**: Enjoy free Single Sign-On (SSO) for secure and seamless access + management, along with advanced collaboration tools designed to meet the needs of large teams. + + ![features](./features.png) + +## Conclusion + +Apollo Federation transformed how we scale GraphQL across large teams and distributed services. Now, +GraphQL Hive takes it even further by offering powerful tools to manage, monitor, and optimize your +supergraph. With Hive, you can confidently build, deploy, and maintain a high-performing, scalable, +and collaborative GraphQL architecture. + +**Ready to enhance your API management?** Get started by +[setting up your federated GraphQL services on GraphQL Hive](/docs/get-started/apollo-federation?utm_source=the_guild&utm_medium=blog&utm_campaign=supercharge-your-subgraph-with-hive) +today and take the first step toward a more efficient, scalable, and collaborative GraphQL +ecosystem. diff --git a/packages/web/docs/src/app/blog/(posts)/swift-graphql/page.mdx b/packages/web/docs/src/app/blog/(posts)/swift-graphql/page.mdx new file mode 100644 index 00000000000..70d1c34e1b8 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/swift-graphql/page.mdx @@ -0,0 +1,140 @@ +--- +title: SwiftGraphQL - A GraphQL client for Swift lovers. +authors: maticzav +tags: [graphql, swift, client, swift-graphql] +date: 2021-08-02 +description: + SwiftGraphQL lets you use all the power Swift gives you to query your data. It feels like magic. +--- + +I love Swift, you love Swift, we want to do everything we can in Swift. It has a fantastic +type-system and a robust compiler. We also like GraphQL. It's a neat way to construct a type-safe +bridge from your server to the client. Its syntax isn't as rich as Swift's, but who cares, right? +[SwiftGraphQL](https://swift-graphql.com) aims to make the best of both worlds, here's how we do it. + +I started working on [SwiftGraphQL](https://swift-graphql.com) because no other client sufficed my +needs for building iOS applications. + +## Goals + +Firstly, I wanted to keep the application's state separated from the GraphQL schema. Keeping it +separate has a couple of benefits: We don't have to turn the whole application upside down whenever +a field name or a type changes in the schema. That might happen if you generate types right from the +queries and reuse the same query in multiple places. Swift's type-system is richer than GraphQL's. +By separating the state from the schema, we can implement enum types with parameters, create new +structures and nest them. We can add logic to the model and verify the data when converting query +results to the state. We can implement structures that represent custom scalar types and treat them +as first-person citizens in our codebase. + +Secondly, my apps usually consist of hundreds of types. I want my client to scale as my app scales, +meaning it should be as easy to handle hundred schema types as ten. Additionally, I want to use +everything that Swift's ecosystem offers without my client restricting me. + +Lastly, there are nuances in my queries that I want to modify using code. Ideally, I want to +dynamically generate my queries and still get the type-safety that GraphQL promises. This way, I can +write recursive queries and fine-tune return types for specific use cases. + +To sum it up, I wanted a flexible, scalable and robust GraphQL client. + +## Checking Out Existing Solutions + +With goals in mind, I started considering existing GraphQL clients. + +[Graphaello](https://graphaello.dev) seemed like a viable option; it is type-safe, I can +programmatically make selections, and it looks like it could scale well. However, Graphaello tightly +binds me to my schema, and I can only use structures to represent my data. + +https://graphaello.dev + +[Apollo iOS](https://apollographql.com/docs/ios), on the other hand, generates Swift types from +queries in our "queries.graphql" files and supports caching out of the box. Generating types from +the SDL is not per se a problem, but it becomes cumbersome and error-prone with large nested +queries. Apollo iOS also strongly-binds the generated structures to your queries, making it almost +impossible to translate fetched data into an internal state. + +https://apollographql.com/docs/ios/ + +## SwiftGraphQL + +Now, let's turn to [SwiftGraphQL](https://swift-graphql.com). Consider a typical StarWars API +example with the following schema. + +```graphql +type Query { + humans: [Human!]! +} + +type Human { + id: ID! + name: String! + home: String +} +``` + +I will show a trivial example, but with [SwiftGraphQL](https://swift-graphql.com), you'll handle +even more complex scenarios just as easily. We want to separate our model from the schema; that's +why we create a Person type. + +```swift +struct Person { + var id: String + var name: String +} +``` + +To make a selection, we use `Selection.` followed by the name of the type that we want to use to +make a selection. In our case, that would be `Selection.Human`. + +```swift +let human = Selection.Human { + Person( + id: try $0.id(), + name: try $0.name() + ) +} +``` + +There's a great deal more happening behind the scenes to make sure you only select fields in the +schema, but for now, let's remember that we make a selection this way. + +Once you have your types covered, you can construct and perform a query using the `send` method. + +```swift +let query = Selection.Query { + $0.human(id: "1000", selection: human) +} + +send(query, to: "https://swapi.graphql.com") { result in + if let data = try? result.get() { + print(data.human) + } +} +``` + +Easy, right? + +## Final Words + +The example above only scratched the surface of what is possible with +[SwiftGraphQL](https://swift-graphql.com). To sum it up, [SwiftGraphQL](https://swift-graphql.com) +is a code generator and a lightweight GraphQL client. It comes with a set of neat features like + +- You only have to generate code once (i.e. every time your schema changes), +- It ensures that every query you can send is valid, +- You can write queries programmatically, +- It supports queries, mutations as well as subscriptions. + +And the best part? It's super easy to get started. + +1. Install the generator using `brew install swift-graphql`, +1. Generate the API by running `swift-graphql --output ./file.swift`, +1. Start querying your data. + +[SwiftGraphQL](https://swift-graphql.com) is a young, slowly evolving library. I want to make it the +best Swift GraphQL client; that's why I'd love to hear how you use it. To support its development, +make sure you leave a star on GitHub and connect with me on Twitter. And if you are actively using +it, consider becoming my sponsor on GitHub. + +PS.: There's a new post coming up that explains in-depth how +[SwiftGraphQL](https://swift-graphql.com) works under the hood. Make sure to subscribe to our +mailing list and check our blog to see when it comes out. diff --git a/packages/web/docs/src/app/blog/(posts)/taking-over-merge-graphql-schemas/page.mdx b/packages/web/docs/src/app/blog/(posts)/taking-over-merge-graphql-schemas/page.mdx new file mode 100644 index 00000000000..b1db81e9850 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/taking-over-merge-graphql-schemas/page.mdx @@ -0,0 +1,155 @@ +--- +title: The Guild is taking over maintenance of merge-graphql-schemas +authors: uri +tags: [graphql, merge-graphql-schemas] +date: 2019-06-18 +description: + The Guild is taking over maintenance of merge-graphql-schemas, so let's talk about GraphQL Schema + management +--- + +How stitching, [GraphQL federation](https://the-guild.dev/graphql/hive/federation) and modules all +fit together? + +## TL;DR + +- After many years of supporting the community, the OK-GROW! Team is transferring ownership and + maintenance of [**merge-graphql-schemas**](https://github.com/urigo/merge-graphql-schemas) to The + Guild +- [merge-graphql-schemas](https://github.com/urigo/merge-graphql-schemas) will be added to the + existing Schema management tools already created by + [**The Guild**](https://github.com/the-guild-org) + ([GraphQL-Toolkit](https://github.com/ardatan/graphql-toolkit), + [GraphQL Modules](https://github.com/urigo/graphql-modules), + [GraphQL Inspector](https://github.com/kamilkisiela/graphql-inspector) and + [graphql-code-generator](https://graphql-code-generator.com)) +- This is a continuation of making order in the schema management tools across the ecosystem +- If you are at GraphQL-Conf this week, come say hi + +[merge-graphql-schemas](https://github.com/urigo/merge-graphql-schemas) is a popular library in the +GraphQL Ecosystem. + +It's one of the first tools people encounter once they went through their first GraphQL +implementation and start to wonder how to organize their GraphQL server code. + +The OK-GROW! Team has been maintaining that library for years, filling a needed gap in the +ecosystem. + +At the same time, [The Guild](https://github.com/the-guild-org) has been working on +[similar tools](https://github.com/the-guild-org/Stack) around GraphQL schema and module management. + +Today we are happy to announce that we are joining forces and merging our efforts to create open +source, easy and scalable solutions for GraphQL servers. + +In a couple of days of work we've refactored the underlying implementation of +[merge-graphql-schemas](https://github.com/urigo/merge-graphql-schemas) to use +[GraphQL-Toolkit](https://github.com/ardatan/graphql-toolkit) under the hood, we can close around +90% of the open issues on the library while making sure all existing tests are passing! + +We are happy to announce a new beta release (1.6.0-beta) is out! + +If you are a user of [merge-graphql-schemas](https://github.com/urigo/merge-graphql-schemas) please +give the new version a try before we make a full release. + +```bash +yarn add merge-graphql-schemas@next +``` + +## What's Next? + +The Guild has been busy for a while creating scalable GraphQL solutions around schema and modules +management for large teams. + +The main issue we see today in the ecosystem is that there is no clear overview of which tools solve +which problem area. + +In order to make sure we solve the right issues in the right place, we need clear boundaries between +the different libraries and solutions. + +Let's try to break down the different areas of solutions needed when splitting GraphQL schemas: + +- Building and executing GraphQL according to spec — **The Engine** +- Structuring multiple building blocks of the same server into a single executable schema — + **GraphQL Tools and Frameworks** +- Structuring multiple servers instances into a single executable schema — **Federation** + +Now we can gather all use cases from the community, put them as tests on the right library and make +sure we solve each one of them. + +## The Engine + +The Engine (in the Javascript world the most popular one is +[graphql.js](https://github.com/graphql/graphql-js)) should be responsible for taking a ready schema +and resolvers, validate the objects, introspecting them and executing documents against them at +runtime. + +That GraphQLSchema input must be strictly valid according to the GraphQL Spec. + +That's why manipulating a ready GraphQLSchema object can be hard. GraphQLSchema object should be the +final object to input the engine. + +The Engine should not care or help you create that final schema or resolvers. + +## GraphQL Management Tooling + +We can think of the Engine like a Web Browser — It is responsible for taking a spec (Javascript and +HTML) and execute it in a consistent way. + +But the browser doesn't care about how you create that Javascript and HTML bundle. + +That means that like we use babel, Typescript or frameworks for manipulating code to generate +spec-compliant output, we can do the same for GraphQL with +[graphql.js](https://github.com/graphql/graphql-js) as our target “browser”. + +Those tools should help you organize and manage your code in your preferred way, without needing to +be bound by what [graphql.js](https://github.com/graphql/graphql-js) expects. + +One of the main things that those tools can provide us is an easy way to split the code and the +schema into small chunks and later merge them with different merging strategies. + +Just like Javascript frameworks, using those tools can get you very far and make it easy to handle +huge codebases with many different teams. + +## Federation + +In some scenarios you would want to run completely different servers and merge them somehow into a +single GraphQL gateway. + +Schema stitching and [schema federation](https://the-guild.dev/graphql/hive/federation) are attempts +to make that work less manual. + +There are some use cases where you might want those types of solutions, but they also add a lot of +complexity, so it's important to understand very well why you really can't handle your use case in a +build-tool type of solution, and to know if Federation is an end goal or a stepping point to get you +further. + +## Breaking It All Down + +After years of working with GraphQL, from small applications to managing schemas across huge +corporations, creating open source tools and best practices around those, we want to share it all +with the community. + +In the next few weeks we will publish articles about each solution type, the tools around and when +you should use what. + +Things like why you should split your schemas, why not, what are the existing solutions out there +and when to use each one. + +We will also add examples of all those use cases into our +[main tutorial](https://tortilla.academy/Urigo/WhatsApp-Clone-Tutorial). + +We need you to send us your questions, use cases and ideas — we want to make sure we cover as many +use cases as possible across our libraries and other libraries we +[contribute too](https://github.com/apollographql/graphql-tools/pull/1150) as well (some things +might fit into graphql.js or Apollo for example). + +You can comment here or submit issues into any of +[our repositories](https://github.com/the-guild-org/Stack). + +## Thank You OK-GROW! + +I want to personally thank the OK-GROW! Team and specifically Paul for being a very early adopter +and supporter of the GraphQL community. + +The work you have done so far has been amazing and valuable and we are dedicated to continue to +support our community in the best way possible. diff --git a/packages/web/docs/src/app/blog/(posts)/the-anatomy-of-a-graphql-request/envelop-breakdown.png b/packages/web/docs/src/app/blog/(posts)/the-anatomy-of-a-graphql-request/envelop-breakdown.png new file mode 100644 index 00000000000..472ac93da8e Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/the-anatomy-of-a-graphql-request/envelop-breakdown.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/the-anatomy-of-a-graphql-request/page.mdx b/packages/web/docs/src/app/blog/(posts)/the-anatomy-of-a-graphql-request/page.mdx new file mode 100644 index 00000000000..17f916ef4a6 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/the-anatomy-of-a-graphql-request/page.mdx @@ -0,0 +1,426 @@ +--- +title: The Anatomy of a GraphQL Request +authors: laurin +tags: [graphql] +date: 2022-01-30 +description: Let's go back to the basics and break down a GraphQL request. +--- + +On a high-level, GraphQL servers are pretty easy to set up. Just passing a GraphQL schema and +starting the server does the job. + +However, for many use-cases, such a setup might not be sufficient. + +Let's dive into each of the phases of a GraphQL request and learn how enhancing each phase can help +to set up a production ready GraphQL server. + +Note: While GraphQL can be done over almost any protocol, this article focuses on the most commonly +used protocol [GraphQL over HTTP](https://github.com/graphql/graphql-over-http). However, most +knowledge can be transferred to other protocols such as GraphQL over WebSockets or other more exotic +ones. + +![The Phases of an GraphQL Request](./phases-breakdown.png) + +## HTTP Parsing and Normalization + +Clients send HTTP requests to the server with a payload that contains an operation document string +(query, mutation, or subscription), optionally some variables, and the operation name from the +document that shall be executed. + +When the requests are executed via the POST HTTP method, the body will be a JSON object: + +**Example JSON POST Body** + +```json +{ + "query": "query UserById($id: ID!) { user(id: $id) { id name } }", + "variables": { "id": "10" }, + "operationName": "UserById" +} +``` + +However, when using the GET HTTP method (e.g. for query operations) those parameters can also be +provided as a query search string. The values are then URL-encoded. + +**Example GET URL** + +``` +http://localhost:8080/graphql?query=query%20UserById%28%24id%3A%20ID%21%29%20%7B%20user%28id%3A%20%24id%29%20%7B%20id%20name%20%7D%20%7D&variables=%7B%20%22id%22%3A%20%2210%22%20%7DoperationName=UserById +``` + +The GraphQL HTTP server's first task is to parse and normalize the body or query string and +furthermore determine the protocol that shall be used for sending the response. The protocols can +be: + +- `application/graphql+json` (or `application/json` for legacy clients) for a single result that + yields from the execution phase +- `multipart/mixed` for incremental delivery (when using `@defer` and `@stream` directives). + +Not officially in the specification, but also used is `text/event-stream` for event streams (e.g. +when executing subscription or live query operations). + +## GraphQL Parse + +After parsing and normalizing the request, the server will pass on the GraphQL parameters onto the +GraphQL engine that will parse the GraphQL operation document (which can contain any number of +query, mutation, or subscription operations and fragment definitions). + +If there is any typo or syntax error, this phase will yield GraphQLErrors for each of those issues +and pass them back to the server layer to send those back to the client. + +For the following invalid GraphQL operation (`)` missing after `$id` at line 2): + +``` +query UserById($id: ID!) { + user(id: $id { + id + name + } +} +``` + +The error will look similar to this: + +```json +{ + "message": "Syntax Error: Expected Name, found {", + "locations": [ + { + "line": 2, + "column": 16 + } + ] +} +``` + +As you can see, the error messages are unfortunately not always straightforward and helpful. The +location can, however, help you track down the syntax error! + +In case no error occurs, the parse phase produces an AST (abstract syntax tree). + +The AST is a handy format that is used for the follow-up phases `validate` and `execute`. + +For our operation it would be identical to the following JSON: + +```json +{ + "kind": "Document", + "definitions": [ + { + "kind": "OperationDefinition", + "operation": "query", + "name": { + "kind": "Name", + "value": "UserById" + }, + "variableDefinitions": [ + { + "kind": "VariableDefinition", + "variable": { + "kind": "Variable", + "name": { + "kind": "Name", + "value": "id" + } + }, + "type": { + "kind": "NonNullType", + "type": { + "kind": "NamedType", + "name": { + "kind": "Name", + "value": "ID" + } + } + } + } + ], + "selectionSet": { + "kind": "SelectionSet", + "selections": [ + { + "kind": "Field", + "name": { + "kind": "Name", + "value": "user" + }, + "arguments": [ + { + "kind": "Argument", + "name": { + "kind": "Name", + "value": "id" + }, + "value": { + "kind": "Variable", + "name": { + "kind": "Name", + "value": "id" + } + } + } + ], + "selectionSet": { + "kind": "SelectionSet", + "selections": [ + { + "kind": "Field", + "name": { + "kind": "Name", + "value": "id" + } + }, + { + "kind": "Field", + "name": { + "kind": "Name", + "value": "name" + } + } + ] + } + } + ] + } + } + ] +} +``` + +This procedure is performed by the `parse` function that is exported from `graphql-js`. Other +languages that are orientate themselves on the `graphql-js` reference implementation have a +corresponding counterpart. + +## GraphQL Validate + +In the validation phase, the parsed document is validated against our GraphQL schema to ensure all +selected fields in the operation are available and valid. The previously parsed AST makes it easier +for the validation rules to have a common interface of traversing the document. Furthermore, other +validation rules that ensure that the document follows the GraphQL specification are checked, E.g. +whether variables referenced in the document are declared in the operation definition. + +As an example the following operation is missing a definition for the `$id` variable: + +```graphql +query UserById { + user(id: $id) { + id + name + } +} +``` + +If the AST of that operation would be validated the following error will be raised. + +```json +[ + { + "message": "Variable \"$id\" is not defined by operation \"UserById\".", + "locations": [ + { + "line": 2, + "column": 12 + }, + { + "line": 1, + "column": 1 + } + ] + } +] +``` + +In case any error is raised the errors are forwarded to the HTTP layer which takes care of sending +them back to the client over the determined protocol. Otherwise, if no errors are raised the +`execution` phase will be performed next. + +This procedure is performed by the `validate` function that is exported from `graphql-js`. Other +languages that are orientate themselves on the `graphql-js` reference implementation have a +corresponding counterpart. + +## GraphQL Execute + +In the execution phase we are actually resolving the data requested by the client using the parsed +and validated GraphQL operation document AST and our GraphQL schema, which contains the resolvers +that specify from where the data the client requests is retrieved. + +Previously, the HTTP request has been parsed and normalized, which yielded the following additional +(but optional) values: `variables` and `operationName`. + +If the GraphQL document used for execution included more than one executable mutation, query or +subscription operation, the `operationName` is determined to identify the document that shall be +used. If the determined executable operation has any variable definitions, those are asserted +against the `variables` values parsed from the HTTP request. + +If anything goes wrong or is incorrect an error is raised. E.g. the variables provided are invalid +or the operation that shall be executed cannot be determined as the `operationName` is invalid or +missing. + +**Example error for anonymous document alongside named document** + +```graphql +query UserById($id: ID!) { + user(id: $id) { + id + name + } +} +query { + __typename +} +``` + +```json +{ + "errors": [ + { + "message": "This anonymous operation must be the only defined operation.", + "locations": [ + { + "line": 7, + "column": 1 + } + ] + } + ] +} +``` + +Such an error will again be forwarded to the client by the HTTP layer. + +Otherwise, if no error occurs, the field values will be resolved with all the parsed and provided +parameters. The phase yields a single or stream of GraphQL execution results. + +```json +{ + "data": { + "user": { + "id": "10", + "name": "Laurin" + } + } +} +``` + +The HTTP layer forwards those to the client that initiated the request. + +This procedure is performed by the `execute` and `subscribe` functions that are exported from +`graphql-js`. Other languages that are orientate themselves on the `graphql-js` reference +implementation have a corresponding counterpart. + +# Why Do We Want to Override Different Phases? + +## Overriding `parse` + +### Add Caching + +Parsing a GraphQL document string comes with overhead. We could cache frequently sent document +strings and serve the document from the cache instead of parsing it every single time. + +### Test New Functionality + +The GraphQL Type system defines the capabilities of the GraphQL service. This phase can be used to +add new capabilities to the type system that may not yet be supported by GraphQL specification. + +## Overriding `validate` + +### Add Caching + +Similar to parsing, validating a GraphQL document AST comes with an overhead. We could cache +recurring document ASTs and server the validation result from the cache instead of validating it +every single time. + +### Add Custom Rules + +You might want to restrict what kind of operations are allowed to be executed. E.g. If we only want +to allow `query` operations, we can provide a custom validation rule that yields errors as soon as a +`mutation` or `subscription` operation is encountered. + +## Overriding `execute` or `subscribe` + +### Add Caching + +We can serve frequently executed GraphQL operation results from a cache instead of calling all our +resolvers and fetching from a remote database/server every time. + +### Add Tracing Information + +We can collect statistics by measuring how long it takes to resolve each field in our documents' +selection set and narrow down bottlenecks. + +### Mask and Report Errors + +We want to make sure the errors occurring during the execution do not contain sensitive information +and are properly reported, so they do not go unnoticed and are properly reported. + +### Add New Functionality + +We could customize the algorithm that is used by `graphql-js` in order to add new features or make +it more performant e.g. by using [graphql-executor](https://npmjs.com/package/graphql-executor). + +## Use Envelop for Extending the Phases + +While making our GraphQL servers production ready and working with our clients we discovered that we +were writing the same custom code over and over again. + +Envelop provides us a user-friendly way of hooking into the before and after phases of `parse`, +`validate`, `execute` and `subscribe`. + +```ts +import { NoSchemaIntrospectionCustomRule } from 'graphql' +import { envelop, Plugin } from '@envelop/core' + +const myPlugin: Plugin = { + onParse() { + console.log('before parsing') + return function afterParse({ result }) { + if (result instanceof Error) { + console.log('Error occured during parsing: ', result) + } + } + }, + onValidate({ addValidationRule }) { + // add a custom validation rule + addValidationRule(NoSchemaIntrospectionCustomRule) + + return function afterValidate({ result }) { + if (result.length) { + console.log('Errors occured duting validation: ', result) + } + } + }, + onExecute({ args }) { + const myVar = args.contextValue.myVar + } +} + +const getEnveloped = envelop({ plugins: [myPlugin] }) +const { parse, validate, execute, subscribe } = getEnveloped() +``` + +![Envelop Plugin Illustration](./envelop-breakdown.png) + +Furthermore, the plugins can be easily shared across projects. + +[You can learn more about Envelop on our introduction blog post](/blog/introducing-envelop) or by +[visiting the Envelop documentation](https://envelop.dev). + +## Why Is GraphQL Yoga V2 so Important? + +As you might have noticed +[we adopted GraphQL Yoga to The Guild ecosystem quite a while ago](https://github.com/dotansimha/graphql-yoga/issues/670). +[We have been thinking a lot on how version 2 should look like](https://github.com/dotansimha/graphql-yoga/issues/704) +and while building and using envelop within our customer projects we realized a few limitations that +can only be solved by owning/wrapping the whole HTTP GraphQL request pipeline. + +While envelop is strictly about wrapping the `graphql-js` core functions (`parse`, `validate`, +`execute` and `subscribe`). GraphQL Yoga will provide a unified plugin interface for writing plugins +that hook into ALL of those phases envelop has plus all the encapsulated phases for parsing and +normalizing the HTTP request, allowing you to write powerful GraphQL servers and making features +such as Automatic Persisted Queries or Response Caching more powerful and easy to adopt. + +For a small sneak peek check out +[GraphQL.wtf Episode 24 - Batteries Included GraphQL Server](https://graphql.wtf/episodes/24-graphql-yoga) +or [try the GraphQL Yoga alpha](https://graphql-yoga.com). + +We are excited to announce more details soon. diff --git a/packages/web/docs/src/app/blog/(posts)/the-anatomy-of-a-graphql-request/phases-breakdown.png b/packages/web/docs/src/app/blog/(posts)/the-anatomy-of-a-graphql-request/phases-breakdown.png new file mode 100644 index 00000000000..ddef0443444 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/the-anatomy-of-a-graphql-request/phases-breakdown.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/the-complete-graphql-scalar-guide/graphql-scalar-flow.jpg b/packages/web/docs/src/app/blog/(posts)/the-complete-graphql-scalar-guide/graphql-scalar-flow.jpg new file mode 100644 index 00000000000..3af66f506d3 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/the-complete-graphql-scalar-guide/graphql-scalar-flow.jpg differ diff --git a/packages/web/docs/src/app/blog/(posts)/the-complete-graphql-scalar-guide/page.mdx b/packages/web/docs/src/app/blog/(posts)/the-complete-graphql-scalar-guide/page.mdx new file mode 100644 index 00000000000..39189a155d7 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/the-complete-graphql-scalar-guide/page.mdx @@ -0,0 +1,510 @@ +--- +title: The complete GraphQL Scalar Guide +authors: eddeee888 +tags: [graphql, scalars, codegen, node, server, typescript] +date: 2023-06-27 +description: + Knowing how native and custom GraphQL Scalar works enables building flexible and extendable + GraphQL schema. +--- + +import { Callout } from '@theguild/components' + +Scalar type is one of the most important GraphQL concepts to understand. Knowing how scalar works +and using the right tools can help us build type safe, robust and extendable schemas. In this +article, we cover various essential scalar topics: + +- What Is GraphQL Scalar? +- Create Custom GraphQL Scalar +- GraphQL Scalar Tools for Type Safety + +## What Is GraphQL Scalar? + +GraphQL scalar type is a primitive type that represents a value in the input or output field's +return type. GraphQL comes with a few native scalars: `ID`, `Int`, `Float`, `String`, `Boolean`. +However, custom scalars can be declared in schemas to represent more complex data types. + +### Scalar Value Coercion + +One important concept of GraphQL scalar is **coercion**. This happens when a scalar is used as +either input or output. Coercion is the process of taking the incoming value that could be one or +many types, and turning it into one outgoing type. + +Let's take the native `ID` scalar as an example: + +- When used as input: a client can send either `string` or `number` value and the value is coerced + into `string` before it gets to a resolver on the server. +- When used as output: a server can return `string` or `number` and it is coerced to `string` before + it is sent to the client. + +Here's the full list of native scalar input and output TypeScript types: + +| Scalar | Client can send | Resolver receives | Resolver can return | Client receives | +| ------- | -------------------------------- | ----------------- | -------------------------------- | ----------------------------------------------------------------- | +| ID | `string` | `string` | `string` | `string` | +| | `number` | `string` | `number` | `string` | +| Int | `number` (32-bit signed integer) | `number` | `string` | `number` (32-bit signed integer) | +| | | | `number` (32-bit signed integer) | `number` (32-bit signed integer) | +| | | | `boolean` | `1` if true, `0` if false | +| Float | `number` (Float or number) | `number` | `string` | `number` | +| | | | `number` | `number` | +| | | | `boolean` | `1` if true, `0` if false | +| String | `string` | `string` | `string` | `string` | +| | | | `boolean` | "true” if true, "false" if false | +| | | | `number` | `string` (numeric value converted into string) | +| Boolean | `boolean` | `boolean` | `boolean` | `boolean` | +| | | | `number` | `false` if incoming value is 0, `true` if incoming value is not 0 | + +## Custom GraphQL Scalar + +As our schemas evolve, we may need to present more complex data types with custom validation rules. +We can create custom GraphQL scalars to solve this use case. + +There are many examples of popular custom scalars that are used in a lot of projects: + +- `DateTime`: string representing an exact point in time +- `BigInt`: representing values which are too large to be represented by `number`. Similar to + JavaScript's `bigint` type +- `EmailAddress`: string representing valid email formats + +There are 3 steps to create a custom GraphQL Scalar: + +### 1. Declare Custom Scalar in Schema + +We can declare custom GraphQL Scalars using the `scalar` keyword: + +```graphql filename="schema.graphql" +scalar CustomScalar +``` + +### 2. Create Custom Scalar Resolver + +Now, we can create a resolver for the scalar using `GraphQLScalarType` from the `graphql` package: + +```shell +# Install `graphql` package if it's not installed already +yarn add graphql +``` + +```tsx filename="resolvers/CustomScalar.ts" +import { GraphQLScalarType } from 'graphql' + +export const CustomScalar = new GraphQLScalarType({ + name: 'CustomScalar', + description: 'Custom Scalar description', + parseValue(inputValue: unknown) { + // (1) Used for input + }, + parseLiteral(ast) { + // (2) Used for input + }, + serialize(outputValue: unknown) { + // (3) Used for output + } +}) +``` + +There are 3 main functions to keep in mind when creating a custom scalar: + +1. `parseValue`: This function is called when the scalar is used as variable in an input. The + validated and returned value of this function is passed to resolvers. Below is an example of a + GraphQL operation that would trigger `parseValue`: + +```graphql +query Example($var: CustomScalar!) { + example(arg: $var) # a variable is used so `parseValue` is called on the server +} +``` + +2. `parseLiteral`: This function is called when the scalar is used as literal value in an input. + The `ast` parameter contains the GraphQL kind (e.g. int or string) and the value of the input. + The validated and returned value of this function is passed to resolvers. Below is an example of + a GraphQL operation that would trigger `parseLiteral`: + +```graphql +query Example { + example(arg: "Hello") # a literal string is used so `parseLiteral` is called on the server +} +``` + +3. `serialize`: This function is called on the output returned by resolvers, before the value is + sent to the client. Since most GraphQL servers send results as JSON over HTTP, the return value + of `serialize` function must turn the value into `string`, `number` or `boolean`. + +Check out this visualisation of the flow of a scalar value from the client to the server, and then +back to the client: + +![Visualisation of how input and output GraphQL Scalar work](./graphql-scalar-flow.jpg) + +1. When a scalar is used as input, the variable or literal value is parsed using `parseValue` or + `parseLiteral` respectively. +2. The parsed value reaches the second param (usually called `args`) of the receiving resolver. +3. If the scalar is used as output, the value reaches the first param (usually called `parent`) of + the receiving resolver. +4. Once ready to be returned to the client, the scalar value is sent to the scalar resolver's + `serialize` function to be turned into JSON-compatible values. +5. The serialised scalar value is sent back to the client. + +### 3. Add Custom Scalar to Resolvers Map + +Finally, we can add the custom scalar resolver to the resolvers map in the GraphQL server. Here's an +example of how to do it with [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server): + +```shell +# Install `graphql-yoga` to create a GraphQL server +yarn add graphql-yoga +``` + +```tsx +import { createServer } from 'http' +import { createSchema, createYoga } from 'graphql-yoga' +// 1. Import the custom resolver +import { CustomScalar } from './resolvers/CustomScalar.ts' + +const yoga = createYoga({ + schema: createSchema({ + typeDefs /* GraphQL typeDefs */, + resolvers: { + CustomScalar // 2. Put the custom scalar resolver into resolvers map + } + }) +}) +const server = createServer(yoga) +server.listen(4000, () => { + console.info('Server is running on http://localhost:4000/graphql') +}) +``` + +Now, running the server allows us to use custom scalars in our project. 🎉 + +## GraphQL Scalar Tools for Type Safety + +There are lots of tools in the GraphQL ecosystem to support working with both native and custom +scalars: + +### 1. GraphQL Code Generator + +[GraphQL Code Generator](https://the-guild.dev/graphql/codegen) is a CLI tool with an extensive +plugin ecosystem to make implementing both GraphQL client and server easier and safer. + +For scalars, GraphQL Code Generator can generate input and output types correctly. On top of that, +we can customise the types for both native and custom types to fit our needs. + + + In the latest major versions of GraphQL Codegen plugins, the generated `Scalar` type has changed to better support scalar input and output types. This change is necessary because it allows us to type scalars more expressively from now on. + +Read on to see how to use Codegen to generate types for [clients](#codegen-config-for-clients) and +[servers](#codegen-config-for-servers). + + + +#### Codegen Config for Clients + +[typescript](https://the-guild.dev/graphql/codegen/plugins/typescript/typescript) is the plugin to +generate the base TypeScript types, including the `Scalars` type for both native and custom scalars. +This type is then used by other client plugins like +[typescript-operations](https://the-guild.dev/graphql/codegen/plugins/typescript/typescript-operations). + +First, install GraphQL Code Generator CLI and `typescript` plugin: + +```shell +yarn add -D @graphql-codegen/cli @graphql-codegen/typescript +``` + +Here's an example config that generates native scalar types and `CustomScalar` scalar for clients: + +```tsx filename="codegen.ts" +import type { CodegenConfig } from '@graphql-codegen/cli' + +const config: CodegenConfig = { + schema: '**/schema.graphql', + generates: { + 'src/schema/types.generated.ts': { + plugins: ['typescript'], + config: { + scalars: { + // Recommended ID scalar type for clients: + ID: { + input: 'string | number', + output: 'string' + }, + // Setting custom scalar type: + CustomScalar: { + input: 'string', // this means our server can take CustomScalar as string + output: 'number' // this means our server will return CustomScalar as number + } + } + } + } + } +} + +export default config +``` + +Running `yarn graphql-codegen` generates the following Scalar type: + +{/* prettier-ignore */} +```tsx filename="src/schema/types.generated.ts" +export type Scalars = { + ID: { input: string | number; output: string }; + String: { input: string; output: string }; + Boolean: { input: boolean; output: boolean }; + Int: { input: number; output: number }; + Float: { input: number; output: number }; + CustomScalar: { input: string; output: number }; +}; +``` + +By default, the `typescript` plugin generates `string` for both ID input and output. This approach +allows the plugin to be used for both client and server type generation without extra configuration. + +On the other hand, using the above recommended config to generate type that's closer to GraphQL's +native behaviour (i.e. clients can send either `string` or `number` as ID input) can make the +experience better. A lot of systems use `number` as the type for ID of an object. If the recommended +config is used for these cases, we don't have to manually convert the ID to a `string` before +sending it to the server. + + + Starting from `typescript` and `typescript-operations` plugin v4, all depedent client plugins + (e.g. Apollo Client, React Request, urql, etc.) must update to use the new input/output format. If + you find issues in these client plugins, create an issue in the [community + repo](https://github.com/dotansimha/graphql-code-generator-community). + + +#### Codegen Config for Servers + + + In `typescript` and `typescript-resolvers` plugin v4.0.0, the default ID Scalar input type was changed to `string | number` from `string`. This was done to match the expected GraphQL client type. + +However, server plugins such as `typescript-resolvers` also depend on the type generated by the +`typescript` plugin. This causes a lot of friction when setting up config to generate server types. + +In `typescript` and `typescript-resolvers` plugin v4.0.1, we have reverted the default ID Scalar +input type to `string`. Read the +[pull request](https://github.com/dotansimha/graphql-code-generator/pull/9497) for more details. + + + +For GraphQL servers, we can use +[typescript-resolvers](https://the-guild.dev/graphql/codegen/plugins/typescript/typescript-resolvers) +plugin with the same `scalars` config as +[typescript](https://the-guild.dev/graphql/codegen/plugins/typescript/typescript) plugin. This +combination changes how scalar types are used: + +- The scalar input is the type of coerced value that resolvers receive _after_ `parseValue` or + `parseLiteral` functions are called. +- The scalar output is the type that resolvers return _before_ `serialize` is called. + +First, install GraphQL Code Generator CLI, `typescript` and `typescript-resolvers` plugin: + +```shell +yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers +``` + +Here's the recommended config server types: + +```tsx +import type { CodegenConfig } from '@graphql-codegen/cli' + +const config: CodegenConfig = { + schema: '**/schema.graphql', + generates: { + 'src/schema/types.generated.ts': { + plugins: ['typescript', 'typescript-resolvers'], + config: { + scalars: { + // Recommended ID scalar type for servers: + ID: { + input: 'string', + output: 'string | number' + }, + // Setting custom scalar type: + CustomScalar: { + input: 'string', // this means our server can take CustomScalar as string + output: 'number' // this means our server will return CustomScalar as number + } + } + } + } + } +} + +export default config +``` + +Running `yarn graphql-codegen` generates the following Scalar type: + +{/* prettier-ignore */} +```tsx +export type Scalars = { + ID: { input: string; output: string | number }; + String: { input: string; output: string }; + Boolean: { input: boolean; output: boolean }; + Int: { input: number; output: number }; + Float: { input: number; output: number }; + CustomScalar: { input: string; output: number }; +}; +``` + +The recommended server config makes it more convenient to handle ID scalar types. This approach is +particularly useful if you have objects with numeric IDs. Without using the recommended config, we +would have to create custom mappers or manually convert the number to string to satisfy TypeScript +typecheck. + + + The recommeded setup is suitable for the most common use cases. It does not cover some edge cases that may make writing resolvers awkward. + +For example, `Int`'s output is technically `string | number | boolean` but it is typed as `number` +by default because most resolvers would never return `string` or `boolean`. + + + +### 2. GraphQL Scalars (`graphql-scalars`) + +[GraphQL Scalars](https://the-guild.dev/graphql/scalars) is a library that contains many commonly +used custom GraphQL Scalars such as `DateTime`, `BigInt`, etc. + +Even if it is not too hard to create custom scalars, testing and publishing scalars to share between +GraphQL servers can quickly distract from building the core business logic. Therefore, it is usually +better to use a well-maintained and documented library like `graphql-scalars`. + +Here's how we can use `DateTime` scalar from `graphql-scalars`: + +1. Install `graphql-scalars`: + +```shell +yarn add graphql-scalars +``` + +2. Declare `DateTime` in the schema: + +```graphql filename="schema.graphql" +scalar DateTime +``` + +3. Import `DateTime` resolver into resolvers map: + +```tsx +import { createServer } from 'http' +// 1. Import scalar resolver +import { DateTimeResolver } from 'graphql-scalars' +import { createSchema, createYoga } from 'graphql-yoga' + +const yoga = createYoga({ + schema: createSchema({ + typeDefs /* GraphQL typeDefs */, + resolvers: { + DateTime: DateTimeResolver // 2. Put resolver to resolvers map + } + }) +}) +const server = createServer(yoga) +server.listen(4000, () => { + console.info('Server is running on http://localhost:4000/graphql') +}) +``` + +4. Use GraphQL Code Generator to create types: + +Most scalar resolvers from `graphql-scalars` come with recommended server type that can be used in +Codegen config. We can import a resolver and use said type very easily: + +```tsx filename="codegen.ts" +import { DateTimeResolver } from 'graphql-scalars' +// 1. Import scalar resolver +import type { CodegenConfig } from '@graphql-codegen/cli' + +const config: CodegenConfig = { + schema: '**/schema.graphql', + generates: { + 'src/schema/types.generated.ts': { + plugins: ['typescript', 'typescript-resolvers'], + config: { + scalars: { + ID: { + input: 'string', + output: 'string | number' + }, + DateTime: DateTimeResolver.extensions.codegenScalarType // 2. Use scalar resolver's recommended server type + } + } + } + } +} + +export default config +``` + + + What about recommended client types for `graphql-scalars`'s custom scalars? + +Currently, there's no official package for this ( _yet_! ). However, there's an +[issue on the GraphQL Scalars repo](https://github.com/Urigo/graphql-scalars/issues/1996). + +Here's the general tips for picking the right client type for `graphql-scalars`: + +1. Check the `serialize` function's return type, then set the scalar `output` type in the codegen + config. +2. If your client converts the returned scalar value to another type, you can set the final + `output` type in the codegen config. + + + +### 3. GraphQL Codegen Server Preset + +GraphQL Code Generator and `graphql-scalars` libraries should be enough to manage our scalar +implementation and type requirements. However, Server Preset +([@eddeee888/gcg-typescript-resolver-files](https://www.npmjs.com/package/@eddeee888/gcg-typescript-resolver-files)) +can simplify the scalar setup further: + +- uses the recommended ID scalar server config by default +- detects if a custom scalar exists in the installed `graphql-scalars` package, then automatically + imports the scalar resolver to the resolvers map and sets up recommended type in Codegen config +- detects if a custom scalar does not exist in the installed `graphql-scalars` package (or if the + package is not installed), then automatically creates a custom scalar resolver and puts it into + resolvers map + +1. Install GraphQL Code Generator CLI, Server Preset and (optionally) GraphQL Scalars: + +```shell +yarn add -D @graphql-codegen/cli @eddeee888/gcg-typescript-resolver-files + +# (Optional) Install `graphql-scalars` if you intend to use custom scalars from this library +yarn add graphql-scalars +``` + +2. Use Server Preset in the Codegen config: + +```ts filename="codegen.ts" +import { defineConfig } from '@eddeee888/gcg-typescript-resolver-files' +import type { CodegenConfig } from '@graphql-codegen/cli' + +const config: CodegenConfig = { + schema: '**/schema.graphql', + generates: { + 'src/schema': defineConfig() + } +} + +export default config +``` + +Run `yarn graphql-codegen`, and... that's it! Now, every time a custom scalar like `DateTime` is +added to the schema, the implementation and type automatically come from `graphql-scalars`! 🚀 + + + The Server Preset does more than just handling custom scalars. There are existing guides and blog posts with more details on its features and concepts: + +- Read how to set up + [GraphQL Yoga / Apollo Server with Server Preset](https://the-guild.dev/graphql/codegen/docs/guides/graphql-server-apollo-yoga) +- Read how to build + [Scalable APIs with GraphQL Server Codegen Preset](https://the-guild.dev/blog/scalable-apis-with-graphql-server-codegen-preset) + + + +## Summary + +In this article, we explored the importance of GraphQL scalar types, how it works and how to create +custom scalar resolvers to extend our schemas. We also explored tools to help working with native +and custom scalars such as GraphQL Code Generator, GraphQL Scalars and Server Preset. diff --git a/packages/web/docs/src/app/blog/(posts)/typed-document-node/page.mdx b/packages/web/docs/src/app/blog/(posts)/typed-document-node/page.mdx new file mode 100644 index 00000000000..0d0dadf0435 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/typed-document-node/page.mdx @@ -0,0 +1,142 @@ +--- +title: 'TypedDocumentNode: the next generation of GraphQL and TypeScript' +authors: dotan +tags: [graphql, codegen, typescript, react] +date: 2020-07-22 +updateDate: 2020-08-02 +description: Using GraphQL and Typescript on the client just became a lot easier! +--- + +// Examples are in: https://codesandbox.io/s/quizzical-browser-1em9r?file=/index.ts + +**Using GraphQL and Typescript on the client just became a lot easier!** + +The [GraphQL Code Generator](https://graphql-code-generator.com) project has been around for 3 +years, and we are constantly keep working on it and listening to your feedback! + +As we were working and thinking about +[the next major version of the codegen](https://github.com/dotansimha/graphql-code-generator/discussions/3955), +we came up with a really awesome tool which didn't require any breaking change! + +So today, after successfully integrating this feature into few of our largest clients, we can +proudly share it with you - +[`TypedDocumentNode`](https://github.com/dotansimha/graphql-typed-document-node). + +A new and easier way to enjoy and integrate GraphQL and Typescript on the client. + +`TypedDocumentNode` is a development tool for creating fully typed `DocumentNode` objects. It means +that just by passing the GraphQL query/mutation/subscription/fragment to a supporting GraphQL client +library, you'll get fully-typed result and variables objects. + +This is made possible by +[TypeScript type inference](https://typescriptlang.org/docs/handbook/type-inference.html). + +## TL;DR + +- [`TypedDocumentNode`](https://github.com/dotansimha/graphql-typed-document-node) is a great + solution for having pre-compiled (`DocumentNode`) GraphQL operations with built-in support for + TypeScript types. +- No need to specify types manually in your application code, all types are inferred automatically + from your operation object. +- You can easily extend any GraphQL client library to support it, even without changing the library + code. +- You can integrate it to your project + [using these instructions](https://github.com/dotansimha/graphql-typed-document-node#how-to-use) + +import { YoutubeIframe } from '#components/youtube-iframe' + + + +## Client-Side Applications, TypeScript and GraphQL + +The integration of GraphQL and TypeScript in client-side applications has evolved in recent times: + +### 2016: Manual Typings + +We started with manually writing TypeScript types for our operations. It worked, but it didn't +scale. It needed maintenance to make sure our types matched the exact selection set we are fetching. + +### 2017: Generated Types + +We moved to generated TypeScript types, with the power of +[GraphQL Code Generator](https://graphql-code-generator.com) - using `@graphql-codegen/typescript` +and `@graphql-codegen/typescript-operations` plugins. + +It's simpler (no need to maintain the types manually) but it requires us to manually specify the +types each time we use the query. + +### 2018: Generated Code + +The next step of that evolution was to generate code - that means that we can generate React Hooks +(`@graphql-codegen/typescript-react-apollo` or `@graphql-codegen/typescript-urql`), Angular Services +(`@graphql-codegen/typescript-apollo-angular`) and much more. We can even generate a pre-compiled +`DocumentNode` instead of dealing with Webpack loaders. + +This generated code takes GraphQL and TypeScript to the next level - because we are getting +ready-to-use code that has TypeScript types built-in and allow us to use it directly from our +application code without the need to specify the types or GraphQL document manually. + +### 2020: New `TypedDocumentNode` + +Generating code is nice, but we don't always need to wrap hooks, services or similar code with more +code. With the power of TypeScript, we can pre-compile the GraphQL operation into a `DocumentNode`, +and add burn-in the TypeScript types. + +With the support of the client-side libraries, we get automatic type inference and auto-complete - +without generating additional code. + +## Live Demo + +[You can try it live here](https://codesandbox.io/s/github/dotansimha/graphql-typed-document-node/tree/master/examples/apollo-client-3); +note the autocomplete and automatic type inference for the `result` variable. + +## How Does It Work? + +This project works in the following way: + +1. You write your GraphQL operations (`query` / `mutation` / `subscription` / `fragment`) in any way + your prefer (for example - in a `.graphql` file). +2. [GraphQL Code Generator](https://graphql-code-generator.com) will generate a `TypedDocumentNode` + for your operations (which is a bundle of pre-compiled `DocumentNode` with the operation result + type and variables type). +3. Instead of using your `.graphql` file, import the generated `TypedDocumentNode` and use it with + your GraphQL client framework. +4. You'll get automatic type inference, auto-complete and type checking based on your GraphQL + operation. + +The definition of `TypedDocumentNode` is super simple - it's all about the TS generics: + +```ts +import { DocumentNode } from 'graphql' + +export interface TypedDocumentNode extends DocumentNode {} +``` + +### Can I Use It Now? + +This library is already available to use, but it requires you to setup it in your project, since +`TypedDocumentNode` isn't supported automatically in all GraphQL client libraries. + +That's why we used `patch-package` to patch existing declarations, and added support to other +libraries without effecting it's runtime. + +[You can find a list of all supported GraphQL clients here](https://github.com/dotansimha/graphql-typed-document-node#supported-libraries) +and +[a short getting started tutorial here](https://github.com/dotansimha/graphql-typed-document-node#how-to-use). + +If you are using a library that isn't supported yet, +[you can always add support to it manually using method overloading](https://github.com/dotansimha/graphql-typed-document-node#how-to-extend-other-libraries-with-this). + +## What's Next? + +GraphQL client libraries +[can easily add support](https://github.com/dotansimha/graphql-typed-document-node#how-can-i-support-this-in-my-library) +for `TypedDocumentNode`, without breaking any API, allowing developers to have direct support for +that, without the need for the supporting libraries. + +And, maybe, one day, it will part of the +[original GraphQL `DocumentNode` interface](https://github.com/graphql/graphql-js/blob/4b7ab47c3f4ea6be99c3d9754b21fe626f7c7177/src/language/ast.d.ts#L212-L216) +;) diff --git a/packages/web/docs/src/app/blog/(posts)/typescript-graphql-unions-types/page.mdx b/packages/web/docs/src/app/blog/(posts)/typescript-graphql-unions-types/page.mdx new file mode 100644 index 00000000000..d291a94bf99 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/typescript-graphql-unions-types/page.mdx @@ -0,0 +1,169 @@ +--- +title: 'Getting the Best of TypeScript and GraphQL: Union Types' +description: + 'The combination of TypeScript and GraphQL can be very powerful. Both TypeScript and GraphQL + support the concept of discriminated unions.' +authors: tuvalSimha +tags: [graphql, typescript, codegen] +date: 2022-10-18 +--- + +## Introduction + +The combination of TypeScript and GraphQL can be very powerful. Both TypeScript and GraphQL support +the concept of discriminated unions. In this short blog post, I want to share my last project +experience and share one of the most powerful methods to create stronger types using TypeScript and +GraphQL. + +One of my assumptions in this blog post is that you know some TypeScript. If you don't, you can +check out ["Basic Types"](https://www.typescriptlang.org/docs/handbook/basic-types.html) handbook. +So, let's talk a little bit about TypeScript union types, one of the most remarkable features of +this language. + +## What Is a Union Type in TypeScript? + +So TypeScript language is based on a type system. TypeScript union operator can be used to define a +variable that can hold multiple data types: integer or number, character, string, float, etc. + +Here you can find a basic example: + +```ts +let pizzaLover: string | boolean // pizzaLover can be a string or boolean type + +pizzaLover = 'YES' // string type +pizzaLover = true // boolean type +``` + +[Here's a live example in TS Playground](https://www.typescriptlang.org/play?#code/DYUwLgBADglgXnAhgGQPYDcQCcBcEDOYWMAdgOYQA+EARqqqIiQLABQbsCKG2EAvBADkATQCiAZUEQA9NIJFSFMAE8oIDvCRpMWfhCIBXEDLl0GIJvtXrWsiAD0A-GyA) + +## How to Use Union Types in TypeScript + +TypeScript can also be used to combine complex types such as interfaces. You can think about one +type that contains some others types. Here is a simple example that combines 2 types: `Sushi` and +`Pizza`, both of them can be united into one type by the name `Food`. + +```ts +type Sushi = { + kind: 'Sushi' + fish: string + amount: number +} + +type Pizza = { + kind: 'Pizza' + topping: string + amount: number +} + +type Food = Sushi | Pizza +``` + +[Here's a live example in TS Playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBAygrgZwBYEsoF4oG8CwAoKKAaxQDsATALigHJ5kUb9CAzFZahYAJzIHNmUAIYBbAPZxSwaqTgiARhG74AvvnyhIUAAooAXnqEZsgkhWo1dBoUwJRgYsGH6ce-QaIlSZcxcrxqeOp4mtAAYmJi5Mb0qFAAPjr6hsFAA) + +And another Union type example, this time with `string` and `string`: + +```ts +type PizzaAddons = 'olives' | 'mushrooms' | 'pepperoni' + +const myPreference: PizzaAddons = 'olives' // ok +const myPreference: PizzaAddons = 'pineapple' // error +``` + +[Here's a live example in TS Playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBACglgLwQQwIIBN0HsB2BnKAXigHIsAbOANwjxKgB9SBbAVzwAsAnLLZuxqUhhIPHHBIBYAFAyAxrjzAozEDC4QAZhA045EAFyxEKDNnxFSFarXoB6O1CwBreYuWr1WnRD2HjSGiYipYkYHA4EMgi5BD2jjo8XDJAA) + +## GraphQL Union and TypeScript Union Type + +GraphQL is a query language that is used to define the API of a server. Like TypeScript or even Go +language, GraphQL supports union and interface types. The type system is one of its core principles +and we can use it to make type-safe API calls. The goal is to use it to propagate our backend types +to the frontend. When I started to learn TypeScript, I found myself making lots of type errors and +annoying misspellings. These errors became more frequent as the project grew. The best solution for +that situation was to add type-safety based on a GraphQL schema. + +A GraphQL schema can return one of the multiple object types thanks to the Union or Interface +keywords. Like TypeScript, we can declare which object types are included in the union: + +```graphql +union Menu = Pizza | Sushi +``` + +Let's see a basic example schema that defines a `Menu` union type that can return either a `Pizza` +or `Sushi`: + +```graphql +union Menu = Pizza | Sushi + +type Pizza { + title: String! + topping: String +} + +type Sushi { + name: String! + fish: String +} + +type Query { + menu: [Menu!] +} +``` + +After we declare our types in GraphQL and create a union type to return some of the other types, we +can generate TypeScript types from the GraphQL schema and use them in the frontend. + +## How to Use GraphQL Codegen to Generate Code and Deal with TypeScript Union Types + +First of all, you are more than welcome to explore all the plugins of GraphQL Codegen. +[GraphQL Code Generator](https://graphql-code-generator.com/) Like we did in the previous example, +let's talk about getting the data typed safely in the front. + +First, let's create our `GetFullMenu` query as follows: + +(in this case, I'll use the `react-query` example) + +```ts +const menuQuery = gql` + query GetFullMenu { + menu { + ...SomeSushi + ...SomePizza + } + } + fragment SomeSushi on Sushi { + name + fish + } + fragment SomePizza on Pizza { + title + topping + } +` +``` + +Get the `Menu` type from the schema with the query, so we will be able to get the objects inside the +`Menu`. + +```ts +const Menu = () => { + const { data } = useQuery('menu', async () => { + const { menu } = await request(endpoint, menuQuery) + return menu + }) +} +``` + +## Conclusion + +- I hope this blog post helped you to better understand TypeScript unions and GraphQL unions and the + way to use them in your project. + +- We can create Union and Interface types in both TypeScript and GraphQL, which helps us to better + define the API of a server and use it in our client. + +- A static type check can help safeguard your application. Especially in large-scale applications. + +- When you use type-safe GraphQL, it helps you to eliminate existing errors like misspellings and + errors. + +- In this brief blog post, we looked at a few features of TypeScript that help you on code-time, + most help you to understand the output of any function you create. diff --git a/packages/web/docs/src/app/blog/(posts)/typescript-with-graphql-done-right/typescript-with-graphql-done-right-1.png b/packages/web/docs/src/app/blog/(posts)/typescript-with-graphql-done-right/typescript-with-graphql-done-right-1.png new file mode 100644 index 00000000000..08f69141e79 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/typescript-with-graphql-done-right/typescript-with-graphql-done-right-1.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/typescript-with-graphql-done-right/typescript-with-graphql-done-right-2.png b/packages/web/docs/src/app/blog/(posts)/typescript-with-graphql-done-right/typescript-with-graphql-done-right-2.png new file mode 100644 index 00000000000..531dcbc98e5 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/typescript-with-graphql-done-right/typescript-with-graphql-done-right-2.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/typescript-with-graphql-done-right/typescript-with-graphql-done-right-3.png b/packages/web/docs/src/app/blog/(posts)/typescript-with-graphql-done-right/typescript-with-graphql-done-right-3.png new file mode 100644 index 00000000000..40a785ffd74 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/typescript-with-graphql-done-right/typescript-with-graphql-done-right-3.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/unleash-the-power-of-fragments-with-graphql-codegen/component-tree-diagram.png b/packages/web/docs/src/app/blog/(posts)/unleash-the-power-of-fragments-with-graphql-codegen/component-tree-diagram.png new file mode 100644 index 00000000000..3fc45726bb2 Binary files /dev/null and b/packages/web/docs/src/app/blog/(posts)/unleash-the-power-of-fragments-with-graphql-codegen/component-tree-diagram.png differ diff --git a/packages/web/docs/src/app/blog/(posts)/unleash-the-power-of-fragments-with-graphql-codegen/page.mdx b/packages/web/docs/src/app/blog/(posts)/unleash-the-power-of-fragments-with-graphql-codegen/page.mdx new file mode 100644 index 00000000000..6cf9511fd16 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/unleash-the-power-of-fragments-with-graphql-codegen/page.mdx @@ -0,0 +1,368 @@ +--- +title: Unleash the power of Fragments with GraphQL Codegen +authors: laurin +tags: [graphql, codegen, react, typescript, relay, apollo, urql] +date: 2022-08-05 +description: + The most important parts of Relay are the concepts of building and scaling applications, let's + show how you can use these patterns in your existing projects. +--- + +import { Callout } from '@theguild/components' + +There is no doubt that Relay is the most advanced JavaScript GraphQL Client out there. However, at +the same time, the learning curve and adoption is yet very sparse compared to other popular +alternatives such as Apollo Client, urql, or GraphQL Request. + +As a consequence, many people don't even know about all the benefits and patterns used within (and +so far being exclusive to) Relay. The Guild gathered and worked on projects whose GraphQL client +usage spans across all the available solutions today. In our opinion, the most important parts of +Relay are the concepts of building and scaling applications. These patterns are actually applicable +to any GraphQL Client, you just need the right code generation tool for the job. + +On a high level these benefits are the following: + +**A fragmentized component tree.** Instead of writing a single GraphQL operation document per +visible component or extracting the component properties from a single query operation document +type, which is often cumbersome), multiple fragment definitions can be composed up to a single query +operation that is sent to the server. This reduces the amount of concurrent requests and together +with `@defer` and `@stream` is an actual improvement in the performance of the application +([compared to batching GraphQL operations](https://apollographql.com/blog/apollo-client/performance/batching-client-graphql-queries)). + +![Fragmentized Component Tree](./component-tree-diagram.png) + +**Colocate GraphQL documents with the component code.** Instead of writing GraphQL operations in a +dedicated file, the operations are written within the component code. Have you ever deleted +component code and forgot to delete the corresponding operation or fragment definitions that are in +some other field? We encountered this issue over and over again generating a lot of dead code. + +```tsx +import { FragmentType, graphql, useFragment } from './gql' + +const Avatar_UserFragment = graphql(/* GraphQL */ ` + fragment Avatar_UserFragment on User { + avatarUrl + } +`) + +type AvatarProps = { + user: FragmentType +} + +export function Avatar(props: AvatarProps) { + const user = useFragment(Avatar_UserFragment, props.user) + return +} +``` + +**Data (fragment) masking.** Ensure that a component can only access the data defined in its +fragment definitions in order to keep the component a self-contained building block that can be +re-used. + +> We are happy to announce that we now have a preset for bringing the above Relay patterns to your +> existing projects, no matter what client library you are currently using, without having to switch +> over and commit to the Relay ecosystem. + +## A New GraphQL Code Generator Preset + +The new preset serves as a drop-in replacement for all the plugins that generate hooks for existing +clients such as urql and apollo-client. + +```bash +yarn add -D @graphql-codegen/client-preset +``` + +```ts +import { CodegenConfig } from '@graphql-codegen/cli' + +const config: CodegenConfig = { + schema: 'https://swapi-graphql.netlify.app/.netlify/functions/index', + documents: ['src/**/*.tsx', '!src/gql/**/*'], + generates: { + './src/gql/': { + preset: 'client', + plugins: [] + } + } +} + +export default config +``` + +Instead of generating the hooks for the existing clients, the preset works with function signature +overloading and [`TypedDocumentNode`](/blog/typed-document-node)) for inferring the correct GraphQL +operation and variables type. + +> You can learn more about `TypedDocumentNode` in +> [graphql.wtf episode #41](https://youtube.com/watch?v=cYIhx8dusa4). + +In your application code you will now effectively write the following code. + +```tsx +import { useQuery } from 'urql' +import { graphql } from './gql/gql' + +const ViewerQueryDocument = graphql(/* GraphQL */ ` + query ViewerQuery { + viewer { + id + name + } + } +`) + +const Viewer = () => { + const [result] = useQuery({ query: ViewerQueryDocument }) + const { data, fetching, error } = result + + if (fetching) return

    Loading…

    + if (error) return

    Oh no… {error.message}

    + // data is fully typed! + return

    {data?.viewer?.name}

    +} +``` + +Because the client has built in support for TypedDocumentNode, it will extract the correct operation +and variables type from the document passed to `useQuery`. + +[Head over to the GraphQL Code Generator documentation](https://graphql-code-generator.com/plugins/presets/client-preset) +for more details. + +> This is now our recommended way of using GraphQL Code Generator for front end development. + +## Describe Component Data Needs via GraphQL Fragments + +Instead of writing one big GraphQL Operation for our whole page and passing that down to the +components, start with describing the component's data dependencies through a GraphQL fragment. This +way, you are making the data-dependency of your component colocated and explicit in the same way +that some would colocate the TypeScript definitions or CSS if you are using the styled components +pattern. + +```tsx +import { FragmentType, graphql, useFragment } from './gql' + +const Avatar_UserFragment = graphql(/* GraphQL */ ` + fragment Avatar_UserFragment on User { + avatarUrl + } +`) + +type AvatarProps = { + user: FragmentType +} + +export function Avatar(props: AvatarProps) { + const user = useFragment(Avatar_UserFragment, props.user) + return +} +``` + +## Restrict Data Access with Fragments + +By utilizing the `useFragment` hook we ensure that the component can only access the properties of +the data that are declared directly within the fragment selection set. The other data declared +through the additional fragment spread(`Avatar_UserFragment`) cannot be accessed within the +`UserListItem` . + +The following example would raise a TypeScript error, as the `avatarUrl` field is not selected +within the `UserListItem_UserFragment` fragment. + +```tsx +import { Avatar } from './avatar' +import { FragmentType, graphql, useFragment } from './gql' + +const UserListItem_UserFragment = graphql(/* GraphQL */ ` + fragment UserListItem_UserFragment on User { + fullName + ...Avatar_UserFragment + } +`) + +type UserListItemProps = { + user: FragmentType +} + +export function UserListItem(props: UserListItemProps) { + const user = useFragment(UserListItem_UserFragment, props.user) + + // ERROR: Property 'avatarUrl' does not exist on type + const icon = + return {user.fullName} +} +``` + +## Compose Fragments for UI Components + +As we compose simple visual UI components into bigger functional UI components we can also compose +the fragments of the consumed UI component. + +```tsx +import { Avatar } from './avatar' +import { FragmentType, graphql, useFragment } from './gql' + +const UserListItem_UserFragment = graphql(/* GraphQL */ ` + fragment UserListItem_UserFragment on User { + fullName + ...Avatar_UserFragment + } +`) + +type UserListItemProps = { + user: FragmentType +} + +export function UserListItem(props: UserListItemProps) { + const user = useFragment(UserListItem_UserFragment, props.user) + return }>{user.fullName} +} +``` + +## Compose Fragment Components for Your Top-Level Route or View + +We can now further compose our components till we reach those components that select the root Query +object type. + +```tsx +import { FragmentType, graphql, useFragment } from './gql' +import { UserListItem } from './user-list-item' + +const FriendList_QueryFragment = graphql(/* GraphQL */ ` + fragment FriendList_QueryFragment on Query { + friends(first: 5) { + id + ...UserListItem_UserFragment + } + } +`) + +type FriendListProps = { + query: FragmentType +} + +export function FriendList(props: FriendListProps) { + const query = useFragment(FriendList_QueryFragment, props.query) + return ( + + {query.friends.map(user => ( + + ))} + + ) +} +``` + +Earlier, we declared the `Avatar` component with the `Avatar_UserFragment` fragment. We can now +re-use this Avatar in a different context by again spreading the fragment and then passing the +relevant data to the `Avatar` component `user` property. + +```tsx +import { FragmentType, graphql, useFragment } from './gql' + +const UserProfileHeader_QueryFragment = graphql(/* GraphQL */ ` + fragment UserProfileHeader_QueryFragment on Query { + viewer { + id + homeTown + registeredAt + ...Avatar_UserFragment + } + } +`) + +type UserProfileHeaderProps = { + query: FragmentType +} + +export function UserProfileHeader(props: UserProfileHeaderProps) { + const query = useFragment(UserProfileHeader_QueryFragment, props.query) + return ( + <> + + {query.viewer.homeTown} + {query.viewer.registeredAt} + + ) +} +``` + +Now, we have two UI components that require data from the root Query type, `FriendList` and +`UserProfileHeader` . + +## Compose All Query Fragments into a Single Query Operation + +Finally, we can spread all our fragments into a single GraphQL Query operation that fetches all the +data on our route component. This allows us to efficiently fetch all the data required for the route +in one server roundtrip. + +```jsx +import { graphql, useFragment, FragmentType } from './gql' +import { UserProfileHeader } from './user-profile-header' +import { FriendList } from './friend-list' + +const UserProfileRoute_Query = graphql(/* GraphQL */ ` + query UserProfile_Query { + ...UserProfileHeader_QueryFragment + ...FriendList_QueryFragment + } +`) + +export function UserProfileRoute_Query() { + const { data, loading, error } = useQuery(UserProfile_Query) + if (loading) return + if (error) return + return ( + <> + + + + ) +} +``` + +Finally, we have the following component tree as mentioned before. + +![Fragmentized Component Tree](./component-tree-diagram.png) + +## Conclusion + +This pattern allows you to re-use and scale your components to the maximum while having a clear and +sane data dependency flow from the top route down to the ends of the UI component trees. + +You can start adopting this pattern within your existing GraphQL application with any GraphQL client +that supports `TypedDocumentNode` through our new and battle-tested GraphQL Code Generator preset +[client-preset](https://graphql-code-generator.com/plugins/presets/client-preset). + +All the following clients are supported with **ANY** framework (React, Vue, Angular, etc.) + +- urql +- Apollo Client +- "Vanilla" Node.js +- graphql-request + ([we recently added support](https://github.com/prisma-labs/graphql-request/pull/350)) + +You can start quickly without much configuration by following +[the preset documentation](https://graphql-code-generator.com/plugins/presets/client-preset). + + + If you want to learn how to optimize the generated code for your frontend applications, check out + our article [Optimize your Bundle Size with SWC and GraphQL + Codegen](/blog/optimize-bundle-size-with-swc-and-graphql-codegen). + + +Maybe this even inspires you to dig deeper into the optimizations the Relay client runtime does on +top of these patterns and convince you to use Relay within your next project. You can learn more +about those on [relay.dev](https://relay.dev). + +We, as The Guild, want you to find the right tool for your expertise and the application you are +building. The most important thing is the patterns that are applicable anywhere! + +## Not Having Enough? + +I also talked about this topic recently at the GraphQL Berlin meetup! + +import { YoutubeIframe } from '#components/youtube-iframe' + + diff --git a/packages/web/docs/src/app/blog/(posts)/whats-new-in-graphql-cli-4.1.0/page.mdx b/packages/web/docs/src/app/blog/(posts)/whats-new-in-graphql-cli-4.1.0/page.mdx new file mode 100644 index 00000000000..fe9acc2197b --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/whats-new-in-graphql-cli-4.1.0/page.mdx @@ -0,0 +1,92 @@ +--- +title: What's new in GraphQL CLI 4.1 +authors: ephelan +tags: [graphql, node, graphql-cli, graphback] +date: 2020-10-15 +description: GraphQL CLI - experience the modern way for developing full-stack GraphQL applications. +--- + +[GraphQL CLI](https://graphql-cli.com) is your one-stop shop for developing full-stack GraphQL +applications in Node.js. With GraphQL CLI you can create and run a new GraphQL application in just a +few seconds! Just declare your GraphQL schema, and you can perform code generation, schema +validation, introspection and more through intuitive CLI commands. + +GraphQL CLI aggregates multiple community projects giving developers the best getting started +experience. Tools included in the CLI are mature and developed over the years based on Guild's +experience in pushing production ready GraphQL solutions and also through collaboration with Red Hat +community projects. + +This post covers a number of enhancements added in GraphQL CLI 4.1, which will further improve your +GraphQL development experience. Check out our previous post +[**GraphQL CLI is back!**](/blog/graphql-cli-is-back) for a full overview of the library and its +features. + +## Graphback + +All the templates are configured with [Graphback](https://graphback.dev), for both runtime and +generation purposes. Graphback 1.0 has recently been released, check out their blog post +[Announcing the Release of Graphback 1.0](https://graphback.dev/blog/2020/10/01/announcing-graphback-1.0) +that goes into deeper details of Graphback's features and capabilities. + +To generate your schema and documents with Graphback, run `graphql generate` from your application +root. See the [generate command docs](https://graphql-cli.com/generate) for a thorough explanation +of this command and usage guides. + +## Serve Command + +The serve command is now powered by [graphql-serve](https://npmjs.com/package/graphql-serve), +letting you start up an in-memory GraphQL server and playground in seconds - perfect for mocking and +testing! + +```sh +$ graphql serve --port 4000 ./model/datamodel.graphql + +Starting server... + +Listening at: http://localhost:4000/graphql +``` + +Check out the [serve command docs](https://graphql-cli.com/serve) for installation and usage guides! + +## Init Command Templates + +The `init` command is your gateway to creating your new GraphQL application with GraphQL CLI. You +will be guided through some questions and after a few seconds a tailor-made starter application will +be created! + +There are several improvements to all of our templates to make them cleaner and more +production-ready. Additionally, we had added two new starter templates: a plain MongoDB template and +a MongoDB template with out-of-the-box data synchronization support. + +To start using these templates, use the `init` command: + +```shell +graphql init +``` + +## Other Updates + +We've built a new website to host the GraphQL CLI documentation! Check it out at +[graphql-cli.com](https://graphql-cli.com). + +GraphQL CLI 4.1 has been updated to use the latest versions of +[GraphQL Code Generator](https://graphql-code-generator.com) and +[GraphQL Inspector](https://graphql-inspector.com), which are included as recommended, best practice +workflows for developing production-ready GraphQL applications. + +## Try It Out + +Start using GraphQL CLI today to create your GraphQL application in just a few steps! + +The easiest way to get started is to initialize your new application with npx: + +```shell +npx graphql-cli init +``` + +GraphQL CLI will guide you through some steps and in a few seconds your project will created and +ready to use. Happy coding! + +As always, we want your feedback! We would love to hear your suggestions and ideas to help make +GraphQL CLI even better. Reach out to us through [GitHub](https://github.com/urigo/graphql-cli) or +join our [Discord community server](https://discord.gg/xud7bH9). diff --git a/packages/web/docs/src/app/blog/(posts)/whats-new-in-graphql-codegen-v2/page.mdx b/packages/web/docs/src/app/blog/(posts)/whats-new-in-graphql-codegen-v2/page.mdx new file mode 100644 index 00000000000..5a20d5fed26 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/whats-new-in-graphql-codegen-v2/page.mdx @@ -0,0 +1,359 @@ +--- +title: What's new in GraphQL-Codegen v2? +authors: dotan +tags: [graphql, codegen, typescript] +date: 2021-08-03 +description: Human-readable types, improved infrastructure, a new plugin for TypeScript and more! +--- + +[GraphQL Code Generator](https://github.com/dotansimha/graphql-code-generator) is around for almost +5 years, and it's improving the developer experience of many developers (>3M downloads a month on +NPM!). Like all The Guild's projects, throughout all these years, the library continuously evolved +based on the experience and feedback from the community and our clients - every day codegen gets +better and better, so keep your feedback coming and keep your dependencies up to date :) But now, +it's time for a big release! + +We recently decided to revisit and improve some parts of codegen, and we're happy to announce that +we're releasing a new version of the tool (`v2`)! + +This release is all about improving the reliability and readability of the generated code, and +adding new features (and a new plugin/preset that might revolutionize how we generate code on our +frontends!). + +## Why `v2`? + +Until now GraphQL Codegen has evolved, without the need for (major) breaking changes, but there were +a couple of things we wanted to do for a better and easier codegen experience that required some +small breaking changes. + +We are currently working on adding ESM support for all [The Guild](/)'s tools. + +Unfortunately, in Codegen, we are not there yet +([you can track the progress here](https://github.com/dotansimha/graphql-code-generator/pull/6102), +because we need to make sure that plugins, presets, schemas and documents are still being loaded +correctly. But it's coming soon! + +But other tools, which we use as a dependency, like +[`graphql-tools` (v8) already supports ESM](/blog/graphql-tools-v8), and dropped support for Node +10, so we are aligning to that. + +We've updated to the latest version of [`graphql-tools`](https://graphql-tools.com) (v8) and +[`graphql-config`](https://graphql-config.com) (v4) to get some upstream bug fixes. + +## Human-Readable Generated Types + +A few years ago, we changed the output of codegen to use `Pick` in order to build the operation +types based on the schema types: + +```ts +// Base types generated based on the schema, from `typescript` plugin: +type Query = { + user: User +} + +type User = { + id: Scalars['ID'] + name: Scalars['String'] +} + +// Types generate based on operations, from `typescript-plugins`: + +type MyQuery = { + user: Pick +} +``` + +Some time after, we added `preResolveTypes` configuration flag as experimental, in order to generate +more readable types with primitive types resolved directly on the generate types: + +```ts +type MyQuery = { + user: { + id: string + name: string + } +} +``` + +We are happy to announce that v2 of `typescript-operations` now uses `preResolveTypes: true` by +default, so all generated types are more readable. + +> As a user of Codegen this is not a breaking change (aside that the generated code is slightly +> different), and all your types are still fully compatible with `v1` of the tool. You can set +> `preResolveTypes: false` if you prefer to keep the old behavior. + +## Type Accuracy + +In addition to more readable types, we also improved the accuracy of the generated types. + +A few months ago, [`chrbala`](https://github.com/chrbala) found +[a bug in the `typescript-operations` plugin](https://github.com/dotansimha/graphql-code-generator/issues/6149), +which was causing the generated types to be incorrect in cases where you use nested and complex +fragments. + +This bug was caused by the fact that we were combining the fragment types using the `&` operator of +TypeScript, and this operator doesn't apply deep merging for nested sub-types. So when using +multiple fragments (`MyFirstFragment & MySecondArgument`), the nested fields are being overwritten, +instead of being merged. + +[`n1ru4l`](https://github.com/n1ru4l) picked that up and +[fixed the bug](https://github.com/dotansimha/graphql-code-generator/pull/6184) and added support +for better handling of these kind of cases. + +The new configuration flag (`inlineFragmentTypes: inline`) is now used by default, and generates +more accurate types, without introducing breaking changes! + +Instead of combining the fragment types they are merged and inlined. + +```diff +export type FeedQuery = { __typename?: 'Query' } & { +- currentUser?: Maybe<{ __typename?: 'User' } & Pick>; +- feed?: Maybe>>; ++ currentUser?: Maybe<{ __typename?: 'User'; login: string }>; ++ feed?: Maybe< ++ Array< ++ Maybe<{ ++ __typename?: 'Entry'; ++ id: number; ++ commentCount: number; ++ score: number; ++ createdAt: number; ++ repository: { ++ __typename?: 'Repository'; ++ full_name: string; ++ html_url: string; ++ description?: Maybe; ++ stargazers_count: number; ++ open_issues_count?: Maybe; ++ owner?: Maybe<{ __typename?: 'User'; avatar_url: string }>; ++ }; ++ vote: { __typename?: 'Vote'; vote_value: number }; ++ postedBy: { __typename?: 'User'; html_url: string; login: string }; ++ }> ++ > ++ >; +}; +``` + +No worries, fragment types are still exported separately! + +> All your types are still fully compatible with `v1` of the tool. You can set +> `inlineFragmentTypes: combine` if you still prefer to keep the old behavior. + +## Removal of Deprecations + +We also used the need for a major version in order to remove a few deprecations from the codebase. + +#### `typescript-resolvers` + +In `typescript-resolvers`, we had a generated signature for `IResolvers` and `IDirectiveResolvers` +(which were deprecated 2 years ago), `v2` of this plugin is removing these proxy types. + +Also, the `noSchemaStitching` flag is now set to `true` by default, so the generated resolvers +signature is simpler and matches the needs of most projects. + +> If you are using Schema Stitching, you can set `noSchemaStitching: false` to keep the old +> behavior. + +#### `typescript-compatibility` + +The `typescript-compatibility` plugin was created to make it easier to migrate from v0 to v1 of +codegen, and hasn't been actively developed for a few years. + +With this release, we no longer support or maintain the `typescript-compatibility` plugin. If you +are still using it, please consider migrating to the new type format. + +## New TypeScript Plugin for Operations! + +Some time ago we started advocating the +[`near-operation-file`](https://graphql-code-generator.com/docs/presets/near-operation-file) preset, +for generating GraphQL client code next to the `.graphql` operation file. + +A month ago, [`Maël Nison`](https://github.com/arcanis) reached out to us with +[a new concept](https://github.com/arcanis/graphql-typescript-integration) for matching your actual +GraphQL operation string and the generated TypeScript types. Without having to add clumsy import +statements for each GraphQL operation. + +It allowed writing the operations in the following way: + +```ts +import { gql } from './gql' + +const { GetTweets, CreateTweet } = gql(`#graphql + query GetTweets { + Tweets { + id + } + } + + mutation CreateTweet { + CreateTweet(body: "Hello") { + id + } + } +`) +``` + +The README on his repository contained a +[list of limitations](https://github.com/arcanis/graphql-typescript-integration#limitations). We +took that that feedback and fixed some upstream bugs within graphql-tools and improved +graphql-codegen according to that. + +With a few adjustments, [`n1ru4l`](https://github.com/n1ru4l) managed to +[turn that amazing idea into a plugin and a preset](https://github.com/dotansimha/graphql-code-generator/pull/6267) +that extends the behavior of `typescript-operation` and `TypedDocumentNode`, and allow you to +generate TypeScript types that magically matches your GraphQL query strings (this also required a +few fixes in `graphql-tools`, so now the Loaders in graphql-tools are better!). + +#### So What Does That Mean? + +Today, GraphQL Codegen scans your codebase and looks for all the operations that are being used by +your components (or, from `.graphql` files), then it generates types and wraps `TypedDocumentNode` +for you. Those are either generated to a single file, from which you import from anywhere within the +app or next to the `.graphql` when using the `near-operation-file-preset`. **Folder structure +(`near-operation-file-preset`)** + +``` +Project +- src + - UserQuery.graphql + - UserQuer.graphql.tsx +``` + +With this new plugin+preset, you can generate typings for your inline `gql` function usages, without +having to manually specify import statements for the documents. All you need to do is import your +`gql` function and run codegen in watch mode. + +```tsx +import { FragmentType, gql } from '@app/gql' + +// TweetFragment is a fully typed document node +const TweetFragment = gql(/* GraphQL */ ` + fragment TweetFragment on Tweet { + id + body + } +`) + +const TweetsQueryWithFragment = gql(/* GraphQL */ ` + query TweetsWithFragmentQuery { + Tweets { + id + ...TweetFragment + } + } +`) + +const TweetComponent = (props: { tweet: FragmentType }) => { + return {props.tweet.body} +} +``` + +That way we don't need to switch context between `.graphql` and our `.ts(x)` files and have less +files within our repository (compared to the `near-operation-file` preset)! + +You can find +[the complete documentation, examples and API reference in codegen website](https://graphql-code-generator.com/docs/presets/gql-tag-operations). + +[We also have an example PR for a migration from `near-operation-file` to the `gql-tag-operations-preset`.](https://github.com/n1ru4l/character-overlay/pull/331) + +We want to hear your thoughts on this way of using Codegen on the frontend. + +Based on your feedback we might want to make this the recommended way of using Codegen on the +frontend in the future. + +> Huge thanks to [Maël Nison](https://github.com/arcanis), who conceptualized the foundation for +> this preset [over here](https://github.com/arcanis/graphql-typescript-integration). Please keep +> pushing the boundaries! + +## What's Next? + +Like we said at the beginning, we continuously keep improving Codegen, so this release is not the +end but just a start. Here are some sneak peeks on things we are currently working on: + +### ESM Support + +ESM support is coming soon (and will probably result in another major version) + +### SDL Resolver Development Flow Improvements + +We are experimenting with a better way to link `typeDefs` with the resolvers signature generated by +`typescript-resolvers` in a similar way to the new `gql-tag-operations` plugin. One of the goal of +this preset is to make sure that every resolver that should be defined and implemented is actually +implemented. + +```ts +import { typeDefs } from 'tsgql' + +const sdl = typeDefs(/* GraphQL */ ` + type Query { + foo: String! + } +`) + +export const resolvers: typeof sdl = { + Query: { + // If this resolver would not be defined it will raise a TypeScript error. + foo: () => 'test' + } +} +``` + +[You can find more info here](https://github.com/dotansimha/graphql-code-generator/pull/6379) + +### Fragment Type Masking + +Today the Relay framework development flow allows hiding (masking) properties from operation results +objects that are added via fragments. Those properties are only accessible from within the component +that consumes the fragment: + +```tsx +import { FragmentProperty, gql } from '@app/gql' + +// TweetFragment is a fully typed document node +const TweetFragment = gql(/* GraphQL */ ` + fragment TweetFragment on Tweet { + id + body + } +`) + +const TweetsQueryWithFragment = gql(/* GraphQL */ ` + query TweetsWithFragmentQuery { + Tweets { + id + ...TweetFragment + } + } +`) + +const AppComponent = () => { + const { data } = useQuery({ query: TweetsQueryWithFragment }) + + // accessing this here is a TypeScript error. + data.Tweets[0].body + + return ( + <> + {data.Tweets.map(tweet => ( + + ))} + + ) +} + +const TweetComponent = (props: { tweet: FragmentProperty }) => { + // accessing props.tweet.body here is legit. + return {props.tweet.body} +} +``` + +This allows building scalable components as each component only receives the data it should have +access to and furthermore components higher up in the tree can not access data it did not explicitly +request. Removing a component will thus not result in surprising behaviors. + +### What Else Is Missing? + +Do you think something else is missing or could be improved? Reach out to us via the chat on this +page, a [GitHub discussion](https://github.com/dotansimha/graphql-code-generator/discussions) or on +the [GraphQL Discord](https://discord.gg/x3MYZRvMrg). diff --git a/packages/web/docs/src/app/blog/(posts)/whatsapp-clone-apollo-angular-graphql-typescript-and-postgresql/page.mdx b/packages/web/docs/src/app/blog/(posts)/whatsapp-clone-apollo-angular-graphql-typescript-and-postgresql/page.mdx new file mode 100644 index 00000000000..bfb372f5344 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/whatsapp-clone-apollo-angular-graphql-typescript-and-postgresql/page.mdx @@ -0,0 +1,129 @@ +--- +title: WhatsApp Clone - Angular, GraphQL, Apollo, TypeScript and PostgreSQL +authors: niccolo +tags: [whatsapp-clone, angular, graphql, typescript, postgres] +date: 2019-02-25 +description: + An open-source full-stack example app made with Angular 7.2, TypeScript, GraphQL Subscriptions, + GraphQL Code Generator, GraphQL Modules, PostgreSQL and TypeORM. +--- + +You might have seen it around already — an open-source WhatsApp Clone tutorial; a project which was +originally started in 2015 by [Urigo](https://github.com/urigo) based on +[Angular-Meteor](https://angular-meteor.com/tutorials/whatsapp2-tutorial) and +[Ionic](https://ionicframework.com), and have been throughout different incarnations ever since. + +You may have also noticed that we recently published a new +[React version](/blog/whatsapp-clone-react-hooks-graphql-typescript-and-postgresql) of the Whatsapp +Clone tutorial. + +This time around, I'm happy to announce that a new version of the WhatsApp Clone is here, and it's +based on Angular 7.2, Angular CLI 7.3.2, Material-UI, TypeScript, Apollo, GraphQL-Subscriptions, +[GraphQL Code Generator](https://graphql-code-generator.com), +[GraphQL Modules](https://graphql-modules.com), PostgreSQL and TypeORM, full with step by step +guides to teach you every step of the way. + +[**Click mme to go to the tutorial page**](https://tortilla.academy/Urigo/whatsapp-textrepo-angularcli-express) + +import { VideoEmbed } from '#components/video-embed' + + + +## What Is It Good for? + +This app was built with all the latest and hottest technologies out there. The purpose is simple — +it should be a guideline for building a proper app, thus we thought very carefully about the design +patterns and architecture used in it, plus, we made sure to cover all communication methods with a +GraphQL-back-end in different variations (query, mutation, subscription). This way whenever you're +looking to start a new app, maintain an existing one or upgrade your dev-stack, the WhatsApp-clone +can be a great source to start with! It's full stack and has a complete flow. + +## Why Did We Choose This Dev-Stack? + +Angular, GraphQL, Apollo, PostgreSQL and TypeScript for obvious reasons — they are backed by a +strong ecosystem that grows rapidly. These technologies can be used in endless variations, and +there's no one way which is the most right of doing so, but we chose a way that makes the most sense +for us and that we truly believe in when it comes to building apps. We've connected it all with +TypeORM, GraphQL-Code-Generator, GraphQL-Modules for the following reasons: + +- The GraphQL back-end was implemented using **GraphQL-Modules** where logic was splitted into + feature based modules. GraphQL-Modules is a library which provides you with the ability to manage + and maintain your GraphQL schema in a scalable and reusable way. Not once nor twice I have seen + people who struggle with that and get tangled upon their own creation, and with GraphQL-Modules + where you have a very defined structure, this problem can be easily solved. You can read more in + [this series of 7 blog posts about it](/blog/graphql-modules). +- Every GraphQL/TypeScript definition was automatically generated with GraphQL-Code-Generator using + a single command call. There's no need to maintain the same thing twice if it already exists in + one way or another. This way you don't have to write TypeScript type definitions for your all your + server responses, you get ready-to-use — fully typed Angular services, GraphQL resolvers and + GraphQL types. +- The new version of Angular 7.2 was used with the Angular Material UI and Angular CLI 7.3.2 (and + we'll keep updating the tutorial with the latest versions) +- We used TypeORM to correctly split the logic of the entities in the database and define the + relationships between them. ORMs are controversial these days, but they can help a lot in some + cases, and we thought a good example could be valuable to the community. + +## What to Expect? + +- Basic authentication. +- Image uploading with [Cloudinary](https://cloudinary.com). +- Live updates with GraphQL Subscriptions. +- Zero latency even on the slowest network thanks to Apollo's Cache + +The tutorial goes through every aspect of building the app, starting from the very basics. We will +start building a very simple server with a fake db, then we will introduce Authentication, +Subscriptions, a real database backed by PostgreSQL and TypeORM plus advanced tooling like GraphQL +Code Generator and GraphQL Modules. + +This can be extremely useful for those who have little to no background in one of the technologies +in our dev-stack. + +## What's Next? + +Right now we implemented a simple REST-based [Passport](https://passportjs.org) authentication, but +we already have PRs for [Accounts-JS](https://github.com/accounts-js/accounts) based authentication +which will use the GraphQL endpoint instead of traditional REST ones. An additional chapter about +Pagination is also expected, as well as a “Performance” chapter tackling the N+1 problem with +GraphQL. Our backend has been designed to handle way more features than the ones currently +implemented, so be ready because features like the Whatsapp blue ticks are going to land on our +clone very soon. + +## Keep up to Date + +This tutorial was written using [Tortilla — the Tutorial framework](https://tortilla.academy). + +This means that we will keep upgrading the tutorial with the latest versions of Angular and the +other libraries, and instead of doing the same tutorial all over again, you will get a +[git-diff](https://tortilla.academy/Urigo/whatsapp-textrepo-angularcli-express/master/1.4.0/diff/2.0.0) +of how to upgrade the existing tutorial! We care about your time as a developer. + +## Influence + +We want to hear your opinions! + +Should we choose another library and technology over another? Could we write the code +better/cleaner? Should we add a specific feature to the app? We want to hear it all! + +Please tell us now, so we could integrate your feedback on the tutorial itself! + +We keep evolving the stack and as the tutorial is based on git commits, we can create the same clone +with different tech-stacks and compare them on this real app using code diffs. + +Soon we will also release yet another version of the Whatsapp Clone, using +[Ionic](https://ionicframework.com), [Stencil](https://stenciljs.com) and Web Components. + +If you are good at creating screencast and videos, we would love your help in creating videos for +some chapters. + +Everything is completely free and open source, and we want your help and (**not financial**) +contribution! + +Best place would be to open an issue or create a PR on the repositories: + +- Server — [Urigo/WhatsApp-Clone-Server](https://github.com/Urigo/WhatsApp-Clone-server) +- Client — + [Urigo/WhatsApp-Clone-Client-Angular](https://github.com/Urigo/whatsapp-client-angularcli-material) + +Again, all types of feedback is welcome, write freely! + +See you in the tutorial! diff --git a/packages/web/docs/src/app/blog/(posts)/whatsapp-clone-react-hooks-graphql-typescript-and-postgresql/page.mdx b/packages/web/docs/src/app/blog/(posts)/whatsapp-clone-react-hooks-graphql-typescript-and-postgresql/page.mdx new file mode 100644 index 00000000000..aeefe7aac1c --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/whatsapp-clone-react-hooks-graphql-typescript-and-postgresql/page.mdx @@ -0,0 +1,112 @@ +--- +title: WhatsApp Clone - GraphQL, Apollo, TypeScript and PostgreSQL +authors: eytan +tags: [whatsapp-clone, react, graphql, typescript, postgres] +date: 2019-02-01 +description: + Fully functional WhatsApp Clone using React (Hooks+Suspense), GraphQL, Apollo, TypeScript and + PostgreSQL +--- + +**An open-source full-stack example app made with React 16.7 (Hooks & Suspense), TypeScript, +GraphQL-Subscriptions/Codegen/Modules and PostgreSQL** + +You might have seen it around already — an open-source WhatsApp Clone tutorial; a project which was +originally started in 2015 by [Urigo](https://github.com/urigo) based on +[Angular-Meteor](https://angular-meteor.com/tutorials/whatsapp2-tutorial) and +[Ionic](https://ionicframework.com), and have been throughout different incarnations ever since. + +This time around, I'm happy to announce that a new version of the WhatsApp Clone is coming, and it's +based on [React 16.7](https://reactjs.org) ([Hooks](https://reactjs.org/docs/hooks-intro.html) & +[Suspense](https://reactjs.org/docs/react-api.html#suspense)), +[Styled-Components](https://github.com/styled-components/styled-components), +[Material-UI](https://github.com/mui-org/material-ui), [TypeScript](https://typescriptlang.org), +[Apollo](https://apollographql.com), +[GraphQL-Subscriptions](https://youtube.com/watch?v=Wi7P39sF2nw)/[Codegen](https://graphql-code-generator.com)/[Modules](https://graphql-modules.com), +[PostgreSQL](https://postgresql.org) and [TypeORM](https://github.com/typeorm/typeorm). + +[Click me to go to the tutorial page](https://tortilla.academy/Urigo/WhatsApp-Clone-Tutorial) + +> A more in depth step-by-step tutorial is expected in the near future + +import { VideoEmbed } from '#components/video-embed' + + + +## What Is It Good for? + +This app was built with all the latest and hottest technologies out there. The purpose is simple — +it should be a guideline for building a proper app, thus we thought very carefully regards the +design patterns and architecture used in it, plus, we made sure to cover all communication methods +with a GraphQL-back-end in different variations (query, mutation, subscription). This way whenever +you're looking to start a new app, maintain an existing one or upgrade your dev-stack, the +WhatsApp-clone can be a great source to start with! It's full stack and has a complete flow. + +## Why Did We Choose This Dev-Stack? + +React, GraphQL, Apollo, PostgreSQL and TypeScript for obvious reasons — they are backed by a strong +ecosystem that grows rapidly. These technologies can be used in endless variations, and there's no +one way which is the most right of doing so, but we chose a way that makes the most sense for us and +that we truly believe in when it comes to building apps. We've connected it all with +[TypeORM](https://github.com/typeorm/typeorm), +[GraphQL-Code-Generator](https://graphql-code-generator.com), +[GraphQL-Modules](https://graphql-modules.com) for the following reasons: + +- The GraphQL back-end was implemented using [GraphQL-Modules](https://graphql-modules.com) where + logic was splitted into feature based modules. GraphQL-Modules is a library which provides you + with the ability to manage and maintain your GraphQL schema in a scalable and reusable way. Not + once nor twice I have seen people who struggle with that and get tangled upon their own creation, + and with GraphQL-Modules where you have a very defined structure, this problem can be easily + solved. You can read more in [this series of 7 blog posts about it](/blog/graphql-modules). +- Every GraphQL/TypeScript definition was automatically generated with + [GraphQL-Code-Generator](https://graphql-code-generator.com) using a single command call. There's + no need to maintain the same thing twice if it already exists in one way or another. This way you + don't have to write TypeScript type definitions for your GraphQL documents (queries, mutations and + subscriptions), GraphQL resolvers and GraphQL types. +- The new version of React 16.7 was used with Hooks and Suspense and 100% of the project is made out + of function components. The front-end communicates with the back-end using only hooks and there + was no use in GraphQL-React components, which makes async tasks look a lot more readable with no + extra indentations. +- We used TypeORM to correctly split the logic of the entities in the database and define the + relationships between them. ORMs are controversial these days, but they can help a lot in some + cases, and we thought a good example could be valuable to the community. + +## What to Expect? + +- Basic authentication +- Image uploading with [Cloudinary](https://cloudinary.com) +- Live updates with GraphQL Subscriptions +- 100% function components with React Hooks +- GraphQL's communication with + [react-apollo-hooks](https://github.com/trojanowski/react-apollo-hooks) + +This can be extremely useful for those who have little to no background in one of the technologies +in our dev-stack. We will let you know when it's ready and will publish it due time, until then be +sure to follow [our blog](/blog) and the GitHub repositories. + +## Influence + +We want to hear your opinions! + +Should we choose another library and technology over another? Could we write the code +better/cleaner? Should we add a specific feature to the app? We want to hear it all! + +Please tell us now, so we could integrate your feedback on the tutorial itself! + +We want to keep evolving the stack and as the tutorial would be based on git commits, we want to +create the same clone with different tech-stacks and compare them on this real app using code diffs. + +Everything is completely free and open source, and we want your help and (**not financial**) +contribution! + +Best place would be to open an issue or create a PR on the repositories: + +- Server — [urigo/WhatsApp-Clone-Server](https://github.com/Urigo/WhatsApp-Clone-server) +- Client — [urigo/WhatsApp-Clone-Client-React](https://github.com/Urigo/WhatsApp-Clone-Client-React) + +Again, all types of feedback is welcome, write freely! + +See you in the tutorial! diff --git a/packages/web/docs/src/app/blog/components/blog-card.tsx b/packages/web/docs/src/app/blog/components/blog-card.tsx index ab33af00652..f3a95714fa8 100644 --- a/packages/web/docs/src/app/blog/components/blog-card.tsx +++ b/packages/web/docs/src/app/blog/components/blog-card.tsx @@ -62,12 +62,7 @@ export function BlogCard({ post, className, variant, tag }: BlogCardProps) { {date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} -

    +

    {title}