SlideShare a Scribd company logo
- INTERNAL USE ONLY -
Apollo ecosystem
1
James Akwuh
Frontend Lead @ Marketplace Group
Minsk, July 2018
- INTERNAL USE ONLY -
Agenda
• Preface
• Setup & Examples
• Tooling
• Security
• Versioning
• Monitoring
• Files upload
• Pagination
• Schema design
• ACL
• State management
• Performance
• Testing react components
• Server-side rendering
2
- INTERNAL USE ONLY -
Agenda
• Apollo 2.1
• Relay
• Conclusion
3
- INTERNAL USE ONLY -
Preface
4
- INTERNAL USE ONLY -
Apollo Platform
• Apollo Client (ApolloClient, Query/Mutation/Subscription)
• Apollo Server (ApolloServer, makeExecutableSchema)
• Apollo Engine $$$ (express app gateway)
5
- INTERNAL USE ONLY -
Apollo consumers
• The NY Times
• Airbnb
• Express (ecommerce retailer)
• Major League Soccer
• Expo
• KLM
• Adform!
6
- INTERNAL USE ONLY -
Setup & Examples
7
- 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
}
- 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;
- 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
})
);
- 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,
});
- 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>
)
};
- INTERNAL USE ONLY -
Apollo Client
13
client
.query({
query: gql`
{
rates(currency: "USD") {
currency
}
}
`
})
.then(result => console.log(result));
- INTERNAL USE ONLY -
Apollo Query*
14
const GET_DOGS = gql`
{
dogs {
id
breed
}
}
`;
const Dogs = props => (
<Query query={GET_DOGS}>
{({ loading, error, data }) => {
// ...
}}
</Query>
);
- INTERNAL USE ONLY -
Tooling
15
- INTERNAL USE ONLY -
Tooling
• GraphQL Playground / GraphiQL
• launchpad.graphql.com
• Apollo Devtools*
• Apollo Engine
• babel-plugin-import-graphql
• ...
16
- INTERNAL USE ONLY -
Security
17
- 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
- INTERNAL USE ONLY -
Security. Injection
No:
deals(filters="limit=1") {
count
}
Yes:
deals(limit=1) {
count
}
• Doesn’t help with string parameters:
19
- 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
- INTERNAL USE ONLY -
Security. DoS
As usual +
- Operation safe-listing
- Complexity limits: simple and advanced
21
- INTERNAL USE ONLY -
Security. DoS
Max Stoiber Advanced security
22
- INTERNAL USE ONLY -
Versioning
23
- 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
- INTERNAL USE ONLY -
Versioning
25
- INTERNAL USE ONLY -
Versioning. Resolver alias
const getUserResolver = (root, args, context) => {
context.User.getById(args.id);
};
const resolvers = {
Query: {
getUser: getUserResolver,
user: getUserResolver,
},
};
26
- INTERNAL USE ONLY -
Versioning. @deprecated
type Query {
user(id: ID!): User @deprecated(reason: "renamed to 'getUser'")
getUser(id: ID!): User
}
27
- INTERNAL USE ONLY -
Versioning. Client fields renaming
query SuggestPublishers($limit: Int, $offset: Int, $text: String) {
items: inventorySources(limit: $limit, offset: $offset, text: $text) {
count
results {
id,
name
}
}
28
- INTERNAL USE ONLY -
Monitoring
29
- INTERNAL USE ONLY -
Monitoring
30
- INTERNAL USE ONLY -
Monitoring
31
- INTERNAL USE ONLY -
Monitoring
32
- INTERNAL USE ONLY -
Monitoring
33
- INTERNAL USE ONLY -
Files Upload
34
- INTERNAL USE ONLY -
Files upload. Server
const resolvers = {
Upload: GraphQLUpload,
Mutation: {
async singleUpload(parent, { file }) {
const { stream, filename, mimetype, encoding } = await file;
// ...
return { stream, filename, mimetype, encoding };
}
},
};
35
- INTERNAL USE ONLY -
Files upload. Server
• maxFieldSize
• maxFileSize
• maxFiles
36
- INTERNAL USE ONLY -
Files upload. Client
import { createUploadLink } from 'apollo-upload-client'
const link = createUploadLink(/* Options */)
37
- INTERNAL USE ONLY -
Files upload. Client
export default graphql(gql`
mutation($files: [Upload!]!) {
uploadFiles(files: $files) {
id
}
}
`)(({ mutate }) => (
<input type="file" multiple
onChange={({ target: { validity, files } }) =>
validity.valid && mutate({ variables: { files } })
}
/>
))
38
- INTERNAL USE ONLY -
Pagination
39
- INTERNAL USE ONLY -
Pagination
options: ({ queryVariables }) => ({
variables: queryVariables,
notifyOnNetworkStatusChange: true,
fetchPolicy: APOLLO_FETCH_POLICIES.CACHE_AND_NETWORK,
}),
40
- INTERNAL USE ONLY -
Pagination
props: ({
data: {
error,
loading: isLoading,
items: { availableCount, results = [] } = {},
fetchMore,
refetch,
networkStatus,
},
}) => {...}
41
- 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
- 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
- INTERNAL USE ONLY -
Pagination. More pitfalls
• 1.x: fetchMore doesn’t trigger rerender on error
44
- INTERNAL USE ONLY -
Pagination. Imperative update
query Feed($type: FeedType!, $offset: Int, $limit: Int) {
currentUser {
login
}
feed(type: $type, offset: $offset, limit: $limit) @connection(key: "feed") {
id
# ...
}
}
…
client.writeData
45
- INTERNAL USE ONLY -
Schema design
46
- INTERNAL USE ONLY -
Schema design
“Design by client needs”
47
- INTERNAL USE ONLY -
Schema design
• Use interfaces
• Use QueryResponse
• Use MutationResponse
• Use input types
• Use your clients style convention (GQL is flexible)
48
- INTERNAL USE ONLY -
Schema design. QueryResponse
type DealResponse {
availableCount: Int
count: Int
results: [Deal]
}
type Query {
deals(
text: String,
...
): DealResponse
}
49
- INTERNAL USE ONLY -
Schema design. MutationResponse
type LikePostMutationResponse implements MutationResponse {
code: String!
success: Boolean!
message: String!
post: Post
user: User
}
50
- 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
- INTERNAL USE ONLY -
ACL
52
- INTERNAL USE ONLY -
ACL
Available options:
• Delegate to REST API
• Authorization via context
• Authorization via directives
53
- 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
- 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
- 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
- INTERNAL USE ONLY -
State management
57
- 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
- INTERNAL USE ONLY -
State management
apollo-link-state
const client = new ApolloClient({
uri: `https://nx9zvp49q7.lp.gql.zone/graphql`,
clientState: {
defaults,
resolvers,
typeDefs
}
});
59
- INTERNAL USE ONLY -
State management. Defaults
initialState → defaults
60
- 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
- INTERNAL USE ONLY -
State management. Query
{
todos @client {
id
completed
text
}
visibilityFilter @client
}
62
- INTERNAL USE ONLY -
State management
63
- INTERNAL USE ONLY -
Performance
64
- 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
- INTERNAL USE ONLY -
Performance. Automatic Persisted Queries
66
- INTERNAL USE ONLY -
Performance. Automatic Persisted Queries
curl -g
'http://localhost:9011/graphql?extensions={"persistedQuery":{"version":1,"sha256Hash":"
ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38"}}'
67
- INTERNAL USE ONLY -
Performance. Automatic Persisted Queries
Client: apollo-link-persisted-queries
Server:
const server = new ApolloServer({
typeDefs,
resolvers,
persistedQueries: {
cache: new InMemoryCache(),
},
});
68
- 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
- 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
- INTERNAL USE ONLY -
Performance. Client caching
<Mutation
mutation={ADD_TODO}
update={(cache, { data: { addTodo } }) => {
const { todos } = cache.readQuery({ query: GET_TODOS });
cache.writeQuery({
query: GET_TODOS,
data: { todos: todos.concat([addTodo]) }
});
}}
>
Variables! (@connection)
71
- INTERNAL USE ONLY -
Performance. Client caching: cacheRedirects
const cache = new InMemoryCache({
cacheRedirects: {
Query: {
book: (_, args, { getCacheKey }) =>
getCacheKey({ __typename: 'Book', id: args.id })
},
},
});
72
- INTERNAL USE ONLY -
Performance. Prefetching
<Component
onMouseOver={() =>
client.query({
query: GET_DOG,
variables: { breed: data.breed }
})
}
/>
73
- INTERNAL USE ONLY -
Performance. Batch requests
import { BatchHttpLink } from "apollo-link-batch-http";
const link = new BatchHttpLink({ uri: "/graphql" });
74
- INTERNAL USE ONLY -
Testing React components
75
- INTERNAL USE ONLY -
Testing React Components
const mocks = [
{
request: {
query: GET_DOG_QUERY,
variables: {
name: 'Buck',
},
},
result: {
data: {
dog: { id: '1', name: 'Buck', breed: 'bulldog' },
},
},
},
];
76
- INTERNAL USE ONLY -
Testing React Components
it('renders without error', () => {
renderer.create(
<MockedProvider mocks={mocks} addTypename={false}>
<Dog name="Buck" />
</MockedProvider>,
);
});
77
- INTERNAL USE ONLY -
Testing React Components: Loading state
By default
78
- INTERNAL USE ONLY -
Testing React Components: Success state
it('should render dog', async () => {
// ...
await wait(0); // wait for nextTick
// ...
});
79
- INTERNAL USE ONLY -
Testing React Components: Error state
const dogMock = {
request: {
query: GET_DOG_QUERY,
variables: { name: 'Buck' },
},
error: new Error('aw shucks'),
};
80
- INTERNAL USE ONLY -
Server-side rendering
81
- INTERNAL USE ONLY -
Server-side rendering
getDataFromTree(App).then(() => {
const content = ReactDOM.renderToString(App);
const initialState = client.extract();
const html = <Html content={content} state={initialState} />;
res.status(200);
res.send(`<!doctype html>n${ReactDOM.renderToStaticMarkup(html)}`);
res.end();
});
82
- 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
- INTERNAL USE ONLY -
Server-side rendering. Avoid network calls
const client = new ApolloClient({
ssrMode: true,
link: new SchemaLink({ schema }),
cache: new InMemoryCache(),
});
84
- 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
- INTERNAL USE ONLY -
Server-side rendering. Rehydration
const client = new ApolloClient({
cache: new InMemoryCache().restore(window.__APOLLO_STATE__),
link,
});
86
- INTERNAL USE ONLY -
Apollo 2.1
87
- INTERNAL USE ONLY -
Apollo 2.1
<Query> / <Mutation> / <Subscription>
<ApolloConsumer />
Apollo Devtools
88
- INTERNAL USE ONLY -
Relay
89
- 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
- INTERNAL USE ONLY -
Conclusion
91
- INTERNAL USE ONLY -
Conclusion
Apollo is a fully-featured open-source* platform
92
- INTERNAL USE ONLY -
Questions?
93

More Related Content

PDF
Apache Hive Hook
PDF
Flask SQLAlchemy
PDF
Reporting solutions for ADF Applications
PPTX
New in php 7
PDF
OGSA-DAI DQP: A Developer's View
PPTX
An ADF Special Report
PDF
Rest API using Flask & SqlAlchemy
PDF
Getting Started-with-Laravel
Apache Hive Hook
Flask SQLAlchemy
Reporting solutions for ADF Applications
New in php 7
OGSA-DAI DQP: A Developer's View
An ADF Special Report
Rest API using Flask & SqlAlchemy
Getting Started-with-Laravel

What's hot (20)

KEY
Php Unit With Zend Framework Zendcon09
PDF
Lazy vs. Eager Loading Strategies in JPA 2.1
PDF
Second Level Cache in JPA Explained
PDF
PofEAA and SQLAlchemy
PDF
Rntb20200805
 
PDF
Core Java - Quiz Questions - Bug Hunt
PDF
Flask patterns
PDF
Flask RESTful Flask HTTPAuth
PDF
Practical PHP 5.3
PDF
JAX-RS and CDI Bike the (Reactive) Bridge
PDF
React native-firebase startup-mtup
 
PDF
Unit testing after Zend Framework 1.8
PPTX
Thinking Beyond ORM in JPA
PDF
Tools for Solving Performance Issues
PPTX
Web весна 2013 лекция 6
PDF
Filling the flask
PPTX
Python and EM CLI: The Enterprise Management Super Tools
PDF
PPTX
Zero to SOLID
PDF
My Top 5 APEX JavaScript API's
Php Unit With Zend Framework Zendcon09
Lazy vs. Eager Loading Strategies in JPA 2.1
Second Level Cache in JPA Explained
PofEAA and SQLAlchemy
Rntb20200805
 
Core Java - Quiz Questions - Bug Hunt
Flask patterns
Flask RESTful Flask HTTPAuth
Practical PHP 5.3
JAX-RS and CDI Bike the (Reactive) Bridge
React native-firebase startup-mtup
 
Unit testing after Zend Framework 1.8
Thinking Beyond ORM in JPA
Tools for Solving Performance Issues
Web весна 2013 лекция 6
Filling the flask
Python and EM CLI: The Enterprise Management Super Tools
Zero to SOLID
My Top 5 APEX JavaScript API's
Ad

Similar to Apollo ecosystem (20)

KEY
How and why i roll my own node.js framework
PDF
Web注入+http漏洞等描述
PDF
Future of Web Apps: Google Gears
PPTX
Iac d.damyanov 4.pptx
PDF
IR Journal (itscholar.codegency.co.in).pdf
PDF
Reason and GraphQL
PDF
Spring boot
PDF
A re introduction to webpack - reactfoo - mumbai
ZIP
Javascript Everywhere
PDF
Presto anatomy
PPT
Play!ng with scala
PDF
服务框架: Thrift & PasteScript
PDF
PuppetDB: A Single Source for Storing Your Puppet Data - PUG NY
PDF
Diseño y Desarrollo de APIs
PPTX
Solving anything in VCL
PDF
Greach 2019 - Creating Micronaut Configurations
PDF
OSMC 2009 | Icinga by Icinga Team
PDF
Hyperproductive JSF 2.0 @ JavaOne Brazil 2010
PDF
Catalyst MVC
PDF
Leveraging Playwright for API Testing.pdf
How and why i roll my own node.js framework
Web注入+http漏洞等描述
Future of Web Apps: Google Gears
Iac d.damyanov 4.pptx
IR Journal (itscholar.codegency.co.in).pdf
Reason and GraphQL
Spring boot
A re introduction to webpack - reactfoo - mumbai
Javascript Everywhere
Presto anatomy
Play!ng with scala
服务框架: Thrift & PasteScript
PuppetDB: A Single Source for Storing Your Puppet Data - PUG NY
Diseño y Desarrollo de APIs
Solving anything in VCL
Greach 2019 - Creating Micronaut Configurations
OSMC 2009 | Icinga by Icinga Team
Hyperproductive JSF 2.0 @ JavaOne Brazil 2010
Catalyst MVC
Leveraging Playwright for API Testing.pdf
Ad

Recently uploaded (20)

PPTX
communication and presentation skills 01
PDF
BIO-INSPIRED HORMONAL MODULATION AND ADAPTIVE ORCHESTRATION IN S-AI-GPT
PPTX
Information Storage and Retrieval Techniques Unit III
PDF
null (2) bgfbg bfgb bfgb fbfg bfbgf b.pdf
PDF
Automation-in-Manufacturing-Chapter-Introduction.pdf
PDF
SMART SIGNAL TIMING FOR URBAN INTERSECTIONS USING REAL-TIME VEHICLE DETECTI...
PPTX
UNIT 4 Total Quality Management .pptx
PDF
737-MAX_SRG.pdf student reference guides
PDF
keyrequirementskkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
PPT
Occupational Health and Safety Management System
PDF
Integrating Fractal Dimension and Time Series Analysis for Optimized Hyperspe...
PPTX
6ME3A-Unit-II-Sensors and Actuators_Handouts.pptx
PDF
COURSE DESCRIPTOR OF SURVEYING R24 SYLLABUS
PDF
The CXO Playbook 2025 – Future-Ready Strategies for C-Suite Leaders Cerebrai...
PDF
Mitigating Risks through Effective Management for Enhancing Organizational Pe...
PPTX
Nature of X-rays, X- Ray Equipment, Fluoroscopy
PDF
Categorization of Factors Affecting Classification Algorithms Selection
PDF
Human-AI Collaboration: Balancing Agentic AI and Autonomy in Hybrid Systems
PPT
Total quality management ppt for engineering students
PDF
Enhancing Cyber Defense Against Zero-Day Attacks using Ensemble Neural Networks
communication and presentation skills 01
BIO-INSPIRED HORMONAL MODULATION AND ADAPTIVE ORCHESTRATION IN S-AI-GPT
Information Storage and Retrieval Techniques Unit III
null (2) bgfbg bfgb bfgb fbfg bfbgf b.pdf
Automation-in-Manufacturing-Chapter-Introduction.pdf
SMART SIGNAL TIMING FOR URBAN INTERSECTIONS USING REAL-TIME VEHICLE DETECTI...
UNIT 4 Total Quality Management .pptx
737-MAX_SRG.pdf student reference guides
keyrequirementskkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
Occupational Health and Safety Management System
Integrating Fractal Dimension and Time Series Analysis for Optimized Hyperspe...
6ME3A-Unit-II-Sensors and Actuators_Handouts.pptx
COURSE DESCRIPTOR OF SURVEYING R24 SYLLABUS
The CXO Playbook 2025 – Future-Ready Strategies for C-Suite Leaders Cerebrai...
Mitigating Risks through Effective Management for Enhancing Organizational Pe...
Nature of X-rays, X- Ray Equipment, Fluoroscopy
Categorization of Factors Affecting Classification Algorithms Selection
Human-AI Collaboration: Balancing Agentic AI and Autonomy in Hybrid Systems
Total quality management ppt for engineering students
Enhancing Cyber Defense Against Zero-Day Attacks using Ensemble Neural Networks

Apollo ecosystem

  • 1. - INTERNAL USE ONLY - Apollo ecosystem 1 James Akwuh Frontend Lead @ Marketplace Group Minsk, July 2018
  • 2. - INTERNAL USE ONLY - Agenda • Preface • Setup & Examples • Tooling • Security • Versioning • Monitoring • Files upload • Pagination • Schema design • ACL • State management • Performance • Testing react components • Server-side rendering 2
  • 3. - INTERNAL USE ONLY - Agenda • Apollo 2.1 • Relay • Conclusion 3
  • 4. - INTERNAL USE ONLY - Preface 4
  • 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
  • 7. - INTERNAL USE ONLY - Setup & Examples 7
  • 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> );
  • 15. - INTERNAL USE ONLY - Tooling 15
  • 16. - INTERNAL USE ONLY - Tooling • GraphQL Playground / GraphiQL • launchpad.graphql.com • Apollo Devtools* • Apollo Engine • babel-plugin-import-graphql • ... 16
  • 17. - INTERNAL USE ONLY - Security 17
  • 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
  • 23. - INTERNAL USE ONLY - Versioning 23
  • 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
  • 25. - INTERNAL USE ONLY - Versioning 25
  • 26. - INTERNAL USE ONLY - Versioning. Resolver alias const getUserResolver = (root, args, context) => { context.User.getById(args.id); }; const resolvers = { Query: { getUser: getUserResolver, user: getUserResolver, }, }; 26
  • 27. - INTERNAL USE ONLY - Versioning. @deprecated type Query { user(id: ID!): User @deprecated(reason: "renamed to 'getUser'") getUser(id: ID!): User } 27
  • 28. - INTERNAL USE ONLY - Versioning. Client fields renaming query SuggestPublishers($limit: Int, $offset: Int, $text: String) { items: inventorySources(limit: $limit, offset: $offset, text: $text) { count results { id, name } } 28
  • 29. - INTERNAL USE ONLY - Monitoring 29
  • 30. - INTERNAL USE ONLY - Monitoring 30
  • 31. - INTERNAL USE ONLY - Monitoring 31
  • 32. - INTERNAL USE ONLY - Monitoring 32
  • 33. - INTERNAL USE ONLY - Monitoring 33
  • 34. - INTERNAL USE ONLY - Files Upload 34
  • 35. - INTERNAL USE ONLY - Files upload. Server const resolvers = { Upload: GraphQLUpload, Mutation: { async singleUpload(parent, { file }) { const { stream, filename, mimetype, encoding } = await file; // ... return { stream, filename, mimetype, encoding }; } }, }; 35
  • 36. - INTERNAL USE ONLY - Files upload. Server • maxFieldSize • maxFileSize • maxFiles 36
  • 37. - INTERNAL USE ONLY - Files upload. Client import { createUploadLink } from 'apollo-upload-client' const link = createUploadLink(/* Options */) 37
  • 38. - INTERNAL USE ONLY - Files upload. Client export default graphql(gql` mutation($files: [Upload!]!) { uploadFiles(files: $files) { id } } `)(({ mutate }) => ( <input type="file" multiple onChange={({ target: { validity, files } }) => validity.valid && mutate({ variables: { files } }) } /> )) 38
  • 39. - INTERNAL USE ONLY - Pagination 39
  • 40. - INTERNAL USE ONLY - Pagination options: ({ queryVariables }) => ({ variables: queryVariables, notifyOnNetworkStatusChange: true, fetchPolicy: APOLLO_FETCH_POLICIES.CACHE_AND_NETWORK, }), 40
  • 41. - INTERNAL USE ONLY - Pagination props: ({ data: { error, loading: isLoading, items: { availableCount, results = [] } = {}, fetchMore, refetch, networkStatus, }, }) => {...} 41
  • 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
  • 45. - INTERNAL USE ONLY - Pagination. Imperative update query Feed($type: FeedType!, $offset: Int, $limit: Int) { currentUser { login } feed(type: $type, offset: $offset, limit: $limit) @connection(key: "feed") { id # ... } } … client.writeData 45
  • 46. - INTERNAL USE ONLY - Schema design 46
  • 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
  • 52. - INTERNAL USE ONLY - ACL 52
  • 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
  • 57. - INTERNAL USE ONLY - State management 57
  • 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
  • 63. - INTERNAL USE ONLY - State management 63
  • 64. - INTERNAL USE ONLY - Performance 64
  • 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
  • 71. - INTERNAL USE ONLY - Performance. Client caching <Mutation mutation={ADD_TODO} update={(cache, { data: { addTodo } }) => { const { todos } = cache.readQuery({ query: GET_TODOS }); cache.writeQuery({ query: GET_TODOS, data: { todos: todos.concat([addTodo]) } }); }} > Variables! (@connection) 71
  • 72. - INTERNAL USE ONLY - Performance. Client caching: cacheRedirects const cache = new InMemoryCache({ cacheRedirects: { Query: { book: (_, args, { getCacheKey }) => getCacheKey({ __typename: 'Book', id: args.id }) }, }, }); 72
  • 73. - INTERNAL USE ONLY - Performance. Prefetching <Component onMouseOver={() => client.query({ query: GET_DOG, variables: { breed: data.breed } }) } /> 73
  • 74. - INTERNAL USE ONLY - Performance. Batch requests import { BatchHttpLink } from "apollo-link-batch-http"; const link = new BatchHttpLink({ uri: "/graphql" }); 74
  • 75. - INTERNAL USE ONLY - Testing React components 75
  • 76. - INTERNAL USE ONLY - Testing React Components const mocks = [ { request: { query: GET_DOG_QUERY, variables: { name: 'Buck', }, }, result: { data: { dog: { id: '1', name: 'Buck', breed: 'bulldog' }, }, }, }, ]; 76
  • 77. - INTERNAL USE ONLY - Testing React Components it('renders without error', () => { renderer.create( <MockedProvider mocks={mocks} addTypename={false}> <Dog name="Buck" /> </MockedProvider>, ); }); 77
  • 78. - INTERNAL USE ONLY - Testing React Components: Loading state By default 78
  • 79. - INTERNAL USE ONLY - Testing React Components: Success state it('should render dog', async () => { // ... await wait(0); // wait for nextTick // ... }); 79
  • 80. - INTERNAL USE ONLY - Testing React Components: Error state const dogMock = { request: { query: GET_DOG_QUERY, variables: { name: 'Buck' }, }, error: new Error('aw shucks'), }; 80
  • 81. - INTERNAL USE ONLY - Server-side rendering 81
  • 82. - INTERNAL USE ONLY - Server-side rendering getDataFromTree(App).then(() => { const content = ReactDOM.renderToString(App); const initialState = client.extract(); const html = <Html content={content} state={initialState} />; res.status(200); res.send(`<!doctype html>n${ReactDOM.renderToStaticMarkup(html)}`); res.end(); }); 82
  • 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
  • 87. - INTERNAL USE ONLY - Apollo 2.1 87
  • 88. - INTERNAL USE ONLY - Apollo 2.1 <Query> / <Mutation> / <Subscription> <ApolloConsumer /> Apollo Devtools 88
  • 89. - INTERNAL USE ONLY - Relay 89
  • 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
  • 91. - INTERNAL USE ONLY - Conclusion 91
  • 92. - INTERNAL USE ONLY - Conclusion Apollo is a fully-featured open-source* platform 92
  • 93. - INTERNAL USE ONLY - Questions? 93