<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rishichandra Wawhal</title>
    <description>The latest articles on DEV Community by Rishichandra Wawhal (@wawhal).</description>
    <link>https://dev.to/wawhal</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F101917%2Fdf52d2cc-9a67-48a1-b050-b8381e95771e.jpeg</url>
      <title>DEV Community: Rishichandra Wawhal</title>
      <link>https://dev.to/wawhal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wawhal"/>
    <language>en</language>
    <item>
      <title>Common access control patterns with Hasura GraphQL Engine</title>
      <dc:creator>Rishichandra Wawhal</dc:creator>
      <pubDate>Thu, 23 May 2019 18:22:36 +0000</pubDate>
      <link>https://dev.to/hasurahq/common-access-control-patterns-with-hasura-graphql-engine-1m4i</link>
      <guid>https://dev.to/hasurahq/common-access-control-patterns-with-hasura-graphql-engine-1m4i</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this post, we will look at some access control patterns that can be used with &lt;a href="https://github.com/hasura/graphql-engine"&gt;Hasura&lt;/a&gt; to granularly allow/restrict the data. This is a summary blog post from &lt;a href="https://twitch.tv/hasurahq"&gt;Hasura Streams&lt;/a&gt; and the video has been uploaded to &lt;a href="https://www.youtube.com/hasurahq"&gt;Youtube&lt;/a&gt;. If you prefer watching, similar examples have been covered in this video:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=wrBOKlVG69A"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q9bKRRPZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/dtvgovo3cjd0dxcnov55.png" alt="video link"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Hasura GraphQL Engine
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/hasura/graphql-engine"&gt;Hasura GraphQL Engine&lt;/a&gt; is a thin GraphQL server that sits on any Postgres database and allows you to CRUD the data with realtime GraphQL and access control&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This post assumes that you have basic understanding of Hasura and relational data models. &lt;a href="https://docs.hasura.io/1.0/graphql/manual/getting-started/index.html"&gt;Check out&lt;/a&gt; this guide if you have never used Hasura before.&lt;/p&gt;

&lt;p&gt;Hasura enables role based access control which can be integrated with most Auth providers. The access control rules in Hasura are functions of session variables. Session variables are x-hasura-* variables like x-hasura-role, x-hasura-user-id that can be decoded from the request headers of the GraphQL request. You could have any number of session variables to make the rules more granular.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PRBCH2Kf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.hasura.io/content/images/2019/05/auth-high-level-overview1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PRBCH2Kf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.hasura.io/content/images/2019/05/auth-high-level-overview1.png" alt="auth flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let us see how to set up access control rules as functions of these session variables. Hasura infers the GraphQL schema from the Postgres schema, which means that setting access control rules on the tables, columns and their relationships corresponds to setting access control rules on the fields of your GraphQL schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Permissions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  UI
&lt;/h3&gt;

&lt;p&gt;The Hasura console has a neat UI for setting permissions. It also has a filter-builder that will make building filters and checks very joyful.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Iwr-sOer--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.hasura.io/content/images/2019/05/insert_perm_example.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Iwr-sOer--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.hasura.io/content/images/2019/05/insert_perm_example.png" alt="perm-ui"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Roles
&lt;/h3&gt;

&lt;p&gt;With each GraphQL request made to it, Hasura checks the role of the client user in the session variable &lt;code&gt;x-hasura-role&lt;/code&gt;. Looking at this role, Hasura looks for the permissions for this role and allows or restricts CRUD accordingly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The roles in Hasura have nothing to do with the Postgres roles and users. These roles are implemented at the Hasura Layer&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can define these roles in the Hasura console and there is no limit on multiple roles being defined.&lt;/p&gt;

&lt;h3&gt;
  
  
  Insert Permission
&lt;/h3&gt;

