5. - INTERNAL USE ONLY -
Apollo Platform
• Apollo Client (ApolloClient, Query/Mutation/Subscription)
• Apollo Server (ApolloServer, makeExecutableSchema)
• Apollo Engine $$$ (express app gateway)
5
6. - INTERNAL USE ONLY -
Apollo consumers
• The NY Times
• Airbnb
• Express (ecommerce retailer)
• Major League Soccer
• Expo
• KLM
• Adform!
6
8. - INTERNAL USE ONLY -
Apollo Server
8
# schema.graphql
type DealResponse {
count: Int
results: [Deal]
}
type Deal {
id: String
name: String
}
type Query {
deals(
text: String
): DealResponse
}
9. - INTERNAL USE ONLY -
Apollo Server
9
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
import fs from 'fs';
const schemaDefinition = fs.readFileSync('./src/schema/schemaDefinition.gql', 'utf8');
const executableSchema = makeExecutableSchema({
typeDefs: schemaDefinition
});
addMockFunctionsToSchema({
schema: executableSchema
});
export default executableSchema;
10. - INTERNAL USE ONLY -
Apollo Server
10
app.post(
['/schema', '/graphql'],
bodyParser.json(),
(req, res, next) => {setTimeout(next, 500)},
graphqlExpress({
schema: executableSchema,
tracing: false,
graphiql: true
})
);
11. - INTERNAL USE ONLY -
Apollo Engine
11
const engine = new ApolloEngine({
apiKey: 'API_KEY_HERE'
});
// Call engine.listen instead of app.listen(port)
engine.listen({
port: 3000,
expressApp: app,
});
12. - INTERNAL USE ONLY -
Apollo Client
12
import ApolloClient from "apollo-boost";
const ConnectedApp = () => {
const client = new ApolloClient({
uri: "https://w5xlvm3vzz.lp.gql.zone/graphql"
});
return (
<ApolloProvider client={client}>
<App />
</ApolloProvider>
)
};
13. - INTERNAL USE ONLY -
Apollo Client
13
client
.query({
query: gql`
{
rates(currency: "USD") {
currency
}
}
`
})
.then(result => console.log(result));
14. - INTERNAL USE ONLY -
Apollo Query*
14
const GET_DOGS = gql`
{
dogs {
id
breed
}
}
`;
const Dogs = props => (
<Query query={GET_DOGS}>
{({ loading, error, data }) => {
// ...
}}
</Query>
);
18. - INTERNAL USE ONLY -
Security. Schema introspection
“For security, Apollo Server introspection is automatically
disabled when the NODE_ENV is set to production or
testing”
18
19. - INTERNAL USE ONLY -
Security. Injection
No:
deals(filters="limit=1") {
count
}
Yes:
deals(limit=1) {
count
}
• Doesn’t help with string parameters:
19
20. - INTERNAL USE ONLY -
Security. DoS
query evil {
album(id: 42) {
songs {
album {
songs {
album {
songs {
album {
songs {
album {
songs {
album {
songs {
album {
# and so on...
}
}
}
}
}
}
}
}
}
}
}
}
}
}
20
21. - INTERNAL USE ONLY -
Security. DoS
As usual +
- Operation safe-listing
- Complexity limits: simple and advanced
21
22. - INTERNAL USE ONLY -
Security. DoS
Max Stoiber Advanced security
22
24. - INTERNAL USE ONLY -
Versioning
• No API versions (well, you can actually)
• Field usage (Apollo Engine schema history)
• Field rollover (resolver alias + @deprecated)
- Provide developers with the helpful deprecation
message referring them to the new name.
- Avoid auto-completing the field.
• Changing arguments (no good ideas, use field rollover).
24
27. - INTERNAL USE ONLY -
Versioning. @deprecated
type Query {
user(id: ID!): User @deprecated(reason: "renamed to 'getUser'")
getUser(id: ID!): User
}
27
42. - INTERNAL USE ONLY -
Pagination
const isFirstPage = networkStatus !== APOLLO_NETWORK_STATUSES.FETCH_MORE;
…
return {
...
/**
* `() => refetch()` is *mandatory* in order to prevent event handlers from passing arguments
such as *event*
* to *Apollo* `refetch` function as it treats arguments as variables for GraphQL queries.
* Otherwise, it would lead to errors in `JSON.parse(...)` when trying to parse circular data.
*/
refetch: () => refetch(),
};
42
43. - INTERNAL USE ONLY -
Pagination
export const APOLLO_NETWORK_STATUSES = {
FETCH_MORE: NetworkStatus.fetchMore,
/**
* networkStatus === ERROR *only* when http status is bad (e.g. 4xx, 5xx)
* however usually you want to handle also errors in resolvers, e.g.
* http status is 200 but response contains `errors` property.
* Consider using Boolean(data.error) instead of (data.networkStatus === ERROR)
*/
ERROR: NetworkStatus.error,
};
43
44. - INTERNAL USE ONLY -
Pagination. More pitfalls
• 1.x: fetchMore doesn’t trigger rerender on error
44
47. - INTERNAL USE ONLY -
Schema design
“Design by client needs”
47
48. - INTERNAL USE ONLY -
Schema design
• Use interfaces
• Use QueryResponse
• Use MutationResponse
• Use input types
• Use your clients style convention (GQL is flexible)
48
49. - INTERNAL USE ONLY -
Schema design. QueryResponse
type DealResponse {
availableCount: Int
count: Int
results: [Deal]
}
type Query {
deals(
text: String,
...
): DealResponse
}
49
50. - INTERNAL USE ONLY -
Schema design. MutationResponse
type LikePostMutationResponse implements MutationResponse {
code: String!
success: Boolean!
message: String!
post: Post
user: User
}
50
51. - INTERNAL USE ONLY -
Schema design. Input types
input PostAndMediaInput {
"A main title for the post"
title: String
"The textual body of the post."
body: String
"A list of URLs to render in the post."
mediaUrls: [String]
}
51
53. - INTERNAL USE ONLY -
ACL
Available options:
• Delegate to REST API
• Authorization via context
• Authorization via directives
53
54. - INTERNAL USE ONLY -
ACL. Authorization via context
context: ({ req }) => {
const token = req.headers.authentication || '';
const user = getUser(token);
if (!user) throw new AuthorizationError('you must be logged in');
return { user };
},
54
55. - INTERNAL USE ONLY -
ACL. Authorization via context
users: (root, args, context) => {
// In this case, we'll pretend there is no data when
// we're not logged in. Another option would be to
// throw an error.
if (!context.user) return [];
return ['bob', 'jake'];
}
55
56. - INTERNAL USE ONLY -
ACL. Authorization via directives
directive @auth(requires: Role = ADMIN) on OBJECT | FIELD_DEFINITION
enum Role {
ADMIN
REVIEWER
USER
}
type User @auth(requires: USER) {
name: String
banned: Boolean @auth(requires: ADMIN)
canPost: Boolean @auth(requires: REVIEWER)
}
56
58. - INTERNAL USE ONLY -
State management
Since Apollo Client supports managing both local and
remote data, you can use the Apollo cache as a single
source of truth for all global state in your application.
58
59. - INTERNAL USE ONLY -
State management
apollo-link-state
const client = new ApolloClient({
uri: `https://nx9zvp49q7.lp.gql.zone/graphql`,
clientState: {
defaults,
resolvers,
typeDefs
}
});
59
60. - INTERNAL USE ONLY -
State management. Defaults
initialState → defaults
60
61. - INTERNAL USE ONLY -
State management. Resolvers
export const resolvers = {
Mutation: {
toggleTodo: (_, variables, { cache, getCacheKey }) => {
const id = getCacheKey({ __typename: 'TodoItem', id: variables.id })
const fragment = gql`
fragment completeTodo on TodoItem {
completed
}
`;
const todo = cache.readFragment({ fragment, id });
const data = { ...todo, completed: !todo.completed };
cache.writeData({ id, data });
return null;
61
62. - INTERNAL USE ONLY -
State management. Query
{
todos @client {
id
completed
text
}
visibilityFilter @client
}
62
65. - INTERNAL USE ONLY -
Performance
Optimization points:
• Do not send full query to server
• Cache read requests
• Cache entities on client
• Prefetch entities
• Batch requests
65
66. - INTERNAL USE ONLY -
Performance. Automatic Persisted Queries
66
67. - INTERNAL USE ONLY -
Performance. Automatic Persisted Queries
curl -g
'http://localhost:9011/graphql?extensions={"persistedQuery":{"version":1,"sha256Hash":"
ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38"}}'
67
68. - INTERNAL USE ONLY -
Performance. Automatic Persisted Queries
Client: apollo-link-persisted-queries
Server:
const server = new ApolloServer({
typeDefs,
resolvers,
persistedQueries: {
cache: new InMemoryCache(),
},
});
68
69. - INTERNAL USE ONLY -
Performance. CDN integration
createPersistedQueryLink({ useGETForHashedQueries: true })
___
type Author @cacheControl(maxAge: 60) {
id: Int
firstName: String
lastName: String
posts: [Post] @cacheControl(maxAge: 180)
}
69
70. - INTERNAL USE ONLY -
Performance. Client caching
• Data is normalized and cached by default
• Update mutation updates cached automagically
• Create mutation requires manual cache writes
70
74. - INTERNAL USE ONLY -
Performance. Batch requests
import { BatchHttpLink } from "apollo-link-batch-http";
const link = new BatchHttpLink({ uri: "/graphql" });
74
83. - INTERNAL USE ONLY -
Server-side rendering
function Html({ content, state }) {
return (
<html>
<body>
<div id="root" dangerouslySetInnerHTML={{ __html: content }} />
<script dangerouslySetInnerHTML={{
__html: `window.__APOLLO_STATE__=${JSON.stringify(state).replace(/</g,
'u003c')};`,
}} />
</body>
</html>
);
}
83
84. - INTERNAL USE ONLY -
Server-side rendering. Avoid network calls
const client = new ApolloClient({
ssrMode: true,
link: new SchemaLink({ schema }),
cache: new InMemoryCache(),
});
84
85. - INTERNAL USE ONLY -
Server-side rendering. Skipping queries
const ClientOnlyUser = () => (
<Query query={GET_USER_WITH_ID} ssr={false}>
{({ data }) => <span>I won't be run on the server</span>}
</Query>
);
85
86. - INTERNAL USE ONLY -
Server-side rendering. Rehydration
const client = new ApolloClient({
cache: new InMemoryCache().restore(window.__APOLLO_STATE__),
link,
});
86
90. - INTERNAL USE ONLY -
Relay
https://open.nytimes.com/the-new-york-times-now-on-apollo-
b9a78a5038c
“Relay takes a “bring your own solution” approach to this
problem. In Apollo, SSR is a first-class feature. This is huge
for us.”
90