How to setup server-side full response caching in Apollo Server - javascript

I am trying to setup a cache according to this guide for one expensive query that has a result that changes only once a day. The query takes 7-8 seconds and most it is after the DB query, because the response returned from the resolver must be heavily processed.
I am using apollo-server-express library and the change pluging is apollo-server-plugin-response-cache.
This is what I have done:
server.js
const { ApolloServer } = require('apollo-server-express')
const responseCachePlugin = require('apollo-server-plugin-response-cache')
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
// ...
},
plugins: [responseCachePlugin()]
resolvers.js
personDetails: async (root, args, ctx, info) => {
info.cacheControl.setCacheHint({ maxAge: 600 })
const persons = await Person.find({}).populate('details')
// processing the data
return processedData
}
I expect the resolver to run once and then after that the response should be returned from the cache almost instantly. This doesn't work. I am doing something wrong or I haven't understood how this should work.
I tried to put cache hints also in the schema, but didn't get any better results.

It should work. Here is a working example:
server.ts:
import { ApolloServer, gql } from 'apollo-server-express';
import express from 'express';
import responseCachePlugin from 'apollo-server-plugin-response-cache';
const typeDefs = gql`
type Query {
personDetails: String
}
`;
const resolvers = {
Query: {
personDetails: async (root, args, ctx, info) => {
console.log(`[${new Date().toLocaleTimeString()}] Query.personDetails`);
info.cacheControl.setCacheHint({ maxAge: 10 });
return 'This is person details';
},
},
};
const app = express();
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
plugins: [responseCachePlugin()],
});
apolloServer.applyMiddleware({ app });
const server = app.listen({ port: 4000 }, () => {
console.log(`The server is running in http://localhost:4000${apolloServer.graphqlPath}`);
});
The request logs:
The server is running in http://localhost:4000/graphql
[1:51:27 PM] Query.personDetails
[1:51:52 PM] Query.personDetails
The response header:
cache-control: max-age=10, public
The first graphql request send at [1:51:27 PM]. In the next 10 seconds, all requests sent will hit the cache(defaults to an in-memory LRU cache), which means the graphql resolver personDetails will not execute. The graphql response will be read from the cache and sent to client-side.
After 10 seconds, I send another graphql request at [1:51:52 PM]. The cache is expired. So this request will not hit the in-memory cache. The graphql resolver will execute and generate a new value.
source code: https://github.com/mrdulin/apollo-graphql-tutorial/tree/master/src/stackoverflow/57243105

I understand that this is an old question but it might help someone solve server side caching issue in Apollo.
So as per my understanding the apollo-server-plugin-response-cache plugin only works if the response from the underlying REST API have control-cache header. In case there is no cache-control header in your REST API it can be overriden in restDataSouce GET calls as below.
return this.get(`uri_path`,"", {cacheOptions: { ttl: 1}});
After that continue using info.cacheControl at the resolvers as suggested in the example from the question snippets. maxAge there will override the time mentioned in the cacheOptions.

Related

Vue.js proxy/target doesnt work when in a different route then the "{baseurl}/" route

