Graphql server + NodeJS, store data in cache with Redis - node.js

I'd like to know how I can improve the response time from connecting graphql to api. I have decided to use Redis. I don't know how exactly should I do it.
I have built my graphql server:
import express from 'express';
import {
graphqlExpress,
graphiqlExpress,
} from 'graphql-server-express';
import bodyParser from 'body-parser';
import { schema } from './src/schema';
import cors from 'cors';
import redis from 'redis';
const PORT = 4000;
const server = express();
const client = redis.createClient();
client.on('error', function (err) {
console.log('error' + err)
});
server.use('*', cors({ origin: 'http://localhost:3000' }));
server.use('/graphql', bodyParser.json(), graphqlExpress({
schema, context: { client }
}));
server.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql'
}));
server.listen(PORT, () =>
console.log(`GraphQL Server is now running on http://localhost:${PORT}`)
);
I have already imported redis here. My graphql server is connected to front page by cors, so I can render strings from api. In resolver I'm connecting to api by Node.js (if necessary I'll add it.), and I have some custom resolvers here. The response time from api (also affect rendering on page) is too slow- around 10~15s.

I recommend checking out Apollo Engine to get a better insight into how GraphQL is working, and make it easier to use add-ons such as a Redis cache. Once you get Apollo Engine working, you can use apollo-server-redis-cache to create a cache.
If your requests are taking 10-15s without a cache, I would also look to optimise them first before throwing Redis in front of them.

Related

Jest and supertest: Test keep exceeding timeout

Hello I am a bit confused by this error I have encountered.
I am working on an Universal React App using Webpack 5 and Express.
I want to implement Jest support by using the React-testing-Library for the frontend (which work) and supertest for the backend (this is where I am blocked).
I am following this basic tutorial recommended by the jest doc himself in order to use jest on an node express environment.
But everytime I get this error:
thrown: "Exceeded timeout of 5000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
Here are my code:
server.js
import app from './app.js';
import { mongooseConnection, disconnectMongoDB } from "./routers/services/url/urlDB.js"; // we call mongooseConnect var to connect only once into the mongoDB database
const PORT = process.env.PORT || 8080;
// the server listen on the port set by node on localhost.
app.listen(PORT, () => {
console.log(
`Server listening on \x1b[42m\x1b[1mhttp://localhost:${PORT}\x1b[0m in \x1b[41m${process.env.NODE_ENV}\x1b[0m`,
);
});
// when when we shut down the app we execute a callback function before closing the server
process.on('exit', function() {
disconnectMongoDB();
});
app.js
import express from 'express';
import path from 'path';
import cors from 'cors';
import {envIsProduction, envIsDevelopment} from './envmode/envUtil.js';
import { enableHMR } from './reload/hotReload.js';
let app = express();
// if we have set the environnent on production then:
if (envIsProduction()) {
console.log(" _______________________________________ ");
console.log("| |");
console.log("| ( PRODUCTION ) |");
console.log("|_______________________________________|");
console.log(" ");
app.use(express.static(path.join(__dirname,'../client'))); // we serve static file like the bundle-app.js to the browser from the current directory where the server is executed and we move to the top root to access the file
}
else if (envIsDevelopment()) {
console.log(" _______________________________________ ");
console.log("| |");
console.log("| ( DEVELOPMENT ) |");
console.log("|_______________________________________|");
console.log(" ");
enableHMR(app); // we enable the Hot MPodule Reload on the frontend and the backend
}
app.use(cors());
app.use(express.urlencoded({extended:false}));
app.use(express.json());
//Hot reload!
//ALL server routes are in this module!
app.use((req, res, next) => {
require("./routers/routers")(req, res, next);
});
export default app;
routers.js
import renderPage from "./renderpage/renderPage.js";
import { serverRoutes, reactRouterRoutes, getReactRouterRoutesString } from "./routes.js";
import express from "express";
import routerLoginDB from "./request/routerLoginDB.js";
import routerSignupDB from "./request/routerSignupDB.js";
const router = express.Router();
// Put all your server routes in here
// When the user connect to the root of the server we send the page
router.get(serverRoutes.root, renderPage);
// When the user send a get request by the /click route a console.log and a respone is send.
router.get(serverRoutes.click, (req, res)=>{
res.status(200).send("Click");
});
// when this user want to login into his account, we ask for the routerLoginDB to handle it
router.post(serverRoutes.login,routerLoginDB);
// when this user want to signup into his account, we ask for the routerSignupDB to handle it
router.post(serverRoutes.signup, routerSignupDB);
// For all the routes that only react-router need to use, if we refresh on a nested route of the react-router from the client side then we redirect it to the root route "/"
router.get(reactRouterRoutes,(req,res) => {
res.redirect("/");
});
router.get("*", (req,res) =>{
res.status(404).send('page not found');
}); //For all other type of request excluding the one specified here, we send back a 404 page;
module.exports = router;
app.test.js
import request from '../utils/test-node-utils.js'
describe("Test the /click path", () => {
test("It should response the GET method", () => {
return request
.get("/click")
.expect(200);
});
});
and finally test-node-utils.js
import supertest from "supertest";
import app from "../serverside/app.js";
const request = supertest(app);
export default request;
Don't believe what the error say because I think it is more deep than that.
I have tried to increased the jest timeout value but it keep being stuck and reach the timeout limit.
I have done exactly like the tutorial say without using my project structure and it worked but when I try to implement the tutorial in my backend structure, it don't work with supertest.
I think it is related to my files or backend structure that make it don't work with the test.
Thanks in advance for your help
I've recently debugged a similar issue where my Jest tests would run successfully (or not) in my dev. environment but when I would try and package the app as a Docker image all my tests would time out.
It turned out that by commenting out the line which setup CORS, which for me I only turned on in production builds (should have been a clue), the tests started to run again when building the image.
...
const NODE_ENV = process.env.NODE_ENV;
const app = express();
NODE_ENV.toUpperCase() === 'PRODUCTION' && app.use(cors);
...
I mentioned this as I can see from your snippet above that you are also using the cors middleware and that, in your case, it's set all the time.
Perhaps not your issue, and you may want CORS in your tests for some reason, but try commenting it out and see if your tests run.

