-
Notifications
You must be signed in to change notification settings - Fork 398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Can not print schema for a subgraph: GraphQLError: Type User must define one or more fields #2765
Comments
Ran into this as well. I managed to get it working by using the full GraphQLModule and its GraphQLSchemaHost export instead like so: import { printSubgraphSchema } from "@apollo/subgraph";
import { NestFactory } from "@nestjs/core";
import { GraphQLModule, GraphQLSchemaHost } from "@nestjs/graphql";
import {
ApolloFederationDriver,
ApolloFederationDriverConfig,
} from "@nestjs/apollo";
async function generateSchema() {
const app = await NestFactory.create(
GraphQLModule.forRoot<ApolloFederationDriverConfig>({
driver: ApolloFederationDriver,
autoSchemaFile: {
federation: 2,
},
include: [ModuleContainingYourResolvers],
})
);
await app.init();
const gqlSchemaFactory = app.get(GraphQLSchemaHost);
console.log(printSubgraphSchema(gqlSchemaFactory.schema));
} EDIT: I think I spoke too soon. While this does add import for federation directives, it does not appear to include all types, e.g. Query is missing. EDIT2: Turns out the 'include' option is a whitelist of modules that should be part of the module graph, hence it acted like there were no resolvers. Weirdly it still found the models themselves, which fooled me. Here's a new version, although it does have some caveats: import { printSubgraphSchema } from "@apollo/subgraph";
import { NestFactory } from "@nestjs/core";
import { GraphQLModule, GraphQLSchemaHost } from "@nestjs/graphql";
import {
ApolloFederationDriver,
ApolloFederationDriverConfig,
} from "@nestjs/apollo";
import { Module } from '@nestjs/common';
async function generateSchema() {
@Module({
imports: [
GraphQLModule.forRoot<ApolloFederationDriverConfig>({
driver: ApolloFederationDriver,
autoSchemaFile: {
federation: 2,
},
}),
],
providers: [
MyResolver,
],
})
class AppModule {}
const app = await NestFactory.create(AppModule);
await app.init();
const gqlSchemaFactory = app.get(GraphQLSchemaHost);
console.log(printSubgraphSchema(gqlSchemaFactory.schema));
} Unfortunately it requires that your resolvers have no dependencies that needs to be injected, so they can be instantiated. Otherwise you'll need to either add its dependencies or mock them. Hence it's not really a solution, just a workaround until someone smarter drops by with a proper solution 🤞 . |
thanks for the answer @Phault ! Unfortunately none of that works for me - I am only getting this: extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@composeDirective", "@extends", "@external", "@inaccessible", "@interfaceObject", "@key", "@override", "@provides", "@requires", "@shareable", "@tag"]) it doesn't contain anything at all from my types (neither actual data types, nor mutations) update ok I managed to make it work by adding my module with resolves to import { NestFactory } from '@nestjs/core';
import { GraphQLModule, GraphQLSchemaHost } from '@nestjs/graphql';
import { printSubgraphSchema } from '@apollo/subgraph';
import {
ApolloFederationDriver,
ApolloFederationDriverConfig,
} from '@nestjs/apollo';
import { UsersModule } from './src/users/users.module';
import { Module } from '@nestjs/common';
async function generateSchema() {
@Module({
imports: [
GraphQLModule.forRoot<ApolloFederationDriverConfig>({
driver: ApolloFederationDriver,
autoSchemaFile: {
federation: 2,
},
}),
UsersModule, // <<<<==== here!!!!!!!!!!!!!
],
})
class AppModule {}
const app = await NestFactory.create(AppModule);
await app.init();
const gqlSchemaHost = app.get(GraphQLSchemaHost);
console.log(printSubgraphSchema(gqlSchemaHost.schema));
}
generateSchema(); that said this still seems a bit hacky, I am eager to learn the right way to do it. |
I kind of managed to get it work with lazy modules but it requires breaking down chain of modules https://docs.nestjs.com/fundamentals/lazy-loading-modules. graph LR
UsersModule --> |providers| UsersResolver
UsersModule --> |providers| UsersService
UsersResolver -->|constructor injection| UsersService
graph LR
UsersModule -->|providers| UsersResolver
UsersResolverLazyServices -->|providers| UsersService
UsersResolver -->|constructor injection| LazyModuleLoader
UsersResolver -->|runtime resolution on demand via LazyModule| UsersService
i.e. before: @Module({
providers: [UsersResolver, UsersService],
})
export class UsersModule {}
export class UsersResolver {
constructor(private readonly usersService: UsersService) {}
...
} after: @Module({
providers: [UsersResolver], // <<<<< remove `UsersService` from providers of users module
})
export class UsersModule {}
// introduce new intermediate modules to have all lazy imports
@Module({
providers: [UsersService],
})
class UsersResolverLazyServices {}
export class UsersResolver {
constructor(private readonly loader: LazyModuleLoader) {
}
private _usersService: UsersService;
async getUserService() {
if (!this._usersService) {
const ref = await this.loader.load(() => UsersResolverLazyServices);
this._usersService = await ref.get(UsersService);
}
return this._usersService;
}
...
} |
I still wonder though why it doesn't generate everything that I can normally get via a live query: query {
_service {
sdl
}
} which returns the following schema: with queryschema
@link(url: "https://specs.apollo.dev/link/v1.0")
{
query: Query
mutation: Mutation
}
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@composeDirective", "@extends", "@external", "@inaccessible", "@interfaceObject", "@key", "@override", "@provides", "@requires", "@shareable", "@tag"])
directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @requires(fields: federation__FieldSet!) on FIELD_DEFINITION
directive @provides(fields: federation__FieldSet!) on FIELD_DEFINITION
directive @external(reason: String) on OBJECT | FIELD_DEFINITION
directive @tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA
directive @extends on OBJECT | INTERFACE
directive @shareable repeatable on OBJECT | FIELD_DEFINITION
directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
directive @override(from: String!) on FIELD_DEFINITION
directive @composeDirective(name: String) repeatable on SCHEMA
directive @interfaceObject on OBJECT
type User
@key(fields: "id")
{
id: ID!
name: String!
dob: String!
age: Int!
email: String!
}
type Query {
users: [User!]!
user(id: ID!): User!
_entities(representations: [_Any!]!): [_Entity]!
_service: _Service!
}
type Mutation {
createUser(name: String!): User!
}
enum link__Purpose {
"""
`SECURITY` features provide metadata necessary to securely resolve fields.
"""
SECURITY
"""
`EXECUTION` features provide metadata necessary for operation execution.
"""
EXECUTION
}
scalar link__Import
scalar federation__FieldSet
scalar _Any
type _Service {
sdl: String
}
union _Entity = User but instead I am getting: with a scriptextend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@composeDirective", "@extends", "@external", "@inaccessible", "@interfaceObject", "@key", "@override", "@provides", "@requires", "@shareable", "@tag"])
type User
@key(fields: "id")
{
id: ID!
name: String!
dob: String!
age: Int!
email: String!
}
type Query {
users: [User!]!
user(id: ID!): User!
}
type Mutation {
createUser(name: String!): User!
} |
Glad to hear you made some progress :) I'm not well-versed in federation land yet, but I think the difference you're seeing is because the script only prints the subgraph schema (which is ready to be consumed by rover compose), whereas the introspection you did with the query includes everything necessary for the subgraph GraphQL server to act like a standalone GraphQL server. Kind of like composing a supergraph with only that single subgraph. |
I want to be able to generate a schema of a subgraph in order to later feed it into
rover compose
. I want to be able to use code-first approach in nest.Is there an existing issue for this?
Current behavior
I am following this guide https://docs.nestjs.com/graphql/generating-sdl and this issue #1597 but the generated schema does not contain relevant directives.
Minimum reproduction code
https://github.com/maxkomarychev/rover-compose-problem-demo
Steps to reproduce
npm i
npx ts-node generate-schema.ts
update 1
after experimenting a bit I got rid of the problem by removing directive
@Directive('@key(fields: "id")')
but I do need it for federation :)Expected behavior
I way to generate a schema of a subgraph with all relevant directives, like so:
expected schema
Package version
11.0.4
Graphql version
graphql
: 16.6.0apollo-server-express
:apollo-server-fastify
:NestJS version
9.3.12
Node.js version
18.9.0
In which operating systems have you tested?
Other
No response<
The text was updated successfully, but these errors were encountered: