I implemented a RESTDataSource, but when I try queries in the playground, the same queries are never cached, and always get fetched from the REST endpoint.
The tutorials say a basic caching system shoud work without additional configuration when using RESTDataSource, but obviously I am missing something. What could make the caching fail?
My ApolloServer creation:
/* ... */
const server = new ApolloServer({
typeDefs,
resolvers,
dataSources: () => ({
comicVineAPI: new ComicVineAPI(),
firebaseAPI: new FirebaseAPI(getFirestore())
})
});
/* ... */
My call to the REST endpoint (in my API class extending RESTDataSource):
/* ... */
async request(path, params = {}) {
params.format = 'json';
params.limit = 50;
params.api_key = process.env.COMIC_VINE_API_KEY;
const response = await this.get(`${path}?${this.buildQuery(params)}`);
return response.status_code === 1 && response;
}
/* ... */
Thank you for your help!
The reason why the REST API response is not cached probably is The upstream web service does not have a cache header: cache-control. You can read this article Layering GraphQL on top of REST for more info.
With data sources, the HTTP requests are automatically cached based on the caching headers returned in the response from the REST API
After known this, I made an example:
rest-api-server.ts:
import express from 'express';
import faker from 'faker';
function createServer() {
const app = express();
const port = 3000;
app.get('/user', (req, res) => {
res.sendFile('index.html', { root: __dirname });
});
app.get('/api/user', (req, res) => {
console.log(`[${new Date().toLocaleTimeString()}] request user`);
const user = { name: faker.name.findName(), email: faker.internet.email() };
res.set('Cache-Control', 'public, max-age=30').json(user);
});
app.get('/api/project', (req, res) => {
console.log(`[${new Date().toLocaleTimeString()}] request project`);
const project = { name: faker.commerce.productName() };
res.json(project);
});
return app.listen(port, () => {
console.log(`HTTP server is listening on http://localhost:${port}`);
});
}
if (require.main === module) {
createServer();
}
export { createServer };
graphql-server.ts:
import { ApolloServer, gql } from 'apollo-server-express';
import { RESTDataSource } from 'apollo-datasource-rest';
import express from 'express';
import { RedisCache } from 'apollo-server-cache-redis';
import { Request } from 'apollo-server-env';
class MyAPI extends RESTDataSource {
constructor() {
super();
this.baseURL = 'http://localhost:3000/api/';
}
public async getUser() {
return this.get('user');
}
public async getProject() {
return this.get('project');
}
protected cacheKeyFor(request: Request) {
return request.url;
}
}
const typeDefs = gql`
type User {
name: String
email: String
}
type Project {
name: String
}
type Query {
user: User
project: Project
}
`;
const resolvers = {
Query: {
user: async (_, __, { dataSources: ds }: IAppContext) => {
return ds.myAPI.getUser();
},
project: async (_, __, { dataSources: ds }: IAppContext) => {
return ds.myAPI.getProject();
},
},
};
const dataSources = () => ({
myAPI: new MyAPI(),
});
interface IAppContext {
dataSources: ReturnType<typeof dataSources>;
}
const app = express();
const port = 3001;
const graphqlPath = '/graphql';
const server = new ApolloServer({
typeDefs,
resolvers,
dataSources,
cache: new RedisCache({
port: 6379,
host: '127.0.0.1',
family: 4,
db: 0,
}),
});
server.applyMiddleware({ app, path: graphqlPath });
if (require.main === module) {
app.listen(port, () => {
console.log(`Apollo server is listening on http://localhost:${port}${graphqlPath}`);
});
}
The logs for rest-api-server.ts:
HTTP server is listening on http://localhost:3000
[2:21:11 PM] request project
[2:21:14 PM] request project
[2:21:25 PM] request user
For /api/user, I set the cache-control response header for it. So, when you send a graphql request to graphql-server.ts, the MyAPI data source will send
the request to REST API. After getting the response from REST API, it detects the cache-control response header, so the apollo Datasource will cache the response.
The following request to graphql server will hit the cache before the cache expires.
Check the cache in the Redis instance after sending a graphql request:
root@d78b7c9e6ac2:/data# redis-cli
127.0.0.1:6379> keys *
1) "httpcache:http://localhost:3000/api/user"
127.0.0.1:6379> ttl httpcache:http://localhost:3000/api/user
(integer) -2
127.0.0.1:6379> keys *
(empty list or set)
For /api/project, due to missing cache header, the apollo datasource will not cache the response. So, every time you send a graphql request, it will call the REST API.
Check the cache in the Redis instance after sending a graphql request:
127.0.0.1:6379> keys *
(empty list or set)
P.S. If your REST API behind an Nginx server, you should enable cache control on the Nginx server.
source code: https://github.com/mrdulin/apollo-graphql-tutorial/tree/master/src/rest-api-caching
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With