Graphql call : Network error - failed to fetch for long tasks only

I am firing a query using on the react front-end which in turn triggers a long task on the server. The task is triggered again on the server (no fire visible on the network tab on front-end) and after some time the front-end throws a network error - failed to fetch.
All short task queries/mutations work just fine. I tried useLazyQuery, refetch from useQuery and creating a custom useLazyQuery as well. Same result for all as well as from the graphql playground.
I'm using apollo-server-express and the timeout set is 1200s. Following is my server.js
import express from "express";
import { ApolloServer } from "apollo-server-express";
import mongoose from "mongoose";
import fs from "fs";
import { resolvers } from "./graphql/resolvers/resolvers.js";
import { typeDefs } from "./GraphQL/typeDefs";
const cors = require("cors");
const startServer = async () => {
console.log("[Server] Creating Express Server");
var dir = "src/graphql/resolvers/audiofiles";
if (!fs.existsSync(dir)) {
console.log("[Server] Creating directory ", dir);
fs.mkdirSync(dir);
}
const app = express();
app.use(cors());
console.log("[Server] Creating ApolloServer");
const apollo = new ApolloServer({
typeDefs,
resolvers
});
apollo.applyMiddleware({ app });
console.log("[Server] Connecting to MongoDB");
await mongoose
.connect("mongodb://localhost:27017/database",
{
useNewUrlParser: true
}
)
.then(
app.listen(
{
port: 4000,
origin: {
requestTimeout: "1200s"
}
},
() =>
console.log(`Server ready at http://****:4000${apollo.graphqlPath}`)
)
)
.catch((err) => console.log(err));
};
startServer();
This is not a cors issue as it works for other queries but and for long tasks, fails after some time.
My front-end apollo-client has a fetch policy "no-cache" and I don't see a second call in the network tab. I think there would've been a third one if the front-end didn't get a network error. I'm sure this has got something to do with the front-end.
I want to understand whether this is a front-end problem or a server side problem. What is a good way to diagnose the problem?

