bonfire-app/graphql.html
2024-04-16 21:21:08 +00:00

287 lines
23 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="ExDoc v0.31.2">
<meta name="project" content="bonfire_umbrella v0.9.10-cooperation-beta.62">
<title>GraphQL API — bonfire_umbrella v0.9.10-cooperation-beta.62</title>
<link rel="stylesheet" href="dist/html-elixir-JKHCEBPC.css" />
<script src="dist/handlebars.runtime-NWIB6V2M.js"></script>
<script src="dist/handlebars.templates-A7S2WMC7.js"></script>
<script src="dist/sidebar_items-0AD831F9.js"></script>
<script src="docs_config.js"></script>
<script async src="dist/html-JRPQ5PR6.js"></script>
</head>
<body data-type="extras" class="page-extra">
<script>
try {
var settings = JSON.parse(localStorage.getItem('ex_doc:settings') || '{}');
if (settings.theme === 'dark' ||
((settings.theme === 'system' || settings.theme == null) &&
window.matchMedia('(prefers-color-scheme: dark)').matches)
) {
document.body.classList.add('dark')
}
} catch (error) { }
</script>
<div class="main">
<button id="sidebar-menu" class="sidebar-button sidebar-toggle" aria-label="toggle sidebar" aria-controls="sidebar">
<i class="ri-menu-line ri-lg" title="Collapse/expand sidebar"></i>
</button>
<div class="background-layer"></div>
<nav id="sidebar" class="sidebar">
<div class="sidebar-header">
<div class="sidebar-projectInfo">
<a href="https://bonfirenetworks.org" class="sidebar-projectImage">
<img src="assets/logo.png" alt="bonfire_umbrella" />
</a>
<div>
<a href="https://bonfirenetworks.org" class="sidebar-projectName" translate="no">
bonfire_umbrella
</a>
<div class="sidebar-projectVersion" translate="no">
v0.9.10-cooperation-beta.62
</div>
</div>
</div>
<ul id="sidebar-listNav" class="sidebar-listNav" role="tablist">
<li>
<button id="extras-list-tab-button" role="tab" data-type="extras" aria-controls="extras-tab-panel" aria-selected="true" tabindex="0">
Pages
</button>
</li>
<li>
<button id="modules-list-tab-button" role="tab" data-type="modules" aria-controls="modules-tab-panel" aria-selected="false" tabindex="-1">
Modules
</button>
</li>
</ul>
</div>
<div id="extras-tab-panel" class="sidebar-tabpanel" role="tabpanel" aria-labelledby="extras-list-tab-button">
<ul id="extras-full-list" class="full-list"></ul>
</div>
<div id="modules-tab-panel" class="sidebar-tabpanel" role="tabpanel" aria-labelledby="modules-list-tab-button" hidden>
<ul id="modules-full-list" class="full-list"></ul>
</div>
</nav>
<main class="content">
<output role="status" id="toast"></output>
<div class="content-outer">
<div id="content" class="content-inner">
<div class="top-search">
<div class="search-settings">
<form class="search-bar" action="search.html">
<label class="search-label">
<span class="sr-only">Search documentation of bonfire_umbrella</span>
<input name="q" type="text" class="search-input" placeholder="Press / to search" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
</label>
<button type="submit" class="search-button" aria-label="Submit Search">
<i class="ri-search-2-line ri-lg" aria-hidden="true" title="Submit search"></i>
</button>
<button type="button" tabindex="-1" class="search-close-button" aria-hidden="true">
<i class="ri-close-line ri-lg" title="Cancel search"></i>
</button>
</form>
<div class="autocomplete">
</div>
<button class="icon-settings display-settings">
<i class="ri-settings-3-line"></i>
<span class="sr-only">Settings</span>
</button>
</div>
</div>
<h1>
<a href="https://github.com/bonfire-networks/bonfire-app/blob/main/docs/GRAPHQL.md#L1" title="View Source" class="icon-action" rel="help">
<i class="ri-code-s-slash-line" aria-hidden="true"></i>
<span class="sr-only">View Source</span>
</a>
<span>GraphQL API</span>
</h1>
<h2 id="graphql-introduction" class="section-heading">
<a href="#graphql-introduction" class="hover-link">
<i class="ri-link-m" aria-hidden="true"></i>
</a>
<span class="text">GraphQL Introduction</span>
</h2>
<p>Go to <a href="http://your-app-url/api/">http://your-app-url/api/</a> to start playing with the GraphQL API. The GraphiQL UI should autocomplete types, queries and mutations for you, and you can also explore the schema there.</p><p>Let's start with a simple GraphQL thoeretical query:</p><pre><code class="makeup graphql" translate="no"><span class="kr">query</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="w"> </span><span class="n">greetings</span><span class="p">(</span><span class="n">limit</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="w"> </span><span class="n">greeting</span><span class="w">
</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="w"> </span><span class="n">name</span><span class="w">
</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">}</span></code></pre><p>Let's break this apart:</p><ul><li><code class="inline">query {}</code> is how you retrieve information from GraphQL.</li><li><code class="inline">greetings</code> is a <code class="inline">field</code> within the query.</li><li><code class="inline">greetings</code> takes a <code class="inline">limit</code> argument, a positive integer.</li><li><code class="inline">greetings</code> has two fields, <code class="inline">greeting</code> and <code class="inline">to</code>.</li><li><code class="inline">to</code> has one <code class="inline">field</code>, <code class="inline">name</code>.</li></ul><p>This query is asking for a list of (up to) 10 greetings and the people
they are for. Notice that the result of both <code class="inline">greetings</code> and <code class="inline">to</code> are
map/object structures with their own fields and that if the type has
multiple fields, we can select more than one field.</p><p>Here is some possible data we could get returned</p><pre><code class="makeup elixir" translate="no"><span class="p" data-group-id="5866374619-1">%{</span><span class="ss">greetings</span><span class="p">:</span><span class="w"> </span><span class="p" data-group-id="5866374619-2">[</span><span class="w">
</span><span class="p" data-group-id="5866374619-3">%{</span><span class="ss">greeting</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;hello&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="p" data-group-id="5866374619-4">%{</span><span class="w"> </span><span class="ss">name</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;dear reader&quot;</span><span class="p" data-group-id="5866374619-4">}</span><span class="p" data-group-id="5866374619-3">}</span><span class="p">,</span><span class="w"> </span><span class="c1"># english</span><span class="w">
</span><span class="p" data-group-id="5866374619-5">%{</span><span class="ss">greeting</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;hallo&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="p" data-group-id="5866374619-6">%{</span><span class="w"> </span><span class="ss">name</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;beste lezer&quot;</span><span class="p" data-group-id="5866374619-6">}</span><span class="p" data-group-id="5866374619-5">}</span><span class="p">,</span><span class="w"> </span><span class="c1"># dutch</span><span class="w">
</span><span class="p" data-group-id="5866374619-2">]</span><span class="p" data-group-id="5866374619-1">}</span></code></pre><p>Where is the magic? Typically an object type will reside in its own
table in the database, so this query is actually querying two tables
in one go. In fact, given a supporting schema, you can nest queries
arbitrarily and the backend will figure out how to run them.</p><p>The fact that you can represent arbitrarily complex queries puts quite
a lot of pressure on the backend to make it all efficient. This is
still a work in progress at this time.</p><h2 id="absinthe-introduction" class="section-heading">
<a href="#absinthe-introduction" class="hover-link">
<i class="ri-link-m" aria-hidden="true"></i>
</a>
<span class="text">Absinthe Introduction</span>
</h2>
<p>Every <code class="inline">field</code> is filled by a resolver. Let's take our existing query
and define a schema for it in Absinthe's query DSL:</p><pre><code class="makeup elixir" translate="no"><span class="kd">defmodule</span><span class="w"> </span><span class="nc">MyApp.Schema</span><span class="w"> </span><span class="k" data-group-id="6595283978-1">do</span><span class="w">
</span><span class="c1"># the schema macro language</span><span class="w">
</span><span class="kn">use</span><span class="w"> </span><span class="nc">Absinthe.Schema.Notation</span><span class="w">
</span><span class="c1"># where we will actually resolve the fields</span><span class="w">
</span><span class="kn">alias</span><span class="w"> </span><span class="nc">MyApp.Resolver</span><span class="w">
</span><span class="c1"># Our user object is pretty simple, just a name</span><span class="w">
</span><span class="n">object</span><span class="w"> </span><span class="ss">:user</span><span class="w"> </span><span class="k" data-group-id="6595283978-2">do</span><span class="w">
</span><span class="n">field</span><span class="w"> </span><span class="ss">:name</span><span class="p">,</span><span class="w"> </span><span class="n">non_null</span><span class="p" data-group-id="6595283978-3">(</span><span class="ss">:string</span><span class="p" data-group-id="6595283978-3">)</span><span class="w">
</span><span class="k" data-group-id="6595283978-2">end</span><span class="w">
</span><span class="c1"># This one is slightly more complicated, we have that nested `to`</span><span class="w">
</span><span class="n">object</span><span class="w"> </span><span class="ss">:greeting</span><span class="w"> </span><span class="k" data-group-id="6595283978-4">do</span><span class="w">
</span><span class="c1"># the easy one</span><span class="w">
</span><span class="n">field</span><span class="w"> </span><span class="ss">:greeting</span><span class="p">,</span><span class="w"> </span><span class="n">non_null</span><span class="p" data-group-id="6595283978-5">(</span><span class="ss">:string</span><span class="p" data-group-id="6595283978-5">)</span><span class="w">
</span><span class="c1"># the hard one. `edge` is the term for when we cross an object boundary.</span><span class="w">
</span><span class="n">field</span><span class="w"> </span><span class="ss">:to</span><span class="p">,</span><span class="w"> </span><span class="n">non_null</span><span class="p" data-group-id="6595283978-6">(</span><span class="ss">:user</span><span class="p" data-group-id="6595283978-6">)</span><span class="p">,</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="n">resolve</span><span class="p" data-group-id="6595283978-7">(</span><span class="o">&amp;</span><span class="nc">Resolver</span><span class="o">.</span><span class="n">to_edge</span><span class="o">/</span><span class="mi">3</span><span class="p" data-group-id="6595283978-7">)</span><span class="w">
</span><span class="k" data-group-id="6595283978-4">end</span><span class="w">
</span><span class="c1"># something to put our top level queries in, because they&#39;re just fields too!</span><span class="w">
</span><span class="n">object</span><span class="w"> </span><span class="ss">:queries</span><span class="w"> </span><span class="k" data-group-id="6595283978-8">do</span><span class="w">
</span><span class="n">field</span><span class="w"> </span><span class="ss">:greetings</span><span class="p">,</span><span class="w"> </span><span class="n">non_null</span><span class="p" data-group-id="6595283978-9">(</span><span class="n">list_of</span><span class="p" data-group-id="6595283978-10">(</span><span class="n">non_null</span><span class="p" data-group-id="6595283978-11">(</span><span class="ss">:string</span><span class="p" data-group-id="6595283978-11">)</span><span class="p" data-group-id="6595283978-10">)</span><span class="p" data-group-id="6595283978-9">)</span><span class="w"> </span><span class="k" data-group-id="6595283978-12">do</span><span class="w">
</span><span class="n">arg</span><span class="w"> </span><span class="ss">:limit</span><span class="p">,</span><span class="w"> </span><span class="ss">:integer</span><span class="w"> </span><span class="c1"># optional</span><span class="w">
</span><span class="n">resolve</span><span class="w"> </span><span class="o">&amp;</span><span class="nc">Resolver</span><span class="o">.</span><span class="n">greetings</span><span class="o">/</span><span class="mi">2</span><span class="w"> </span><span class="c1"># we need to manually process this one</span><span class="w">
</span><span class="k" data-group-id="6595283978-12">end</span><span class="w">
</span><span class="k" data-group-id="6595283978-8">end</span><span class="w">
</span><span class="k" data-group-id="6595283978-1">end</span></code></pre><p>There are a couple of interesting things about this:</p><ul><li>Sprinklings of <code class="inline">not_null</code> to require that values be present (if you
don't return them, absinthe will get upset).</li><li>Only two fields have explicit resolvers. Anything else will default
to a map key lookup (which is more often than not what you want).</li><li><code class="inline">greeting.to_edge</code> has a <code class="inline">/3</code> resolver and <code class="inline">queries.greetings</code> a
<code class="inline">/2</code> resolver.</li></ul><p>To understand the last one (and partially the middle one), we must
understand how resolution works and what a parent is. The best way of
doing that is probably to look at the resolver code:</p><pre><code class="makeup elixir" translate="no"><span class="kd">defmodule</span><span class="w"> </span><span class="nc">MyApp.Resolver</span><span class="w"> </span><span class="k" data-group-id="5716022593-1">do</span><span class="w">
</span><span class="c1"># For purposes of this, we will just fake the data out</span><span class="w">
</span><span class="kd">defp</span><span class="w"> </span><span class="nf">greetings_data</span><span class="p" data-group-id="5716022593-2">(</span><span class="p" data-group-id="5716022593-2">)</span><span class="w"> </span><span class="k" data-group-id="5716022593-3">do</span><span class="w">
</span><span class="p" data-group-id="5716022593-4">[</span><span class="w"> </span><span class="p" data-group-id="5716022593-5">%{</span><span class="ss">greeting</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;hello&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="p" data-group-id="5716022593-6">%{</span><span class="w"> </span><span class="ss">name</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;dear reader&quot;</span><span class="p" data-group-id="5716022593-6">}</span><span class="p" data-group-id="5716022593-5">}</span><span class="p">,</span><span class="w"> </span><span class="c1"># english</span><span class="w">
</span><span class="p" data-group-id="5716022593-7">%{</span><span class="ss">greeting</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;hallo&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="p" data-group-id="5716022593-8">%{</span><span class="w"> </span><span class="ss">name</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;beste lezer&quot;</span><span class="p" data-group-id="5716022593-8">}</span><span class="p" data-group-id="5716022593-7">}</span><span class="p">,</span><span class="w"> </span><span class="c1"># dutch</span><span class="w">
</span><span class="p" data-group-id="5716022593-4">]</span><span class="w">
</span><span class="k" data-group-id="5716022593-3">end</span><span class="w">
</span><span class="c1"># the /2 resolver takes only arguments (which in this case is just limit)</span><span class="w">
</span><span class="c1"># and an info (which we&#39;ll explain later)</span><span class="w">
</span><span class="kd">def</span><span class="w"> </span><span class="nf">greetings</span><span class="p" data-group-id="5716022593-9">(</span><span class="p" data-group-id="5716022593-10">%{</span><span class="ss">limit</span><span class="p">:</span><span class="w"> </span><span class="n">limit</span><span class="p" data-group-id="5716022593-10">}</span><span class="p">,</span><span class="w"> </span><span class="c">_info</span><span class="p" data-group-id="5716022593-9">)</span><span class="w"> </span><span class="ow">when</span><span class="w"> </span><span class="n">is_integer</span><span class="p" data-group-id="5716022593-11">(</span><span class="n">limit</span><span class="p" data-group-id="5716022593-11">)</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">limit</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="k" data-group-id="5716022593-12">do</span><span class="w">
</span><span class="p" data-group-id="5716022593-13">{</span><span class="ss">:ok</span><span class="p">,</span><span class="w"> </span><span class="nc">Enum</span><span class="o">.</span><span class="n">take</span><span class="p" data-group-id="5716022593-14">(</span><span class="n">greetings_data</span><span class="p" data-group-id="5716022593-15">(</span><span class="p" data-group-id="5716022593-15">)</span><span class="p">,</span><span class="w"> </span><span class="n">limit</span><span class="p" data-group-id="5716022593-14">)</span><span class="p" data-group-id="5716022593-13">}</span><span class="w"> </span><span class="c1"># absinthe expects an ok/error tuple</span><span class="w">
</span><span class="k" data-group-id="5716022593-12">end</span><span class="w">
</span><span class="kd">def</span><span class="w"> </span><span class="nf">greetings</span><span class="p" data-group-id="5716022593-16">(</span><span class="c">_args</span><span class="p">,</span><span class="w"> </span><span class="c">_info</span><span class="p" data-group-id="5716022593-16">)</span><span class="p">,</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="p" data-group-id="5716022593-17">{</span><span class="ss">:ok</span><span class="p">,</span><span class="w"> </span><span class="n">greetings_data</span><span class="p" data-group-id="5716022593-18">(</span><span class="p" data-group-id="5716022593-18">)</span><span class="p" data-group-id="5716022593-17">}</span><span class="w"> </span><span class="c1"># no limit</span><span class="w">
</span><span class="c1"># the /3 resolver takes an additional parent argument in first position.</span><span class="w">
</span><span class="c1"># `parent` here will be the `greeting` we are resolving `to` for.</span><span class="w">
</span><span class="kd">def</span><span class="w"> </span><span class="nf">to_edge</span><span class="p" data-group-id="5716022593-19">(</span><span class="n">parent</span><span class="p">,</span><span class="w"> </span><span class="n">args</span><span class="p">,</span><span class="w"> </span><span class="n">info</span><span class="p" data-group-id="5716022593-19">)</span><span class="p">,</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="nc">Map</span><span class="o">.</span><span class="n">get</span><span class="p" data-group-id="5716022593-20">(</span><span class="n">parent</span><span class="p">,</span><span class="w"> </span><span class="ss">:to</span><span class="p" data-group-id="5716022593-20">)</span><span class="w">
</span><span class="k" data-group-id="5716022593-1">end</span></code></pre><p>The keen-eyed amongst you may have noticed I said the default resolver
is a map lookup and our <code class="inline">to_edge/3</code> is a map lookup too, so
technically we didn't need to write it. But then you wouldn't have an
example of a <code class="inline">/3</code> resolver! In most of the app, these will be querying
from the database, not looking up in a constant.</p><p>So for every field, a resolver function is run. It defaults to a map
lookup, but you can override it with <code class="inline">resolve/1</code>. It may or may not
have arguments. And all absinthe resolvers return an ok/error tuple.</p><h2 id="patterns" class="section-heading">
<a href="#patterns" class="hover-link">
<i class="ri-link-m" aria-hidden="true"></i>
</a>
<span class="text">Patterns</span>
</h2>
<div class="bottom-actions">
<div class="bottom-actions-item">
<a href="boundaries.html" class="bottom-actions-button" rel="prev">
<span class="subheader">
← Previous Page
</span>
<span class="title">
Boundaries & Access Control
</span>
</a>
</div>
<div class="bottom-actions-item">
<a href="mrf.html" class="bottom-actions-button" rel="next">
<span class="subheader">
Next Page →
</span>
<span class="title">
Message Rewrite Facility
</span>
</a>
</div>
</div>
<footer class="footer">
<p>
<span class="line">
<button class="a-main footer-button display-quick-switch" title="Search HexDocs packages">
Search HexDocs
</button>
<a href="bonfire_umbrella.epub" title="ePub version">
Download ePub version
</a>
</span>
</p>
<p class="built-using">
Built using
<a href="https://github.com/elixir-lang/ex_doc" title="ExDoc" target="_blank" rel="help noopener" translate="no">ExDoc</a> (v0.31.2) for the
<a href="https://elixir-lang.org" title="Elixir" target="_blank" translate="no">Elixir programming language</a>
</p>
</footer>
</div>
</div>
</main>
</div>
</body>
</html>