Is there a way to stop the playground from firing introspection query? - node.js

Iam using ApolloServer with ExpressJS. I disabled introspection in my apollo server , but Playground is still sending an introspection query.
setting the options:
introspection: false
and
settings: {
"schema.enablePolling": false, // or even "schema.polling.enable": false
}}
Doesn't change anything. I reloaded the playground but still doesn't change a thing. Here the ApolloServer config in my server.js file:
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
introspection: false,
playground: {
settings: {
"schema.enablePolling": false, // even "schema.polling.enable": false, does not work
}},
context: async ({ req }) => {
if(req){
console.log(req) // here we can see the introspection query firing every 2 seconds
}
}
});
What could be the reason(s) ?
Thanks

Related

Astro: How to proxy service calls

I am setting up an Astro site which will display data fetched from a simple service running on the same host but a different port.
The service is a simple Express app.
server.js:
const express = require('express')
const app = express()
const port = 3010
const response = {
message: "hello"
}
app.get('/api/all', (_req, res) => {
res.send(JSON.stringify(response))
})
app.listen(port, () => {
console.log(`listening on port ${port}`)
})
Since the service is running on port 3010, which is different from the Astro site, I configure a server proxy at the Vite level.
astro.config.mjs:
import { defineConfig } from 'astro/config';
import react from '#astrojs/react';
export default defineConfig({
integrations: [react()],
vite: {
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis'
}
}
},
server: {
proxy: {
'/api/all': 'http://localhost:3010'
}
}
},
});
Here is where I am trying to invoke the service.
index.astro:
---
const response = await fetch('/api/all');
const data = await response.json();
console.log(data);
---
When I run yarn dev I get this console output:
Response {
size: 0,
[Symbol(Body internals)]: {
body: Readable {
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 1,
_maxListeners: undefined,
_read: [Function (anonymous)],
[Symbol(kCapture)]: false
},
stream: Readable {
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 1,
_maxListeners: undefined,
_read: [Function (anonymous)],
[Symbol(kCapture)]: false
},
boundary: null,
disturbed: false,
error: null
},
[Symbol(Response internals)]: {
type: 'default',
url: undefined,
status: 404,
statusText: '',
headers: { date: 'Tue, 02 Aug 2022 19:41:02 GMT' },
counter: undefined,
highWaterMark: undefined
}
}
It looks like the network request is returning a 404.
I'm not seeing in the doc much more about server configuration.
Am I going about this the right way?
I have this working correctly with a vanilla Vite app and the same config/setup.
How can I proxy local service calls for an Astro application?
Short Answer
You cannot proxy service calls with Astro but also you don't have to
For direct resolution answer see section functional test without proxy
Details
Astro does not forward the server.proxy config to Vite (unless you patch your own version of Astro), the Astro Vite server config can be seen empty
proxy: {
// add proxies here
},
reference https://github.com/withastro/astro/blob/8c100a6fe6cc652c3799d1622e12c2c969f30510/packages/astro/src/core/create-vite.ts#L125
there is a merge of Astro server with Astro vite.server config but it does not take the proxy param. This is not obvious to get from the code, see tests later.
let result = commonConfig;
result = vite.mergeConfig(result, settings.config.vite || {});
result = vite.mergeConfig(result, commandConfig);
reference https://github.com/withastro/astro/blob/8c100a6fe6cc652c3799d1622e12c2c969f30510/packages/astro/src/core/create-vite.ts#L167
Tests
Config tests
I tried all possible combinations of how to input config to Astro and in each location a different port number to show which one takes an override
a vite.config.js file on root with
export default {
server: {
port:6000,
proxy: {
'/api': 'http://localhost:4000'
}
}
}
in two locations in the root file astro.config.mjs
server
vite.server
export default defineConfig({
server:{
port: 3000,
proxy: {
'/api': 'http://localhost:4000'
}
},
integrations: [int_test()],
vite: {
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis'
}
}
},
server: {
port:5000,
proxy: {
'/api': 'http://localhost:4000'
}
}
}
});
in an Astro integration
Astro has a so called integration that helps update the config (sort of Astro plugins) the integration helps identify what was finally kept in the config and also gives a last chance to update the config
integration-test.js
async function config_setup({ updateConfig, config, addPageExtension, command }) {
green_log(`astro:config:setup> running (${command})`)
updateConfig({
server:{proxy : {'/api': 'http://localhost:4000'}},
vite:{server:{proxy : {'/api': 'http://localhost:4000'}}}
})
console.log(config.server)
console.log(config.vite)
green_log(`astro:config:setup> end`)
}
this is the output log
astro:config:setup> running (dev)
{ host: false, port: 3000, streaming: true }
{
optimizeDeps: { esbuildOptions: { define: [Object] } },
server: { port: 5000, proxy: { '/api': 'http://localhost:4000' } }
}
astro:config:setup> end
the proxy parameter is removed from astro server config, the vite config is visible but has no effect as it is overridden, and not forwarded to Vite
test results
dev server runs on port 3000 which is from Astro config server all other configs overridden
the fetch api fails with the error
error Failed to parse URL from /api
File:
D:\dev\astro\astro-examples\24_api-proxy\D:\dev\astro\astro-examples\24_api-proxy\src\pages\index.astro:15:20
Stacktrace:
TypeError: Failed to parse URL from /api
at Object.fetch (node:internal/deps/undici/undici:11118:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
functional test without proxy
Given that Astro front matter runs on the server side, in SSG mode during build and in SSR mode on page load on the server then the server sends the result html, Astro has access to all host ports and can directly use the service port like this
const response = await fetch('http://localhost:4000/api');
const data = await response.json();
console.log(data);
The code above runs as expected without errors
Reference Example
All tests and files mentioned above are available on the reference example github repo : https://github.com/MicroWebStacks/astro-examples/tree/main/24_api-proxy
You can add your own proxy middleware with the astro:server:setup hook.
For example use http-proxy-middleware in the server setup hook.
// plugins/proxy-middleware.mjs
import { createProxyMiddleware } from "http-proxy-middleware"
export default (context, options) => {
const apiProxy = createProxyMiddleware(context, options)
return {
name: 'proxy',
hooks: {
'astro:server:setup': ({ server }) => {
server.middlewares.use(apiProxy)
}
}
}
}
Usage:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import proxyMiddleware from './plugins/proxy-middleware.mjs';
// https://astro.build/config
export default defineConfig({
integrations: [
proxyMiddleware("/api/all", {
target: "http://localhost:3010",
changeOrigin: true,
}),
],
});

Jest+NextJs: You should only use "next/router" on the client side of your app

I'm mocking the next/router dependency in my Jest+React-testing-libray tests as I always have:
import * as nextRouter from 'next/router';
export const routerData = {
pathname: '/users/create',
route: '/users/create',
query: { },
asPath: '/users/create',
isFallback: false,
basePath: '',
isReady: true,
isPreview: false,
isLocaleDomain: false,
events: {},
};
// mock router
jest.mock('next/router');
nextRouter.useRouter.mockImplementation(() => (routerData));
describe('a component that requires next/router, () => ... );
This had been working correctly but after updating to NextJs 12.2.0 I get this warning:
No router instance found.
You should only use "next/router" on the client side of your app.
This warning makes all my tests with the mocked router to fail.
Ideas to fix this?
Well, it appears that this is not related to 12.2.0. Somehow my last version of Next - 12.0.0 - wasn't thrownig this error but other older versions did.
Thanks to bistacos for the response here.
const useRouter = jest.spyOn(require('next/router'), 'useRouter');
useRouter.mockImplementation(() => ({
pathname: '/',
...moreRouterData
}));

Caching mechanism is not storing data in Redis

I am quite desperate right now and I am looking for any kind of help.
I am trying to setup cache mechanism in my project using GraphQL and Redis.
This is how I configure GraphQLModule:
GraphQLModule.forRoot({
cache: new BaseRedisCache({
client: new Redis({
host: 'localhost',
port: 6379,
password: 'Zaq1xsw#',
}),
cacheControl: {
defaultMaxAge: 10000
},
}),
plugins: [
responseCachePlugin()
],
autoSchemaFile: path.resolve(__dirname, `../generated/schema.graphql`),
installSubscriptionHandlers: true,
}),
This is how I’ve created queries and mutations:
#Resolver()
export class AuthResolver {
constructor(
private readonly prismaService: PrismaService,
private readonly authService: AuthService,
){}
#Query(returns => String)
async testowe(#Args(`input`) input: String, #Info() info: any) {
info.cacheControl.setCacheHint({ maxAge: 5000, scope: 'PUBLIC' });
return 'test';
}}
When I am using GraphQL Playground and try this query I get the response and header looks like that:
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8
cache-control: max-age=5000, public
Content-Length: 28
ETag: W/"1c-2Df/lONPXcLzs1yVERHhOmONyns"
Date: Tue, 28 Dec 2021 21:35:11 GMT
Connection: keep-alive
Keep-Alive: timeout=5
As You may see there is a part with “cache-control”.
My problem is that I cannot see any keys or values stored in Redis. I am connected to Redis server with redis-cli tool and Ive tried “KEYS ‘*’” command. There is nothing stored in Redis.
Also I have problem with more complex queries - I do not even get a header with “cache-control” part.
Do You have any idea what I am doing wrong here? Should I be able to see stored values in Redis with such approach?
Thank You in advance for any advice.
For what i can see, you don't tell your resolver to store it's result in Redis. The Apollo Server docs are not super clear about this.
I've did a research project around caching & graphql so feel free to read my Medium post about it: https://medium.com/#niels.onderbeke.no/research-project-which-is-the-best-caching-strategy-with-graphql-for-a-big-relational-database-56fedb773b97
But to answer your question, I've implemented Redis with GraphQL this way:
Create a function that handles the caching, like so:
export const globalTTL: number = 90;
export const checkCache = async (
redisClient: Redis,
key: string,
callback: Function,
maxAge: number = globalTTL
): Promise<Object | Array<any> | number> => {
return new Promise(async (resolve, reject) => {
redisClient.get(key, async (err, data) => {
if (err) return reject(err);
if (data != null) {
return resolve(JSON.parse(data));
// logger.info("read from cache");
} else {
// logger.info("read from db");
let newData = await callback();
if (!newData) newData = null;
redisClient.setex(key, maxAge, JSON.stringify(newData));
resolve(newData);
}
});
});
};
Then in your resolver, you can call this function like so:
#Query(() => [Post])
async PostsAll(#Ctx() ctx: any, #Info() info: any) {
const posts = await checkCache(ctx.redisClient, "allposts", async () => {
return await this.postService.all();
});
return posts;
}
You have to pass your Redis client in to the context of GraphQL, that way you can acces your client inside your resolver using ctx.redisClient ...
This is how I've passed it:
const apolloServer = new ApolloServer({
schema,
context: ({ req, res }) => ({
req,
res,
redisClient: new Redis({
host: "redis",
password: process.env.REDIS_PASSWORD,
}),
}),
});
This way you should be able to store your data in your Redis cache.
The info.cacheControl.setCacheHint({ maxAge: 5000, scope: 'PUBLIC' }); way you are trying is for using another caching strategy within Apollo Server. Apollo is able to calculate the cache-control header with this information, but you have to set this setting then:
const apolloServer = new ApolloServer({
schema,
plugins: [
ApolloServerPluginCacheControl({
// Cache everything for 1 hour by default.
defaultMaxAge: 3600,
// Send the `cache-control` response header.
calculateHttpHeaders: true,
}),
],
});
Note: You can set the default max-age to a value that suits your needs.
Hope this solves your problem!
You can find my implementation of it at my research repo: https://github.com/OnderbekeNiels/research-project-3mct/tree/redis-server-cache
I faced the same problem. Try the following
GraphQLModule.forRoot({
plugins: [
responseCachePlugin({
cache: new BaseRedisCache({
client: new Redis({
host: 'localhost',
port: 6379,
password: 'Zaq1xsw#',
}),
}),
})
],
autoSchemaFile: path.resolve(__dirname, `../generated/schema.graphql`),
installSubscriptionHandlers: true,
}),

Apollo Server Express - Playground cannot be reached

I am trying to follow this tutorial https://www.youtube.com/watch?v=I6ypD7qv3Z8&t=48972s but I am stuck on trying to make the playground work.
I get to the playground on "http://localhost:4000/graphql" but somehow I get the "Server cannot be reached" error. In the network inspector I see "Cannot POST /" 404s.
Here's my code (app.ts):
import { ApolloServer } from "apollo-server-express";
import { ApolloServerPluginLandingPageGraphQLPlayground } from "apollo-server-core";
import { buildSchema } from "type-graphql";
import { PORT } from "./constants";
import { HelloResolver } from "./graphql/resolvers";
export const main = async () => {
const app = express();
const apolloServer = new ApolloServer({
schema: await buildSchema({ resolvers: [HelloResolver], validate: false }),
plugins: [ApolloServerPluginLandingPageGraphQLPlayground],
});
await apolloServer.start();
apolloServer.applyMiddleware({ app });
app.listen(PORT, () => {
console.log(
`Server started on http://localhost:${PORT}${apolloServer.graphqlPath}`
);
});
};
Things I did extra from the tut:
Made PORT a variable with value 4000
Added the "ApolloServerPluginLandingPageGraphQLPlayground" for the old school playground (newest doesn't work either)
Added the "await apolloServer.start();" line as specified on the doc, I get an error if I don't
Things I tried:
Using the http server stuff from the doc (https://www.apollographql.com/docs/apollo-server/integrations/middleware/#apollo-server-express) -> same issue
Any idea on where could be the issue? Seems like express didn't create the POST endpoint for the /graphql route.
EDIT: It works if I change this:
apolloServer.applyMiddleware({ app });
to this:
apolloServer.applyMiddleware({ app, path: "/" });
I found the answer here! helpful. Do check it out!
Try using 127.0.0.1 instead of localhost. It worked for me. Also If you have cached queries and mutations that you still want to use, switching back to localhost from 127.0.0.1 will have the localhost working again.
I recently had this issue too. This was the case where the
Server cannot be reached/no visible schema but still able to execute a query
To fix this you should have this as in your ApolloServer initializer:
const server = new ApolloServer({
csrfPrevention: true,
plugins: [
ApolloServerPluginLandingPageGraphQLPlayground(),
],
instrospection: true,
})
Read more about instrospection here and this github issue.

How to use express-winston and winston-mongodb together?

I'm using express-winston and winston-mongodb to log express requests to mongodb.
express-winston config:
expressWinston.logger({
meta: true,
//...other unrelated config
})
winston-mongodb config:
new MongoDB({
//...other unrelated config
})
Logging to mongodb works, but meta field is null. Logging to file/console works perfectly.
The mongo plugin doesn't recognize the express plugin's log format.
How can I get them to play nice?
When you look at the express-winston and winston-mongodb codes, you can easily see the difference.
winston-mongodb writes the value that matches the metaKey field to the specified collection.
Therefore, if you define it as follows, the meta field will not be null.
...
transports: [
new winston.transports.Console({
format: winston.format.json({
space: 2
})
}),
new winston.transports.MongoDB({
db: config.db.mongooseURI,
options: config.db.options,
collection:'logs',
capped:true,
metaKey:'meta'
}),
],
meta: true,
...
This is what finally worked for me: v6.12.1 Noticed later that while this works for db logging, the meta is empty for the file transport. Not sure why.
const winston = require('winston');
module.exports = function(err, req, res, next) {
winston.error(err.message, {metadata: { prop: err } });
res.status(500).send('Something failed.');
};
If you want to log to File and MongoDB then:
winston.add(
new winston.transports.MongoDB({
db: process.env.CONNECTIONSTRING,
options: { useUnifiedTopology: true },
metaKey: 'meta'
})
)
module.exports = function (err, req, res, next) {
// Log the exception
winston.error({message: err.message, level: err.level, stack: err.stack, meta: err})
res.status(500).send("Something failed..Cannot connect to Server");
};

Resources