Skip to content
This repository has been archived by the owner on Dec 17, 2018. It is now read-only.

Commit

Permalink
feat(express-middleware): Add DataLoader middleware
Browse files Browse the repository at this point in the history
Added an express middleware to provide per-request DataLoaders for fetching data from the
HealthGraph API to prevent multiple calls to the same URI in the same request.
  • Loading branch information
Simon Wears committed Mar 19, 2017
1 parent 08defbe commit a817695
Show file tree
Hide file tree
Showing 11 changed files with 81 additions and 55 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
},
"dependencies": {
"cookie-parser": "^1.4.3",
"dataloader": "^1.3.0",
"ejs": "^2.5.6",
"express": "^4.15.2",
"express-graphql": "^0.6.3",
Expand Down
3 changes: 2 additions & 1 deletion res/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
"USERID": "The unique ID for the user"
}
},
"RUNKEEPER_REQUEST": "Request made to Runkeeper API %{endpoint}",
"HEALTHGRAP_LOADER_REQUEST": "Request made to loader for %{uri}",
"HEALTHGRAPH_REQUEST": "Request made to Runkeeper API %{uri}",
"SERVER_START": "Server started on port %{port}"
}
3 changes: 2 additions & 1 deletion src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ const cookieParser = require('cookie-parser');
const express = require('express');
const log = require('./helpers/logging');
const path = require('path');
const HealthGraphLoader = require('./middleware/healthgraph-loader');


/* Create application */
const app = express();
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use(HealthGraphLoader);

/* Server settings */
app.set('view engine', 'ejs');
Expand Down
39 changes: 19 additions & 20 deletions src/data-types/fitness-activities.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const GraphQLList = graphql.GraphQLList;
const GraphQLInt = graphql.GraphQLInt;
const GraphQLBoolean = graphql.GraphQLBoolean;
const CommentType = require('./comment');
const fetch = require('../helpers/fetch');
const resolveItems = require('../helpers/resolve-items');
const i18n = require('../helpers/i18n');

Expand Down Expand Up @@ -99,7 +98,7 @@ const FitnessImage = new GraphQLObjectType({
uri: {
description: i18n.t('GRAPHQL.FITNESS.IMAGE.URI'),
type: GraphQLString
},
}
}
});

