Browse Source

completed graphql-express-mysql implementation and passed tests (#4040)

* completed graphql-express-mysql implementation and passed tests

* moved everything over to the express folder, removed superfluous line from .travis.yml

* added uniformity by setting the var declarations as let declarations

* refactored code to use less functions

* removed unused variable
jenriquez-techempower 7 years ago
parent
commit
987944341f

+ 23 - 0
frameworks/JavaScript/express/benchmark_config.json

@@ -66,6 +66,29 @@
       "display_name": "express",
       "notes": "",
       "versus": "nodejs"
+    },
+    "graphql-mysql": {
+      "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "framework": "graphql-express-mysql",
+      "language": "JavaScript",
+      "flavor": "NodeJS",
+      "orm": "Full",
+      "platform": "NodeJS",
+      "webserver": "None",
+      "os": "Linux",
+      "database": "MySQL",
+      "database_os": "Linux",
+      "display_name": "express",
+      "notes": "",
+      "versus": "None"
     }
   }]
 }

+ 9 - 0
frameworks/JavaScript/express/express-graphql-mysql.dockerfile

@@ -0,0 +1,9 @@
+FROM node:10.3.0
+
+COPY ./ ./
+
+RUN npm install
+
+ENV NODE_ENV production
+
+CMD ["node", "graphql-mysql-app.js"]

+ 265 - 0
frameworks/JavaScript/express/graphql-mysql-app.js

