eolas/neuron/d0ed26d0-cdc8-4643-8c09-445408195f9b/.neuron/output/Apollo_Server.html

144 lines
22 KiB
HTML
Raw Normal View History

2024-10-20 19:00:04 +01:00
<!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.zette
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>