eolas/neuron/d0ed26d0-cdc8-4643-8c09-445408195f9b/.neuron/output/Apollo_Server.html
2024-10-20 19:00:04 +01:00

144 lines
No EOL
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html><html><head><meta content="text/html; charset=utf-8" http-equiv="Content-Type" /><meta content="width=device-width, initial-scale=1" name="viewport" /><!--replace-start-0--><!--replace-start-5--><!--replace-start-8--><title>Apollo Server - My Zettelkasten</title><!--replace-end-8--><!--replace-end-5--><!--replace-end-0--><link href="https://cdn.jsdelivr.net/npm/fomantic-ui@2.8.7/dist/semantic.min.css" rel="stylesheet" /><link href="https://fonts.googleapis.com/css?family=Merriweather|Libre+Franklin|Roboto+Mono&amp;display=swap" rel="stylesheet" /><!--replace-start-1--><!--replace-start-4--><!--replace-start-7--><link href="https://raw.githubusercontent.com/srid/neuron/master/assets/neuron.svg" rel="icon" /><meta content="Apollo Server is the part of the Apollo suite that we use to create the backend of a GraphQL project: a GraphQL server." name="description" /><meta content="Apollo Server" property="og:title" /><meta content="My Zettelkasten" property="og:site_name" /><meta content="article" property="og:type" /><meta content="Apollo_Server" property="neuron:zettel-id" /><meta content="Apollo_Server" property="neuron:zettel-slug" /><meta content="APIs" property="neuron:zettel-tag" /><meta content="REST" property="neuron:zettel-tag" /><meta content="graphql" property="neuron:zettel-tag" /><script type="application/ld+json">[]</script><style type="text/css">body{background-color:#eeeeee !important;font-family:"Libre Franklin", serif !important}body .ui.container{font-family:"Libre Franklin", serif !important}body h1, h2, h3, h4, h5, h6, .ui.header, .headerFont{font-family:"Merriweather", sans-serif !important}body code, pre, tt, .monoFont{font-family:"Roboto Mono","SFMono-Regular","Menlo","Monaco","Consolas","Liberation Mono","Courier New", monospace !important}body div.z-index p.info{color:#808080}body div.z-index ul{list-style-type:square;padding-left:1.5em}body div.z-index .uplinks{margin-left:0.29999em}body .zettel-content h1#title-h1{background-color:rgba(33,133,208,0.1)}body nav.bottomPane{background-color:rgba(33,133,208,2.0e-2)}body div#footnotes{border-top-color:#2185d0}body p{line-height:150%}body img{max-width:100%}body .deemphasized{font-size:0.94999em}body .deemphasized:hover{opacity:1}body .deemphasized:not(:hover){opacity:0.69999}body .deemphasized:not(:hover) a{color:#808080 !important}body div.container.universe{padding-top:1em}body div.zettel-view ul{padding-left:1.5em;list-style-type:square}body div.zettel-view .pandoc .highlight{background-color:#ffff00}body div.zettel-view .pandoc .ui.disabled.fitted.checkbox{margin-right:0.29999em;vertical-align:middle}body div.zettel-view .zettel-content .metadata{margin-top:1em}body div.zettel-view .zettel-content .metadata div.date{text-align:center;color:#808080}body div.zettel-view .zettel-content h1{padding-top:0.2em;padding-bottom:0.2em;text-align:center}body div.zettel-view .zettel-content h2{border-bottom:solid 1px #4682b4;margin-bottom:0.5em}body div.zettel-view .zettel-content h3{margin:0px 0px 0.4em 0px}body div.zettel-view .zettel-content h4{opacity:0.8}body div.zettel-view .zettel-content div#footnotes{margin-top:4em;border-top-style:groove;border-top-width:2px;font-size:0.9em}body div.zettel-view .zettel-content div#footnotes ol > li > p:only-of-type{display:inline;margin-right:0.5em}body div.zettel-view .zettel-content aside.footnote-inline{width:30%;padding-left:15px;margin-left:15px;float:right;background-color:#d3d3d3}body div.zettel-view .zettel-content .overflows{overflow:auto}body div.zettel-view .zettel-content code{margin:auto auto auto auto;font-size:100%}body div.zettel-view .zettel-content p code, li code, ol code{padding:0.2em 0.2em 0.2em 0.2em;background-color:#f5f2f0}body div.zettel-view .zettel-content pre{overflow:auto}body div.zettel-view .zettel-content dl dt{font-weight:bold}body div.zettel-view .zettel-content blockquote{background-color:#f9f9f9;border-left:solid 10px #cccccc;margin:1.5em 0px 1.5em 0px;padding:0.5em 10px 0.5em 10px}body div.zettel-view .zettel-content.raw{background-color:#dddddd}body .ui.label.zettel-tag{color:#000000}body .ui.label.zettel-tag a{color:#000000}body nav.bottomPane ul.backlinks > li{padding-bottom:0.4em;list-style-type:disc}body nav.bottomPane ul.context-list > li{list-style-type:lower-roman}body .footer-version img{-webkit-filter:grayscale(100%);-moz-filter:grayscale(100%);-ms-filter:grayscale(100%);-o-filter:grayscale(100%);filter:grayscale(100%)}body .footer-version img:hover{-webkit-filter:grayscale(0%);-moz-filter:grayscale(0%);-ms-filter:grayscale(0%);-o-filter:grayscale(0%);filter:grayscale(0%)}body .footer-version, .footer-version a, .footer-version a:visited{color:#808080}body .footer-version a{font-weight:bold}body .footer-version{margin-top:1em !important;font-size:0.69999em}@media only screen and (max-width: 768px){body div#zettel-container{margin-left:0.4em !important;margin-right:0.4em !important}}body span.zettel-link-container span.zettel-link a{color:#2185d0;font-weight:bold;text-decoration:none}body span.zettel-link-container span.zettel-link a:hover{background-color:rgba(33,133,208,0.1)}body span.zettel-link-container span.extra{color:auto}body span.zettel-link-container.errors{border:solid 1px #ff0000}body span.zettel-link-container.errors span.zettel-link a:hover{text-decoration:none !important;cursor:not-allowed}body [data-tooltip]:after{font-size:0.69999em}body div.tag-tree div.node{font-weight:bold}body div.tag-tree div.node a.inactive{color:#555555}body .tree.flipped{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}body .tree{overflow:auto}body .tree ul.root{padding-top:0px;margin-top:0px}body .tree ul{position:relative;padding:1em 0px 0px 0px;white-space:nowrap;margin:0px auto 0px auto;text-align:center}body .tree ul::after{content:"";display:table;clear:both}body .tree ul:last-child{padding-bottom:0.1em}body .tree li{display:inline-block;vertical-align:top;text-align:center;list-style-type:none;position:relative;padding:1em 0.5em 0em 0.5em}body .tree li::before{content:"";position:absolute;top:0px;right:50%;border-top:solid 2px #cccccc;width:50%;height:1.19999em}body .tree li::after{content:"";position:absolute;top:0px;right:50%;border-top:solid 2px #cccccc;width:50%;height:1.19999em}body .tree li::after{right:auto;left:50%;border-left:solid 2px #cccccc}body .tree li:only-child{padding-top:0em}body .tree li:only-child::after{display:none}body .tree li:only-child::before{display:none}body .tree li:first-child::before{border-style:none;border-width:0px}body .tree li:first-child::after{border-radius:5px 0px 0px 0px}body .tree li:last-child::after{border-style:none;border-width:0px}body .tree li:last-child::before{border-right:solid 2px #cccccc;border-radius:0px 5px 0px 0px}body .tree ul ul::before{content:"";position:absolute;top:0px;left:50%;border-left:solid 2px #cccccc;width:0px;height:1.19999em}body .tree li div.forest-link{border:solid 2px #cccccc;padding:0.2em 0.29999em 0.2em 0.29999em;text-decoration:none;display:inline-block;border-radius:5px 5px 5px 5px;color:#333333;position:relative;top:2px}body .tree.flipped li div.forest-link{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}</style><script
async=""
id="MathJax-script"
src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"
></script>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/themes/prism.min.css"
rel="stylesheet"
/><link rel="preconnect" href="https://fonts.googleapis.com" /><link
rel="preconnect"
href="https://fonts.gstatic.com"
crossorigin
/><link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&family=IBM+Plex+Sans+Condensed:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&family=IBM+Plex+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&family=IBM+Plex+Serif:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap"
rel="stylesheet"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/components/prism-core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/plugins/autoloader/prism-autoloader.min.js"></script>
<style>
body .ui.container,
body ul {
font-family: "IBM Plex Sans" !important;
}
body blockquote {
border-left-width: 3px !important;
font-style: italic;
}
.headerFont,
.ui.header,
body h1,
h2,
h3,
h4,
h5,
h6 {
font-family: "IBM Plex Sans Condensed" !important;
}
body p {
line-height: 1.4;
}
.monoFont,
body code,
pre,
tt {
font-family: "IBM Plex Mono" !important;
font-size: 12px !important;
line-height: 1.4 !important;
}
</style>
<!--replace-end-7--><!--replace-end-4--><!--replace-end-1--></head><body><div class="ui fluid container universe"><!--replace-start-2--><!--replace-start-3--><!--replace-start-6--><div class="ui text container" id="zettel-container" style="position: relative"><div class="zettel-view"><article class="ui raised attached segment zettel-content"><div class="pandoc"><h1 id="title-h1">Apollo Server</h1><blockquote><p>Apollo Server is the part of the Apollo suite that we use to create the backend of a GraphQL project: a GraphQL server.</p></blockquote><p>It is able to do the following:</p><ul><li>Receive an incoming GraphQL query from a client</li><li>Validate that query against the server schema</li><li>Populate the queried schema fields</li><li>Return the fields as a JSON response object</li></ul><h2 id="example-schema">Example schema</h2><p>We will use the following schema in the examples.</p><pre><code class="js language-js">// schema.js
const typeDefs = gql`
&quot; Our schema types will be nested here
`;
module.exports = typeDefs;</code></pre><pre><code class="js language-js">type Query {
tracksForHome: [Track!]!
}
type Track {
id: ID!
author: Author!
thumbnail: String
length: Int
modulesCount: Int
}
type Author {
id: ID!
name: String!
photo: String
}</code></pre><h2 id="setting-up-the-server">Setting up the server</h2><p>We instantiate an <code>ApolloServer</code> instance and pass our schema to it. We then subscribe to it with a <a href="Node_JS_events_module.md#extending-the-eventemitter-class">listener</a>.</p><pre><code class="js language-js">// index.js
const { ApolloServer } = require(&quot;apollo-server&quot;);
const typeDefs = require(&quot;./schema&quot;);
const server = new ApolloServer({ typeDefs });
server.listen().then(() =&gt; {
console.log(`
Server is running!
Listening on port 4000
Query at http://localhost:4000
`);
});</code></pre><p>When we access the local URL we are able to access the Apollo server using the Explorer GUI. This automatically loads our schema and is basically a more fancy version of GraphiQL:</p><p><img src="/static/apollo-explorer.png" /></p><p>It makes it easy to read descriptions of the dataypes and to construct queries by clicking to insert fields.</p><h3 id="adding-some-mock-data">Adding some mock data</h3><p>We are not connected to a database yet but we can create a mock that will enable us to run test queries.</p><p>We do this just by updating the Apollo Server options. We can either use generic dummy data or provide our own mock.</p><h4 id="generic-mock">Generic mock</h4><pre><code class="js language-js">const server = new ApolloServer({ typeDefs, mocks: true });</code></pre><h4 id="custom-mock">Custom mock</h4><pre><code class="js language-js">const mocks = {
Track: () =&gt; ({
id: () =&gt; &quot;track_01&quot;,
author: () =&gt; {
return {
name: &quot;Grumpy Cat&quot;,
photo:
&quot;https://res.cloudinary.com/dety84pbu/image/upload/v1606816219/kitty-veyron-sm_mctf3c.jpg&quot;,
};
},
thumbnail: () =&gt;
&quot;https://res.cloudinary.com/dety84pbu/image/upload/v1598465568/nebula_cat_djkt9r.jpg&quot;,
length: () =&gt; 1210,
modulesCount: () =&gt; 6,
}),
};
const server = new ApolloServer({ typeDefs, mocks });</code></pre><p>We can now <a href="Apollo_Client.md#running-a-query">run queries</a> against our server.</p><h2 id="implementing-resolvers">Implementing resolvers</h2><p>A resolver is a <a href="Creating_a_GraphQL_server.md#resolvers">function</a> that populates data for a given query. It should have <strong>the same name as the field for the query</strong>. So far we have one query in our schema: <code>tracksForHome</code> which returns the tracks to be listed on the home page. We must therefore also name our resolver for this query <code>tracksForHome</code>.</p><p>It can fetch data from a single data source or multiple data sources (other servers, databases, REST APIs) and present this as a single integrated resource to the client, matching the shape requested.</p><p>As per the <a href="Creating_a_GraphQL_server.md#resolvers">generic example</a>, you write write your resolvers as keys on a <code>resolvers</code> object, e.g:</p><pre><code class="js language-js">const resolvers = {};</code></pre><p>The <code>resolvers</code> objects keys will correspond to the schemas types and fields. You distinguish resolvers which directly correspond to a query in the schema from other resolver types by wraping them in <code>Query {}</code>.</p><pre><code class="js language-js">const resolvers = {
Query: {
tracksForHome: () =&gt; {},
},
};</code></pre><h3 id="resolver-parameters">Resolver parameters</h3><p>Each resolver function has the same standard parameters that you can invoke when implementing the resolution: <code>resolverFunction(parent, args, context, info)</code>.</p><ul><li><code>parent</code><ul><li>Used with <a href="Using_arguments_with_Apollo_Client.md#resolver-chains">resolver chains</a> ---add example</li></ul></li><li><code>args</code><ul><li>an object comprising arguments provided for the given field by the client. For instance if the client requests a field with an accompanying <code>id</code> argument, <code>id</code> can be parsed via the <code>args</code> object</li></ul></li><li><code>context</code><ul><li>shared state between different resolvers that contains essential connection parameters such as authentication, a database connection, or a <code>RESTDataSource</code> (see below). This will be typically instantiated via a class which is then invoked within the <code>ApolloServer</code> instance under the <code>dataSources</code> key.</li></ul></li><li><code>info</code><ul><li>not used so frequently but employed as part of caching</li></ul></li></ul><p>Typically you wont use every parameter with every resolver. You can ommit them with <code>_, __</code>; the number of dashes indicating the argument placement.</p><h3 id="restdatasource"><code>RESTDataSource</code></h3><p>A resolver can return data from multiple sources. One of the most common sources is a RESTful endpoint. Apollo provides a specific class for handling REST endpoints in your resolvers: <code>RESTDataSource</code>.</p><p>REST APIs fall victim to the “n + 1” problem: say you want to get an array of one resource type, then for each element returned you need to send another request using one of its properties to fetch a related resource.</p><p>This is implicit in the case of the <code>Track</code> type in the schema. Each <code>Track</code> has an <code>author</code> key but the <code>Author</code> type isnt embedded in <code>Track</code> it has to be fetched using an <code>id</code>. In a REST API, this would require a request to a separate end point for each <code>Track</code> returned, increasing the time complexity of the request.</p><p>Here is an example of <code>RESTDataSource</code> being used. It is just a class that can be extended and which provides inbuilt methods for running fetches against a REST API:</p><pre><code class="js language-js">const { RESTDataSource } = require(&quot;apollo-datasource-rest&quot;);
class TrackAPI extends RESTDataSource {
constructor() {
super();
this.baseURL = &quot;https://odyssey-lift-off-rest-api.herokuapp.com/&quot;;
}
getTracksForHome() {
return this.get(&quot;tracks&quot;);
}
getAuthor(authorId) {
return this.get(`author/${authorId}`);
}
}</code></pre><h3 id="using-our-restdatasource-in-our-resolver">Using our <code>RESTDataSource</code> in our resolver</h3><p>As our GraphQL server is sourcing data from a REST API, we can now integrate the <code>RESTDataSource</code> class with our resolver.</p><p>First thing, we need to instantiate an instance of our <code>TrackApi</code> class, otherwise we wont be able to use any of its methods in the resolver.</p><p>We will create an instance of this class and pass it into <code>ApolloServer</code> object we established at the beginning. We will pass it to the <code>dataSources</code> key. <strong>This will allow us to access it from within the <code>context</code> parameter in our resolver function</strong></p><p>We can also get rid of the <code>mocks</code> object since we dont need it any more. We will replace it with our <code>resolvers</code> constant:</p><pre><code class="diff language-diff">const server = new ApolloServer({
typeDefs,
- mocks,
+ resolvers,
+ dataSources: () =&gt; {
+ return {
+ trackApi: new TrackApi()
+ }
}
})</code></pre><p>Now we can complete our resolver:</p><pre><code class="js language-js">const resolvers = {
Query: {
tracksForHome: (_, __, {dataSources}) =&gt; {},
return dataSources.trackApi.getTracksForHome()
},
};</code></pre><p>So we destructure the <code>dataSources</code> object from the parent Apollo Server instance (in the place of the <code>context</code> parameter) which gives us access to our <code>trackApi</code> class. This resolver will now make the API request and return the tracks.</p><p>The <code>tracksForHome</code> query returns <code>Track</code> objects and these have a required <code>author</code> key that returns an <code>Author</code> type. So we are also going to need a resolver that can return the author data that will be populated along with <code>Track</code>.</p><p>We already have this functionality in our class: <code>getAuthor</code> so we just need to integrate it:</p><pre><code class="js language-js">const resolvers = {
Query: {
tracksForHome: (_, __, { dataSources }) =&gt; {
return dataSources.trackApi.getTracksForHome();
},
},
Track: {
author: ({ authorId }, _, { dataSources }) =&gt; {
return dataSources.trackApi.getAuthor(authorId);
},
},
};</code></pre><ul><li>Just as we nest the <code>tracksForHome</code> resolver under <code>Query</code>, we must nest <code>author</code> under <code>Track</code> to match the structure of the schema. This resolver doesnt respond to a query that is exposed to the client so it shouldnt go under <code>Query</code>.</li></ul><ul><li>We invoke the <code>context</code> again when we destructure <code>dataSources</code> from the <code>ApolloServer</code> instance.</li><li>This time we utilise the <code>args</code> parameter in the resolver since an <code>id</code> will be provided as a client-side <span class="zettel-link-container cf"><span class="zettel-link" title="Zettel: Using arguments with Apollo Client"><a href="Using_arguments_with_Apollo_Client.html">argument</a></span></span> to return a specific author.</li></ul><h2 id="the-usemutation-hook">The <code>useMutation</code> hook</h2><p>We invoke the <code>useMutation</code> hook to issue mutations from the client-side.</p><p>As with queries and <a href="Apollo_Client.md#query-constants">query constants</a></p></div></article><nav class="ui attached segment deemphasized backlinksPane" id="neuron-backlinks-pane"><h3 class="ui header">Backlinks</h3><ul class="backlinks"><li><span class="zettel-link-container cf"><span class="zettel-link"><a href="Using_arguments_with_Apollo_Client.html">Using arguments with Apollo Client</a></span></span><ul class="context-list" style="zoom: 85%;"><li class="item"><div class="pandoc"><p>Now we have to create a resolver for our new <code>track</code> query. We will quickly run through the <span class="zettel-link-container cf"><span class="zettel-link" title="Zettel: Apollo Server"><a href="Apollo_Server.html">server-side process</a></span></span>.</p></div></li></ul></li><li><span class="zettel-link-container cf"><span class="zettel-link"><a href="Apollo_Client.html">Apollo Client</a></span></span><ul class="context-list" style="zoom: 85%;"><li class="item"><div class="pandoc"><p>Apollo Client is the client-side counterpart to <span class="zettel-link-container cf"><span class="zettel-link" title="Zettel: Apollo Server"><a href="Apollo_Server.html">Apollo Server</a></span></span>. We use it for managing queries and mutations from the frontend to our Apollo GraphQL server. It is specifically designed to work with React.</p></div></li></ul></li></ul></nav><nav class="ui attached segment deemphasized bottomPane" id="neuron-tags-pane"><div><span class="ui basic label zettel-tag" title="Tag">APIs</span><span class="ui basic label zettel-tag" title="Tag">REST</span><span class="ui basic label zettel-tag" title="Tag">graphql</span></div></nav><nav class="ui bottom attached icon compact inverted menu blue" id="neuron-nav-bar"><!--replace-start-9--><!--replace-end-9--><a class="right item" href="impulse.html" title="Open Impulse"><i class="wave square icon"></i></a></nav></div></div><!--replace-end-6--><!--replace-end-3--><!--replace-end-2--><div class="ui center aligned container footer-version"><div class="ui tiny image"><a href="https://neuron.zettel.page"><img alt="logo" src="https://raw.githubusercontent.com/srid/neuron/master/assets/neuron.svg" title="Generated by Neuron 1.9.35.3" /></a></div></div></div></body></html>