@@ -0,0 +1,265 @@
+const express = require('express');
+const app = express();
+const graphqlHTTP = require('express-graphql');
+const escape = require('escape-html');
+const dateFormat = require('dateformat');
+
+const bodyParser = require('body-parser');
+const port = 8080;
+const { makeExecutableSchema } = require('graphql-tools');
+
+const typeDefs = require('./schema');
+const resolvers = require('./resolver');
+
+const schema = makeExecutableSchema({
+    typeDefs,
+    resolvers
+});
+
+app.use(bodyParser.urlencoded({ extended:false }));
+app.use(bodyParser.json());
+
+// Routes
+
+app.get('/json', async(req, res) => {
+    
+    const graphql = await graphqlHTTP((req, res, graphQLParams) => {
+        
+        graphQLParams.query = "{ helloWorld }";
+        graphQLParams.operationName = null;
+        graphQLParams.variables = {};
+
+        return graphqlOpts()
+    });
+
+    res.real_end = res.end;
+
+    res.end = (data) => {
+        
+        let toRet;
+        const json = JSON.parse(data.toString('utf8'));
+
+        if(json.data.helloWorld) {
+            toRet = json.data.helloWorld;
+        } else {
+            toRet = { helloWorld: null };
+        }
+        
+        setResponseHeaders(res, toRet.length);
+
+        res.real_end(toRet);
+    }
+
+    await graphql(req, res);
+});
+
+app.get('/db', async (req, res) => {
+    
+    const graphql = await graphqlHTTP((req, res, graphQLParams) => {
+    
+        graphQLParams.query = "{ singleDatabaseQuery { id, randomNumber }}";
+        graphQLParams.variables = {};
+        graphQLParams.operationName = null;
+    
+        return graphqlOpts();
+    });
+
+    formatResData(res, "singleDatabaseQuery");
+
+    await graphql(req, res);
+});
+
+app.get('/queries', async (req, res) => {
+    
+    const graphql = await graphqlHTTP((req, res, graphQLParams) => {
+        
+        let totalNumOfQueries = ensureQueryIsAnInt(req.query.queries);
+
+        graphQLParams.query = `{ multipleDatabaseQueries(total: ${totalNumOfQueries}) { id, randomNumber }}`
+        graphQLParams.variables = {};
+        graphQLParams.operationName = null;
+
+        return graphqlOpts();
+    });
+
+    formatResData(res, "multipleDatabaseQueries");
+
+    await graphql(req, res);
+});
+
+app.get('/fortunes', async (req, res) => {
+
+    const graphql = await graphqlHTTP((req, res, graphQLParams) => {
+
+        graphQLParams.query = "{ getAllFortunes { id, message } }"
+        graphQLParams.operationName = null;
+        graphQLParams.variables = {};
+
+        return graphqlOpts();
+    });
+
+    retrieveAndFormatAllFortunes(res);
+    
+    await graphql(req, res);
+});
+
+app.get('/updates', async (req, res) => {
+
+    const totalNumOfQueries = ensureQueryIsAnInt(req.query.queries);
+
+    const graphql = await graphqlHTTP((req, res, graphQLParams) => {
+
+        graphQLParams.query = `{ getRandomAndUpdate(total: ${totalNumOfQueries}) { id, randomNumber } }`
+        graphQLParams.operationName = null;
+        graphQLParams.variables = {};
+
+        return graphqlOpts();
+    });
+
+    formatResData(res, 'getRandomAndUpdate');
+
+    await graphql(req, res);
+});
+
+app.get('/plaintext', (req, res) => {
+
+    const responseTxt = "Hello, World!";
+
+    res.setHeader('Content-Length', responseTxt.length);
+    res.setHeader('Server', 'Express-GraphQL-MySQL');
+    res.contentType('text/plain');
+    res.status(200);
+
+    res.send(responseTxt);
+});
+
+// Helper Functions
+
+const ensureQueryIsAnInt = (queryString) => {
+
+    if(queryString === undefined) return 1;
+
+    const possibleInt = parseInt(queryString);
+    if(!possibleInt) return 1;
+
+    return possibleInt;
+};
+
+const graphqlOpts = (params) => {
+    return {
+        schema,
+        graphiql: false,
+        context: params || {}
+    }
+};
+
+const retrieveAndFormatAllFortunes = res => {
+
+    res.real_end = res.end;
+
+    res.end = async (data) => {
+        let toRet;
+        const json = JSON.parse(data.toString('utf8'));
+
+        if(json.data.getAllFortunes) {
+            toRet = json.data.getAllFortunes;
+        } else {
+            toRet = [];
+        }
+
+        const newFortune = { "id": 0, "message": "Additional fortune added at request time." };
+        toRet.push(newFortune);
+        toRet.sort((a, b) => (a.message < b.message) ? -1 : 1);
+
+        const htmlToRet = await spoofHTML(toRet);
+
+        res.contentType('html');
+        res.setHeader('Server', 'GraphQL-MySQL');
+        res.setHeader('Content-Length', htmlToRet.length + 32);
+        res.status(200);
+
+        res.real_end(htmlToRet);
+    }
+};
+
+const spoofHTML = arr => {
+
+    return new Promise((resolve, reject) => {
+
+        let count = 0;
+
+        let htmlToRet = `<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>`;
+
+        for (let fortune of arr) {
+
+            htmlToRet += `<tr><td>${escape(fortune.id)}</td><td>${escape(fortune.message)}</td></tr>`;
+            count++;
+        }
+
+        htmlToRet += '</table></body></html>';
+    
+        if(count == arr.length)resolve(htmlToRet);
+
+    });
+};
+
+const formatResData = (res, queryName) => {
+
+    res.real_end = res.end;
+    
+    res.end = (data) => {
+
+        const json = JSON.parse(data.toString('utf8'));
+        
+        let toRet = formatJson(queryName, json);
+
+        const jsonToRet = JSON.stringify(toRet);
+        
+        setResponseHeaders(res, jsonToRet.length);
+
+        res.real_end(jsonToRet);
+    };
+};
+
+const formatJson = (queryName, jsonData) => {
+
+    const isQueryReturningAnArray = {
+
+        singleDatabaseQuery: false,
+        multipleDatabaseQueries: true,
+        getRandomAndUpdate: true
+    };
+
+    if (isQueryReturningAnArray[queryName] == false) {
+
+        if(jsonData.data[`${queryName}`]) {
+            return {
+                id: jsonData.data[`${queryName}`].id,
+                randomNumber: jsonData.data[`${queryName}`].randomNumber
+            };
+        } else {
+            return {
+                id: null,
+                randomNumber: null
+            }
+        }
+    } else {
+
+        return jsonData.data[`${queryName}`] || [];
+    }
+};
+
+const setResponseHeaders = (res, jsonLength) => {
+
+    let now = new Date();
+
+    res.status(200);
+    res.contentType('application/json', 'charset=UTF-8');
+    res.setHeader('Date', dateFormat(now, "ddd, dd mmm yyyy hh:MM:ss Z"));
+    res.setHeader('Server', 'GraphQL-Express-MySQL');
+    res.setHeader('Content-Length', jsonLength);
+};
+
+app.listen(port, () => {
+    console.log(`Listening on localhost:${port}`);
+});

+ 8 - 2
frameworks/JavaScript/express/package.json

@@ -7,7 +7,13 @@
     "body-parser": "1.18.2",
     "pug": "2.0.1",
     "sequelize": "3.30.0",
-    "mysql": "2.15.0",
-    "mongoose": "5.0.6"
+    "express-graphql": "^0.6.12",
+    "graphql": "^0.13.2",
+    "graphql-tools": "^3.1.1",
+    "mysql": "^2.16.0",
+    "mysql2": "^1.6.1",
+    "mongoose": "5.0.6",
+    "dateformat": "^3.0.3",
+    "escape-html": "^1.0.3"
   }
 }

