Skip to content

Commit

Permalink
GraphQL Yoga driver for NestJS GraphQL (#2525)
Browse files Browse the repository at this point in the history
  • Loading branch information
enisdenjo authored Mar 28, 2023
1 parent 3fe79d1 commit 41f4a54
Show file tree
Hide file tree
Showing 41 changed files with 3,789 additions and 39 deletions.
5 changes: 5 additions & 0 deletions .changeset/happy-months-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-yoga/nestjs-federation': major
---

GraphQL Yoga driver with Apollo Federation for NestJS GraphQL
20 changes: 20 additions & 0 deletions .changeset/little-socks-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@graphql-yoga/nestjs': major
---

GraphQL Yoga driver for NestJS GraphQL.

### BREAKING CHANGES

- No more `subscriptionWithFilter` in YogaBaseDriver.
- `YogaBaseDriver.yogaInstance` has been renamed to `YogaBaseDriver.yoga`
- `YogaBaseDriver` has been renamed to `AbstractYogaDriver`
- Drop `@envelop/apollo-server-errors`, if you want to use it - supply it to the plugins yourself
- `graphql` is now a peer dependency
- `graphql-yoga` is now a peer dependency
- `installSubscriptionHandlers` driver option has been dropped, please use the `subscriptions`
option
- Apollo Federation v2 support
- Apollo Federation driver has been moved to a separate package `@graphql-yoga/nestjs-federation`
- Dropped support for `@nestjs/graphql@v10`, now at least v11 is required (https://github.com/nestjs/graphql/pull/2435)
- Minimum Node.js engine is v14
46 changes: 46 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,49 @@ jobs:
failOnWarning: true
failOnRequired: true
debug: true

nestjs-apollo-federation-compatibility:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install pnpm
uses: pnpm/action-setup@v2.2.4
with:
version: 7
- name: Get pnpm store path
id: pnpm-store
run: echo "PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Cache pnpm
uses: actions/cache@v3
with:
path: ${{ steps.pnpm-store.outputs.PATH }}
key: ${{ runner.os }}-pnpm-store-graphql-v${{ matrix.graphql-version }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-graphql-v${{ matrix.graphql-version }}-
- name: Install Dependencies
run: pnpm i
- name: Build Packages
run: pnpm build
- name: Bundle NestJS Apollo Federation Subgraph Example
run: pnpm --filter=example-nestjs-apollo-federation-compatibility build
- name: Install Rover
run: curl -sSL https://rover.apollo.dev/nix/v0.11.1 | sh
- name: Add Rover to PATH
run: echo "$HOME/.rover/bin" >> $GITHUB_PATH
- name: Apollo Federation Subgraph Compatibility
uses: apollographql/federation-subgraph-compatibility@v1
with:
workingDirectory: examples/nestjs-apollo-federation-compatibility
compose: docker-compose.yaml
schema: schema.graphql
path: /graphql
# no token = no comment
# token: ${{ secrets.GITHUB_TOKEN }}
failOnWarning: true
failOnRequired: true
debug: true
59 changes: 59 additions & 0 deletions .pnpmfile.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// WARNING: please make sure the versions are the same across all workspaces
const singletons = [
'@nestjs/core',
'@nestjs/common',
'@nestjs/graphql',
'@apollo/subgraph',
'@apollo/federation-subgraph-compatibility',
]

function afterAllResolved(lockfile, context) {
context.log('Enforcing single version for: ' + singletons.join(', '))

// find and choose one version for the singletons
const singletonsMap = {}
const danglingSingletons = []
for (const pkg of Object.keys(lockfile.packages)) {
const singlePkg = singletons.find((singlePkg) =>
pkg.startsWith(`/${singlePkg}/`),
)
if (!singlePkg) {
continue
}
if (singlePkg in singletonsMap) {
danglingSingletons.push(pkg)
continue
}
singletonsMap[singlePkg] = pkg.replace(`/${singlePkg}/`, '')
}

// remove dangling singletons from lockfile
for (const dangling of danglingSingletons) {
delete lockfile.packages[dangling]
}

// apply singleton versions
;[lockfile.packages, lockfile.importers].forEach((list) => {
for (const info of Object.values(list)) {
const deps = info.dependencies
const devDeps = info.devDependencies

for (const [pkg, ver] of Object.entries(singletonsMap)) {
if (pkg in (deps || {})) {
deps[pkg] = ver
}
if (pkg in (devDeps || {})) {
devDeps[pkg] = ver
}
}
}
})

return lockfile
}

module.exports = {
hooks: {
afterAllResolved,
},
}
2 changes: 1 addition & 1 deletion examples/apollo-federation-compatibility/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"author": "Charly POLY",
"license": "ISC",
"dependencies": {
"@apollo/subgraph": "2.4.0",
"@apollo/subgraph": "^2.4.0",
"@graphql-yoga/plugin-apollo-inline-trace": "1.7.3",
"graphql": "16.6.0",
"graphql-tag": "2.12.6",
Expand Down
2 changes: 1 addition & 1 deletion examples/apollo-federation/service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"check": "exit 0"
},
"dependencies": {
"@apollo/subgraph": "2.4.0",
"@apollo/subgraph": "^2.4.0",
"graphql-yoga": "3.7.3",
"graphql": "16.6.0"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
services:
products:
image: 'node:19'
user: 'node'
working_dir: /web
environment:
- NODE_ENV=production
volumes:
- ./dist:/web
command: 'node index.js'
ports:
- 4001:4001
40 changes: 40 additions & 0 deletions examples/nestjs-apollo-federation-compatibility/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "example-nestjs-apollo-federation-compatibility",
"version": "1.0.0",
"description": "Apollo Federation implemented with GraphQL Yoga running as a Nest.js driver.",
"private": true,
"scripts": {
"build": "nest build && node scripts/bundle.js",
"prebuild": "rimraf dist",
"start": "nest start",
"test": "fedtest docker --compose ./docker-compose.yaml --schema ./schema.graphql --path /graphql --port 4001 --debug --format markdown"
},
"devDependencies": {
"@apollo/federation-subgraph-compatibility": "1.2.1",
"@apollo/rover": "^0.13.0",
"@graphql-yoga/nestjs-federation": "0.0.0",
"@grpc/proto-loader": "^0.7.5",
"@nestjs/cli": "^9.3.0",
"@nestjs/common": "^9.3.12",
"@nestjs/core": "^9.3.12",
"@nestjs/graphql": "^11.0.4",
"@nestjs/microservices": "^9.3.12",
"@nestjs/platform-socket.io": "^9.3.12",
"@nestjs/websockets": "^9.3.12",
"amqp-connection-manager": "^4.1.11",
"amqplib": "^0.10.3",
"cache-manager": "^5.2.0",
"class-transformer": "0.3.1",
"class-validator": "^0.14.0",
"esbuild": "0.17.12",
"graphql": "^16.6.0",
"kafkajs": "^2.2.4",
"mqtt": "^4.3.7",
"nats": "^2.13.1",
"reflect-metadata": "^0.1.13",
"rimraf": "^4.1.2",
"rxjs": "^7.8.0",
"ts-morph": "^17.0.1",
"typescript": "^4.9.5"
}
}
83 changes: 83 additions & 0 deletions examples/nestjs-apollo-federation-compatibility/schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
extend schema
@link(
url: "https://specs.apollo.dev/federation/v2.3"
import: [
"@composeDirective"
"@extends"
"@external"
"@key"
"@inaccessible"
"@interfaceObject"
"@override"
"@provides"
"@requires"
"@shareable"
"@tag"
]
)
@link(url: "https://myspecs.dev/myCustomDirective/v1.0", import: ["@custom"])
@composeDirective(name: "@custom")

directive @custom on OBJECT

type Product
@custom
@key(fields: "id")
@key(fields: "sku package")
@key(fields: "sku variation { id }") {
id: ID!
sku: String
package: String
variation: ProductVariation
dimensions: ProductDimension
createdBy: User @provides(fields: "totalProductsCreated")
notes: String @tag(name: "internal")
research: [ProductResearch!]!
}

type DeprecatedProduct @key(fields: "sku package") {
sku: String!
package: String!
reason: String
createdBy: User
}

type ProductVariation {
id: ID!
}

type ProductResearch @key(fields: "study { caseNumber }") {
study: CaseStudy!
outcome: String
}

type CaseStudy {
caseNumber: ID!
description: String
}

type ProductDimension @shareable {
size: String
weight: Float
unit: String @inaccessible
}

extend type Query {
product(id: ID!): Product
deprecatedProduct(sku: String!, package: String!): DeprecatedProduct
@deprecated(reason: "Use product query instead")
}

extend type User @key(fields: "email") {
averageProductsCreatedPerYear: Int
@requires(fields: "totalProductsCreated yearsOfEmployment")
email: ID! @external
name: String @override(from: "users")
totalProductsCreated: Int @external
yearsOfEmployment: Int! @external
}

type Inventory @interfaceObject @key(fields: "id") {
id: ID!
deprecatedProducts: [DeprecatedProduct!]!
}
29 changes: 29 additions & 0 deletions examples/nestjs-apollo-federation-compatibility/scripts/bundle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* eslint-disable */
const { build } = require('esbuild')
const { copyFileSync } = require('fs')
const { join } = require('path')

async function main() {
await build({
entryPoints: ['./dist/main.js'],
outfile: 'dist/index.js',
format: 'cjs',
minify: false,
bundle: true,
platform: 'node',
target: 'es2020',
loader: { '.node': 'file' },
})

console.info(`NestJS Apollo Subgraph test build done!`)

copyFileSync(
join(__dirname, '../schema.graphql'),
join(__dirname, '../dist/schema.graphql'),
)
}

main().catch((e) => {
console.error(e)
process.exit(1)
})
28 changes: 28 additions & 0 deletions examples/nestjs-apollo-federation-compatibility/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
YogaFederationDriver,
YogaFederationDriverConfig,
} from '@graphql-yoga/nestjs-federation'
import { Module } from '@nestjs/common'
import { GraphQLModule } from '@nestjs/graphql'
import { DeprecatedProductsResolver } from './deprecated-products.resolver'
import { InventoryResolver } from './inventory.resolver'
import { ProductResearchResolver } from './product-research.resolver'
import { ProductsResolver } from './products.resolver'
import { UsersResolver } from './users.resolver'

@Module({
imports: [
GraphQLModule.forRoot<YogaFederationDriverConfig>({
driver: YogaFederationDriver,
typePaths: ['**/*.graphql'],
}),
],
providers: [
UsersResolver,
ProductsResolver,
ProductResearchResolver,
DeprecatedProductsResolver,
InventoryResolver,
],
})
export class AppModule {}
Loading

0 comments on commit 41f4a54

Please sign in to comment.