├── .gitignore ├── .meteor ├── .gitignore ├── release ├── platforms ├── packages ├── .id ├── .finished-upgraders └── versions ├── client ├── main.html └── main.js ├── README.md ├── .babelrc ├── package.json ├── server └── main.js └── imports ├── app.js └── schema.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.6 2 | -------------------------------------------------------------------------------- /.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /client/main.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /.meteor/packages: -------------------------------------------------------------------------------- 1 | static-html 2 | standard-minifier-js 3 | es5-shim 4 | ecmascript 5 | server-render 6 | meteor 7 | webapp 8 | dynamic-import 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Sample App 2 | 3 | ### Installation 4 | - install [meteor](https://www.meteor.com/install) 5 | - `npm install` 6 | - `npm start` 7 | 8 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react" 4 | ], 5 | "plugins": [ 6 | "transform-react-require", 7 | "transform-inline-environment-variables", 8 | "transform-dead-code-elimination", 9 | "emotion/babel" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 9bnsqv1ia2dh11nxikou 8 | -------------------------------------------------------------------------------- /.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | 1.3.0-split-minifiers-package 14 | 1.4.0-remove-old-dev-bundle-link 15 | 1.4.1-add-shell-server-package 16 | 1.4.3-split-account-service-packages 17 | 1.5-add-dynamic-import-package 18 | -------------------------------------------------------------------------------- /client/main.js: -------------------------------------------------------------------------------- 1 | import { render } from "react-dom"; 2 | import { onPageLoad } from "meteor/server-render"; 3 | import { hydrate } from "emotion"; 4 | import { BrowserRouter } from "react-router-dom"; 5 | 6 | import { ApolloProvider } from "react-apollo"; 7 | import { ApolloClient } from "apollo-client"; 8 | import { createHttpLink } from "apollo-link-http"; 9 | import { InMemoryCache } from "apollo-cache-inmemory"; 10 | 11 | import { App } from "/imports/app"; 12 | 13 | export const start = () => { 14 | hydrate(window.__CSS__); 15 | 16 | const client = new ApolloClient({ 17 | link: createHttpLink({ uri: "/graphql" }), 18 | cache: new InMemoryCache().restore(window.__APOLLO_STATE__), 19 | }); 20 | 21 | const WrappedApp = ( 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | 29 | render(WrappedApp, document.getElementById("app")); 30 | }; 31 | 32 | onPageLoad(start); 33 | -------------------------------------------------------------------------------- /.meteor/versions: -------------------------------------------------------------------------------- 1 | babel-compiler@6.24.7 2 | babel-runtime@1.1.1 3 | base64@1.0.10 4 | blaze-tools@1.0.10 5 | boilerplate-generator@1.3.0 6 | caching-compiler@1.1.9 7 | caching-html-compiler@1.1.2 8 | callback-hook@1.0.10 9 | check@1.2.5 10 | ddp@1.4.0 11 | ddp-client@2.2.0 12 | ddp-common@1.3.0 13 | ddp-server@2.1.1 14 | deps@1.0.12 15 | diff-sequence@1.0.7 16 | dynamic-import@0.2.1 17 | ecmascript@0.9.0 18 | ecmascript-runtime@0.5.0 19 | ecmascript-runtime-client@0.5.0 20 | ecmascript-runtime-server@0.5.0 21 | ejson@1.1.0 22 | es5-shim@4.6.15 23 | geojson-utils@1.0.10 24 | html-tools@1.0.11 25 | htmljs@1.0.11 26 | id-map@1.0.9 27 | logging@1.1.19 28 | meteor@1.8.0 29 | minifier-js@2.2.0 30 | minimongo@1.4.1 31 | modules@0.11.0 32 | modules-runtime@0.9.0 33 | mongo-id@1.0.6 34 | ordered-dict@1.0.9 35 | promise@0.10.0 36 | random@1.0.10 37 | retry@1.0.9 38 | routepolicy@1.0.12 39 | server-render@0.2.0 40 | spacebars-compiler@1.1.2 41 | standard-minifier-js@2.2.0 42 | static-html@1.2.2 43 | templating-tools@1.1.2 44 | tracker@1.1.3 45 | underscore@1.0.10 46 | webapp@1.4.0 47 | webapp-hashing@1.0.9 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ssr", 3 | "private": true, 4 | "scripts": { 5 | "start": "meteor run", 6 | "flow": "flow", 7 | "visualize": "meteor --production --extra-packages bundle-visualizer", 8 | "lint-staged": "lint-staged", 9 | "lint-fix": "prettier --trailing-comma all --write \"{client,server,imports}/**/*.js*\"" 10 | }, 11 | "lint-staged": { 12 | "*.ts*": ["prettier --trailing-comma all --write", "git add"], 13 | "*.js*": ["prettier --trailing-comma all --write", "git add"] 14 | }, 15 | "pre-commit": "lint-staged", 16 | "dependencies": { 17 | "apollo-cache-inmemory": "^1.1.0", 18 | "apollo-client": "^2.0.2", 19 | "apollo-link": "^1.0.0", 20 | "apollo-link-http": "^1.1.0", 21 | "apollo-server-express": "^1.1.0", 22 | "babel-runtime": "^6.20.0", 23 | "body-parser": "^1.17.2", 24 | "core-js": "^2.5.0", 25 | "emotion": "^7.0.13", 26 | "express": "^4.15.4", 27 | "graphql": "^0.10.5", 28 | "graphql-tag": "^2.4.2", 29 | "graphql-tools": "^1.1.0", 30 | "meteor-node-stubs": "~0.2.4", 31 | "optics-agent": "^1.1.6", 32 | "react": "^15.6.1", 33 | "react-apollo": "^2.0.1", 34 | "react-dom": "^15.6.1", 35 | "react-helmet": "^5.1.3", 36 | "react-router": "^4.1.2", 37 | "react-router-dom": "^4.1.2", 38 | "theming": "^1.1.0" 39 | }, 40 | "devDependencies": { 41 | "babel-plugin-transform-dead-code-elimination": "^2.2.2", 42 | "babel-plugin-transform-inline-environment-variables": "^0.1.1", 43 | "babel-plugin-transform-react-require": "^1.0.1", 44 | "babel-preset-react": "6.24.1", 45 | "lint-staged": "^4.0.3", 46 | "pre-commit": "^1.2.2", 47 | "prettier": "^1.5.3" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /server/main.js: -------------------------------------------------------------------------------- 1 | import { renderToString } from "react-dom/server"; 2 | import { onPageLoad } from "meteor/server-render"; 3 | import { getDataFromTree, ApolloProvider } from "react-apollo"; 4 | import { ApolloClient } from "apollo-client"; 5 | import { ApolloLink, Observable } from "apollo-link"; 6 | import { InMemoryCache } from "apollo-cache-inmemory"; 7 | import { Helmet } from "react-helmet"; 8 | import { extractCritical } from "emotion/server"; 9 | import { StaticRouter } from "react-router"; 10 | 11 | import { WebApp } from "meteor/webapp"; 12 | import { graphqlExpress, graphiqlExpress } from "apollo-server-express"; 13 | import bodyParser from "body-parser"; 14 | import express from "express"; 15 | import { graphql } from "graphql"; 16 | import { print } from "graphql/language/printer"; 17 | 18 | import { schema } from "/imports/schema"; 19 | import { App } from "/imports/app"; 20 | 21 | export const render = async sink => { 22 | const client = new ApolloClient({ 23 | // simple local interface to query graphql directly 24 | link: new ApolloLink(({ query, variables, operationName }) => { 25 | return new Observable(obs => { 26 | graphql(schema, print(query), {}, {}, variables, operationName) 27 | .then(result => { 28 | obs.next(result); 29 | obs.complete(); 30 | }) 31 | .catch(obs.error.bind(obs)); 32 | }); 33 | }), 34 | cache: new InMemoryCache(), 35 | ssrMode: true, 36 | }); 37 | 38 | const context = {}; 39 | const WrappedApp = ( 40 | 41 | 42 | 43 | 44 | 45 | ); 46 | 47 | // load all data from local server; 48 | await getDataFromTree(WrappedApp); 49 | 50 | const { html, ids, css } = extractCritical(renderToString(WrappedApp)); 51 | const meta = Helmet.renderStatic(); 52 | 53 | sink.appendToHead(meta.title.toString()); 54 | sink.appendToHead(``); 55 | 56 | sink.renderIntoElementById("app", html); 57 | 58 | sink.appendToBody(` 59 | 63 | `); 64 | }; 65 | 66 | // hanlde SSR 67 | onPageLoad(render); 68 | 69 | // expose graphql endpoint 70 | // setup GraphQL endpoint and GraphiQL 71 | const server = express(); 72 | 73 | server.use( 74 | "/graphql", 75 | bodyParser.json(), 76 | graphqlExpress(req => ({ 77 | schema, 78 | })), 79 | ); 80 | server.use("/graphiql", graphiqlExpress({ endpointURL: "/graphql" })); 81 | 82 | WebApp.connectHandlers.use(server); 83 | -------------------------------------------------------------------------------- /imports/app.js: -------------------------------------------------------------------------------- 1 | import { graphql } from "react-apollo"; 2 | import { Helmet } from "react-helmet"; 3 | import gql from "graphql-tag"; 4 | import styled from "emotion/react"; 5 | import { css } from "emotion"; 6 | import { Link, Route, Switch } from "react-router-dom"; 7 | 8 | const Card = styled(Link)` 9 | width: 200px; 10 | height: 200px; 11 | border: 15px solid white; 12 | background-size: cover; 13 | background-position: center center; 14 | margin: auto; 15 | background-image: ${props => `url('${props.image}')`}; 16 | `; 17 | 18 | const background = css` 19 | display: flex; 20 | align-items: center; 21 | justify-content: center; 22 | background-size: cover; 23 | background-position: center center; 24 | position: fixed; 25 | top: 0; 26 | bottom: 0; 27 | left: 0; 28 | right: 0; 29 | `; 30 | 31 | const Background = styled("div")` 32 | composes: ${background}; 33 | background-color: darksalmon; 34 | `; 35 | 36 | const FullScreen = styled("div")` 37 | composes: ${background}; 38 | background-color: transparent; 39 | `; 40 | 41 | const BlurryBackground = styled("div")` 42 | composes: ${background}; 43 | background-image: ${props => `url('${props.image}')`}; 44 | -webkit-filter: blur(.25px); 45 | filter: blur(.25px); 46 | `; 47 | 48 | const MOVIE_QUERY = gql` 49 | query GetMovies { 50 | movies { 51 | id 52 | name 53 | image 54 | episode 55 | } 56 | } 57 | `; 58 | 59 | const withMovies = graphql(MOVIE_QUERY, { 60 | props: ({ data }) => ({ ...data }), 61 | }); 62 | 63 | export const Movies = withMovies(({ loading, movies, error }) => { 64 | if (loading) return
Loading
; 65 | if (error) return