+ 135 - 0
frameworks/JavaScript/express/resolver.js

@@ -0,0 +1,135 @@
+const Sequelize = require('sequelize');
+
+// MySQL
+
+const connection = {
+    database: 'hello_world',
+    username: 'benchmarkdbuser',
+    password: 'benchmarkdbpass',
+    host: 'tfb-database',
+    dialect: 'mysql'
+}
+
+const sequelize = new Sequelize(
+    connection.database,
+    connection.username,
+    connection.password, {
+        host: connection.host,
+        dialect: connection.dialect,
+        logging: false
+    },
+);
+
+const World = sequelize.define('world', {
+    id: {
+        autoIncrement: true,
+        type: 'Sequelize.INTEGER',
+        primaryKey: true
+    },
+    randomNumber: {
+        type: 'Sequelize.INTEGER'
+    }
+}, {
+    timestamps: false,
+    freezeTableName: true
+});
+
+const Fortune = sequelize.define('fortune', {
+    id: {
+      type: 'Sequelize.INTEGER',
+      primaryKey: true
+    },
+    message: {
+      type: 'Sequelize.STRING'
+    }
+  }, {
+      timestamps: false,
+      freezeTableName: true
+});
+
+const randomizeNum = () => {
+
+    let randomInt = Math.floor(Math.random() * 10000) + 1
+    return randomInt;
+}
+
+async function arrayOfRandomWorlds(totalWorldToReturn) {
+
+    var totalIterations = sanititizeTotal(totalWorldToReturn);
+    var arr = [];
+
+    return new Promise(async (resolve, reject) => {
+        for(var i = 0; i < totalIterations; i++) {
+            let world = await World.findById(randomizeNum());
+            arr.push(world);
+        }
+        if(arr.length == totalIterations) {
+            resolve(arr);
+        }
+    });
+};
+
+async function updateRandomWorlds(totalToUpdate) {
+
+    const total = sanititizeTotal(totalToUpdate);
+    var arr = [];
+
+    return new Promise(async (resolve, reject) => {
+        for(var i = 0; i < total; i++) {
+
+            const world = await World.findById(randomizeNum());
+            world.updateAttributes({
+                randomNumber: randomizeNum()
+            })
+            arr.push(world);
+        }
+        if(arr.length == total) {
+            resolve(arr);
+        }
+    });
+};
+
+const sanititizeTotal = (total) => {
+
+    var totalIterations;
+
+    if (!total || typeof(total) != 'number') {
+        totalIterations = 1;
+    } else if(total < 501 && total > 0) {
+        totalIterations = total;
+    } else if (total > 500) {
+        totalIterations = 500;
+    } else {
+        totalIterations = 1;
+    }
+    return totalIterations;
+};
+
+const sayHello = () => {
+
+    var helloWorld = new Object;
+    helloWorld.message = "Hello, World!";
+
+    return JSON.stringify(helloWorld);
+};
+
+module.exports = {
+    Query: {
+        helloWorld: () => sayHello(),
+        getAllWorlds: async() => await World.findAll(),
+        singleDatabaseQuery: async() => await World.findById(randomizeNum()),
+        multipleDatabaseQueries: async(parent, args) => await arrayOfRandomWorlds(args.total),
+        getWorldById: async(parent, args) => await World.findById(args.id),
+        getAllFortunes: async() => await Fortune.findAll(),
+        getRandomAndUpdate: async(parent, args) => await updateRandomWorlds(args.total)
+    },
+    Mutation: {
+        createWorld: async(parent, args) => {
+            let randInt = Math.floor(Math.random() * 1000) + 1;
+            return await World.create({ id: null, randomNumber: randInt });
+        },
+        updateWorld: async(parent, args) => {
+            return await World.update({id: args.id, randomNumber: args.randomNumber});
+        }
+    }
+}

+ 23 - 0
frameworks/JavaScript/express/schema.js

@@ -0,0 +1,23 @@
+module.exports = `
+    type Query {
+        getWorldById(id:Int!): World,
+        singleDatabaseQuery: World,
+        multipleDatabaseQueries(total:Int!): [World],
+        getAllWorlds: [World],
+        helloWorld: String,
+        getAllFortunes: [Fortune],
+        getRandomAndUpdate(total:Int!): [World]
+    }
+    type Mutation {
+        createWorld: World!,
+        updateWorld(id:Int! randomNumber:String!): World!
+    }
+    type World {
+        id: Int!,
+        randomNumber: Int!
+    }
+    type Fortune {
+        id: Int!
+        message: String!
+    }
+`;