&lt;p&gt;The permissions to insert rows in a table is composed of three parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Check constraint&lt;/strong&gt;: This is a boolean value built out of session variables, values of the fields of the row being inserted and all the logical operators. For example, to insert into users table, you want to set a condition like &lt;code&gt;{ "id": {  "_eq": "x-hasura-user-id" } }&lt;/code&gt;. This condition enforces that whenever a row is being inserted to the users table, it can only be inserted if the &lt;code&gt;id&lt;/code&gt; of the row is equal to the &lt;code&gt;x-hasura-user-id&lt;/code&gt; value in the session variables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Columns&lt;/strong&gt;: You can restrict insertion to only particular columns (rest of them being null, default or being inferred from &lt;code&gt;column presets&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Column Presets&lt;/strong&gt;: Column presets are values that you can set to be assigned to fields while they are being inserted. For example, when an entry is being inserted in a users table, we can take the &lt;code&gt;id&lt;/code&gt; from &lt;code&gt;x-hasura-user-id&lt;/code&gt; session variable. This helps to avoid parsing the session information on the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Select Permission
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Filter&lt;/strong&gt;: This is a boolean value built out of session variables and the values of the fields being selected. For example, in an articles table, you want all users to be allowed to query all published articles, but only their own unpublished articles. To implement that, you would add a filter like:&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"or"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"author_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"_eq"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"x-hasura-user-id"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"is_published"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Columns&lt;/strong&gt;: Sometimes you want some roles to not have access to particular columns of the table. In such cases, you can explicitly choose which columns to allow and restrict.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Update Permission
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Filter&lt;/strong&gt;: This is a boolean value built out of session variables and the values of the fields being updated. For example, in an &lt;code&gt;articles&lt;/code&gt; table, you want users to modify only their own articles. To do that, your update filter would look like:&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"author_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"_eq"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"x-hasura-user-id"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Columns&lt;/strong&gt;: You can restrict which columns can be updated. This comes handy in cases when you do not want &lt;code&gt;created_at&lt;/code&gt; field to ever be updated, but you might want to update the &lt;code&gt;title&lt;/code&gt; field in the articles table.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Column presets&lt;/strong&gt;: Like with insert, if you want to automatically update certain fields with certain values without it being explicitly mentioned in the request.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Delete Permission
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Filter&lt;/strong&gt;: Like with update, the filter on delete permisson is a boolean condition which needs to be satisfied before the the row can be deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Examples
&lt;/h2&gt;

&lt;p&gt;With the above ideas, let us look at some specific cases and how they can be modelled. Firstly, lets use a base schema for a &lt;a href="https://news.ycombinator.com"&gt;HackerNews&lt;/a&gt; like application. This is the postgres schema:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SDLEdNPg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.hasura.io/content/images/2019/05/acl-schemaspy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SDLEdNPg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.hasura.io/content/images/2019/05/acl-schemaspy.png" alt="schemaspy-schema"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me show you a sample GraphQL query to get all the articles along with their authors, comments, upvotes and downvotes.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;articles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;article_upvotes_aggregate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;article_downvotes_aggregate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now let us try to target some specific access control use cases with the above schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enforcing users to insert articles as themselves
&lt;/h3&gt;

&lt;p&gt;Say I am a user with &lt;code&gt;id&lt;/code&gt; = &lt;code&gt;123&lt;/code&gt;. This means that the value of &lt;code&gt;x-hasura-user-id&lt;/code&gt; in my session information would be &lt;code&gt;123&lt;/code&gt;. Now, when I am inserting an article in the &lt;code&gt;articles&lt;/code&gt; table, you would want the &lt;code&gt;author_id&lt;/code&gt; to be &lt;code&gt;123&lt;/code&gt;, which means you would want me to insert the article with myself as the author. To enforce that, you could take two approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Through check constraint&lt;/strong&gt;: In the insert permission of the &lt;code&gt;articles&lt;/code&gt; table, you can set a check constraint like &lt;code&gt;{ "author_id": { "_eq": "x-hasura-user-id" } }&lt;/code&gt;. This would ensure that the article would be inserted only if the &lt;code&gt;author_id&lt;/code&gt; in the insert payload matches the &lt;code&gt;x-hasura-user-id&lt;/code&gt; in the session variables.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Through column presets&lt;/strong&gt;: In the insert permission of the &lt;code&gt;articles&lt;/code&gt; table, you can disable inserting into &lt;code&gt;author_id&lt;/code&gt; column and set a column preset such that the &lt;code&gt;author_id&lt;/code&gt; is automatically taken from the &lt;code&gt;x-hasura-user-id&lt;/code&gt; session variable. In this way, the &lt;code&gt;author_id&lt;/code&gt; would be inserted appropriately without the client needing to explicitly mention it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can use exactly the same approach for restricting users from updating &lt;code&gt;articles&lt;/code&gt; that are not published by them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple roles
&lt;/h3&gt;

&lt;p&gt;Say you have two roles: &lt;code&gt;user&lt;/code&gt; and &lt;code&gt;moderator&lt;/code&gt;. A &lt;code&gt;moderator&lt;/code&gt; should be allowed to update the &lt;code&gt;title&lt;/code&gt; of every post unconditionally while a user should be allowed to update the &lt;code&gt;title&lt;/code&gt; only for their own posts. So the update permission for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;moderator&lt;/code&gt;: Should be allowed to update &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;content&lt;/code&gt; of without any filter.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user&lt;/code&gt;: Should be allowed to update the &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;content&lt;/code&gt; with the filter &lt;code&gt;{ "author_id": { "_eq": "x-hasura-user-id" } }&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, the &lt;code&gt;moderator&lt;/code&gt; should be allowed to delete every post while a user should be allowed to delete only their own posts. So the delete permission for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;moderator&lt;/code&gt;: Should be allowed to delete without checks.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user&lt;/code&gt;: Should be allowed to delete  with the filter &lt;code&gt;{ "author_id": { "_eq": "x-hasura-user-id" } }&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Access control through views
&lt;/h3&gt;

&lt;p&gt;In a typical news discussion app, the downvotes or upvotes on an article must be anonymous. This means, that the users should not be able to get all the information from &lt;code&gt;article_downvotes&lt;/code&gt; and &lt;code&gt;article_upvotes&lt;/code&gt; tables. To achieve anonymity, you can create a view that just has the &lt;code&gt;article_id&lt;/code&gt; and the number of upvotes or downvotes. For &lt;code&gt;article_downvotes&lt;/code&gt;, the view would look like:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE VIEW article_downvote_count as
SELECT article_id,
  count(*) AS downvotes
FROM article_downvotes
GROUP BY article_id;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, once this view is created, you can set a select permission on this view such that it can be selected without any checks. You can implement the restriction similarly for &lt;code&gt;article_upvotes&lt;/code&gt; table as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Downvoting based on Karma
&lt;/h3&gt;

&lt;p&gt;In a website like &lt;a href="https://news.ycombinator.com"&gt;HackerNews&lt;/a&gt;, you get down-voting privilege only when you reach a particular Karma (say 500). So on a table like &lt;code&gt;article_downvotes&lt;/code&gt; which is a many to many relationship between &lt;code&gt;articles&lt;/code&gt; and &lt;code&gt;users&lt;/code&gt;, your insert permission would be:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"and"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"_eq"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"x-hasura-user-id"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"karma"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"_gte"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The above insert permission ensures that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user is inserting an entry as themselves (&lt;code&gt;author_id&lt;/code&gt; is equal to &lt;code&gt;x-hasura-user-id&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The insert is successful only if the inserting user has karma greater than or eual to 500.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Enforcing fields to have only particular values
&lt;/h3&gt;

&lt;p&gt;You can emulate enum-like behavior using Hasura Permissions. For example, in the &lt;code&gt;articles&lt;/code&gt; table, the &lt;code&gt;content_type&lt;/code&gt; field should be either &lt;code&gt;"url"&lt;/code&gt; or &lt;code&gt;"text"&lt;/code&gt;. Any other value is invalid. Therefore, the check condition of the insert permission on &lt;code&gt;articles&lt;/code&gt; table looks like:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"_and"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"_eq"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"x-hasura-user-id"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"content_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"_in"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This permission makes sure that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user is allowed to insert only as themselves. (&lt;code&gt;author_id&lt;/code&gt; is equal to &lt;code&gt;x-hasura-user-id&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;In the article being inserted, the &lt;code&gt;content_type&lt;/code&gt; field must be either &lt;code&gt;"text"&lt;/code&gt; or &lt;code&gt;"url"&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;You can similarly add different complicated access control rules for different roles and secure your data. Let us know if you have any questions in comments or join our Discord channel where we are super active. Also, let us know if you need more examples in the comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.hasura.io/1.0/graphql/manual/auth/index.html"&gt;Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hasura/graphql-engine/"&gt;GraphQL Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=wrBOKlVG69A"&gt;Video&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hasura</category>
      <category>graphql</category>
      <category>postgres</category>
      <category>accesscontrol</category>
    </item>
    <item>
      <title>Building an image processing app with GraphQL and async serverless</title>
      <dc:creator>Rishichandra Wawhal</dc:creator>
      <pubDate>Tue, 26 Mar 2019 11:17:50 +0000</pubDate>
      <link>https://dev.to/hasurahq/building-an-image-processing-app-with-graphql-and-async-serverless-7fn</link>
      <guid>https://dev.to/hasurahq/building-an-image-processing-app-with-graphql-and-async-serverless-7fn</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.hasura.io%2Fcontent%2Fimages%2F2019%2F03%2Fimage_processing_demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.hasura.io%2Fcontent%2Fimages%2F2019%2F03%2Fimage_processing_demo.gif" alt="demo gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Image processing is best done asynchronously so that the state is not lost when the app is refreshed&lt;/li&gt;
&lt;li&gt;Serverless functions can be used to run the image processing logic&lt;/li&gt;
&lt;li&gt;Realtime GraphQL helps you be in sync with the processing state&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/hasura/graphql-engine" rel="noopener noreferrer"&gt;Hasura GraphQL Engine&lt;/a&gt; gives instant realtime GraphQL APIs over Postgres&lt;/li&gt;
&lt;li&gt;Source code: &lt;a href="https://github.com/wawhal/image-processing-app/tree/master/client" rel="noopener noreferrer"&gt;client&lt;/a&gt;, &lt;a href="https://github.com/wawhal/image-processing-app/tree/master/image-processing-logic" rel="noopener noreferrer"&gt;image processing logic&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this post we'll build a basic image processing app that lets you upload an image and add a sepia tone to it asynchronously. We will not go into the code subtleties as this is more of a philosophical rant about how async image processing apps can be built using realtime GraphQL and serverless.  &lt;/p&gt;

&lt;p&gt;We'll use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hasura GraphQL Engine as a free realtime GraphQL server over Postgres.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://hasura.io" rel="noopener noreferrer"&gt;Hasura&lt;/a&gt; gives you realtime GraphQL APIs over any Postgres database&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;An event sourcing system to trigger external webhooks on mutations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;This app follows the 3factor.app architecture pattern. 3factor app is an architecture pattern for resilient and scalabale fullstack apps. It involves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Realtime GraphQL&lt;/li&gt;
&lt;li&gt;Reliable Eventing&lt;/li&gt;
&lt;li&gt;Async serverless&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.hasura.io%2Fcontent%2Fimages%2F2019%2F03%2Fimage-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.hasura.io%2Fcontent%2Fimages%2F2019%2F03%2Fimage-3.png" alt="3factor-arch"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In case of our image processing app, the flow boils down to:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.hasura.io%2Fcontent%2Fimages%2F2019%2F03%2Frsz_image_processing_app_flow.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.hasura.io%2Fcontent%2Fimages%2F2019%2F03%2Frsz_image_processing_app_flow.jpg" alt="image processing arch"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The client uploads the image to cloud and inserts the image URL into the database with a GraphQL mutation. The client then watches the changes in the database for that particular image with GraphQL subscriptions.&lt;/li&gt;
&lt;li&gt;When the image URL has been inserted in the database, Hasura's event system calls an external webhook with the image details&lt;/li&gt;
&lt;li&gt;The external webhook converts the image to sepia tone, uploads the converted image to cloud and updates the database with the converted image URL&lt;/li&gt;
&lt;li&gt;The client receives the update when the converted image has been uploaded to the database (GraphQL subscriptions)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Backend
&lt;/h2&gt;

&lt;p&gt;First step is to get a realtime GraphQL server running in the form of Hasura GraphQL Engine. &lt;a href="https://heroku.com/deploy?template=https://github.com/hasura/graphql-engine-heroku" rel="noopener noreferrer"&gt;Click here to deploy it to Heroku's free tier with free Postgres&lt;/a&gt; (no credit card required).&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Model
&lt;/h3&gt;

&lt;p&gt;We need only one table for this app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;images&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;image_uri&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;converted_image_uri&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.hasura.io%2Fcontent%2Fimages%2F2019%2F03%2Fimages_table_create.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.hasura.io%2Fcontent%2Fimages%2F2019%2F03%2Fimages_table_create.png" alt="create table ss"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you create this table called images, Hasura provides the following root fields in its GraphQL schema:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;images&lt;/strong&gt;: To query or subscribe to the list of &lt;code&gt;images&lt;/code&gt; (comes with clauses such as where, order_by, limit)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;insert_images&lt;/strong&gt;: To insert one or more images into the &lt;code&gt;images&lt;/code&gt; table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;update_images&lt;/strong&gt;: To update one or more images in the &lt;code&gt;images&lt;/code&gt; table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;delete_images&lt;/strong&gt;: To delete one or more images in the &lt;code&gt;images&lt;/code&gt; table&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Image processing logic
&lt;/h3&gt;

&lt;p&gt;We need our image processing logic that takes our uploaded image and adds a sepia tone to it. This could be written in any language or framework and deployed on any platform. For example, in NodeJS, you can write this logic using &lt;a href="https://www.npmjs.com/package/jimp" rel="noopener noreferrer"&gt;jimp&lt;/a&gt; and serverlessify it using &lt;a href="https://zeit.co" rel="noopener noreferrer"&gt;Zeit&lt;/a&gt;. The code would look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jimp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jimp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;convertImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;jimp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image_uri&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sepia&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;writeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/tmp/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.png`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above function simply takes an object of the following form, converts it to sepia and stores the converted image at /tmp/.png.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "id": 233,
    "image_uri": "https://mycloudbucket.com/image"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the image has been converted, you also want to update the converted image URI to the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateConvertedImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;convertImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;uploadToCloud&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/tmp/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.png`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uploadResp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://image-processing-app.herokuapp.com/v1alpha1/graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
              mutation ($id: Int, $converted: String) {
                update_images (
                  _set: { converted_image_uri: $converted }
                  where: { id: { _eq: $id } }
                ) {
                  returning {
                    converted_image_uri
                    id
                  }
                }
              }
            `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;converted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uploadResp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secure_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseObj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseObj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Event sourcing
&lt;/h3&gt;

&lt;p&gt;Hasura lets you define event triggers that listen on mutations and invoke an external webhook with the mutation data. We will create one such event trigger that listens on insert_images mutation and calls the webhook that performs the logic discussed above.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.hasura.io%2Fcontent%2Fimages%2F2019%2F03%2Finsert_images_trigger_create.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.hasura.io%2Fcontent%2Fimages%2F2019%2F03%2Finsert_images_trigger_create.png" alt="create trigger"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this, our backend is ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend
&lt;/h2&gt;

&lt;p&gt;Most of the logic happens on the backend, so the front-end stays fairly clean. The front-end has two screens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Upload screen&lt;/strong&gt;: Uploads image to cloud, inserts the image URL in the database and redirects to the Convert screen with URL param &lt;code&gt;id=&amp;lt;image_id&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Convert screen&lt;/strong&gt;: Waits for the image to get processed and shows the converted image&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Upload screen
&lt;/h3&gt;

&lt;p&gt;This screen does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Takes image from user&lt;/li&gt;
&lt;li&gt;Uploads image to cloud&lt;/li&gt;
&lt;li&gt;Inserts this image in the database with GraphQL mutation&lt;/li&gt;
&lt;li&gt;Redirects to the new screen with URL parameter id= where image_id is the unique id of the inserted image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The GraphQL mutation for inserting the image to the database looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;insert_images&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;image_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;returning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;image_uri&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above mutation inserts the image URI to database and returns the &lt;code&gt;id&lt;/code&gt; of the inserted row. Next, you redirect to the Convert screen where you wait for the processed image.&lt;/p&gt;

&lt;h3&gt;
  
  
  Convert screen
&lt;/h3&gt;

&lt;p&gt;In the convert screen, you look at the id of the image in URL parameters and make a live query to the database with GraphQL subscriptions. The subscription looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;subscription&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;_eq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;image_uri&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;converted_image_uri&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While rendering the UI, you would check if the received  converted_image_uri is null. If it is, you show a loading indicator, or you show the converted image. If done in React with Apollo's Subscription components, it would look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Subscription&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-apollo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;graphql-tag&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ConvertScreen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;urlParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Subscription&lt;/span&gt;
      &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="s2"&gt;`
          subscription ($id: Int) {
            images (
              where: {
                id: {
                  _eq: $id
                }
              }
            ) {
              id
              image_uri
              converted_image_uri
            }
          }
        `&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ErrorScreen&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LoadingScreen&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid image ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;converted_image_uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LoadingScreen&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ImageScreen&lt;/span&gt;
              &lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;image_uri&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="nx"&gt;converted&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;converted_image_uri&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Subscription&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you see, in the above component:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If there is an error in subscription, we render some error UI.&lt;/li&gt;
&lt;li&gt;If the subscription is in loading state, we show a loading UI.&lt;/li&gt;
&lt;li&gt;If the subscription response is empty i.e. there is no row in the database where id is equal to the given id, we say that the id in the URL parameters is invalid.&lt;/li&gt;
&lt;li&gt;If we get a response but the converted_image_uri is null, we assume that the processing is still in progress&lt;/li&gt;
&lt;li&gt;If we have a valid converted_image_uri, we render it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Finishing up
&lt;/h2&gt;

&lt;p&gt;We discussed a pattern to build async image processing applications. You could use this architecture to build mostly all kinds of async applications. Check out &lt;a href="https://3factor.app" rel="noopener noreferrer"&gt;3factor.app&lt;/a&gt; and &lt;a href="https://hasura.io" rel="noopener noreferrer"&gt;hasura.io&lt;/a&gt; to know more about building resilient and scalable fullstack applications.&lt;/p&gt;

&lt;p&gt;If you have any questions, stack them up in the comments and they'll be answered ASAP.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.hasura.io" rel="noopener noreferrer"&gt;Hasura docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.hasura.io/1.0/graphql/manual/event-triggers/index.html" rel="noopener noreferrer"&gt;Event Triggers docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://graphql.org" rel="noopener noreferrer"&gt;GraphQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apollographql.com/docs/react/" rel="noopener noreferrer"&gt;Apollo client&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>graphql</category>
      <category>serverless</category>
      <category>postgres</category>
      <category>imageprocessing</category>
    </item>
    <item>
      <title>graphql2chartjs: Realtime charts made easy with GraphQL and ChartJS</title>
      <dc:creator>Rishichandra Wawhal</dc:creator>
      <pubDate>Fri, 15 Mar 2019 09:49:11 +0000</pubDate>
      <link>https://dev.to/hasurahq/graphql2chartjs-realtime-charts-made-easy-with-graphql-and-chartjs-33gi</link>
      <guid>https://dev.to/hasurahq/graphql2chartjs-realtime-charts-made-easy-with-graphql-and-chartjs-33gi</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fgraphql-engine-cdn.hasura.io%2Fassets%2Fgraphql2chartjs%2Flive-chart.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fgraphql-engine-cdn.hasura.io%2Fassets%2Fgraphql2chartjs%2Flive-chart.gif" alt="realtime chart with live data"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hasura.io" rel="noopener noreferrer"&gt;We&lt;/a&gt; started using &lt;a href="https://www.chartjs.org" rel="noopener noreferrer"&gt;ChartJS&lt;/a&gt; with GraphQL so that we could leverage GraphQL's realtime subscriptions to build realtime charts. Soon enough we realised that we can automate this process of restructuring the GraphQL data to a form that ChartJS expects.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;graphql2chartjs is an open source tool that reshapes your GraphQL data as per the ChartJS API. This makes building charts as easy as simply making a GraphQL query.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The idea behind this tool is to generate ChartJS compliant data object from your GraphQL response by simply adding a few aliases in your GraphQL query.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://graphql2chartjs-examples.herokuapp.com/" rel="noopener noreferrer"&gt;&lt;em&gt;Check out a live demo here&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage with Hasura
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/hasura/graphql-engine" rel="noopener noreferrer"&gt;Hasura&lt;/a&gt; gives you an instant realtime GraphQL API on an existing Postgres database. You can create views to capture analytics and aggregations on your database and instantly turn them into charts.&lt;br&gt;
If you're using Postgres and Hasura, this is what using graphql2chartjs looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fgraphql-engine-cdn.hasura.io%2Fimg%2Fgraphql2chartjs-explained.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fgraphql-engine-cdn.hasura.io%2Fimg%2Fgraphql2chartjs-explained.png" alt="graphql2chartjs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Watch this video below to see a demo/tutorial of using Hasura with an existing Postgres database, creating views and building charts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=153iv1-qFuc&amp;amp;feature=youtu.be" rel="noopener noreferrer"&gt;&lt;br&gt;
    &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.googleapis.com%2Fgraphql-engine-cdn.hasura.io%2Fassets%2Fgraphql2chartjs%2Fg2c-youtube-embed.png" alt="youtube video demo"&gt;&lt;br&gt;
  &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Continue reading: &lt;a href="https://blog.hasura.io/graphql2chartjs-realtime-charts-made-easy-with-graphql-and-chartjs/" rel="noopener noreferrer"&gt;https://blog.hasura.io/graphql2chartjs-realtime-charts-made-easy-with-graphql-and-chartjs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>charts</category>
      <category>hasura</category>
      <category>postgres</category>
    </item>
  </channel>
</rss>