ERROR

; 66 | return ( 67 |
68 | 69 | 70 | {movies.map(x => x.name).join(" || ")} 71 | 72 | 73 | 74 | {movies.map(movie => 75 | , 76 | )} 77 | 78 |
79 | ); 80 | }); 81 | 82 | const HERO_QUERY = gql` 83 | query GetEpisode($episode: Episode!) { 84 | character: hero(episode: $episode) { 85 | id 86 | name 87 | image 88 | friends { 89 | id 90 | name 91 | image 92 | } 93 | } 94 | } 95 | `; 96 | 97 | export const Character = ({ loading, character, error, networkStatus }) => { 98 | if (!character) return null; 99 | return ( 100 |
101 | 102 | 103 | {character.friends.map(friend => 104 | , 109 | )} 110 | 111 |
112 | ); 113 | }; 114 | 115 | const withHero = graphql(HERO_QUERY, { 116 | options: ({ match }) => ({ 117 | variables: { 118 | episode: match.params.episode, 119 | }, 120 | }), 121 | props: ({ data }) => ({ ...data }), 122 | }); 123 | 124 | export const Hero = withHero(Character); 125 | 126 | export const CHARACTER_QUERY = gql` 127 | query GetCharacter($id: ID!) { 128 | character(id: $id) { 129 | id 130 | name 131 | image 132 | friends { 133 | id 134 | name 135 | image 136 | } 137 | } 138 | } 139 | `; 140 | 141 | const withCharacter = graphql(CHARACTER_QUERY, { 142 | options: ({ match }) => ({ variables: { id: match.params.id } }), 143 | props: ({ data }) => ({ ...data }), 144 | }); 145 | 146 | export const CharacterWithData = withCharacter(Character); 147 | 148 | export const App = () => 149 |
150 | 151 | 152 | 153 | 154 | 155 |
; 156 | -------------------------------------------------------------------------------- /imports/schema.js: -------------------------------------------------------------------------------- 1 | // This is the Star Wars schema used in all of the interactive GraphiQL 2 | // examples on GraphQL.org. License reproduced at the bottom. 3 | 4 | /** 5 | * Copyright (c) 2015, Facebook, Inc. 6 | * All rights reserved. 7 | * 8 | * This source code is licensed under the license found in the 9 | * LICENSE file in the root directory of this source tree. 10 | */ 11 | 12 | import { makeExecutableSchema } from "graphql-tools"; 13 | 14 | const schemaString = ` 15 | schema { 16 | query: Query 17 | mutation: Mutation 18 | } 19 | # The query type, represents all of the entry points into our object graph 20 | type Query { 21 | hero(episode: Episode): Character 22 | reviews(episode: Episode!): [Review] 23 | search(text: String): [SearchResult] 24 | character(id: ID!): Character 25 | droid(id: ID!): Droid 26 | human(id: ID!): Human 27 | starship(id: ID!): Starship 28 | movies: [Movie] 29 | } 30 | # The mutation type, represents all updates we can make to our data 31 | type Mutation { 32 | createReview(episode: Episode, review: ReviewInput!): Review 33 | } 34 | # The episodes in the Star Wars trilogy 35 | enum Episode { 36 | # Star Wars Episode IV: A New Hope, released in 1977. 37 | NEWHOPE 38 | # Star Wars Episode V: The Empire Strikes Back, released in 1980. 39 | EMPIRE 40 | # Star Wars Episode VI: Return of the Jedi, released in 1983. 41 | JEDI 42 | } 43 | # A character from the Star Wars universe 44 | interface Character { 45 | # The ID of the character 46 | id: ID! 47 | # The name of the character 48 | name: String! 49 | # The friends of the character, or an empty list if they have none 50 | friends: [Character] 51 | # The friends of the character exposed as a connection with edges 52 | friendsConnection(first: Int, after: ID): FriendsConnection! 53 | # The movies this character appears in 54 | appearsIn: [Episode]! 55 | # the image of the character 56 | image: String! 57 | } 58 | # Units of height 59 | enum LengthUnit { 60 | # The standard unit around the world 61 | METER 62 | # Primarily used in the United States 63 | FOOT 64 | } 65 | # A humanoid creature from the Star Wars universe 66 | type Human implements Character { 67 | # The ID of the human 68 | id: ID! 69 | # What this human calls themselves 70 | name: String! 71 | # Height in the preferred unit, default is meters 72 | height(unit: LengthUnit = METER): Float 73 | # Mass in kilograms, or null if unknown 74 | mass: Float 75 | # This human's friends, or an empty list if they have none 76 | friends: [Character] 77 | # The friends of the human exposed as a connection with edges 78 | friendsConnection(first: Int, after: ID): FriendsConnection! 79 | # The movies this human appears in 80 | appearsIn: [Episode]! 81 | # A list of starships this person has piloted, or an empty list if none 82 | starships: [Starship] 83 | # the image of the character 84 | image: String! 85 | } 86 | # An autonomous mechanical character in the Star Wars universe 87 | type Droid implements Character { 88 | # The ID of the droid 89 | id: ID! 90 | # What others call this droid 91 | name: String! 92 | # This droid's friends, or an empty list if they have none 93 | friends: [Character] 94 | # The friends of the droid exposed as a connection with edges 95 | friendsConnection(first: Int, after: ID): FriendsConnection! 96 | # The movies this droid appears in 97 | appearsIn: [Episode]! 98 | # This droid's primary function 99 | primaryFunction: String 100 | # the image of the character 101 | image: String! 102 | } 103 | # A connection object for a character's friends 104 | type FriendsConnection { 105 | # The total number of friends 106 | totalCount: Int 107 | # The edges for each of the character's friends. 108 | edges: [FriendsEdge] 109 | # A list of the friends, as a convenience when edges are not needed. 110 | friends: [Character] 111 | # Information for paginating this connection 112 | pageInfo: PageInfo! 113 | } 114 | # An edge object for a character's friends 115 | type FriendsEdge { 116 | # A cursor used for pagination 117 | cursor: ID! 118 | # The character represented by this friendship edge 119 | node: Character 120 | } 121 | # Information for paginating this connection 122 | type PageInfo { 123 | startCursor: ID 124 | endCursor: ID 125 | hasNextPage: Boolean! 126 | } 127 | # Represents a review for a movie 128 | type Review { 129 | # The number of stars this review gave, 1-5 130 | stars: Int! 131 | # Comment about the movie 132 | commentary: String 133 | } 134 | # The input object sent when someone is creating a new review 135 | input ReviewInput { 136 | # 0-5 stars 137 | stars: Int! 138 | # Comment about the movie, optional 139 | commentary: String 140 | } 141 | type Starship { 142 | # The ID of the starship 143 | id: ID! 144 | # The name of the starship 145 | name: String! 146 | # Length of the starship, along the longest axis 147 | length(unit: LengthUnit = METER): Float 148 | } 149 | union SearchResult = Human | Droid | Starship 150 | type Movie { 151 | # the Id of the movie 152 | id: ID! 153 | # the name of the movie 154 | name: String! 155 | # the episode 156 | episode: Episode! 157 | # the poster of the movie 158 | image: String! 159 | } 160 | `; 161 | 162 | /** 163 | * This defines a basic set of data for our Star Wars Schema. 164 | * 165 | * This data is hard coded for the sake of the demo, but you could imagine 166 | * fetching this data from a backend service rather than from hardcoded 167 | * JSON objects in a more complex demo. 168 | */ 169 | 170 | const humans = [ 171 | { 172 | id: "1000", 173 | name: "Luke Skywalker", 174 | friends: ["1002", "1003", "2000", "2001"], 175 | appearsIn: ["NEWHOPE", "EMPIRE", "JEDI"], 176 | height: 1.72, 177 | mass: 77, 178 | starships: ["3001", "3003"], 179 | image: 180 | "https://mir-s3-cdn-cf.behance.net/project_modules/max_1200/9ae95a32222089.567434549f642.jpg", 181 | }, 182 | { 183 | id: "1001", 184 | name: "Darth Vader", 185 | friends: ["1004"], 186 | appearsIn: ["NEWHOPE", "EMPIRE", "JEDI"], 187 | height: 2.02, 188 | mass: 136, 189 | starships: ["3002"], 190 | image: 191 | "https://mir-s3-cdn-cf.behance.net/project_modules/disp/4cccd816984159.562b4037c502c.jpg", 192 | }, 193 | { 194 | id: "1002", 195 | name: "Han Solo", 196 | friends: ["1000", "1003", "2001"], 197 | appearsIn: ["NEWHOPE", "EMPIRE", "JEDI"], 198 | height: 1.8, 199 | mass: 80, 200 | starships: ["3000", "3003"], 201 | image: 202 | "https://mir-s3-cdn-cf.behance.net/project_modules/disp/8f612a19912933.562e25a31c908.jpg", 203 | }, 204 | { 205 | id: "1003", 206 | name: "Leia Organa", 207 | friends: ["1000", "1002", "2000", "2001"], 208 | appearsIn: ["NEWHOPE", "EMPIRE", "JEDI"], 209 | height: 1.5, 210 | mass: 49, 211 | starships: [], 212 | image: 213 | "https://mir-cdn.behance.net/v1/rendition/project_modules/fs/6ea87455699513.598f548ce49c3.jpg", 214 | }, 215 | ]; 216 | 217 | const humanData = {}; 218 | humans.forEach(ship => { 219 | humanData[ship.id] = ship; 220 | }); 221 | 222 | const droids = [ 223 | { 224 | id: "2000", 225 | name: "C-3PO", 226 | friends: ["1000", "1002", "1003", "2001"], 227 | appearsIn: ["NEWHOPE", "EMPIRE", "JEDI"], 228 | primaryFunction: "Protocol", 229 | image: 230 | "https://mir-s3-cdn-cf.behance.net/project_modules/fs/e91a0e32833045.5695736631a8d.jpg", 231 | }, 232 | { 233 | id: "2001", 234 | name: "R2-D2", 235 | friends: ["1000", "1002", "1003"], 236 | appearsIn: ["NEWHOPE", "EMPIRE", "JEDI"], 237 | primaryFunction: "Astromech", 238 | image: 239 | "https://mir-s3-cdn-cf.behance.net/project_modules/fs/bb1dde49635909.58ba18a073115.jpg", 240 | }, 241 | ]; 242 | 243 | const droidData = {}; 244 | droids.forEach(ship => { 245 | droidData[ship.id] = ship; 246 | }); 247 | 248 | const starships = [ 249 | { 250 | id: "3000", 251 | name: "Millenium Falcon", 252 | length: 34.37, 253 | }, 254 | { 255 | id: "3001", 256 | name: "X-Wing", 257 | length: 12.5, 258 | }, 259 | { 260 | id: "3002", 261 | name: "TIE Advanced x1", 262 | length: 9.2, 263 | }, 264 | { 265 | id: "3003", 266 | name: "Imperial shuttle", 267 | length: 20, 268 | }, 269 | ]; 270 | 271 | const starshipData = {}; 272 | starships.forEach(ship => { 273 | starshipData[ship.id] = ship; 274 | }); 275 | 276 | const movies = [ 277 | { 278 | id: "4", 279 | name: "A New Hope", 280 | episode: "NEWHOPE", 281 | image: 282 | "https://mir-s3-cdn-cf.behance.net/project_modules/max_1200/fd9cef50812813.58da69a85361d.jpg", 283 | }, 284 | { 285 | id: "5", 286 | name: "The Empire Strikes Back", 287 | episode: "EMPIRE", 288 | image: 289 | "https://mir-s3-cdn-cf.behance.net/project_modules/max_1200/555e9b50812813.58da69a853ac3.jpg", 290 | }, 291 | { 292 | id: "6", 293 | name: "Return of the Jedi", 294 | episode: "JEDI", 295 | image: 296 | "https://mir-s3-cdn-cf.behance.net/project_modules/max_1200/f0e07750812813.58da69a854034.jpg", 297 | }, 298 | ]; 299 | 300 | const movieData = {}; 301 | movies.forEach(movie => { 302 | movieData[movie.id] = movie; 303 | }); 304 | 305 | /** 306 | * Helper function to get a character by ID. 307 | */ 308 | function getCharacter(id) { 309 | // Returning a promise just to illustrate GraphQL.js's support. 310 | return Promise.resolve(humanData[id] || droidData[id]); 311 | } 312 | 313 | /** 314 | * Allows us to fetch the undisputed hero of the Star Wars trilogy, R2-D2. 315 | */ 316 | function getHero(episode) { 317 | if (episode === "EMPIRE") { 318 | // Luke is the hero of Episode V. 319 | return humanData["1000"]; 320 | } 321 | // Artoo is the hero otherwise. 322 | return droidData["2001"]; 323 | } 324 | 325 | /** 326 | * Allows us to query for the human with the given id. 327 | */ 328 | function getHuman(id) { 329 | return humanData[id]; 330 | } 331 | 332 | /** 333 | * Allows us to query for the droid with the given id. 334 | */ 335 | function getDroid(id) { 336 | return droidData[id]; 337 | } 338 | 339 | function getStarship(id) { 340 | return starshipData[id]; 341 | } 342 | 343 | function getMovie(id) { 344 | return movieData[id]; 345 | } 346 | 347 | function toCursor(str) { 348 | return Buffer("cursor" + str).toString("base64"); 349 | } 350 | 351 | function fromCursor(str) { 352 | return Buffer.from(str, "base64").toString().slice(6); 353 | } 354 | 355 | const resolvers = { 356 | Query: { 357 | hero: (root, { episode }) => getHero(episode), 358 | character: (root, { id }) => getCharacter(id), 359 | human: (root, { id }) => getHuman(id), 360 | droid: (root, { id }) => getDroid(id), 361 | starship: (root, { id }) => getStarship(id), 362 | movies: () => movies, 363 | reviews: () => null, 364 | search: (root, { text }) => { 365 | const re = new RegExp(text, "i"); 366 | 367 | const allData = [...humans, ...droids, ...starships]; 368 | 369 | return allData.filter(obj => re.test(obj.name)); 370 | }, 371 | }, 372 | Mutation: { 373 | createReview: (root, { episode, review }) => review, 374 | }, 375 | Character: { 376 | __resolveType(data, context, info) { 377 | if (humanData[data.id]) { 378 | return info.schema.getType("Human"); 379 | } 380 | if (droidData[data.id]) { 381 | return info.schema.getType("Droid"); 382 | } 383 | return null; 384 | }, 385 | }, 386 | Human: { 387 | height: ({ height }, { unit }) => { 388 | if (unit === "FOOT") { 389 | return height * 3.28084; 390 | } 391 | 392 | return height; 393 | }, 394 | friends: ({ friends }) => friends.map(getCharacter), 395 | friendsConnection: ({ friends }, { first, after }) => { 396 | first = first || friends.length; 397 | after = after ? parseInt(fromCursor(after), 10) : 0; 398 | const edges = friends 399 | .map((friend, i) => ({ 400 | cursor: toCursor(i + 1), 401 | node: getCharacter(friend), 402 | })) 403 | .slice(after, first + after); 404 | const slicedFriends = edges.map(({ node }) => node); 405 | return { 406 | edges, 407 | friends: slicedFriends, 408 | pageInfo: { 409 | startCursor: edges.length > 0 ? edges[0].cursor : null, 410 | hasNextPage: first + after < friends.length, 411 | endCursor: edges.length > 0 ? edges[edges.length - 1].cursor : null, 412 | }, 413 | totalCount: friends.length, 414 | }; 415 | }, 416 | starships: ({ starships }) => starships.map(getStarship), 417 | image: ({ image }) => image, 418 | appearsIn: ({ appearsIn }) => appearsIn, 419 | }, 420 | Droid: { 421 | friends: ({ friends }) => friends.map(getCharacter), 422 | friendsConnection: ({ friends }, { first, after }) => { 423 | first = first || friends.length; 424 | after = after ? parseInt(fromCursor(after), 10) : 0; 425 | const edges = friends 426 | .map((friend, i) => ({ 427 | cursor: toCursor(i + 1), 428 | node: getCharacter(friend), 429 | })) 430 | .slice(after, first + after); 431 | const slicedFriends = edges.map(({ node }) => node); 432 | return { 433 | edges, 434 | friends: slicedFriends, 435 | pageInfo: { 436 | startCursor: edges.length > 0 ? edges[0].cursor : null, 437 | hasNextPage: first + after < friends.length, 438 | endCursor: edges.length > 0 ? edges[edges.length - 1].cursor : null, 439 | }, 440 | totalCount: friends.length, 441 | }; 442 | }, 443 | appearsIn: ({ appearsIn }) => appearsIn, 444 | image: ({ image }) => image, 445 | }, 446 | FriendsConnection: { 447 | edges: ({ edges }) => edges, 448 | friends: ({ friends }) => friends, 449 | pageInfo: ({ pageInfo }) => pageInfo, 450 | totalCount: ({ totalCount }) => totalCount, 451 | }, 452 | FriendsEdge: { 453 | node: ({ node }) => node, 454 | cursor: ({ cursor }) => cursor, 455 | }, 456 | Starship: { 457 | length: ({ length }, { unit }) => { 458 | if (unit === "FOOT") { 459 | return length * 3.28084; 460 | } 461 | 462 | return length; 463 | }, 464 | }, 465 | SearchResult: { 466 | __resolveType(data, context, info) { 467 | if (humanData[data.id]) { 468 | return info.schema.getType("Human"); 469 | } 470 | if (droidData[data.id]) { 471 | return info.schema.getType("Droid"); 472 | } 473 | if (starshipData[data.id]) { 474 | return info.schema.getType("Starship"); 475 | } 476 | return null; 477 | }, 478 | }, 479 | }; 480 | 481 | /** 482 | * Finally, we construct our schema (whose starting query type is the query 483 | * type we defined above) and export it. 484 | */ 485 | export const schema = makeExecutableSchema({ 486 | typeDefs: [schemaString], 487 | resolvers, 488 | }); 489 | 490 | /* 491 | License from https://github.com/graphql/graphql.github.io/blob/source/LICENSE 492 | 493 | LICENSE AGREEMENT For graphql.org software 494 | 495 | Facebook, Inc. (“Facebook”) owns all right, title and interest, including all 496 | intellectual property and other proprietary rights, in and to the graphql.org 497 | software. Subject to your compliance with these terms, you are hereby granted a 498 | non-exclusive, worldwide, royalty-free copyright license to (1) use and copy the 499 | graphql.org software; and (2) reproduce and distribute the graphql.org software 500 | as part of your own software (“Your Software”). Facebook reserves all rights not 501 | expressly granted to you in this license agreement. 502 | 503 | THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS OR 504 | IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 505 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. IN NO 506 | EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICES, DIRECTORS OR EMPLOYEES BE 507 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 508 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 509 | GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 510 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 511 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 512 | THE USE OF THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 513 | 514 | You will include in Your Software (e.g., in the file(s), documentation or other 515 | materials accompanying your software): (1) the disclaimer set forth above; (2) 516 | this sentence; and (3) the following copyright notice: 517 | 518 | Copyright (c) 2015, Facebook, Inc. All rights reserved. 519 | */ 520 | --------------------------------------------------------------------------------