{"action":"created","comment":{"url":"https://api.github.com/repos/artsy/artsy.github.io/pulls/comments/88775634","pull_request_review_id":9341892,"id":88775634,"diff_hunk":"@@ -0,0 +1,113 @@\n+---\n+layout: post\n+title: \"Improving Page Speed with GraphQL\"\n+date: 2016-11-02 14:21\n+author: kana\n+categories: [graphql, javascript, positron, joi]\n+---\n+\n+This past year, our team started using a GraphQL orchestration layer that connects various APIs with multiple front-end apps including [iOS](http://artsy.github.io/blog/2016/06/19/graphql-for-mobile). It also handles caching and extracts some business logic out of our client apps. This helped us not only to be more consistent with the way we fetch data across apps, but also improved developer happiness and even bridged teams by having our web and iOS developers work with the same API layer. This got me thinking what other problems GraphQL could solve at Artsy.\n+\n+I work on the Publishing Team at Artsy, and we've recently been focused on page speed as a KPI. With so many ways of measuring speed, it's a daunting task but for this post, I'll focus on the way we handled things on the server-side and how integrating GraphQL on our API improved page speed.\n+\n+<!-- more -->\n+\n+## Prior to GraphQL\n+\n+Before we went ahead with adding a GraphQL-based endpoint in Positron, I spent about a week and refactoring our current codebase. We refactored our router code to make less fetches, made better use of caching, and moved some below-the-fold rendering into the client side. These are important steps towards a faster page, but it doesn't push our technology in a direction that lets us reimagine things.\n+\n+## Speed Issues\n+\n+In our publishing CMS called [Positron](http://github.com/artsy/positron), we serve a separate API, database, and front-end from the rest of the Artsy stack. We're also responsible for the delivery and presentation of the content itself. Over the past year we've focused a lot on how the content appears, and now we've opened up time to focus on speed since in the land of content, every second counts.\n+\n+We split the speed problem in two: `Server Response Time` (server-side) and `Document Interactive Time` (client-side). We currently use Google Analytics to track these [metrics](https://support.google.com/analytics/answer/2383341?hl=en). There are some inaccuracies with GA such as small sampling, geographic noise (countries with different download speeds), and timeouts being counted as zero, but for our initial testing this will suffice for relative measuring.\n+\n+## Enter GraphQL and JoiQL\n+\n+Our very own [Craig Spaeth](https://twitter.com/craigspaeth) recently started working on a project called [Mural](https://github.com/muraljs/mural). It's a framework for React and GraphQL. One library that came out of this project is called [JoiQL](http://github.com/muraljs/joiql). It's a The main purpose of JoiQL is to convert [Joi](http://github.com/hapijs/joi) schemas into GraphQL schemas, and vice versa.\n+\n+We already use Joi in Positron so creating a GraphQL-based endpoint was trivial with JoiQL. Note that while JoiQL is currently used in production, it's still a beta project! Below is an example of how the JoiQL setup works.\n+\n+\n+```javascript\n+const joiql = require('../')\n+const { object, string, number, array, date } = require('joi')\n+const app = require('express')()\n+const graphqlHTTP = require('express-graphql')\n+\n+# Given a Joi schema:\n+const Article = object({\n+  title: string(),\n+  body: string(),\n+  published_at: date()\n+}).meta({\n+  args: { id: number().required() }\n+})\n+\n+# Define api with JoiQL like this:\n+const api = joiql({\n+  query: {\n+    article: Article\n+  }\n+})\n+\n+# Resolve the request using a Koa 2 style middleware pattern:\n+api.use((ctx, next) => {\n+  return new Promise (resolve, reject) ->\n+    # Method that fetches an article based on the query\n+    findArticle(ctx.req.query.article.args), (err, results) ->\n+      ctx.res.article = results.article\n+      next()\n+      resolve()\n+})\n+\n+# Finally, mount our schema to express:\n+app.use('/graphql', graphqlHTTP({\n+  schema: api.schema,\n+  graphiql: true\n+}))\n+app.listen(3000, () => console.log('listening on 3000'))\n+\n+```\n+\n+You can see how simple it becomes to convert apps that already use Joi with JoiQL. It's less JSON blobs to handle and we also get a nice Koa 2 style middleware pattern that lets us hook into a downstream/upstream flow. This could be useful later on when if we were to get really granular with measuring speed and decide to track the speed of a request manually. We could do something like this:","path":"_posts/2016-11-02-improving-page-speed-with-graphql.markdown","position":73,"original_position":73,"commit_id":"dac6fad0fbc770d37dc237a41bc6f0b2d3499b63","original_commit_id":"dac6fad0fbc770d37dc237a41bc6f0b2d3499b63","user":{"login":"orta","id":49038,"avatar_url":"https://avatars.githubusercontent.com/u/49038?v=3","gravatar_id":"","url":"https://api.github.com/users/orta","html_url":"https://github.com/orta","followers_url":"https://api.github.com/users/orta/followers","following_url":"https://api.github.com/users/orta/following{/other_user}","gists_url":"https://api.github.com/users/orta/gists{/gist_id}","starred_url":"https://api.github.com/users/orta/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/orta/subscriptions","organizations_url":"https://api.github.com/users/orta/orgs","repos_url":"https://api.github.com/users/orta/repos","events_url":"https://api.github.com/users/orta/events{/privacy}","received_events_url":"https://api.github.com/users/orta/received_events","type":"User","site_admin":false},"body":"I might not be the target audience, but I'm not really sure what this sentence means:\r\n\r\n> It's less JSON blobs to handle and we also get a nice Koa 2 style middleware pattern that lets us hook into a downstream/upstream flow","created_at":"2016-11-19T08:44:51Z","updated_at":"2016-11-19T08:47:15Z","html_url":"https://github.com/artsy/artsy.github.io/pull/299#discussion_r88775634","pull_request_url":"https://api.github.com/repos/artsy/artsy.github.io/pulls/299","_links":{"self":{"href":"https://api.github.com/repos/artsy/artsy.github.io/pulls/comments/88775634"},"html":{"href":"https://github.com/artsy/artsy.github.io/pull/299#discussion_r88775634"},"pull_request":{"href":"https://api.github.com/repos/artsy/artsy.github.io/pulls/299"}}},"pull_request":{"url":"https://api.github.com/repos/artsy/artsy.github.io/pulls/299","id":94411638,"html_url":"https://github.com/artsy/artsy.github.io/pull/299","diff_url":"https://github.com/artsy/artsy.github.io/pull/299.diff","patch_url":"https://github.com/artsy/artsy.github.io/pull/299.patch","issue_url":"https://api.github.com/repos/artsy/artsy.github.io/issues/299","number":299,"state":"open","locked":false,"title":"[WIP] Improving Page Speed with GraphQL","user":{"login":"kanaabe","id":2236794,"avatar_url":"https://avatars.githubusercontent.com/u/2236794?v=3","gravatar_id":"","url":"https://api.github.com/users/kanaabe","html_url":"https://github.com/kanaabe","followers_url":"https://api.github.com/users/kanaabe/followers","following_url":"https://api.github.com/users/kanaabe/following{/other_user}","gists_url":"https://api.github.com/users/kanaabe/gists{/gist_id}","starred_url":"https://api.github.com/users/kanaabe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kanaabe/subscriptions","organizations_url":"https://api.github.com/users/kanaabe/orgs","repos_url":"https://api.github.com/users/kanaabe/repos","events_url":"https://api.github.com/users/kanaabe/events{/privacy}","received_events_url":"https://api.github.com/users/kanaabe/received_events","type":"User","site_admin":false},"body":"Hm definitely still a draft on this, but I'm feeling pretty close! Any tips, suggestions, edits would be highly appreciated! ","created_at":"2016-11-18T18:21:41Z","updated_at":"2016-11-19T08:47:15Z","closed_at":null,"merged_at":null,"merge_commit_sha":"8437616104fbb041ddabc31cf7ad5062e00fd5d9","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/artsy/artsy.github.io/pulls/299/commits","review_comments_url":"https://api.github.com/repos/artsy/artsy.github.io/pulls/299/comments","review_comment_url":"https://api.github.com/repos/artsy/artsy.github.io/pulls/comments{/number}","comments_url":"https://api.github.com/repos/artsy/artsy.github.io/issues/299/comments","statuses_url":"https://api.github.com/repos/artsy/artsy.github.io/statuses/dac6fad0fbc770d37dc237a41bc6f0b2d3499b63","head":{"label":"artsy:speed-graphql","ref":"speed-graphql","sha":"dac6fad0fbc770d37dc237a41bc6f0b2d3499b63","user":{"login":"artsy","id":546231,"avatar_url":"https://avatars.githubusercontent.com/u/546231?v=3","gravatar_id":"","url":"https://api.github.com/users/artsy","html_url":"https://github.com/artsy","followers_url":"https://api.github.com/users/artsy/followers","following_url":"https://api.github.com/users/artsy/following{/other_user}","gists_url":"https://api.github.com/users/artsy/gists{/gist_id}","starred_url":"https://api.github.com/users/artsy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/artsy/subscriptions","organizations_url":"https://api.github.com/users/artsy/orgs","repos_url":"https://api.github.com/users/artsy/repos","events_url":"https://api.github.com/users/artsy/events{/privacy}","received_events_url":"https://api.github.com/users/artsy/received_events","type":"Organization","site_admin":false},"repo":{"id":3114581,"name":"artsy.github.io","full_name":"artsy/artsy.github.io","owner":{"login":"artsy","id":546231,"avatar_url":"https://avatars.githubusercontent.com/u/546231?v=3","gravatar_id":"","url":"https://api.github.com/users/artsy","html_url":"https://github.com/artsy","followers_url":"https://api.github.com/users/artsy/followers","following_url":"https://api.github.com/users/artsy/following{/other_user}","gists_url":"https://api.github.com/users/artsy/gists{/gist_id}","starred_url":"https://api.github.com/users/artsy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/artsy/subscriptions","organizations_url":"https://api.github.com/users/artsy/orgs","repos_url":"https://api.github.com/users/artsy/repos","events_url":"https://api.github.com/users/artsy/events{/privacy}","received_events_url":"https://api.github.com/users/artsy/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/artsy/artsy.github.io","description":"The Artsy Engineering Open-Source Developers Blog","fork":false,"url":"https://api.github.com/repos/artsy/artsy.github.io","forks_url":"https://api.github.com/repos/artsy/artsy.github.io/forks","keys_url":"https://api.github.com/repos/artsy/artsy.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/artsy/artsy.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/artsy/artsy.github.io/teams","hooks_url":"https://api.github.com/repos/artsy/artsy.github.io/hooks","issue_events_url":"https://api.github.com/repos/artsy/artsy.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/artsy/artsy.github.io/events","assignees_url":"https://api.github.com/repos/artsy/artsy.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/artsy/artsy.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/artsy/artsy.github.io/tags","blobs_url":"https://api.github.com/repos/artsy/artsy.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/artsy/artsy.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/artsy/artsy.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/artsy/artsy.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/artsy/artsy.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/artsy/artsy.github.io/languages","stargazers_url":"https://api.github.com/repos/artsy/artsy.github.io/stargazers","contributors_url":"https://api.github.com/repos/artsy/artsy.github.io/contributors","subscribers_url":"https://api.github.com/repos/artsy/artsy.github.io/subscribers","subscription_url":"https://api.github.com/repos/artsy/artsy.github.io/subscription","commits_url":"https://api.github.com/repos/artsy/artsy.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/artsy/artsy.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/artsy/artsy.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/artsy/artsy.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/artsy/artsy.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/artsy/artsy.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/artsy/artsy.github.io/merges","archive_url":"https://api.github.com/repos/artsy/artsy.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/artsy/artsy.github.io/downloads","issues_url":"https://api.github.com/repos/artsy/artsy.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/artsy/artsy.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/artsy/artsy.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/artsy/artsy.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/artsy/artsy.github.io/labels{/name}","releases_url":"https://api.github.com/repos/artsy/artsy.github.io/releases{/id}","deployments_url":"https://api.github.com/repos/artsy/artsy.github.io/deployments","created_at":"2012-01-06T00:31:22Z","updated_at":"2016-11-15T00:49:42Z","pushed_at":"2016-11-18T20:57:18Z","git_url":"git://github.com/artsy/artsy.github.io.git","ssh_url":"git@github.com:artsy/artsy.github.io.git","clone_url":"https://github.com/artsy/artsy.github.io.git","svn_url":"https://github.com/artsy/artsy.github.io","homepage":"https://artsy.github.io","size":70009,"stargazers_count":174,"watchers_count":174,"language":"CSS","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":92,"mirror_url":null,"open_issues_count":24,"forks":92,"open_issues":24,"watchers":174,"default_branch":"source"}},"base":{"label":"artsy:source","ref":"source","sha":"670a9e7d557d745144cb63f9b367aa989a993972","user":{"login":"artsy","id":546231,"avatar_url":"https://avatars.githubusercontent.com/u/546231?v=3","gravatar_id":"","url":"https://api.github.com/users/artsy","html_url":"https://github.com/artsy","followers_url":"https://api.github.com/users/artsy/followers","following_url":"https://api.github.com/users/artsy/following{/other_user}","gists_url":"https://api.github.com/users/artsy/gists{/gist_id}","starred_url":"https://api.github.com/users/artsy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/artsy/subscriptions","organizations_url":"https://api.github.com/users/artsy/orgs","repos_url":"https://api.github.com/users/artsy/repos","events_url":"https://api.github.com/users/artsy/events{/privacy}","received_events_url":"https://api.github.com/users/artsy/received_events","type":"Organization","site_admin":false},"repo":{"id":3114581,"name":"artsy.github.io","full_name":"artsy/artsy.github.io","owner":{"login":"artsy","id":546231,"avatar_url":"https://avatars.githubusercontent.com/u/546231?v=3","gravatar_id":"","url":"https://api.github.com/users/artsy","html_url":"https://github.com/artsy","followers_url":"https://api.github.com/users/artsy/followers","following_url":"https://api.github.com/users/artsy/following{/other_user}","gists_url":"https://api.github.com/users/artsy/gists{/gist_id}","starred_url":"https://api.github.com/users/artsy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/artsy/subscriptions","organizations_url":"https://api.github.com/users/artsy/orgs","repos_url":"https://api.github.com/users/artsy/repos","events_url":"https://api.github.com/users/artsy/events{/privacy}","received_events_url":"https://api.github.com/users/artsy/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/artsy/artsy.github.io","description":"The Artsy Engineering Open-Source Developers Blog","fork":false,"url":"https://api.github.com/repos/artsy/artsy.github.io","forks_url":"https://api.github.com/repos/artsy/artsy.github.io/forks","keys_url":"https://api.github.com/repos/artsy/artsy.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/artsy/artsy.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/artsy/artsy.github.io/teams","hooks_url":"https://api.github.com/repos/artsy/artsy.github.io/hooks","issue_events_url":"https://api.github.com/repos/artsy/artsy.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/artsy/artsy.github.io/events","assignees_url":"https://api.github.com/repos/artsy/artsy.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/artsy/artsy.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/artsy/artsy.github.io/tags","blobs_url":"https://api.github.com/repos/artsy/artsy.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/artsy/artsy.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/artsy/artsy.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/artsy/artsy.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/artsy/artsy.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/artsy/artsy.github.io/languages","stargazers_url":"https://api.github.com/repos/artsy/artsy.github.io/stargazers","contributors_url":"https://api.github.com/repos/artsy/artsy.github.io/contributors","subscribers_url":"https://api.github.com/repos/artsy/artsy.github.io/subscribers","subscription_url":"https://api.github.com/repos/artsy/artsy.github.io/subscription","commits_url":"https://api.github.com/repos/artsy/artsy.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/artsy/artsy.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/artsy/artsy.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/artsy/artsy.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/artsy/artsy.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/artsy/artsy.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/artsy/artsy.github.io/merges","archive_url":"https://api.github.com/repos/artsy/artsy.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/artsy/artsy.github.io/downloads","issues_url":"https://api.github.com/repos/artsy/artsy.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/artsy/artsy.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/artsy/artsy.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/artsy/artsy.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/artsy/artsy.github.io/labels{/name}","releases_url":"https://api.github.com/repos/artsy/artsy.github.io/releases{/id}","deployments_url":"https://api.github.com/repos/artsy/artsy.github.io/deployments","created_at":"2012-01-06T00:31:22Z","updated_at":"2016-11-15T00:49:42Z","pushed_at":"2016-11-18T20:57:18Z","git_url":"git://github.com/artsy/artsy.github.io.git","ssh_url":"git@github.com:artsy/artsy.github.io.git","clone_url":"https://github.com/artsy/artsy.github.io.git","svn_url":"https://github.com/artsy/artsy.github.io","homepage":"https://artsy.github.io","size":70009,"stargazers_count":174,"watchers_count":174,"language":"CSS","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":92,"mirror_url":null,"open_issues_count":24,"forks":92,"open_issues":24,"watchers":174,"default_branch":"source"}},"_links":{"self":{"href":"https://api.github.com/repos/artsy/artsy.github.io/pulls/299"},"html":{"href":"https://github.com/artsy/artsy.github.io/pull/299"},"issue":{"href":"https://api.github.com/repos/artsy/artsy.github.io/issues/299"},"comments":{"href":"https://api.github.com/repos/artsy/artsy.github.io/issues/299/comments"},"review_comments":{"href":"https://api.github.com/repos/artsy/artsy.github.io/pulls/299/comments"},"review_comment":{"href":"https://api.github.com/repos/artsy/artsy.github.io/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/artsy/artsy.github.io/pulls/299/commits"},"statuses":{"href":"https://api.github.com/repos/artsy/artsy.github.io/statuses/dac6fad0fbc770d37dc237a41bc6f0b2d3499b63"}}}}
{"actor":{"display_login":"orta"}}