What is the correct implementation for multiple simultaneous servers when running NestJS with #nestjs/platform-fastify?

I am trying to run two simultaneous servers on a NestJS app running with the #nestjs/platform-fastify integration (specifically I am trying to run one HTTP and one HTTPS server of the same app).
NestJS documentation includes multiple-servers for express-integration but does not mention how to do this with fastify (https://docs.nestjs.com/faq/multiple-servers). Does anyone know the correct implementation to be able to do this?
Edit 11/20/19
So it appears my original answer was incorrect. I for the most part was just going off the docs and trying to piece something together, forgetting that Fastify's server instance is different than that of Express's and because of that is incompatible with the built in http(s) module(s). If you don't mind having to let NestJS set up your dependencies again, you can always do something like this:
import { NestFactory } from '#nestjs/core';
import { FastifyAdapter } from '#nestjs/platform-fastify';
import { AppModule } from './app.module';
import { readFileSync } from 'fs';
import { join } from 'path';
import fastify = require('fastify');
const httpsOptions = {
key: readFileSync(join(__dirname, '..', 'secrets', 'ssl.key')),
cert: readFileSync(join(__dirname, '..', 'secrets', 'ssl.crt')),
};
async function bootstrap() {
const fastifyHttps = fastify({https: httpsOptions});
const httpApp = await NestFactory.create(AppModule , new FastifyAdapter() );
const httpsApp = await NestFactory.create(AppModule, new FastifyAdapter(fastifyHttps));
await httpApp.listen(3000);
console.log('http running on port 3000');
await httpsApp.listen(3333);
console.log('https running on port 3333');
}
bootstrap();
And this time, I tested it to make sure it works.
This only works with Express
Nest provides methods to get the server instance back after creating the application with app.getHttpServer() so that you can get your server veriable no matter what. Due to this you can set up your application as you normally would with your fastify adapter and the after running await app.init() you can get the server instance. From there you can create the server and set the listening port with the http and https built in packages like the docs are showing with express.
const httpsOptions = {
key: fs.readFileSync('./secrets/private-key.pem'),
cert: fs.readFileSync('./secrets/public-certificate.pem'),
};
const app = await NestFactory.create(
ApplicationModule
);
await app.init();
const server = app.getHttpServer();
http.createServer(server).listen(3000);
https.createServer(httpsOptions, server).listen(443);
And that should do the trick

How to resolve Chrome cookies resulting in nodejs ERR_CONNECTION_RESET when I have to store cookies olve

Currently making a MERN app, so I am using Node. I have the user's information stored in localstorage so when they refresh the page they are still logged in. Problem is, if I were to make any api calls to my backend it will result in a net::ERR_CONNECTION_RESET. And it is also weird that the error message will look something like: GET http://localhost:3000/api/roadmap/getAllRoadmapsButThumbnails net::ERR_CONNECTION_RESET, when localhost:5000 is my backend port but it is showing 3000, my client port.
My api calls perfectly fine when the user is logged out & thus there is nothing in the localstorage.
Has anyone encountered similar problems and have any insights?
What I have tried:
Changing proxy from just "http://localhost:5000" to
"proxy": {
"secure": true,
"target": {
"host": "https://localhost",
"port": 5000
}
},
Using full path in axios requests, this resulted in a CORS error, and I didn't proceed to make the CORS thing work because using the full path might cause weird stuff when website is deployed, and fixing the proxy routing seemed like a priority
More of my code
server.js:
const express = require("express");
const mongoose = require("mongoose");
const bodyParser = require("body-parser");
const multer = require("multer");
const path = require("path");
const passport = require("passport");
const users = require("./routes/api/users");
const roadmap = require("./routes/api/roadmap");
const app = express();
// Bodyparser middleware
app.use(
bodyParser.urlencoded({
extended: false
})
);
app.use(bodyParser.json());
// DB Config
const db = require("./config/keys").mongoURI;
// Connect to MongoDB
mongoose
.connect(
db,
{ useNewUrlParser: true }
)
.then(() => console.log("MongoDB successfully connected"))
.catch(err => console.log(err));
// Passport middleware
app.use(passport.initialize());
// Passport config
require("./config/passport")(passport);
// Routes
app.use("/api/roadmap", roadmap);
app.use("/api/users", users);
const port = process.env.PORT || 5000; // process.env.port is Heroku's port if you choose to deploy the app there
Everything is http right now, I didn't get errors like this in my last website which also utilized jwt stored in localstorage, that was about 5 months ago.
EDIT
new things I tried:
Configuring the proxy manually
Starting chrome without certificate
Solution for my case: Use fetch instead of axios. I didn't post any client code but I used to fetch the data with:
axios.get("/api/roadmap/getAllRoadmapsThumbnails")
now, simply do:
fetch("/api/roadmap/getAllRoadmapsButThumbnails", { method: "GET" })
Why does it work. I have no idea, maybe if I put a few headers in axios axios would function properly as well. Still there is something weird, in console you can see the fetch log (you might have to check the "Log XMLHttpRequests" checkbox in console), and it shows:
Fetch finished loading: GET "http://localhost:3000/api/roadmap/getAllRoadmapsButThumbnails".
While my backend is 5000 and so it should be localhost:5000/api/...
If you are new to this problem, and getting problems ranging from ERR_CONNECTION_RESET to FAILED_TO_LOAD_RESOURCES to other ERRs,
Try these:
try using fetch. And best to define headers like {method: "POST", headers: {"Content-Type" : "application/json"}
turn off chrome extensions (use incognito will automatically disable extensions)
for a CORS error download npm install cors and then do app.use(cors()) in your server.js, according to this: https://github.com/axios/axios/issues/1414
Manually set up proxy & starting chrome ignoring certificate

Cannot GET / error using express

I'm new to nodeJS, I'm trying to follow this tutorial.
My code:
// server/index.js
import express from 'express';
import { graphqlExpress, graphiqlExpress } from 'graphql-server-express';
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
import bodyParser from 'body-parser';
import { createServer } from 'http';
import { Schema } from './data/schema';
import { Mocks } from './data/mocks';
const GRAPHQL_PORT = 8000;
const app = express();
const executableSchema = makeExecutableSchema({
typeDefs: Schema,
});
addMockFunctionsToSchema({
schema: executableSchema,
mocks: Mocks,
preserveResolvers: true,
});
// `context` must be an object and can't be undefined when using connectors
app.use('/graphql', bodyParser.json(), graphqlExpress({
schema: executableSchema,
context: {}, // at least(!) an empty object
}));
app.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
}));
const graphQLServer = createServer(app);
graphQLServer.listen(GRAPHQL_PORT, () => console.log(`GraphQL Server is now running on http://localhost:${GRAPHQL_PORT}/graphql`));
reports an error Cannot GET /
I've read that maybe the createServer function is deprecated, but I'm not sure how to fix it.
When you use express, you have to be explicitly define the routes used by your application. For example, if you define a route with app.get('/hello', handler) then any GET requests to localhost/hello will get routed to that handler. It can then execute whatever logic and return a response, be that a JSON object, a webpage, etc.
Express will only handle the routes you've defined in this way. So if you've only defined a route for requests that GET /hello, it will not know how to GET /foo, or GET your root path /. If you wanted to implement a way to POST or PUT to /hello, that would need to be a different route as well.
You can use app.use in a similar way to implement middleware in your application. While middleware typically takes your request, manipulates it and pass it on, it can also be used to break up your routing logic.
In the case of GraphQL, requests are typically made using the POST method, but the specification does allow for both POST and GET requests. To do this, we would have to define handlers for both app.get('/graphql') and app.post('/graphql'). The graphqlExpress middleware you're importing and using conveniently does that for you.
So with your set up, you've created some routes that allow you to POST to and GET from localhost:8000/graphql. You've also enabled GraphiQL on localhost:8000/graphiql. If you don't see any errors in the console when you start your server, you should be able to navigate to the GraphiQL page at localhost:8000/graphiql and play around with your schema.
But these are the only routes you've set up on your server. If you attempt to navigate anywhere else, like the root at localhost:8000/, express won't know how to handle that request and you will see the error you reported.
Use app.listen(port, function(){console.log('server started')}); instead of createServer(app).

Resources