Expand All @@ -109,42 +108,42 @@ const FitnessItem = new GraphQLObjectType({
activity: {
description: i18n.t('GRAPHQL.FITNESS.ITEM.ACTIVITY'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.activity);
return req.healthGraphLoader.load(parent.uri).then(d => d.activity);
},
type: GraphQLString
},
average_heart_rate: {
description: i18n.t('GRAPHQL.FITNESS.ITEM.AVERAGE_HEART_RATE'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.average_heart_rate);
return req.healthGraphLoader.load(parent.uri).then(d => d.average_heart_rate);
},
type: GraphQLInt
},
calories: {
description: i18n.t('GRAPHQL.FITNESS.ITEM.CALORIES'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.calories);
return req.healthGraphLoader.load(parent.uri).then(d => d.calories);
},
type: new GraphQLList(FitnessCalories)
},
climb: {
description: i18n.t('GRAPHQL.FITNESS.ITEM.CLIMB'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.climb);
return req.healthGraphLoader.load(parent.uri).then(d => d.climb);
},
type: GraphQLInt
},
comments: {
description: i18n.t('GRAPHQL.FITNESS.ITEM.COMMENTS'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => fetch(d.comments, req)).then(c => c.comments);
return req.healthGraphLoader.load(parent.uri).then(d => req.healthGraphLoader.load(d.comments)).then(c => c.comments);
},
type: new GraphQLList(CommentType)
},
distance: {
description: i18n.t('GRAPHQL.FITNESS.ITEM.DISTANCE'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.distance);
return req.healthGraphLoader.load(parent.uri).then(d => d.distance);
},
type: new GraphQLList(FitnessDistance)
},
Expand All @@ -160,7 +159,7 @@ const FitnessItem = new GraphQLObjectType({

description: i18n.t('GRAPHQL.FITNESS.ITEM.EQUIPMENT'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.equipment);
return req.healthGraphLoader.load(parent.uri).then(d => d.equipment);
},
type: GraphQLString
},
Expand All @@ -171,59 +170,59 @@ const FitnessItem = new GraphQLObjectType({
heart_rate: {
description: i18n.t('GRAPHQL.FITNESS.ITEM.HEART_RATE'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.heart_rate);
return req.healthGraphLoader.load(parent.uri).then(d => d.heart_rate);
},
type: new GraphQLList(FitnessHeartRate)
},
images: {
description: i18n.t('GRAPHQL.FITNESS.ITEM.IMAGES'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.images);
return req.healthGraphLoader.load(parent.uri).then(d => d.images);
},
type: new GraphQLList(FitnessImage)
},
is_live: {

description: i18n.t('GRAPHQL.FITNESS.ITEM.IS_LIVE'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.is_live);
return req.healthGraphLoader.load(parent.uri).then(d => d.is_live);
},
type: GraphQLBoolean
},
notes: {
description: i18n.t('GRAPHQL.FITNESS.ITEM.NOTES'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.notes);
return req.healthGraphLoader.load(parent.uri).then(d => d.notes);
},
type: GraphQLString
},
path: {
description: i18n.t('GRAPHQL.FITNESS.ITEM.PATH'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.path);
return req.healthGraphLoader.load(parent.uri).then(d => d.path);
},
type: new GraphQLList(FitnessPath)
},
secondary_type: {
description: i18n.t('GRAPHQL.FITNESS.ITEM.SECONDARY_TYPE'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.secondary_type);
return req.healthGraphLoader.load(parent.uri).then(d => d.secondary_type);
},
type: GraphQLString
},
share: {
description: i18n.t('GRAPHQL.FITNESS.ITEM.SHARE'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.share);
return req.healthGraphLoader.load(parent.uri).then(d => d.share);
},
type: GraphQLString,
type: GraphQLString
},
share_map: {
description: i18n.t('GRAPHQL.FITNESS.ITEM.SHARE_MAP'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.share_map);
return req.healthGraphLoader.load(parent.uri).then(d => d.share_map);
},
type: GraphQLString,
type: GraphQLString
},
source: {
description: i18n.t('GRAPHQL.FITNESS.ITEM.SOURCE'),
Expand Down Expand Up @@ -252,7 +251,7 @@ const FitnessItem = new GraphQLObjectType({
userID: {
description: i18n.t('GRAPHQL.FITNESS.ITEM.USERID'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.userID);
return req.healthGraphLoader.load(parent.uri).then(d => d.userID);
},
type: GraphQLString
},
Expand Down
17 changes: 8 additions & 9 deletions src/data-types/strength-training-activities.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const GraphQLObjectType = graphql.GraphQLObjectType;
const GraphQLString = graphql.GraphQLString;
const GraphQLList = graphql.GraphQLList;
const GraphQLInt = graphql.GraphQLInt;
const fetch = require('../helpers/fetch');
const CommentType = require('./comment');
const resolveItems = require('../helpers/resolve-items');
const i18n = require('../helpers/i18n');
Expand Down Expand Up @@ -66,30 +65,30 @@ const StrengthTrainingItem = new GraphQLObjectType({
comments: {
description: i18n.t('GRAPHQL.STRENGTH_TRAINING.ITEM.COMMENTS'),
resolve (parent, args, req) {
return fetch(parent.uri, req)
.then(d => fetch(d.comments, req))
return req.healthGraphLoader.load(parent.uri)
.then(d => req.healthGraphLoader.load(d.comments))
.then(c => c.comments);
},
type: new GraphQLList(CommentType)
},
exercises: {
description: i18n.t('GRAPHQL.STRENGTH_TRAINING.ITEM.EXERCISES'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.exercises);
return req.healthGraphLoader.load(parent.uri).then(d => d.exercises);
},
type: new GraphQLList(StrengthTrainingExercise)
},
notes: {
description: i18n.t('GRAPHQL.STRENGTH_TRAINING.ITEM.NOTES'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.notes);
return req.healthGraphLoader.load(parent.uri).then(d => d.notes);
},
type: GraphQLString
},
source: {
description: i18n.t('GRAPHQL.STRENGTH_TRAINING.ITEM.SOURCE'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.source);
return req.healthGraphLoader.load(parent.uri).then(d => d.source);
},
type: GraphQLString
},
Expand All @@ -100,14 +99,14 @@ const StrengthTrainingItem = new GraphQLObjectType({
total_calories: {
description: i18n.t('GRAPHQL.STRENGTH_TRAINING.ITEM.TOTAL_CALORIES'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.total_calories);
return req.healthGraphLoader.load(parent.uri).then(d => d.total_calories);
},
type: GraphQLInt
},
userID: {
description: i18n.t('GRAPHQL.STRENGTH_TRAINING.ITEM.USERID'),
resolve (parent, args, req) {
return fetch(parent.uri, req).then(d => d.userID);
return req.healthGraphLoader.load(parent.uri).then(d => d.userID);
},
type: GraphQLString
}
Expand All @@ -120,7 +119,7 @@ const StrengthTrainingActivities = new GraphQLObjectType({
fields: {
size: {
description: i18n.t('GRAPHQL.STRENGTH_TRAINING.ACTIVITIES.SIZE'),
type: GraphQLInt,
type: GraphQLInt
},
items: {
args: resolveItems.args,
Expand Down
17 changes: 12 additions & 5 deletions src/data-types/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ const graphql = require('graphql');
const GraphQLObjectType = graphql.GraphQLObjectType;
const GraphQLString = graphql.GraphQLString;
const i18n = require('../helpers/i18n');
const fetch = require('../helpers/fetch');
const ProfileType = require('./profile');
const StrengthTrainingActivitiesType = require('./strength-training-activities').StrengthTrainingActivities;
const FitnessActivitiesType = require('./fitness-activities').FitnessActivities;
Expand All @@ -13,28 +12,36 @@ module.exports = new GraphQLObjectType({
fitness_activities: {
description: i18n.t('GRAPHQL.USER.FITNESS_ACTIVITIES'),
resolve (parent, args, req) {
return fetch('/user', req).then(data => fetch(data.fitness_activities, req));
return req.healthGraphLoader
.load('/user')
.then(data => req.healthGraphLoader.load(data.fitness_activities));
},
type: FitnessActivitiesType
},
profile: {
description: i18n.t('GRAPHQL.USER.PROFILE'),
resolve (parent, args, req) {
return fetch('/user', req).then(data => fetch(data.profile, req));
return req.healthGraphLoader
.load('/user')
.then(data => req.healthGraphLoader.load(data.profile));
},
type: ProfileType
},
strength_training_activities: {
description: i18n.t('GRAPHQL.USER.STRENGTH_TRAINING_ACTIVITIES'),
resolve (parent, args, req) {
return fetch('/user', req).then(data => fetch(data.strength_training_activities, req));
return req.healthGraphLoader
.load('/user')
.then(data => req.healthGraphLoader.load(data.strength_training_activities));
},
type: StrengthTrainingActivitiesType
},
userID: {
description: i18n.t('GRAPHQL.USER.USERID'),
resolve (parent, args, req) {
return fetch('/user', req).then(data => data.userID);
return req.healthGraphLoader
.load('/user')
.then(data => data.userID);
},
type: GraphQLString
}
Expand Down
13 changes: 0 additions & 13 deletions src/helpers/fetch.js

This file was deleted.

11 changes: 6 additions & 5 deletions src/helpers/resolve-items.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const fetch = require('./fetch');
const graphql = require('graphql');
const GraphQLInt = graphql.GraphQLInt;
const GraphQLString = graphql.GraphQLString;
Expand Down Expand Up @@ -36,9 +35,9 @@ module.exports = {
let items = parent.items.filter(filterItems);

function getItems (count, next) {
return new Promise(res => {
return new Promise(resolveItems => {

fetch(next, req).then(data => { // eslint-disable-line consistent-return
req.healthGraphLoader.load(next).then(data => {
items = items.concat(data.items).filter(filterItems);

let stopGettingItems = false;
Expand All @@ -50,10 +49,12 @@ module.exports = {
}

if (!stopGettingItems && count > items.length && data.next) {
return getItems(count, data.next);
return getItems(count, data.next).then(resolveItems);
}

}).then(res);
return resolveItems();

});

});
}
Expand Down
27 changes: 27 additions & 0 deletions src/middleware/healthgraph-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const request = require('superagent-promise')(require('superagent'), Promise);
const DataLoader = require('dataloader');
const log = require('./../helpers/logging');

module.exports = function createLoader (req, res, next) {

const HealthGraphLoader = new DataLoader(
urls => Promise.all(urls.map(uri => {

log.info('HEALTHGRAPH_REQUEST', {uri});

return request
.get(`https://api.runkeeper.com${uri}`)
.query({
access_token: req.cookies.auth
})
.end()
.then(data => data.body);

}))
);

req.healthGraphLoader = HealthGraphLoader; // eslint-disable-line no-param-reassign

next();

};
1 change: 0 additions & 1 deletion src/routes/graphql.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const graphqlHTTP = require('express-graphql');
const graphql = require('graphql');
const GraphQLSchema = graphql.GraphQLSchema;
const fetch = require('../helpers/fetch');
const UserType = require('../data-types/user');

module.exports = function (app) {
Expand Down
Loading

0 comments on commit a817695

Please sign in to comment.