So i have a vue project where i have a js file that requests data from an API and that goes fine. I set up a proxy in my vue.config.js file so that every request starting with /api will be redirected to the baseurl of the API, like this:
const { defineConfig } = require('#vue/cli-service');
const path = require('path');
module.exports = defineConfig({
transpileDependencies: true
})
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://dev.liveflow.nl'
},
}
}
}
This works fine for when im in my websites base url and do an axios.get request like axios.get(api/batches/). But when im in my websites 'batches' route, and i do axios.get(api/batches/:id/, the proxy doesnt work. Instead of requesting to (target from the vue.config.js file above) target/api/batches/:id, its requesting to my own websites url/batches/api/batches/:id. So the problem is, that because im in the /batches route, it will leave the /batches in the url, so the proxy i configured doesnt work (i think)
So the code i use in my js file to do all http requests looks like this:
import axios from "axios";
const url = "api/batches/";
class BatchService {
// Get Batches
static async getBatches() {
try {
const res = await axios.get(url);
const data = res.data;
return data;
} catch(err) {
return err;
}
}
// Get Batch by ID
static async getBatchById(id) {
try {
const res = await axios.get(`${url}${id}`);
const data = res.data;
return data;
} catch(err) {
return err;
}
}
export default BatchService;
Because of the proxy, i only have to do "api/batches/" as url, because the proxy recognizes the /api and redirects it to the target.
These to methods work fine when im in my websites baseurl, but when im in a different route, like baseurl/batches, the requests does baseurl/batches/api/batches/:id, while i want it to be target/api/batches/:id.
The error i get is simple: GET http://localhost:8080/batches/api/batches/63722a8ab1194203d268b220 404 (Not Found). It is just requesting the wrong url and using the websites baseurl instead of the target baseurl.
BTW: I need to do the proxy setup. When i just do const url = "target/api/batches/" the API server will deny my request
My english is not that good so i hope im clear enough.

React 404 Axios Cannot POST

New to Axios and Node as a backend- have inherited some code in React for a login page and am trying to figure out why I can't make a POST to the backend.
This is my .env file:
REACT_APP_BACKEND_URL=http://localhost:3000/admin
And there is a file called API.js
import axios from "axios";
// Set config defaults when creating the instance
export const CompanyAPI = () => {
let api = axios.create({
baseURL: process.env.REACT_APP_BACKEND_URL,
timeout: 10000,
headers: {
"Content-Type": "application/json",
},
});
return api;
};
And then there is a LoginPage.js:
export const LoginPage = () => {
const API = CompanyAPI();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const getAuth = (e) => {
e.preventDefault();
API.post("/auth/login", {
email: email,
password: password,
})
.then(async (response) => {
if (response.status === 200) {
await localStorage.setItem("jwt_key_admin", response.data.token);
setTokenKey(response.data.token);
setIsAuth(true);
navigate("/");
}
})
.catch((error) =>
alert(
"Login Failed"
)
);
};
My question is, is there an example on how I could use express to handle the /auth/login endpoint and complement the existing API.js file?
Basically what I see from this code is:
An axios instance was created and the baseURL was set to being http://localhost:3000/admin
From first glance I can tell that all Api calls that you make a resulting to 404 reason being React locally will always run on port 3000 unless that port is in use.
So now your axios baseURL being to set to port 3000 definitely axios should return a 404 because you surely do not have the endpoint that you are trying to hit
Solution:
Here you are to change the baseURL's port number to the port where Nodejs server is listening on
Then once that is said and done then make sure that even the endpoints that you are trying to hit do exist then your axios calls should work now
Advise:
If the Axios instance created is confusing drop it for a bit and import raw axios not the instance then use axios in it's basic form then once you have that working you surely will have established the correct port number and everything then you can edit the axios instance created with baseURL you have established.
Add
"proxy":"http://localhost:3000/admin"
in your package.json file and restart your React App.

How can I solve the problem with forwarding messages?

I'm created a chat-bot using 'botact' library, but
when I'm trying to verificate my bot on vk-community API working page I receive an error in 'Windows PowerShell' (Here I started the server for bot):
TypeError: Cannot read property 'fwd_messages' of undefined
at Botact.getLastMessage (C:\Users\whoami\Desktop\Bot-test\node_modules\botact\lib\utils\getLastMessage.js:2:11)
at Botact.module.exports (C:\Users\whoami\Desktop\Bot-test\node_modules\botact\lib\methods\listen.js:29:28).
The file 'getLastMessage.js' contains this code:
const getLastMessage = (msg) => {
if (msg.fwd_messages && msg.fwd_messages.length) {
return getLastMessage(msg.fwd_messages[0])
}
return msg
}
module.exports = getLastMessage
So I don't know much about botact but according to the code when you are hitting the / route, you need to pass a body containing an object property.
Now as this is bot framework for vk bots maybe it automatically sends the request body. You can make sure by logging the request body.
server.post('/', async (req,res)=>{
console.dir(req.body);
await bot.listen(req, res);
});
/lib/methods/listen.js:
const { type, secret, object, group_id } = framework === 'koa'
? args[0].request.body
: args[0].body
...
...
...
const { events, middlewares } = actions
const forwarded = this.getLastMessage(object)
Now, when you do bot.listen express passes req as first argument. and { type, secret, object, group_id } these fields get distructured from the req.body.
And then object is getting passed to the getLastMessage function.
So for the request body in minimum you would need
{ "object": {} }
Here is the 200 OK output that I got after added that to the request body from Postman
POC Code:
const express = require("express");
const bodyParser = require("body-parser");
const { Botact } = require("botact");
const server = express();
const bot = new Botact({
token: "token_for_my_group_i_just_hided_it",
confirmation: "code_for_my_group_i_just_hided_it"
});
server.use(bodyParser.json());
server.post("/",bot.listen);
server.listen(8080);

Access mongodb+nodjs from javascript?

I'm running a server with nodejs+mongodb:
let MongoClient = require('mongodb').MongoClient.connect("mongodb://localhost:27017/mydb", { useNewUrlParser: true } ),
(async () =>{
let client;
try {
client = await MongoClient;
...
I'm creating some data-visualizations and I need a simple way to access my backend data from javascript, is this possible? Ideally I would like full access.
You have to build a bridge, e.g. using a REST API:
// server.js
// npm install express, body-parser, mongodb
const app = require("express")();
const bodyParser = require("body-parser");
const db = require("mongodb").MongoClient.connect(/*...*/);
app.use(bodyParser.json());
app.post("/findOne", async (req, res) => {
try {
const connection = await db;
const result = await connection.findOne(req.body);
if(!result) throw new Error("Not found!");
res.status(200).json(result);
} catch(error) {
res.status(500).json(error);
}
});
// ... all those other methods ...
app.listen(80);
That way you can easily connect to it on the client:
// client.js
function findOne(query) {
const result = await fetch("/findOne/", {
method: "POST",
body: JSON.stringify(query),
headers:{
'Content-Type': 'application/json'
}
});
if(!result.ok) throw await result.json();
return await result.json();
}
Note: I hope you are aware that you also allow some strangers to play with your database if you do not validate the requests properly / add authentication.
For security purposes you should never do this, but hypothetically you could make an AJAX endpoint or WebSockets server on the node application that passes the input straight to mongoDB and takes the output straight back to the client.
It would be a much better practice to write a simple API using AJAX requests or WS to prevent the user from compromising your database.

ApolloServer 2.0 context and public/private parts of the GraphQL API

I'm not a pro in any way but I've started and ApolloServer/Express backend to host a site where I will have public parts and private parts for members. I am generating at JWT token in the login mutation and get's it delivered to the client.
With context I want to check if the token is set or not and based on this handle what GraphQL queries are allowed. My Express/Apollo server looks like this at the moment.
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
// get the user token from the headers
const token = (await req.headers.authorization) || '';
if (token) {
member = await getMember(token);
}
}
});
The problem is that this locks down the GraphQL API from any queries and I want/need to reach signup/login mutations for example.
Could anyone spread some light on this to help me understand what I need to do to get this to work.
the way i am doing it is that i will construct auth middleware even before graphql server as sometimes is needed to have information about authenticated user also in other middlewares not just GraphQL schema. Will add some codes, that you need to get it done
const auth = (req, res, next) => {
if (typeof req.headers.authorization !== 'string') {
return next();
}
const header = req.headers.authorization;
const token = header.replace('Bearer ', '');
try {
const jwtData = jwt.verify(token, JWT_SECRET);
if (jwtData && jwtData.user) {
req.user = jwtData.user;
} else {
console.log('Token was not authorized');
}
} catch (err) {
console.log('Invalid token');
}
return next();
};
This way i am injecting the user into each request if the right token is set. Then in apollo server 2 you can do it as follows.
const initGraphQLserver = () => {
const graphQLConfig = {
context: ({ req, res }) => ({
user: req.user,
}),
rootValue: {},
schema,
};
const apolloServer = new ApolloServer(graphQLConfig);
return apolloServer;
};
This function will initiate ApolloServer and you will apply this middleware in the right place. We need to have auth middleware before applyin apollo server 2
app.use(auth);
initGraphQLserver().applyMiddleware({ app });
assuming the app is
const app = express();
Now you will have user from user jwtData injected into context for each resolver as "user", or in req.user in other middlewares and you can use it for example like this. This is me query for saying which user is authenticated or not
me: {
type: User,
resolve: async (source, args, ctx) => {
const id = get(ctx, 'user.id');
if (!id) return null;
const oneUser = await getOneUser({}, { id, isActive: true });
return oneUser;
},
},
I hope that everything make sense even with fractionized code. Feel free to ask any more questions. There is definitely more complex auth, but this basic example is usually enough for simple app.
Best David

Categories

Resources