I am trying to generate schema for my prisma data model while at the same time using secrets to restrict prisma access. After running prisma delete and prisma deploy, I run the command graphql get-schema -p prisma and get the following error message:
✖ prisma/prisma.yml could not be found.
Is there something wrong I am doing in my .graphqlconfig or how I am listing my prisma.yml? Thanks.
.graphqlconfig:
{
"projects": {
"prisma": {
"schemaPath": "generated/prisma.graphql",
"extensions": {
"prisma": "prisma/prisma.yml",
"endpoints": {
"default": "http://localhost:4466"
}
}
}
}
}
prisma/prisma.yml:
endpoint: http://localhost:4466
datamodel: datamodel.prisma
secret: 'secretFoo'
index.js:
import http from 'http';
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import resolvers from './resolvers';
import schema from './generated/prisma.graphql';
import { Prisma } from 'prisma-binding';
const prisma = new Prisma({
endpoint: 'http://localhost:4466',
secret: 'secretFoo',
typeDefs: 'server/generated/prisma.graphql',
});
const server = new ApolloServer({
context: {
prisma,
},
resolvers,
typeDefs: schema,
});
const app = express();
server.applyMiddleware({ app });
const PORT = 5000;
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
httpServer.listen(PORT, () => {
console.log(`Server ready at http://localhost:${PORT}${server.graphqlPath}`);
console.log(`Subscriptions ready at ws://localhost:${PORT}${server.subscriptionsPath}`);
});
if (module.hot) {
module.hot.accept();
module.hot.dispose(() => server.stop());
}
You can generate a schema directly from your prisma.yml file, by adding the following to the file:
generate:
- generator: graphql-schema
output: ./generated/prisma.graphql
Then you can refer your .graphqlconfig to the generated file:
projects:
prisma:
schemaPath: generated/prisma.graphql
extensions:
endpoints:
dev: http://localhost:4466
You would generally restrict access to the management functionality of your endpoint through the Prisma docker-compose file (managementApiSecret in PRISMA_CONFIG). Then when you run commands like prisma deploy you would need to pass the appropriate environment variables through either the --env-file flag, or by having a dotenv file in the root of your application's directory (you also need the dotenv package installed in package.json.
Another way to secure your endpoint is to disable the GraphQL Playground altogether. I believe Apollo Server does this automatically when NODE_ENV is set to production, although you can do it explicitly with:
const server = new ApolloServer({
context: {
prisma,
},
resolvers,
typeDefs: schema,
playground: false, // <- Here
});
I'm sorry, I don't think this directly answered your question, but it may assist either way.
Related
Nestjs application does not display the error message during development only during production. I found no module where the apollo -server can be configured to cache: "bounded". The Nestjs documentation itself makes no mention of it anywhere.
The complete error message says:
Persisted queries are enabled and are using an unbounded cache. Your server is vulnerable to denial of service attacks via memory exhaustion. Set cache: "bounded" or persistedQueries: false in your ApolloServer constructor, or see https://go.apollo.dev/s/cache-backends for other alternatives.
Here are some dependencies I suspect could be related to it.
"#nestjs/apollo": "10.0.19",
"#nestjs/common": "9.0.5",
"#nestjs/core": "9.0.5",
"#nestjs/graphql": "10.0.20",
A similar issue was opened at github and sadly it was closed without any solution.
This is related to the Apollo module's configuration, by default the cache is "unbounded" and not safe for production (see : https://www.apollographql.com/docs/apollo-server/performance/cache-backends/#ensuring-a-bounded-cache)
You can easily follow their recommendation by adding the optional "cache" configuration inside your GraphQL module.
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
cache: 'bounded', // <--- This option
...
}
Or provide an external caching with KeyvAdapter see: https://github.com/apollographql/apollo-utils/tree/main/packages/keyvAdapter#keyvadapter-class
In the app.module.ts inside the import array,
GraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: ApolloDriver,
useClass: GqlConfigService,
}),
Then in the GqlConfigService I add cache: 'bounded' in the ApolloDriverConfig options.
import { ConfigService } from '#nestjs/config';
import { ApolloDriverConfig } from '#nestjs/apollo';
import { Injectable } from '#nestjs/common';
import { GqlOptionsFactory } from '#nestjs/graphql';
import { GraphqlConfig } from './config.interface';
#Injectable()
export class GqlConfigService implements GqlOptionsFactory {
constructor(private configService: ConfigService) {}
createGqlOptions(): ApolloDriverConfig {
const graphqlConfig = this.configService.get<GraphqlConfig>('graphql');
return {
// schema options
cache: 'bounded', // ! <== Added here
autoSchemaFile: graphqlConfig.schemaDestination || './src/schema.graphql',
sortSchema: graphqlConfig.sortSchema,
buildSchemaOptions: {
numberScalarMode: 'integer',
},
// subscription
installSubscriptionHandlers: true,
debug: graphqlConfig.debug,
playground: graphqlConfig.playgroundEnabled,
context: ({ req }) => ({ req }),
};
}
}
I load my GraphQL schema like:
const schema = loadSchemaSync('./src/graphql/server/schema/*.gql', {
loaders: [new GraphQLFileLoader()],
})
This works fine locally, however, when deploying to vercel I get the error:
Unable to find any GraphQL type definitions for the following pointers:
- ./src/graphql/server/schema/*.gql
I think this is because vercel is dropping the relevant files after build?
The problem is that you cannot use dynamic loaders in Vercel Serverless Functions.
A workaround for this problem is to use a inline GraphQL schema.
// src/graphql/schema.ts
import { gql } from "apollo-server-core";
export default gql`
type Query {
greet: String!
}
`;
// src/pages/api/graphql.ts
import { ApolloServerPluginLandingPageGraphQLPlayground } from "apollo-server-core";
import Schema from "../../graphql/schema";
const apolloServer = new ApolloServer({
typeDefs: Schema,
resolvers,
plugins: [ApolloServerPluginLandingPageGraphQLPlayground],
introspection: true,
});
If you are using tools like codegen:
// codegen.ts
import { CodegenConfig } from "#graphql-codegen/cli";
const config: CodegenConfig = {
schema: "src/graphql/schema.ts",
documents: ["./src/**/*.{ts,tsx}"],
ignoreNoDocuments: true,
generates: {
"src/graphql/types/server.ts": {
plugins: [
"#graphql-codegen/typescript",
"#graphql-codegen/typescript-resolvers",
],
},
"src/graphql/types/client/": {
preset: "client",
plugins: [],
},
},
};
export default config;
I need help with custom directives when using graphql-modules library. Have no idea where to place my custom directives so it is combined with overall schema
I would like to post answer from Discord community, from user Maapteh.
here it the quote from him
in our app with old version we had everything in common module. We
kept that approach partly when using the latest version of modules.
See some parts of our codebase:
import { ApolloServer, SchemaDirectiveVisitor } from
'apollo-server-express';
const schema = AppModule.createSchemaForApollo();
SchemaDirectiveVisitor.visitSchemaDirectives(schema, {
isMember: IsMemberDirective,
deprecated: isDeprecated,
...SNIP... });
as you can see we create the schema we pass eventually to the apollo
server (example using apollo). We add our generic directives like so.
The schema for them is in our common module. Read further...
For common scalars we added a common module. With their schema (so in
schema directory we also have the directives schemas) and their
resolver.
const typeDefsArray = loadFilesSync(${__dirname}/schema/*.graphql, {
useRequire: true, }); const typeDefs = mergeTypeDefs(typeDefsArray, { useSchemaDefinition: false });
const resolverFunctions = {
ImageUrl: ImageUrlType,
PageSize: PageSizeType,
Date: DateResolver,
...SNIP... };
export const CommonModule = createModule({
id: 'common',
typeDefs: typeDefs,
resolvers: resolverFunctions, });
hope it helps you
https://discord.com/channels/625400653321076807/631489837416841253/832595211166548049
I'am trying to use the GraphQL federation with the schema first approach. Before I switch to federation, I was using ts-node and a little script to generate my typings like this :
import { GraphQLDefinitionsFactory } from '#nestjs/graphql';
import { join } from 'path';
const definitionsFactory = new GraphQLDefinitionsFactory();
definitionsFactory.generate({
typePaths: ['./src/**/*.graphql'],
path: join(process.cwd(), 'src/graphql.schema.ts'),
outputAs: 'class',
});
This was working well until I switch to the federation approach and modifying my schema adding the #Key() directive in my sites.graphql file (schema first!) :
type Site #key(fields: "siteId") {
siteId: ID!
contractId: Int
dateStart: String
siteName: String
}
type Query {
GetSite(id: ID!): Site
}
Now, when I generate my classes, I have the following error :
> ts-node src/tools/generate-typings.ts
(node:84388) UnhandledPromiseRejectionWarning: Error: Unknown directive "#key".
The #key directive does not seem to be recognized. Do I miss something?
Thank you for your help
Ok, digging in the source code of graphql-definitions.factory.ts I found an undocumented option federation.
Changing my script to :
import { GraphQLDefinitionsFactory } from '#nestjs/graphql';
import { join } from 'path';
const definitionsFactory = new GraphQLDefinitionsFactory();
definitionsFactory.generate({
typePaths: ['./src/**/*.graphql'],
path: join(process.cwd(), 'src/graphql.schema.ts'),
outputAs: 'class',
federation: true
});
And it works now.
Ps: to run the project, don't forget to eventually disable the installSubscriptionHandlers in the GraphQLModule options.
For federated subgraphs you should used GraphQLFederationDefinitionsFactory instead:
import { join } from 'path';
import { GraphQLFederationDefinitionsFactory } from '#nestjs/graphql';
const definitionsFactory = new GraphQLFederationDefinitionsFactory();
definitionsFactory.generate({
typePaths: ['./src/**/*.graphql'],
path: join(process.cwd(), 'src/graphql.ts'),
defaultScalarType: 'unknown',
customScalarTypeMapping: {
DateTime: 'Date',
},
watch: true,
});
I have created the script to generate the federation schema
import { writeFileSync, readFileSync } from 'fs';
import { buildSubgraphSchema } from '#apollo/subgraph';
import { printSchema, DocumentNode } from 'graphql';
import gql from 'graphql-tag';
async function generateSchema() {
// auto generate schema file
const subGraphschema = readFileSync('./src/schema/schema.graphql', {
encoding: 'utf8',
flag: 'r',
});
const schema: DocumentNode = gql(subGraphschema);
const generatedSchema = buildSubgraphSchema({
typeDefs: schema,
});
writeFileSync(
'./public/schema.graphql',
printSchema(generatedSchema),
'utf8',
);
console.log(`Generated GraphQL schema:\n\n${printSchema(generatedSchema)}`);
}
generateSchema();
I am trying to deploy my NestJS REST API on Heroku but I always get the following error:
Web process failed to bind to $PORT within 60 seconds of launch
My configuration is pretty straight forward:
In my main.ts I start my server with:
await app.listen(process.env.PORT || AppModule.port);
I added a Procfile in the root directory of my project which contains:
web: npm run start:prod
My package.json files contains these scripts:
"build": "tsc -p tsconfig.build.json",
"prestart:prod": "rimraf dist && npm run build",
"start:prod": "node dist/main.js",
The process on Heroku builds succesfully, prints out these seamingly reassuring lines:
TypeOrmModule dependencies initialized
SharedModule dependencies initialized
AppModule dependencies initialized
But then immediately crashes with:
Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 60 seconds of launch
I use .env configuration across my application but I removed all HOST and PORT variables (and code references), so I have no clue what could be the cause of this error.
Am I missing something?
EDIT
I am hereby sharing my app.module and main.ts files:
app.module.ts
#Module({
imports: [
SharedModule,
TypeOrmModule.forRootAsync({
imports: [SharedModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
type: 'postgres',
host: configService.getString('POSTGRES_HOST'),
port: configService.getNumber('POSTGRES_DB_PORT'),
username: configService.getString('POSTGRES_USER'),
password: configService.getString('POSTGRES_PASSWORD'),
database: configService.getString('POSTGRES_DB'),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
} as PostgresConnectionOptions),
}),
UserModule,
],
controllers: [
AppController,
],
providers: [
AppService,
],
})
export class AppModule {
static port: number;
static isDev: boolean;
constructor(configurationService: ConfigService) {
console.log(process.env.PORT);
AppModule.port = configurationService.getNumber('PORT');
AppModule.isDev = configurationService.getBoolean('ISDEV');
}
}
My configuration.service.ts is a simple utility that reads from .env files:
import * as dotenv from 'dotenv';
import * as path from 'path';
#Injectable()
export class ConfigService {
constructor() {
const filePath = path.resolve('.env');
dotenv.config({
path: filePath,
});
}
getNumber(key: string): number | undefined {
return +process.env[key] as number | undefined;
}
getBoolean(key: string): boolean {
return process.env[key] === 'true';
}
getString(key: string): string | undefined {
return process.env[key];
}
}
And finally my main.ts file:
async function bootstrap() {
console.log(process.env.PORT);
const app = await NestFactory.create(AppModule);
app.enableCors();
app.useGlobalPipes(new ValidationPipe(), new TimeStampPipe());
app.use(json({ limit: '5mb' }));
app.setGlobalPrefix('api/v1');
await app.listen(process.env.PORT || AppModule.port);
}
bootstrap();
Could it be that my configuration.service.ts is interfering with heroku's env file?
If you are using fastify instead express as your platform, you need to define the host to 0.0.0.0 explicitly like this :
const port = process.env.PORT || AppModule.port;
const host = '0.0.0.0';
await app.listen(port, host);
This problem is caused by the fastify library. See the related discussion here: Fastify with Heroku.
Just as a summary, be careful on the database connection timeout that could lead to a global timeout of the heroku bootstrap as describe above