mirror of
https://github.com/bonfire-networks/bonfire-app.git
synced 2024-05-17 00:22:40 +00:00
287 lines
23 KiB
HTML
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">"hello"</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">"dear reader"</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">"hallo"</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">"beste lezer"</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">&</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'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">&</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">"hello"</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">"dear reader"</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">"hallo"</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">"beste lezer"</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'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">></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>
|