DEV Community: Saswata Mukherjee The latest articles on DEV Community by Saswata Mukherjee (@saswatamcode). https://dev.to/saswatamcode 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%2F333769%2Fe21e2c05-9a2c-4774-8b85-e52773ff6247.jpg DEV Community: Saswata Mukherjee https://dev.to/saswatamcode en Build a chat app with GraphQL Subscriptions & TypeScript: Part 3 Saswata Mukherjee Sun, 28 Feb 2021 09:09:01 +0000 https://dev.to/gdsckiitdev/build-a-chat-app-with-graphql-subscriptions-typescript-part-3-30dd https://dev.to/gdsckiitdev/build-a-chat-app-with-graphql-subscriptions-typescript-part-3-30dd <p>Now that our server's ready let's start making our frontend! We won't be adding any CSS in this article, but you can definitely style it later on!</p> <h2> Initializing your frontend </h2> <p>At the root of your project run the following. We'll be using TypeScript here as well.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>npx create-react-app chat-client <span class="nt">--template</span> typescript </code></pre> </div> <p>Once that's done, add the dependencies we'll need. We'll be using <a href="proxy.php?url=https://www.apollographql.com/docs/react/">Apollo Client</a> for this tutorial, so run,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>yarn add @apollo/client graphql subscriptions-transport-ws </code></pre> </div> <p>As Apollo Client subscriptions communicate over the <a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API">WebSocket</a> protocol, we use the <a href="proxy.php?url=https://github.com/apollographql/subscriptions-transport-ws">subscription-transport-ws</a> library.</p> <h2> Apollo Client setup </h2> <p>Now let's add in our initial setup! Open up <code>App.tsx</code> and add the following,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">ApolloClient</span><span class="p">,</span> <span class="nx">InMemoryCache</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">ApolloProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ApolloClient</span><span class="p">({</span> <span class="na">uri</span><span class="p">:</span> <span class="dl">'</span><span class="s1">http://localhost:9000/graphql</span><span class="dl">'</span><span class="p">,</span> <span class="na">cache</span><span class="p">:</span> <span class="k">new</span> <span class="nx">InMemoryCache</span><span class="p">(),</span> <span class="p">});</span> <span class="kd">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">name</span><span class="p">,</span> <span class="nx">setName</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="kr">string</span><span class="o">&gt;</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">entered</span><span class="p">,</span> <span class="nx">setEntered</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="nx">boolean</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nc">ApolloProvider</span> <span class="na">client</span><span class="p">=</span><span class="si">{</span><span class="nx">client</span><span class="si">}</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"App"</span><span class="p">&gt;</span> <span class="si">{</span><span class="o">!</span><span class="nx">entered</span> <span class="o">&amp;&amp;</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> <span class="na">id</span><span class="p">=</span><span class="s">"name"</span> <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">name</span><span class="si">}</span> <span class="na">onChange</span><span class="p">=</span><span class="si">{</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">setName</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span><span class="si">}</span> <span class="p">&gt;&lt;/</span><span class="nt">input</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">setEntered</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="si">}</span><span class="p">&gt;</span>Enter chat<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">)</span><span class="si">}</span> <span class="si">{</span><span class="nx">name</span> <span class="o">!==</span> <span class="dl">""</span> <span class="o">&amp;&amp;</span> <span class="nx">entered</span> <span class="o">&amp;&amp;</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> Chats <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">)</span><span class="si">}</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nc">ApolloProvider</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span> </code></pre> </div> <p>Alright, let's breakdown what we wrote!</p> <p>First, we initialized an <code>ApolloClient</code> instance, <code>client</code>, with our GraphQL server endpoint and the <code>InMemoryCache()</code> class provided by apollo. We then connect our <code>client</code> to React, by passing it as a prop to <code>ApolloProvider</code>. This will wrap our React app and place our client in context which means that we can access our <code>client</code> from anywhere in our component tree and execute GraphQL operations.</p> <p>Now, we would want a name from our user, so that the user can send chats in our chat app. So we declare a <code>name</code> state to store our user's name and an <code>entered</code> state so that we can figure when to show the chats and when to show an "enter chat" screen which would let the user enter their name. We use pretty simple conditional rendering to do this. </p> <p>If the user hasn't entered the chat or provided their name, i.e, if <code>entered</code> is false, we show an input field to set the <code>name</code> state and an "Enter chat" button which sets <code>entered</code> to true. If <code>entered</code> is true and <code>name</code> isn't an empty string, we show chats (we'll be adding components for this soon). Also, we'll be using <code>name</code> as a local state and threading it through our components for now.</p> <p>This is great up till now, but if you remember, our GraphQL API has a query, mutation, and a subscription. The query and mutation are resolved via our HTTP endpoint, but the subscription requires a separate WebSocket endpoint, which we haven't provided to our client yet. So let's go ahead and add that!<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">ApolloClient</span><span class="p">,</span> <span class="nx">InMemoryCache</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">ApolloProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">WebSocketLink</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client/link/ws</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">split</span><span class="p">,</span> <span class="nx">HttpLink</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">getMainDefinition</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client/utilities</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">wsLink</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">WebSocketLink</span><span class="p">({</span> <span class="na">uri</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ws://localhost:9000/subscriptions</span><span class="dl">"</span><span class="p">,</span> <span class="na">options</span><span class="p">:</span> <span class="p">{</span> <span class="na">reconnect</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="p">},</span> <span class="p">});</span> <span class="kd">const</span> <span class="nx">httpLink</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HttpLink</span><span class="p">({</span> <span class="na">uri</span><span class="p">:</span> <span class="dl">"</span><span class="s2">http://localhost:9000/graphql</span><span class="dl">"</span><span class="p">,</span> <span class="na">credentials</span><span class="p">:</span> <span class="dl">"</span><span class="s2">include</span><span class="dl">"</span><span class="p">,</span> <span class="p">});</span> <span class="kd">const</span> <span class="nx">link</span> <span class="o">=</span> <span class="nx">split</span><span class="p">(</span> <span class="p">({</span> <span class="nx">query</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">definition</span> <span class="o">=</span> <span class="nx">getMainDefinition</span><span class="p">(</span><span class="nx">query</span><span class="p">);</span> <span class="k">return</span> <span class="p">(</span> <span class="nx">definition</span><span class="p">.</span><span class="nx">kind</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">OperationDefinition</span><span class="dl">"</span> <span class="o">&amp;&amp;</span> <span class="nx">definition</span><span class="p">.</span><span class="nx">operation</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">subscription</span><span class="dl">"</span> <span class="p">);</span> <span class="p">},</span> <span class="nx">wsLink</span><span class="p">,</span> <span class="nx">httpLink</span> <span class="p">);</span> <span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ApolloClient</span><span class="p">({</span> <span class="nx">link</span><span class="p">,</span> <span class="na">cache</span><span class="p">:</span> <span class="k">new</span> <span class="nx">InMemoryCache</span><span class="p">(),</span> <span class="p">});</span> <span class="kd">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">name</span><span class="p">,</span> <span class="nx">setName</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="kr">string</span><span class="o">&gt;</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">entered</span><span class="p">,</span> <span class="nx">setEntered</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="nx">boolean</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nc">ApolloProvider</span> <span class="na">client</span><span class="p">=</span><span class="si">{</span><span class="nx">client</span><span class="si">}</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"App"</span><span class="p">&gt;</span> <span class="si">{</span><span class="o">!</span><span class="nx">entered</span> <span class="o">&amp;&amp;</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> <span class="na">id</span><span class="p">=</span><span class="s">"name"</span> <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">name</span><span class="si">}</span> <span class="na">onChange</span><span class="p">=</span><span class="si">{</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">setName</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span><span class="si">}</span> <span class="p">&gt;&lt;/</span><span class="nt">input</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">setEntered</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="si">}</span><span class="p">&gt;</span>Enter chat<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">)</span><span class="si">}</span> <span class="si">{</span><span class="nx">name</span> <span class="o">!==</span> <span class="dl">""</span> <span class="o">&amp;&amp;</span> <span class="nx">entered</span> <span class="o">&amp;&amp;</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> Chats <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">)</span><span class="si">}</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nc">ApolloProvider</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span> </code></pre> </div> <p>Alright, so our <code>client</code> changed up quite a bit!</p> <p>First, we initialize a <code>WebSocketLink</code> instance with our GraphQL API's subsciption endpoint. We also initialize a <code>HttpLink</code> instance with our GraphQL API's HTTP endpoint. </p> <p>Now, since queries and mutations don't require a long-lasting real-time connection, http would be much more efficient for them. Thus, we could like to split our communication on the basis of the GraphQL operation required, i.e, we want to use <code>HttpLink</code> if it's a query or a mutation, but would switch over to <code>WebSocketLink</code> if it's a subscription.</p> <p>We achieve this by using the <code>split()</code> function which assigns <code>link</code> based on a boolean check. It takes in three parameters, a function that's called for each operation to execute, a link if the function returns a "truthy" value, and a link if the function returns a "falsy" value. Here, we use the <code>getMainDefinition()</code> function to check if the operation in a subscription. If that returns true we use <code>wsLink</code> otherwise we use <code>httpLink</code>. <code>link</code> is later passed into our <code>client</code>.</p> <h2> Executing a mutation </h2> <p>Now that that's out of the way, let's figure out how to send a message in our chat app. We'll be using our <code>createChat</code> mutation in this case. Create a new file, <code>SendMessage.tsx</code> in the <code>src</code> directory and type the following,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span><span class="p">,</span> <span class="nx">FC</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">gql</span><span class="p">,</span> <span class="nx">useMutation</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">SEND_MESSAGE</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">` mutation createChat($name: String!, $message: String!) { createChat(name: $name, message: $message) { id name message } } `</span><span class="p">;</span> <span class="kr">interface</span> <span class="nx">SendMessageProps</span> <span class="p">{</span> <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">SendMessage</span><span class="p">:</span> <span class="nx">FC</span><span class="o">&lt;</span><span class="nx">SendMessageProps</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">name</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">input</span><span class="p">,</span> <span class="nx">setInput</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="kr">string</span><span class="o">&gt;</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">sendMessage</span><span class="p">,</span> <span class="p">{</span> <span class="nx">data</span> <span class="p">}]</span> <span class="o">=</span> <span class="nx">useMutation</span><span class="p">(</span><span class="nx">SEND_MESSAGE</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">handleSend</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">sendMessage</span><span class="p">({</span> <span class="na">variables</span><span class="p">:</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="nx">name</span><span class="p">,</span> <span class="na">message</span><span class="p">:</span> <span class="nx">input</span> <span class="p">}</span> <span class="p">})</span> <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">data</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span> <span class="nx">setInput</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span> <span class="p">})</span> <span class="p">.</span><span class="k">catch</span><span class="p">((</span><span class="nx">err</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">err</span><span class="p">));</span> <span class="p">};</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> <span class="na">id</span><span class="p">=</span><span class="s">"message"</span> <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">input</span><span class="si">}</span> <span class="na">onChange</span><span class="p">=</span><span class="si">{</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">setInput</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span><span class="si">}</span> <span class="p">&gt;&lt;/</span><span class="nt">input</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">handleSend</span><span class="si">}</span><span class="p">&gt;</span>Send message<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">SendMessage</span><span class="p">;</span> </code></pre> </div> <p>Alright, we have a really simple component this time, with one input field to fill out the message the user wants to send, which is stored in our <code>input</code> state and a button that calls the <code>handleSend()</code> function when it's clicked. It also takes in the name of the user as a prop. The most important thing to note here is our mutation. </p> <p>We use the <code>useMutation</code> hook from Apollo to call our mutation. We've defined our mutation query as a GraphQL string, <code>SEND_MESSAGE</code> which we pass into our hook. The <code>useMutation</code> hook in turn returns a tuple that has a mutate function (<code>sendMessage()</code> here) which we can call to execute the mutation and an object with fields that represent the current status of the mutation. We won't be using that object here for now.</p> <p>We call the <code>sendMessage()</code> mutate function inside our <code>handleSend</code> method. Since our mutation has input variables, namely, <code>name</code> and <code>message</code>, we pass those in as the <code>variables</code> object, with values from our props and state. The mutate function returns a <code>Promise</code> so we use <code>then()</code> here to wait for the mutation to execute. Once the mutation is done we clear out the <code>input</code> state so that the user can type and send the next message. You can test this out now and view the messages you send in the console!</p> <h2> Executing a query </h2> <p>Now, we also need to be able to show our previous chats and update that whenever a new chat is sent. So let's define a new <code>Chats.tsx</code> component with the following code to accomplish this,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">gql</span><span class="p">,</span> <span class="nx">useQuery</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">ALL_CHATS</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">` query allChats { getChats { id name message } } `</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">Chats</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">{</span> <span class="nx">loading</span><span class="p">,</span> <span class="nx">error</span><span class="p">,</span> <span class="nx">data</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useQuery</span><span class="p">(</span><span class="nx">ALL_CHATS</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="nx">loading</span><span class="p">)</span> <span class="k">return</span> <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>"Loading...";<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;;</span> <span class="k">if</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="k">return</span> <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>`Error! $<span class="si">{</span><span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="si">}</span>`<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;;</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">getChats</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">chat</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">key</span><span class="p">=</span><span class="si">{</span><span class="nx">chat</span><span class="p">.</span><span class="nx">id</span><span class="si">}</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span> <span class="si">{</span><span class="nx">chat</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span>: <span class="si">{</span><span class="nx">chat</span><span class="p">.</span><span class="nx">message</span><span class="si">}</span> <span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">))</span><span class="si">}</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">Chats</span><span class="p">;</span> </code></pre> </div> <p>Alright, let's understand what we wrote. We used the <code>useQuery</code> hook by Apollo, to execute our <code>allChats</code> query, which is defined as a GraphQL string, <code>ALL_CHATS</code>. When our component renders, the <code>useQuery</code> hook returns an object with <code>loading</code>, <code>error</code>, and <code>data</code> which we then use to render our UI. </p> <p>When there's no error, and the data is done loading, we loop through our chats and display the name of the sender and the message. Keep in mind that Apollo Client automatically caches our query results locally, to make subsequent query results faster. </p> <h2> Use subscription to update query result </h2> <p>There's no real-time aspect in the <code>Chat</code> component yet. So sending in new chats won't update our UI unless we refresh. Let's fix this by adding in our subscription.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">gql</span><span class="p">,</span> <span class="nx">useQuery</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">useEffect</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">ALL_CHATS</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">` query allChats { getChats { id name message } } `</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">CHATS_SUBSCRIPTION</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">` subscription OnNewChat { messageSent { id name message } } `</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">Chats</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">{</span> <span class="nx">loading</span><span class="p">,</span> <span class="nx">error</span><span class="p">,</span> <span class="nx">data</span><span class="p">,</span> <span class="nx">subscribeToMore</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useQuery</span><span class="p">(</span><span class="nx">ALL_CHATS</span><span class="p">);</span> <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">subscribeToMore</span><span class="p">({</span> <span class="na">document</span><span class="p">:</span> <span class="nx">CHATS_SUBSCRIPTION</span><span class="p">,</span> <span class="na">updateQuery</span><span class="p">:</span> <span class="p">(</span><span class="nx">prev</span><span class="p">,</span> <span class="p">{</span> <span class="nx">subscriptionData</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">subscriptionData</span><span class="p">.</span><span class="nx">data</span><span class="p">)</span> <span class="k">return</span> <span class="nx">prev</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">newChat</span> <span class="o">=</span> <span class="nx">subscriptionData</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">messageSent</span><span class="p">;</span> <span class="k">return</span> <span class="p">{</span> <span class="na">getChats</span><span class="p">:</span> <span class="p">[...</span><span class="nx">prev</span><span class="p">.</span><span class="nx">getChats</span><span class="p">,</span> <span class="nx">newChat</span><span class="p">],</span> <span class="p">};</span> <span class="p">},</span> <span class="p">});</span> <span class="p">},</span> <span class="p">[]);</span> <span class="k">if</span> <span class="p">(</span><span class="nx">loading</span><span class="p">)</span> <span class="k">return</span> <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>"Loading...";<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;;</span> <span class="k">if</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="k">return</span> <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>`Error! $<span class="si">{</span><span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="si">}</span>`<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;;</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">getChats</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">chat</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">key</span><span class="p">=</span><span class="si">{</span><span class="nx">chat</span><span class="p">.</span><span class="nx">id</span><span class="si">}</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span> <span class="si">{</span><span class="nx">chat</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span>: <span class="si">{</span><span class="nx">chat</span><span class="p">.</span><span class="nx">message</span><span class="si">}</span> <span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">))</span><span class="si">}</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">Chats</span><span class="p">;</span> </code></pre> </div> <p>We just changed a bunch of stuff so let's figure out what we did.</p> <p>If you look closely, our UI logic hasn't changed one bit. However, our data fetching logic has. </p> <p>The <code>useQuery</code> hook returns another function, <code>subscribeToMore()</code>. We can use this function to execute a followup GraphQL subscription that can push updates to our query's, i.e <code>allChats</code>, original results.</p> <p>Now, we use the <code>subscribeToMore()</code> function inside a <code>useEffect</code> hook which has an empty dependency array, i.e, it fires when the component is mounted. We pass in two options to the <code>subscribeToMore()</code> function, <code>document</code> which indicates which subscription needs to be executed, and <code>updateQuery</code> which is a function that tells Apollo Client how to combine the query's currently cached result (<code>prev</code> here) with the <code>subscriptionData</code> that's pushed by our GraphQL subscription. The return value of this function completely replaces the current cached result for the query. </p> <p>Thus, for <code>document</code> we pass in our subscription <code>CHATS_SUBSCRIPTION</code> defined as a GraphQL string, and for <code>updateQuery</code>, we pass in a function that appends the <code>newChat</code> received from our subscription to our previous chat data and returns that as an object that our UI can iterate over. The object is of the same type as the results of our <code>allChats</code> query but now has the latest chat at the last index of the <code>getChats</code> field array. Since this is a subscription, our cached chats will now get updated the moment a new chat arrives!</p> <p>You might be wondering why we don't just execute the subscription using a <code>useSubscription</code> hook, eliminating the query altogether. We could, but this would result in the user getting only the messages after the user has entered the chat. We want to show previous chats as well which is why we chose this approach.</p> <h2> Test it out </h2> <p>Finally, let's use the <code>Chats</code> and <code>SendMessage</code> component in our <code>App.tsx</code><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">ApolloClient</span><span class="p">,</span> <span class="nx">InMemoryCache</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">ApolloProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">WebSocketLink</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client/link/ws</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">split</span><span class="p">,</span> <span class="nx">HttpLink</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">getMainDefinition</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client/utilities</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="nx">Chats</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./Chats</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="nx">SendMessage</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./SendMessage</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">wsLink</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">WebSocketLink</span><span class="p">({</span> <span class="na">uri</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ws://localhost:9000/subscriptions</span><span class="dl">"</span><span class="p">,</span> <span class="na">options</span><span class="p">:</span> <span class="p">{</span> <span class="na">reconnect</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="p">},</span> <span class="p">});</span> <span class="kd">const</span> <span class="nx">httpLink</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HttpLink</span><span class="p">({</span> <span class="na">uri</span><span class="p">:</span> <span class="dl">"</span><span class="s2">http://localhost:9000/graphql</span><span class="dl">"</span><span class="p">,</span> <span class="na">credentials</span><span class="p">:</span> <span class="dl">"</span><span class="s2">include</span><span class="dl">"</span><span class="p">,</span> <span class="p">});</span> <span class="kd">const</span> <span class="nx">link</span> <span class="o">=</span> <span class="nx">split</span><span class="p">(</span> <span class="p">({</span> <span class="nx">query</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">definition</span> <span class="o">=</span> <span class="nx">getMainDefinition</span><span class="p">(</span><span class="nx">query</span><span class="p">);</span> <span class="k">return</span> <span class="p">(</span> <span class="nx">definition</span><span class="p">.</span><span class="nx">kind</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">OperationDefinition</span><span class="dl">"</span> <span class="o">&amp;&amp;</span> <span class="nx">definition</span><span class="p">.</span><span class="nx">operation</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">subscription</span><span class="dl">"</span> <span class="p">);</span> <span class="p">},</span> <span class="nx">wsLink</span><span class="p">,</span> <span class="nx">httpLink</span> <span class="p">);</span> <span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ApolloClient</span><span class="p">({</span> <span class="nx">link</span><span class="p">,</span> <span class="na">cache</span><span class="p">:</span> <span class="k">new</span> <span class="nx">InMemoryCache</span><span class="p">(),</span> <span class="p">});</span> <span class="kd">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">name</span><span class="p">,</span> <span class="nx">setName</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="kr">string</span><span class="o">&gt;</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">entered</span><span class="p">,</span> <span class="nx">setEntered</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="o">&lt;</span><span class="nx">boolean</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nc">ApolloProvider</span> <span class="na">client</span><span class="p">=</span><span class="si">{</span><span class="nx">client</span><span class="si">}</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"App"</span><span class="p">&gt;</span> <span class="si">{</span><span class="o">!</span><span class="nx">entered</span> <span class="o">&amp;&amp;</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> <span class="na">id</span><span class="p">=</span><span class="s">"name"</span> <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">name</span><span class="si">}</span> <span class="na">onChange</span><span class="p">=</span><span class="si">{</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">setName</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span><span class="si">}</span> <span class="p">&gt;&lt;/</span><span class="nt">input</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">setEntered</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="si">}</span><span class="p">&gt;</span>Enter chat<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">)</span><span class="si">}</span> <span class="si">{</span><span class="nx">name</span> <span class="o">!==</span> <span class="dl">""</span> <span class="o">&amp;&amp;</span> <span class="nx">entered</span> <span class="o">&amp;&amp;</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">Chats</span> <span class="p">/&gt;</span> <span class="p">&lt;</span><span class="nc">SendMessage</span> <span class="na">name</span><span class="p">=</span><span class="si">{</span><span class="nx">name</span><span class="si">}</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">)</span><span class="si">}</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nc">ApolloProvider</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span> </code></pre> </div> <p>After saving, run <code>yarn start</code> and visit <a href="proxy.php?url=http://localhost:3000">localhost:3000</a>, enter the chat from 2 or 3 different browser tabs, and see the chats you send appear instantaneously in all tabs.</p> <p>And voilà! We've successfully managed to make a full-stack chat application using GraphQL and TypeScript! You can now build on this even further and add in styles, a database, and even an authentication mechanism!</p> <h2> Conclusion </h2> <p>If you'd like to dig deeper into GraphQL, Apollo Client/Server, and TypeGraphQL and discover all the cool things you can make with it, read the official docs,</p> <p><a href="proxy.php?url=https://www.apollographql.com/docs/react/">Apollo Client Docs</a></p> <p><a href="proxy.php?url=https://www.apollographql.com/docs/apollo-server/">Apollo Server Docs</a></p> <p><a href="proxy.php?url=https://typegraphql.com/docs/introduction.html">TypeGraphQL Docs</a></p> <p><a href="proxy.php?url=https://graphql.org/learn/">GraphQL Docs</a></p> <p>Also, here's an <a href="proxy.php?url=https://github.com/chentsulin/awesome-graphql">awesome list of resources</a> to learn further!</p> <p>If you get stuck here's the <a href="proxy.php?url=https://github.com/saswatamcode/graphQLChat">repo</a> with all the code!</p> <p>For any queries reach out to my <a href="proxy.php?url=https://twitter.com/saswatamcode">socials</a> or <a href="proxy.php?url=https://github.com/saswatamcode">GitHub</a>!</p> graphql react typescript webdev Build a chat app with GraphQL Subscriptions & TypeScript: Part 2 Saswata Mukherjee Sun, 28 Feb 2021 09:08:29 +0000 https://dev.to/gdsckiitdev/build-a-chat-app-with-graphql-subscriptions-typescript-part-2-3k35 https://dev.to/gdsckiitdev/build-a-chat-app-with-graphql-subscriptions-typescript-part-2-3k35 <p>In this part, we'll be adding our subscription to our GraphQL API.</p> <h2> What are subscriptions? </h2> <blockquote> <p>Subscriptions are long-lasting GraphQL read operations that can update their result whenever a particular server-side event occurs. Most commonly, updated results are pushed from the server to subscribing clients. For example, a chat application's server might use a subscription to push newly received messages to all clients in a particular chat room.</p> </blockquote> <p>That's according to the official <a href="proxy.php?url=https://www.apollographql.com/docs/apollo-server/data/subscriptions/">Apollo Server documentation</a>. Essentially, it allows us to update our clients based on any server-side events. And since subscription updates are usually pushed by the server, they usually use the <a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API">WebSocket</a> protocol instead of HTTP. </p> <p>Now that our GraphQL resolvers are working, we can send chats through our mutation and view all the chats which are presently there through our query! However, we also want to be alerted when a new chat arrives and that too in real-time(the exact moment the chat arrived! This is why we need a subscription operation as well!</p> <p>So let's go ahead and add one! First, we need to set up our server, so that it can handle subscriptions since subscriptions use a completely different protocol from http! Apollo Server makes this setup relatively easy by allowing us to have a completely different endpoint only for our subscriptions.</p> <h2> Setting up our server to handle subscriptions </h2> <p>Open up <code>index.ts</code> and make the following changes<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="dl">"</span><span class="s2">reflect-metadata</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="nx">express</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">express</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">ApolloServer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">apollo-server-express</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">buildSchema</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">type-graphql</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">ChatResolver</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./resolvers/chat</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="nx">http</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">http</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="nx">cors</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">cors</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="nx">dotenv</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">dotenv</span><span class="dl">"</span><span class="p">;</span> <span class="nx">dotenv</span><span class="p">.</span><span class="nx">config</span><span class="p">();</span> <span class="kd">const</span> <span class="nx">main</span> <span class="o">=</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span> <span class="kd">const</span> <span class="nx">httpServer</span> <span class="o">=</span> <span class="nx">http</span><span class="p">.</span><span class="nx">createServer</span><span class="p">(</span><span class="nx">app</span><span class="p">);</span> <span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">cors</span><span class="p">({</span> <span class="na">origin</span><span class="p">:</span> <span class="dl">"</span><span class="s2">http://localhost:3000</span><span class="dl">"</span><span class="p">,</span> <span class="na">credentials</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}));</span> <span class="kd">const</span> <span class="nx">apolloServer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ApolloServer</span><span class="p">({</span> <span class="na">schema</span><span class="p">:</span> <span class="k">await</span> <span class="nx">buildSchema</span><span class="p">({</span> <span class="na">resolvers</span><span class="p">:</span> <span class="p">[</span><span class="nx">ChatResolver</span><span class="p">],</span> <span class="na">validate</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="p">}),</span> <span class="na">subscriptions</span><span class="p">:</span> <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">"</span><span class="s2">/subscriptions</span><span class="dl">"</span><span class="p">,</span> <span class="na">onConnect</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Client connected for subscriptions</span><span class="dl">"</span><span class="p">);</span> <span class="p">},</span> <span class="na">onDisconnect</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Client disconnected from subscriptions</span><span class="dl">"</span><span class="p">);</span> <span class="p">},</span> <span class="p">},</span> <span class="p">});</span> <span class="nx">apolloServer</span><span class="p">.</span><span class="nx">applyMiddleware</span><span class="p">({</span> <span class="nx">app</span><span class="p">,</span> <span class="na">cors</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="p">});</span> <span class="nx">apolloServer</span><span class="p">.</span><span class="nx">installSubscriptionHandlers</span><span class="p">(</span><span class="nx">httpServer</span><span class="p">);</span> <span class="nx">httpServer</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">PORT</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="s2">`Server ready at http://localhost:</span><span class="p">${</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">PORT</span><span class="p">}${</span><span class="nx">apolloServer</span><span class="p">.</span><span class="nx">graphqlPath</span><span class="p">}</span><span class="s2">`</span> <span class="p">);</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="s2">`Subscriptions ready at ws://localhost:</span><span class="p">${</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">PORT</span><span class="p">}${</span><span class="nx">apolloServer</span><span class="p">.</span><span class="nx">subscriptionsPath</span><span class="p">}</span><span class="s2">`</span> <span class="p">);</span> <span class="p">});</span> <span class="p">};</span> <span class="nx">main</span><span class="p">().</span><span class="k">catch</span><span class="p">((</span><span class="nx">err</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span> <span class="p">});</span> </code></pre> </div> <p>That's a ton of changes, so let's understand why we made them! </p> <p>Firstly, we need to pass in a <code>subscriptions</code> object to our <code>apolloServer</code> instance with the endpoint path we want to reserve only for subscriptions as well as functions <code>onConnect</code> and <code>onDisconnect</code>, which will fire every time a client connects and disconnects from the endpoint we specified. And since we're using a middleware integration with Apollo Server and Express, we need to call the <code>installSubscriptionHandlers()</code> method defined by our <code>apolloServer</code> instance.</p> <p>This leads to a limitation since we can only pass in an instance of <code>http.Server</code> to our <code>installSubscriptionHandlers()</code> method. We can't pass in an instance of <code>express.Application</code> or <code>app</code> as defined here. Thus, we need to define our own <code>httpServer</code> using the baked-in <code>http</code> Node library instead of using the one created for us by Express.</p> <p>So we import the <code>http</code> module and create a http server based on our express application, i.e, <code>app</code> using the <code>http.createServer(app)</code> method. We call the <code>installSubscriptionHandlers()</code> method and pass in our <code>httpServer</code>. </p> <p>Finally, instead of using <code>app.listen()</code> we use <code>httpServer.listen()</code>. Both of these methods achieve the exact same thing and return the same type(<code>http.Server</code>), but <code>httpServer</code> now has the required code to handle subscriptions, so we use that instead of <code>app</code>.</p> <p>On saving and restarting the server, you should see your subscription url get logged in your console, i.e, <code>ws://localhost:9000/subscription</code>. Here <code>ws</code> signifies that the endpoint uses the WebSocket protocol!</p> <p>Now, that our server can handle subscriptions let's actually add one!</p> <h2> Adding our subscription </h2> <p>Subscription resolvers are similar to queries and mutations but are slightly more complex. We'll be creating a class method as we did previously but with the <code>@Subscription()</code> decorator.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Mutation</span><span class="p">,</span> <span class="nx">Query</span><span class="p">,</span> <span class="nx">Subscription</span><span class="p">,</span> <span class="nx">Resolver</span><span class="p">,</span> <span class="nx">Arg</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">type-graphql</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">Chat</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../entities/Chat</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">chats</span><span class="p">:</span> <span class="nx">Chat</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[];</span> <span class="p">@</span><span class="nd">Resolver</span><span class="p">()</span> <span class="k">export</span> <span class="kd">class</span> <span class="nx">ChatResolver</span> <span class="p">{</span> <span class="p">@</span><span class="nd">Query</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">[</span><span class="nx">Chat</span><span class="p">])</span> <span class="nx">getChats</span><span class="p">():</span> <span class="nx">Chat</span><span class="p">[]</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">chats</span><span class="p">;</span> <span class="p">}</span> <span class="p">@</span><span class="nd">Mutation</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">Chat</span><span class="p">)</span> <span class="nx">createChat</span><span class="p">(</span> <span class="p">@</span><span class="nd">Arg</span><span class="p">(</span><span class="dl">"</span><span class="s2">name</span><span class="dl">"</span><span class="p">)</span> <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="p">@</span><span class="nd">Arg</span><span class="p">(</span><span class="dl">"</span><span class="s2">message</span><span class="dl">"</span><span class="p">)</span> <span class="nx">message</span><span class="p">:</span> <span class="kr">string</span> <span class="p">):</span> <span class="nx">Chat</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">chat</span> <span class="o">=</span> <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="nx">chats</span><span class="p">.</span><span class="nx">length</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">message</span> <span class="p">};</span> <span class="nx">chats</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">chat</span><span class="p">);</span> <span class="k">return</span> <span class="nx">chat</span><span class="p">;</span> <span class="p">}</span> <span class="p">@</span><span class="nd">Subscription</span><span class="p">({</span> <span class="na">topics</span><span class="p">:</span> <span class="dl">"</span><span class="s2">CHAT_CHANNEL</span><span class="dl">"</span> <span class="p">})</span> <span class="nx">messageSent</span><span class="p">():</span> <span class="nx">Chat</span> <span class="p">{}</span> <span class="p">}</span> </code></pre> </div> <p>We just created a <code>messageSent()</code> method in our <code>ChatResolver</code> class with a <code>@Subscription()</code> decorator. Thus our new method is now marked as a GraphQL subscription resolver. We have to pass in the name of the topic we wish to subscribe to, in our decorator as well. This can be a single topic, an array of topics, or even a dynamic topic. Since we will only be maintaining one chat channel in our app, we passed in the <code>CHAT_CHANNEL</code> string as our topic. </p> <h2> Triggering subscriptions and receiving payloads </h2> <p>Let's now add in the logic for triggering our subscription topic.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Mutation</span><span class="p">,</span> <span class="nx">Query</span><span class="p">,</span> <span class="nx">Resolver</span><span class="p">,</span> <span class="nx">Arg</span><span class="p">,</span> <span class="nx">Root</span><span class="p">,</span> <span class="nx">PubSub</span><span class="p">,</span> <span class="nx">PubSubEngine</span><span class="p">,</span> <span class="nx">Subscription</span><span class="p">,</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">type-graphql</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">Chat</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../entities/Chat</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">chats</span><span class="p">:</span> <span class="nx">Chat</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[];</span> <span class="kd">const</span> <span class="nx">channel</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">CHAT_CHANNEL</span><span class="dl">"</span><span class="p">;</span> <span class="p">@</span><span class="nd">Resolver</span><span class="p">()</span> <span class="k">export</span> <span class="kd">class</span> <span class="nx">ChatResolver</span> <span class="p">{</span> <span class="p">@</span><span class="nd">Query</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">[</span><span class="nx">Chat</span><span class="p">])</span> <span class="nx">getChats</span><span class="p">():</span> <span class="nx">Chat</span><span class="p">[]</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">chats</span><span class="p">;</span> <span class="p">}</span> <span class="p">@</span><span class="nd">Mutation</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">Chat</span><span class="p">)</span> <span class="k">async</span> <span class="nx">createChat</span><span class="p">(</span> <span class="p">@</span><span class="nd">PubSub</span><span class="p">()</span> <span class="nx">pubSub</span><span class="p">:</span> <span class="nx">PubSubEngine</span><span class="p">,</span> <span class="p">@</span><span class="nd">Arg</span><span class="p">(</span><span class="dl">"</span><span class="s2">name</span><span class="dl">"</span><span class="p">)</span> <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="p">@</span><span class="nd">Arg</span><span class="p">(</span><span class="dl">"</span><span class="s2">message</span><span class="dl">"</span><span class="p">)</span> <span class="nx">message</span><span class="p">:</span> <span class="kr">string</span> <span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">Chat</span><span class="o">&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">chat</span> <span class="o">=</span> <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="nx">chats</span><span class="p">.</span><span class="nx">length</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">message</span> <span class="p">};</span> <span class="nx">chats</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">chat</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">payload</span> <span class="o">=</span> <span class="nx">chat</span><span class="p">;</span> <span class="k">await</span> <span class="nx">pubSub</span><span class="p">.</span><span class="nx">publish</span><span class="p">(</span><span class="nx">channel</span><span class="p">,</span> <span class="nx">payload</span><span class="p">);</span> <span class="k">return</span> <span class="nx">chat</span><span class="p">;</span> <span class="p">}</span> <span class="p">@</span><span class="nd">Subscription</span><span class="p">({</span> <span class="na">topics</span><span class="p">:</span> <span class="nx">channel</span> <span class="p">})</span> <span class="nx">messageSent</span><span class="p">(@</span><span class="nd">Root</span><span class="p">()</span> <span class="p">{</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">message</span> <span class="p">}:</span> <span class="nx">Chat</span><span class="p">):</span> <span class="nx">Chat</span> <span class="p">{</span> <span class="k">return</span> <span class="p">{</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">message</span> <span class="p">};</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>Again, that's a lot of new code to breakdown! </p> <p>First, let's try to understand what <code>PubSub</code> is exactly. Apollo Server uses a publish-subscribe (pub/sub) model to track events that update subscriptions. The <a href="proxy.php?url=https://github.com/apollographql/graphql-subscriptions">graphql-subscriptions</a> library included in all <code>apollo-server</code> packages (including middleware integrations) provides a <code>PubSub</code> class as a basic in-memory event bus. </p> <p>However, do keep in mind that this isn't suitable for production, since it only supports a single server instance. TypeGraphQL uses this <code>PubSub</code> system to define the <code>@PubSub()</code> decorator. For production, other implementations of such a pubsub system are <a href="proxy.php?url=https://github.com/apollographql/graphql-subscriptions#pubsub-implementations">recommended</a>.</p> <p>In this case, we want to trigger our <code>CHAT_CHANNEL</code> topic whenever a new chat is created, i.e, in our <code>createChat()</code> mutation. So we use the <code>@PubSub()</code> decorator to pass in <code>pubSub</code> as a method parameter, which is of type <code>PubSubEngine</code>. We can now use this to send a payload to all subscribers of the <code>CHAT_CHANNEL</code> topic. </p> <p>Thus, we use <code>pubSub.publish(channel, chat)</code> method to publish the payload in our topic, in the pubsub system, by passing in our topic name ( <code>channel</code> now has the <code>CHAT_CHANNEL</code> string), and the <code>chat</code> object as arguments. </p> <p>Since this returns a <code>Promise</code>, we need to use <code>await</code>. This also results in <code>createChat()</code> being an <code>async</code> method which now returns a <code>Promise</code> of type <code>Chat</code>.</p> <p>Finally, in our subscription method, <code>messageSent()</code>, we use the <code>@Root()</code> decorator to receive the payload from the triggered topic in our pubsub system. For convenience, we made sure the payload is of type <code>Chat</code> which is again returned from our subscription method.</p> <h2> Run your subscription in GraphQL Playground </h2> <p>And that's pretty much it! We now have a complete GraphQL API, with a query, mutation, and a subscription! Let's test this out by heading over to our Playground at <code>localhost:9000/graphql</code> and try running the following.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight graphql"><code><span class="k">subscription</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">messageSent</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="n">message</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre> </div> <p>While this is running, i.e the play button switches to red and you can see "Listening..." below, switch over to your <code>createChat()</code> mutation tab and create a new chat. You should be able to see the new chat pop up in the window where you left your subscriptions running!</p> <p>Woohoo! Our subscription works!</p> <p>Now that our backend is complete, we'll explore how to use all these GraphQL operations in the frontend using React. See you in the next part!</p> <h2> Conclusion </h2> <p>Visit the next post of this series to use your GraphQL server in React!</p> <p>If you'd like to dig deeper into GraphQL, Apollo Server and TypeGraphQL and discover all the cool things you can make with it, read the official docs,</p> <p><a href="proxy.php?url=https://www.apollographql.com/docs/apollo-server/">Apollo Server Docs</a></p> <p><a href="proxy.php?url=https://typegraphql.com/docs/introduction.html">TypeGraphQL Docs</a></p> <p><a href="proxy.php?url=https://graphql.org/learn/">GraphQL Docs</a></p> <p>Also, here's an <a href="proxy.php?url=https://github.com/chentsulin/awesome-graphql">awesome list of resources</a> to learn further!</p> <p>If you get stuck here's the <a href="proxy.php?url=https://github.com/saswatamcode/graphQLChat">repo</a> with all the code! Visit the <code>part-2</code> branch to get all the code covered in this post.</p> <p>For any queries reach out to my <a href="proxy.php?url=https://twitter.com/saswatamcode">socials</a> or <a href="proxy.php?url=https://github.com/saswatamcode">GitHub</a>!</p> graphql react typescript webdev Build a chat app with GraphQL Subscriptions & TypeScript: Part 1 Saswata Mukherjee Sun, 28 Feb 2021 09:07:53 +0000 https://dev.to/gdsckiitdev/build-a-chat-app-with-graphql-subscriptions-typescript-part-1-2p70 https://dev.to/gdsckiitdev/build-a-chat-app-with-graphql-subscriptions-typescript-part-1-2p70 <p>Hey there!</p> <p>If you're a little familiar with <a href="proxy.php?url=https://graphql.org/">GraphQL</a>, you've probably heard of subscriptions and how useful they are in building real-time applications. In this series of blogs, we're going to build a simple chat application using Node.js and React with GraphQL. We'll use TypeScript throughout this series and will be following a code-first approach!</p> <h2> Installing dependencies </h2> <p>We'll be using <a href="proxy.php?url=https://www.apollographql.com/docs/apollo-server/">Apollo Server</a>, <a href="proxy.php?url=https://expressjs.com/">Express</a> and <a href="proxy.php?url=https://typegraphql.com/docs/introduction.html">TypeGraphQL</a> for this server. </p> <p>Developing a GraphQL API in Node.js with TypeScript is always a bit of a pain since you'd have to manually create all your types, a lot of which will lead to redundancies later on, but TypeGraphQL really makes it easy using classes and decorator.</p> <p>Let's start by running <code>npm init -y</code> in a fresh new directory to generate our <code>package.json</code> and install the required dependencies.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>yarn add apollo-server-express class-validator cors dotenv express graphql reflect-metadata type-graphql yarn add <span class="nt">-D</span> @types/cors @types/express @types/node typescript </code></pre> </div> <p>We're basically using Express as a middleware integration for Apollo Server using the <a href="proxy.php?url=https://github.com/apollographql/apollo-server">apollo-server-express</a> package. Once all your dependencies are installed, create a <code>src</code> folder. This is where all our TS files will exist. That'll help us easily manage compilation. </p> <p>We'll also need a <code>tsconfig.json</code> file to setup TypeScript to our liking. There's an awesome utility by <a href="proxy.php?url=https://twitter.com/benawad">Ben Awad</a> which can automatically generate this for you. Run <code>npx tsconfig.json</code> and select <code>node</code>. Now we're all set to code up our GraphQL API!</p> <p>We'll be following the file structure described below!<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>├── server │ ├── src │ │ ├── entities │ | | ├── Chat.ts │ │ ├── resolvers │ | | ├── chat.ts │ │ ├── index.ts │ ├── package.json │ ├── tsconfig.json │ ├── .env </code></pre> </div> <h2> Building our server </h2> <p>Create an <code>index.ts</code> file and initialize our server using the code below,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="dl">"</span><span class="s2">reflect-metadata</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">ApolloServer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">apollo-server-express</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="nx">express</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">express</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">buildSchema</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">type-graphql</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="nx">cors</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">cors</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="nx">dotenv</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">dotenv</span><span class="dl">"</span><span class="p">;</span> <span class="nx">dotenv</span><span class="p">.</span><span class="nx">config</span><span class="p">();</span> <span class="kd">const</span> <span class="nx">main</span> <span class="o">=</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span> <span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">cors</span><span class="p">({</span> <span class="na">origin</span><span class="p">:</span> <span class="dl">"</span><span class="s2">http://localhost:3000</span><span class="dl">"</span><span class="p">,</span> <span class="na">credentials</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}));</span> <span class="kd">const</span> <span class="nx">apolloServer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ApolloServer</span><span class="p">({</span> <span class="na">schema</span><span class="p">:</span> <span class="k">await</span> <span class="nx">buildSchema</span><span class="p">({</span> <span class="na">resolvers</span><span class="p">:</span> <span class="p">[</span><span class="nx">ChatResolver</span><span class="p">],</span> <span class="na">validate</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="p">}),</span> <span class="p">});</span> <span class="nx">apolloServer</span><span class="p">.</span><span class="nx">applyMiddleware</span><span class="p">({</span> <span class="nx">app</span><span class="p">,</span> <span class="na">cors</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="p">});</span> <span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">PORT</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span> <span class="s2">`Server ready at http://localhost:</span><span class="p">${</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">PORT</span><span class="p">}${</span><span class="nx">apolloServer</span><span class="p">.</span><span class="nx">graphqlPath</span><span class="p">}</span><span class="s2">`</span> <span class="p">);</span> <span class="p">});</span> <span class="p">};</span> <span class="nx">main</span><span class="p">().</span><span class="k">catch</span><span class="p">((</span><span class="nx">err</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span> <span class="p">});</span> </code></pre> </div> <p>Let's breakdown what we just wrote. We initialized our server inside an async <code>main()</code> function, just in case we need to <code>await</code> anything, and declared an express application, <code>app</code>. We also allowed cors from <code>localhost:3000</code>, which is where we'll run our React app later on. Also, do keep in mind to import the <code>reflect-metadata</code> shim package before importing <code>type-graphql</code> or any of your resolvers.</p> <p>We then made a new instance of <code>ApolloServer</code> and applied our express middleware to that. Finally, we started our server, using <code>app.listen()</code>. We're also using <code>dotenv</code> to load env variables, namely <code>PORT</code>, from our <code>.env</code> file. For this example, we'll consider <code>PORT=9000</code>.</p> <p>But as you've probably noticed by now, this won't run, since we don't have a <code>ChatResolver</code> yet. In fact, we don't have any resolvers for our GraphQL API yet, so let's go ahead and make a resolver. </p> <p>But before that, we need to define our entity. Think of this as the universal type on which you'll write your GraphQL resolvers, i.e queries, mutations, and subscriptions as well as your database operations. This is exactly where TypeGraphQL comes in handy. We won't be using a database here, since our chat system will be ephemeral, but you get the idea!</p> <h2> Defining our entity </h2> <p>So create the <code>entities/Chat.ts</code> file and define our <code>Chat</code> entity using the following code!<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">ObjectType</span><span class="p">,</span> <span class="nx">Field</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">type-graphql</span><span class="dl">"</span><span class="p">;</span> <span class="p">@</span><span class="nd">ObjectType</span><span class="p">()</span> <span class="k">export</span> <span class="kd">class</span> <span class="nx">Chat</span> <span class="p">{</span> <span class="p">@</span><span class="nd">Field</span><span class="p">()</span> <span class="nx">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="p">@</span><span class="nd">Field</span><span class="p">()</span> <span class="nx">message</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="p">@</span><span class="nd">Field</span><span class="p">()</span> <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>Alright, let's understand what we just wrote! We defined an exported TypeScript class <code>Chat</code> with multiple decorators. The class has three property members, <code>id</code>, <code>message</code>, and <code>name</code>, each with their own types. This is pretty straightforward, but let's understand what those decorators accomplish. </p> <p>The main idea behind using TypeGraphQL decorators is to automatically create GraphQL schema definitions from TypeScript classes in SDL(schema definition language). This eliminates the need to make schema definition files and their equivalent interfaces in TypeScript.</p> <p>Here, the first thing we did was decorate the <code>Chat</code> class with the <code>@ObjectType</code> decorator. It marks the class as <code>type</code> from the GraphQL SDL or <code>GraphQLObjectType</code> from <code>graphql-js</code>. Then we declared class properties that need to be mapped to the GraphQL fields. To do this, we use the <code>@Field</code> decorator, which is also used to collect metadata from the TypeScript type reflection system. By default all the fields in our entity here are non-nullable!</p> <p>This entity will result in the generation of the following part of the GraphQL schema in the SDL.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="kd">type</span> <span class="nx">Chat</span> <span class="p">{</span> <span class="nl">id</span><span class="p">:</span> <span class="nx">Float</span><span class="o">!</span> <span class="nx">message</span><span class="p">:</span> <span class="nb">String</span><span class="o">!</span> <span class="nx">name</span><span class="p">:</span> <span class="nb">String</span><span class="o">!</span> <span class="p">}</span> </code></pre> </div> <p>As you can see all fields are required(<code>!</code>) here, i.e non-nullable! </p> <p>We've now successfully defined a GraphQL schema and its types for each of our chats! Now let's define a GraphQL resolver on our <code>Chat</code> entity.</p> <h2> Queries and Mutations </h2> <p>Create a <code>resolvers/chat.ts</code> file and type in the following,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Mutation</span><span class="p">,</span> <span class="nx">Query</span><span class="p">,</span> <span class="nx">Resolver</span><span class="p">,</span> <span class="nx">Arg</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">type-graphql</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">Chat</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../entities/Chat</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">chats</span><span class="p">:</span> <span class="nx">Chat</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[];</span> <span class="p">@</span><span class="nd">Resolver</span><span class="p">()</span> <span class="k">export</span> <span class="kd">class</span> <span class="nx">ChatResolver</span> <span class="p">{</span> <span class="p">@</span><span class="nd">Query</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">[</span><span class="nx">Chat</span><span class="p">])</span> <span class="nx">getChats</span><span class="p">():</span> <span class="nx">Chat</span><span class="p">[]</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">chats</span><span class="p">;</span> <span class="p">}</span> <span class="p">@</span><span class="nd">Mutation</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">Chat</span><span class="p">)</span> <span class="nx">createChat</span><span class="p">(</span> <span class="p">@</span><span class="nd">Arg</span><span class="p">(</span><span class="dl">"</span><span class="s2">name</span><span class="dl">"</span><span class="p">)</span> <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="p">@</span><span class="nd">Arg</span><span class="p">(</span><span class="dl">"</span><span class="s2">message</span><span class="dl">"</span><span class="p">)</span> <span class="nx">message</span><span class="p">:</span> <span class="kr">string</span> <span class="p">):</span> <span class="nx">Chat</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">chat</span> <span class="o">=</span> <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="nx">chats</span><span class="p">.</span><span class="nx">length</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">message</span> <span class="p">};</span> <span class="nx">chats</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">chat</span><span class="p">);</span> <span class="k">return</span> <span class="nx">chat</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>That's a lot of new code so let's understand what we're doing here. Aside from GraphQL object types, TypeGraphQL also allows us to create GraphQL queries, mutations, and subscriptions in a REST controller type fashion.</p> <p>First, we define an array based on the <code>Chat</code> entity which will basically act as our database. Then we define an exported class <code>ChatResolver</code> with the methods, <code>getChat()</code>, which returns our entire chat array and <code>createChat()</code>, which appends a new chat object to the end of our array by taking the arguments, <code>name</code> and <code>message</code>. Now that we understand the normal functionality of this class, let's understand what those decorators add in.</p> <p>The first decorator, <code>@Resolver()</code>, makes the class behave like a classic REST controller. Thus, the methods inside this class can now act like GraphQL query, mutation, and subscription handlers.</p> <p>This brings us to the next decorators, which are <code>@Query(() =&gt; [Chat])</code> and <code>@Mutation(() =&gt; Chat)</code>, which lets us mark our resolver class methods as a GraphQL query or mutation resolver. We also need to explicitly declare the type that those methods resolve to, i.e, their return type, which here is an array of <code>Chat</code> objects for <code>getChats()</code> and a single object for <code>createChat()</code>. </p> <p>Finally, there's the inline <code>@Arg()</code> decorator, which lets us specify the arguments for a particular GraphQL query/mutation. We pass in the name of those arguments in this decorator.</p> <p>Woohoo! Our resolver is now workable! Let's go ahead and try to run our server! But first, import the <code>ChatResolver</code> in <code>index.ts</code> and add the following scripts into <code>package.json</code><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight json"><code><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"watch"</span><span class="p">:</span><span class="w"> </span><span class="s2">"tsc -w"</span><span class="p">,</span><span class="w"> </span><span class="nl">"dev"</span><span class="p">:</span><span class="w"> </span><span class="s2">"nodemon dist/index.js"</span><span class="p">,</span><span class="w"> </span><span class="nl">"build"</span><span class="p">:</span><span class="w"> </span><span class="s2">"tsc"</span><span class="p">,</span><span class="w"> </span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node dist/index.js"</span><span class="w"> </span><span class="p">}</span><span class="err">,</span><span class="w"> </span></code></pre> </div> <p>Finally, fire up your terminal and run <code>yarn watch</code> in one and <code>yarn dev</code> in another! The watch command basically allows you to make changes in your TS files, which immediately get compiled into JS files inside a <code>dist/</code> directory. Then we use <code>nodemon</code>, to run our compiled JS files and also restart on any changes. This results in a pretty near to prod dev environment!</p> <p>Visit <code>localhost:9000/graphql</code> to view your GraphQL playground where you can run your queries!</p> <h2> Running GraphQL operations in GraphQL Playground </h2> <p>Now, visit <code>localhost:9000/graphql</code> to view your GraphQL Playground, and let's execute our queries and mutations.</p> <p>To add a new chat, you'll run the following mutation:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight graphql"><code><span class="k">mutation</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">createChat</span><span class="p">(</span><span class="n">name</span><span class="p">:</span><span class="w"> </span><span class="s2">"John"</span><span class="p">,</span><span class="w"> </span><span class="n">message</span><span class="p">:</span><span class="w"> </span><span class="s2">"first chat"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="n">message</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre> </div> <p>and to get all chats, you'll run the following query<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight graphql"><code><span class="k">query</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">getChats</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="n">message</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre> </div> <p>As you can see, our class methods have turned into actual GraphQL operations which take in arguments and return <code>Chat</code> object fields! Do keep in mind that since we're storing our chats in an in-memory array, all your chats will disappear the moment you restart your server.</p> <p>In the next part, we'll explore how to add a subscription to our new GraphQL API!</p> <h2> Conclusion </h2> <p>Visit the next post of this series to learn about GraphQL subscriptions and how to add them!</p> <p>If you'd like to dig deeper into GraphQL, Apollo Server and TypeGraphQL and discover all the cool things you can make with it, read the official docs,</p> <p><a href="proxy.php?url=https://www.apollographql.com/docs/apollo-server/">Apollo Server Docs</a></p> <p><a href="proxy.php?url=https://typegraphql.com/docs/introduction.html">TypeGraphQL Docs</a></p> <p><a href="proxy.php?url=https://graphql.org/learn/">GraphQL Docs</a></p> <p>Also, here's an <a href="proxy.php?url=https://github.com/chentsulin/awesome-graphql">awesome list of resources</a> to learn further!</p> <p>If you get stuck here's the <a href="proxy.php?url=https://github.com/saswatamcode/graphQLChat">repo</a> with all the code! Visit the <code>part-1</code> branch to get the code covered in this post.</p> <p>For any queries reach out to my <a href="proxy.php?url=https://twitter.com/saswatamcode">socials</a> or <a href="proxy.php?url=https://github.com/saswatamcode">GitHub</a>!</p> graphql react typescript webdev Build a gRPC server in Go Saswata Mukherjee Sat, 05 Dec 2020 19:17:31 +0000 https://dev.to/gdsckiitdev/build-a-grpc-server-in-go-1890 https://dev.to/gdsckiitdev/build-a-grpc-server-in-go-1890 <p>Hey there! </p> <p>If you've dealt with microservices before, you've probably heard about <code>gRPC</code>.</p> <p>In this blog, we'll be exploring the awesome world of gRPC and write our very own microservice with it. We'll also delve into why gRPC might be better than our traditional REST architectures as well the caveats associated with it.</p> <h2> What is gRPC? </h2> <blockquote> <p>gRPC is a modern open source high performance RPC framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.</p> </blockquote> <p>That's according to the <a href="proxy.php?url=http://grpc.io">official documentation</a>. Let's try to understand what an RPC framework is.</p> <p>Remote Procedure Calls or RPC allow applications to communicate with each other in distributed systems. Simply put, it allows us to expose methods in our application that we want other applications to access. </p> <p>It is sort of similar to REST in the sense that we are exposing functionality in our application over HTTP.</p> <h2> Differences between gRPC and REST </h2> <p>There are a few fundamental differences in how these two work:</p> <ol> <li>gRPC utilizes <code>HTTP/2</code> whereas REST utilizes <code>HTTP/1.1</code>. Using <code>HTTP/2</code> enables certain capabilites such as server-side streaming, client-side streaming or even bidirectional-streaming. If you want to dig deeper into the difference between <code>HTTP/2</code> and <code>1.1</code> you can refer to this <a href="proxy.php?url=https://www.digitalocean.com/community/tutorials/http-1-1-vs-http-2-what-s-the-difference#:~:text=As%20opposed%20to%20HTTP%2F1.1,verbs%2C%20methods%2C%20and%20headers.">article</a>.</li> <li>gRPC uses Protocol Buffers as opposed to the standard JSON data format typically used in REST. You can read about them <a href="proxy.php?url=https://developers.google.com/protocol-buffers">here</a>.</li> </ol> <h2> gRPC Caveats </h2> <p>While gRPC allows use you to use the latest and greatest stuff, there are certain challenges that it introduces. Traditional REST prototyping tools like Postman don't work easily with gRPC. There are workarounds to make it work but it isn't available natively. Howerver alternatives like <a href="proxy.php?url=https://github.com/uw-labs/bloomrpc">BloomRPC</a> and <a href="proxy.php?url=https://github.com/fullstorydev/grpcurl">gRPCurl</a> do exist.</p> <p>There are even options to use tools such as <a href="proxy.php?url=https://www.envoyproxy.io/">envoy</a> to reverse proxy standard JSON requests and transcode them into the right data format but this is an additional dependency that can be tricky to set up for simple projects.</p> <h2> Prerequisites </h2> <p>Before proceeding, make sure you have, atleast a high level understanding of protocol buffers. If you aren't familiar with them, I've written a <a href="proxy.php?url=https://dev.to/dsckiitdev/protocol-buffers-in-go-5bl7">tutorial</a> which you can check out.</p> <p>Also make sure you have the <a href="proxy.php?url=https://github.com/golang/protobuf">protoc</a> tool installed on your system by running <code>protoc --version</code>.</p> <h2> Building our server </h2> <p>Let's start by initializing a new go module in a new directory. Run the following<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>go mod init grpc_using_go </code></pre> </div> <p>Great! Now we'll start by defining a really simple server which our gRPC client will interact with. </p> <p>First we'll listen on a a port for incoming TCP connections. We'll also be using <a href="proxy.php?url=https://github.com/sirupsen/logrus">logrus</a> to generate structured logs. Create a <code>main.go</code> with the following code.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="k">package</span> <span class="n">main</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"net"</span> <span class="s">"os"</span> <span class="n">log</span> <span class="s">"github.com/sirupsen/logrus"</span> <span class="p">)</span> <span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">SetFormatter</span><span class="p">(</span><span class="o">&amp;</span><span class="n">log</span><span class="o">.</span><span class="n">TextFormatter</span><span class="p">{</span> <span class="n">FullTimestamp</span><span class="o">:</span> <span class="no">true</span><span class="p">,</span> <span class="p">})</span> <span class="k">var</span> <span class="n">port</span> <span class="kt">string</span> <span class="k">var</span> <span class="n">ok</span> <span class="kt">bool</span> <span class="n">port</span><span class="p">,</span> <span class="n">ok</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">LookupEnv</span><span class="p">(</span><span class="s">"PORT"</span><span class="p">)</span> <span class="k">if</span> <span class="n">ok</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">WithFields</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">Fields</span><span class="p">{</span> <span class="s">"PORT"</span><span class="o">:</span> <span class="n">port</span><span class="p">,</span> <span class="p">})</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"PORT env var defined"</span><span class="p">)</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">port</span> <span class="o">=</span> <span class="s">"9000"</span> <span class="n">log</span><span class="o">.</span><span class="n">WithFields</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">Fields</span><span class="p">{</span> <span class="s">"PORT"</span><span class="o">:</span> <span class="n">port</span><span class="p">,</span> <span class="p">})</span><span class="o">.</span><span class="n">Warn</span><span class="p">(</span><span class="s">"PORT env var not defined. Going with default"</span><span class="p">)</span> <span class="p">}</span> <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">net</span><span class="o">.</span><span class="n">Listen</span><span class="p">(</span><span class="s">"tcp"</span><span class="p">,</span> <span class="s">":"</span><span class="o">+</span><span class="n">port</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">WithFields</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">Fields</span><span class="p">{</span> <span class="s">"Error"</span><span class="o">:</span> <span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">(),</span> <span class="p">})</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="s">"Failed to listen"</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>Here, I'm using <code>os.LookupEnv</code> which checks if a particular environment variable exists and returns the the value of the variable and a boolean. So if <code>PORT</code> is defined we'll use that or default to 9000. We're using logrus to generate nice structured color coded output with levels like Info, Warn, Error and Fatal.</p> <p>Next, we'll import the official gRPC package from <a href="proxy.php?url=http://golang.org">golang.org</a> in order to create our gRPC server. We'll also register our endpoints and server over our TCP connection which we defined above.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="k">package</span> <span class="n">main</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"net"</span> <span class="s">"os"</span> <span class="n">log</span> <span class="s">"github.com/sirupsen/logrus"</span> <span class="s">"google.golang.org/grpc"</span> <span class="p">)</span> <span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">SetFormatter</span><span class="p">(</span><span class="o">&amp;</span><span class="n">log</span><span class="o">.</span><span class="n">TextFormatter</span><span class="p">{</span> <span class="n">FullTimestamp</span><span class="o">:</span> <span class="no">true</span><span class="p">,</span> <span class="p">})</span> <span class="k">var</span> <span class="n">port</span> <span class="kt">string</span> <span class="k">var</span> <span class="n">ok</span> <span class="kt">bool</span> <span class="n">port</span><span class="p">,</span> <span class="n">ok</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">LookupEnv</span><span class="p">(</span><span class="s">"PORT"</span><span class="p">)</span> <span class="k">if</span> <span class="n">ok</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">WithFields</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">Fields</span><span class="p">{</span> <span class="s">"PORT"</span><span class="o">:</span> <span class="n">port</span><span class="p">,</span> <span class="p">})</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"PORT env var defined"</span><span class="p">)</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">port</span> <span class="o">=</span> <span class="s">"9000"</span> <span class="n">log</span><span class="o">.</span><span class="n">WithFields</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">Fields</span><span class="p">{</span> <span class="s">"PORT"</span><span class="o">:</span> <span class="n">port</span><span class="p">,</span> <span class="p">})</span><span class="o">.</span><span class="n">Warn</span><span class="p">(</span><span class="s">"PORT env var not defined. Going with default"</span><span class="p">)</span> <span class="p">}</span> <span class="n">l</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">net</span><span class="o">.</span><span class="n">Listen</span><span class="p">(</span><span class="s">"tcp"</span><span class="p">,</span> <span class="s">":"</span><span class="o">+</span><span class="n">port</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">WithFields</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">Fields</span><span class="p">{</span> <span class="s">"Error"</span><span class="o">:</span> <span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">(),</span> <span class="p">})</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="s">"Failed to listen"</span><span class="p">)</span> <span class="p">}</span> <span class="n">grpcServer</span> <span class="o">:=</span> <span class="n">grpc</span><span class="o">.</span><span class="n">NewServer</span><span class="p">()</span> <span class="n">log</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"gRPC server started at "</span><span class="p">,</span> <span class="n">port</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">grpcServer</span><span class="o">.</span><span class="n">Serve</span><span class="p">(</span><span class="n">l</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">WithFields</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">Fields</span><span class="p">{</span> <span class="s">"Error"</span><span class="o">:</span> <span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">(),</span> <span class="p">})</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="s">"Failed to serve"</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>We just defined our <code>grpcServer</code> but currently it doesn't do a whole lot. So let's start adding some functionality to our server.</p> <p>We'll start by defining our schema which will be a simple book structure with the properties of name and isbn number as well as an rpc service and method. Let's make our <code>book.proto</code> file like so.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="n">syntax</span> <span class="o">=</span> <span class="s">"proto3"</span><span class="p">;</span> <span class="k">package</span> <span class="n">book</span><span class="p">;</span> <span class="n">message</span> <span class="n">Book</span> <span class="p">{</span> <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="m">1</span><span class="p">;</span> <span class="kt">int32</span> <span class="n">isbn</span> <span class="o">=</span> <span class="m">2</span><span class="p">;</span> <span class="p">}</span> <span class="n">service</span> <span class="n">BookService</span> <span class="p">{</span> <span class="n">rpc</span> <span class="n">GetBook</span><span class="p">(</span><span class="n">Book</span><span class="p">)</span> <span class="n">returns</span> <span class="p">(</span><span class="n">Book</span><span class="p">)</span> <span class="p">{}</span> <span class="p">}</span> </code></pre> </div> <p>This file defines the structure for <code>Book</code> protocol buffers and exposes a single service <code>BookService</code>. This service again has a single method <code>GetBook</code> which can now be called by any gRPC client written in any supported language.</p> <p>These <code>.proto</code> files act as our "contracts" and typically can be shared across all clients. They can generate their own code and then communicate smoothly with our gRPC server.</p> <p>Now, we'll generate the Go code for our <code>.proto</code> file using the <code>protoc</code> tool as mentioned above. Make a directory named <code>book</code> in the root of the project.</p> <p>Now, run the following,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>protoc <span class="nt">--go_out</span><span class="o">=</span><span class="nv">plugins</span><span class="o">=</span>grpc:book book.proto </code></pre> </div> <p>You should see that this generated a <code>book/book.pb.go</code> file. This file contains all the generated Go code which we can now use in our code to register our services and use our methods.</p> <p>But as you can probably guess, this auto-generated code does not contain a definition for our service method <code>GetBook</code>. This is intentional since we would like to define what our gRPC service methods can do.</p> <p>So let's go ahead and define our service method <code>GetBook</code> first. Create another file within the book directory <code>book.go</code>. This file will define the <code>GetBook</code>. For the sake of learning, we're just going to take a <code>Book</code> protocol buffer as argument, read the name and isbn, and then return a new <code>Book</code>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="k">package</span> <span class="n">book</span> <span class="k">import</span> <span class="p">(</span> <span class="n">log</span> <span class="s">"github.com/sirupsen/logrus"</span> <span class="s">"golang.org/x/net/context"</span> <span class="p">)</span> <span class="c">// Server interface for our service methods</span> <span class="k">type</span> <span class="n">Server</span> <span class="k">struct</span> <span class="p">{</span> <span class="p">}</span> <span class="c">// GetBook logs Book from client and returns new Book</span> <span class="k">func</span> <span class="p">(</span><span class="n">s</span> <span class="o">*</span><span class="n">Server</span><span class="p">)</span> <span class="n">GetBook</span><span class="p">(</span><span class="n">ctx</span> <span class="n">context</span><span class="o">.</span><span class="n">Context</span><span class="p">,</span> <span class="n">input</span> <span class="o">*</span><span class="n">Book</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="n">Book</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">WithFields</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">Fields</span><span class="p">{</span> <span class="s">"Name"</span><span class="o">:</span> <span class="n">input</span><span class="o">.</span><span class="n">Name</span><span class="p">,</span> <span class="s">"Isbn"</span><span class="o">:</span> <span class="n">input</span><span class="o">.</span><span class="n">Isbn</span><span class="p">,</span> <span class="p">})</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"Book data received from client"</span><span class="p">)</span> <span class="k">return</span> <span class="o">&amp;</span><span class="n">Book</span><span class="p">{</span><span class="n">Name</span><span class="o">:</span> <span class="s">"The Great Gatsby"</span><span class="p">,</span> <span class="n">Isbn</span><span class="o">:</span> <span class="m">90393</span><span class="p">},</span> <span class="no">nil</span> <span class="p">}</span> </code></pre> </div> <p>As you can see our method now has a definition. If we wanted to add more methods in our <code>BookService</code> we would simply need to add it to <code>book.proto</code> and define it off of our Server <code>struct</code>. Thus our application would be able to expose that method that gRPC clients can use.</p> <p>Finally let's register this newly-defined method in <code>main.go</code>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="k">package</span> <span class="n">main</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"net"</span> <span class="s">"os"</span> <span class="s">"grpc_using_go/book"</span> <span class="n">log</span> <span class="s">"github.com/sirupsen/logrus"</span> <span class="s">"google.golang.org/grpc"</span> <span class="p">)</span> <span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">SetFormatter</span><span class="p">(</span><span class="o">&amp;</span><span class="n">log</span><span class="o">.</span><span class="n">TextFormatter</span><span class="p">{</span> <span class="n">FullTimestamp</span><span class="o">:</span> <span class="no">true</span><span class="p">,</span> <span class="p">})</span> <span class="k">var</span> <span class="n">port</span> <span class="kt">string</span> <span class="k">var</span> <span class="n">ok</span> <span class="kt">bool</span> <span class="n">port</span><span class="p">,</span> <span class="n">ok</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">LookupEnv</span><span class="p">(</span><span class="s">"PORT"</span><span class="p">)</span> <span class="k">if</span> <span class="n">ok</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">WithFields</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">Fields</span><span class="p">{</span> <span class="s">"PORT"</span><span class="o">:</span> <span class="n">port</span><span class="p">,</span> <span class="p">})</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"PORT env var defined"</span><span class="p">)</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">port</span> <span class="o">=</span> <span class="s">"9000"</span> <span class="n">log</span><span class="o">.</span><span class="n">WithFields</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">Fields</span><span class="p">{</span> <span class="s">"PORT"</span><span class="o">:</span> <span class="n">port</span><span class="p">,</span> <span class="p">})</span><span class="o">.</span><span class="n">Warn</span><span class="p">(</span><span class="s">"PORT env var not defined. Going with default"</span><span class="p">)</span> <span class="p">}</span> <span class="n">l</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">net</span><span class="o">.</span><span class="n">Listen</span><span class="p">(</span><span class="s">"tcp"</span><span class="p">,</span> <span class="s">":"</span><span class="o">+</span><span class="n">port</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">WithFields</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">Fields</span><span class="p">{</span> <span class="s">"Error"</span><span class="o">:</span> <span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">(),</span> <span class="p">})</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="s">"Failed to listen"</span><span class="p">)</span> <span class="p">}</span> <span class="n">s</span> <span class="o">:=</span> <span class="n">book</span><span class="o">.</span><span class="n">Server</span><span class="p">{}</span> <span class="n">grpcServer</span> <span class="o">:=</span> <span class="n">grpc</span><span class="o">.</span><span class="n">NewServer</span><span class="p">()</span> <span class="n">book</span><span class="o">.</span><span class="n">RegisterBookServiceServer</span><span class="p">(</span><span class="n">grpcServer</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">s</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"gRPC server started at "</span><span class="p">,</span> <span class="n">port</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">grpcServer</span><span class="o">.</span><span class="n">Serve</span><span class="p">(</span><span class="n">l</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">WithFields</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">Fields</span><span class="p">{</span> <span class="s">"Error"</span><span class="o">:</span> <span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">(),</span> <span class="p">})</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="s">"Failed to serve"</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>Now, that it has been registered, it's finally time to run and test our code. Go ahead and run <code>go run main.go</code>. You should see the following logs pop up in your terminal.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>WARN[2020-12-05T23:27:12+05:30] PORT <span class="nb">env </span>var not defined. Going with default <span class="nv">PORT</span><span class="o">=</span>9000 INFO[2020-12-05T23:27:12+05:30] gRPC server started at 9000 </code></pre> </div> <p>Aside from the timestamp, it should be the same. </p> <p>Yay! You're gRPC server is now running. You can make the WARN log go away by simply setting an environment variable using <code>export PORT=9000</code>.</p> <p>Alright, now let's test this. Fire up a new terminal. We'll be using a tool known as <a href="proxy.php?url=https://github.com/fullstorydev/grpcurl">gRPCurl</a> to test our gRPC server. If you're on a Mac, simply install it using <a href="proxy.php?url=https://brew.sh/">homebrew</a> by running,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>brew <span class="nb">install </span>grpcurl </code></pre> </div> <p>For other platforms and package managers, refer to installation instructions in the linked repo.</p> <p>Now, to test our server using this, we need to enable something known as gRPC Server Reflection. gRPC Server Reflection provides information about publicly-accessible gRPC services on a server, and assists clients at runtime to construct RPC requests and responses without precompiled service information. You can learn more about it <a href="proxy.php?url=https://chromium.googlesource.com/external/github.com/grpc/grpc-go/+/HEAD/Documentation/server-reflection-tutorial.md">here</a>.</p> <p>All we need to do is add the reflection package and simply add one line of code as shown below.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="k">package</span> <span class="n">main</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"net"</span> <span class="s">"os"</span> <span class="s">"grpc_using_go/book"</span> <span class="n">log</span> <span class="s">"github.com/sirupsen/logrus"</span> <span class="s">"google.golang.org/grpc"</span> <span class="s">"google.golang.org/grpc/reflection"</span> <span class="p">)</span> <span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">SetFormatter</span><span class="p">(</span><span class="o">&amp;</span><span class="n">log</span><span class="o">.</span><span class="n">TextFormatter</span><span class="p">{</span> <span class="n">FullTimestamp</span><span class="o">:</span> <span class="no">true</span><span class="p">,</span> <span class="p">})</span> <span class="k">var</span> <span class="n">port</span> <span class="kt">string</span> <span class="k">var</span> <span class="n">ok</span> <span class="kt">bool</span> <span class="n">port</span><span class="p">,</span> <span class="n">ok</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">LookupEnv</span><span class="p">(</span><span class="s">"PORT"</span><span class="p">)</span> <span class="k">if</span> <span class="n">ok</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">WithFields</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">Fields</span><span class="p">{</span> <span class="s">"PORT"</span><span class="o">:</span> <span class="n">port</span><span class="p">,</span> <span class="p">})</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"PORT env var defined"</span><span class="p">)</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">port</span> <span class="o">=</span> <span class="s">"9000"</span> <span class="n">log</span><span class="o">.</span><span class="n">WithFields</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">Fields</span><span class="p">{</span> <span class="s">"PORT"</span><span class="o">:</span> <span class="n">port</span><span class="p">,</span> <span class="p">})</span><span class="o">.</span><span class="n">Warn</span><span class="p">(</span><span class="s">"PORT env var not defined. Going with default"</span><span class="p">)</span> <span class="p">}</span> <span class="n">l</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">net</span><span class="o">.</span><span class="n">Listen</span><span class="p">(</span><span class="s">"tcp"</span><span class="p">,</span> <span class="s">":"</span><span class="o">+</span><span class="n">port</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">WithFields</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">Fields</span><span class="p">{</span> <span class="s">"Error"</span><span class="o">:</span> <span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">(),</span> <span class="p">})</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="s">"Failed to listen"</span><span class="p">)</span> <span class="p">}</span> <span class="n">s</span> <span class="o">:=</span> <span class="n">book</span><span class="o">.</span><span class="n">Server</span><span class="p">{}</span> <span class="n">grpcServer</span> <span class="o">:=</span> <span class="n">grpc</span><span class="o">.</span><span class="n">NewServer</span><span class="p">()</span> <span class="n">reflection</span><span class="o">.</span><span class="n">Register</span><span class="p">(</span><span class="n">grpcServer</span><span class="p">)</span> <span class="n">book</span><span class="o">.</span><span class="n">RegisterBookServiceServer</span><span class="p">(</span><span class="n">grpcServer</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">s</span><span class="p">)</span> <span class="n">log</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"gRPC server started at "</span><span class="p">,</span> <span class="n">port</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">grpcServer</span><span class="o">.</span><span class="n">Serve</span><span class="p">(</span><span class="n">l</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">WithFields</span><span class="p">(</span><span class="n">log</span><span class="o">.</span><span class="n">Fields</span><span class="p">{</span> <span class="s">"Error"</span><span class="o">:</span> <span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">(),</span> <span class="p">})</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="s">"Failed to serve"</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>Simply restart your server again, open another terminal and run the following command,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>grpcurl <span class="nt">-plaintext</span> localhost:9000 list </code></pre> </div> <p>The <code>-plaintext</code> flag is due to the fact that our local server has no TLS certificate yet. <code>list</code> lists all our registered service on our server. Our gRPC server is <code>[localhost:9000](http://localhost:9000)</code> You should see the following output.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>book.BookService grpc.reflection.v1alpha.ServerReflection </code></pre> </div> <p>Nice! Our gRPC service shows up. Let's see a bit more detail by running the following,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>grpcurl <span class="nt">-plaintext</span> localhost:9000 describe </code></pre> </div> <p><code>describe</code> describes all our service and methods as you can see below,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>book.BookService is a service: service BookService <span class="o">{</span> rpc GetBook <span class="o">(</span> .book.Book <span class="o">)</span> returns <span class="o">(</span> .book.Book <span class="o">)</span><span class="p">;</span> <span class="o">}</span> grpc.reflection.v1alpha.ServerReflection is a service: service ServerReflection <span class="o">{</span> rpc ServerReflectionInfo <span class="o">(</span> stream .grpc.reflection.v1alpha.ServerReflectionRequest <span class="o">)</span> returns <span class="o">(</span> stream .grpc.reflection.v1alpha.ServerReflectionResponse <span class="o">)</span><span class="p">;</span> <span class="o">}</span> </code></pre> </div> <p>Finally let's test our <code>GetBook</code> method by running the following,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>grpcurl <span class="nt">-d</span> <span class="s1">'{"name": "To Kill a Mockingbird", "isbn": 12345}'</span> <span class="nt">-plaintext</span> localhost:9000 book.BookService/GetBook </code></pre> </div> <p>The <code>-d</code> flag is used to send non-empty request body while invoking a RPC method. Here we specify the name and isbn of a book. All arguments must come before the server address. At the end we specify the service and method name in <code>Service/Method</code> format. You should see the following output as well as a new server log.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="o">{</span> <span class="s2">"name"</span>: <span class="s2">"The Great Gatsby"</span>, <span class="s2">"isbn"</span>: 90393 <span class="o">}</span> INFO[2020-12-05T23:31:04+05:30] Book data received from client <span class="nv">Isbn</span><span class="o">=</span>12345 <span class="nv">Name</span><span class="o">=</span><span class="s2">"To Kill a Mockingbird"</span> </code></pre> </div> <h2> Conclusion </h2> <p>If you'd like to dig deeper into gRPC and discover all the cool things you can make with it, read the official docs,</p> <p><a href="proxy.php?url=https://grpc.io/docs/">gRPC Docs</a></p> <p>Also, here's an <a href="proxy.php?url=https://github.com/grpc-ecosystem/awesome-grpc">awesome list of resources</a> to learn further!</p> <p>If you get stuck here's the <a href="proxy.php?url=https://github.com/saswatamcode/grpc_using_go">repo</a> with all the code!</p> <p>For any queries reach out to my <a href="proxy.php?url=https://twitter.com/saswatamcode">socials</a> or <a href="proxy.php?url=https://github.com/saswatamcode">GitHub</a>!</p> go grpc microservices webdev Protocol Buffers in Go Saswata Mukherjee Wed, 05 Aug 2020 13:17:57 +0000 https://dev.to/gdsckiitdev/protocol-buffers-in-go-5bl7 https://dev.to/gdsckiitdev/protocol-buffers-in-go-5bl7 <p>Hey there! If you've explored the world of microservices, you've probably come across the term <code>gRPC</code>. It's a modern open source high performance Remote Procedure Call framework which can run in any environment. It's grown super popular recently with scalable distributed system architecture. </p> <p>A major reason why <code>gRPC</code> has grown so popular, is due to the fact that it uses a special mechanism for data serialisation which makes payloads smaller, faster and simpler. This helps us save those precious milliseconds. But what is this special mechanism?</p> <p>Enter Protocol Buffers.</p> <h2> What are Protocol Buffers? </h2> <blockquote> <p>Protocol buffers are Google's language-neutral, platform-neutral, extensible mechanism for serialising structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.</p> </blockquote> <p>That's according to the <a href="proxy.php?url=https://developers.google.com/protocol-buffers">official documentation</a>.</p> <p>They are essentially a data format like JSON or XML i.e, they can store structured data which can then be serialised and de-serialised by a wide number of languages. Let's understand this with a few examples!</p> <p>Imagine you're storing data about books. So a sample XML will look like this.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight xml"><code><span class="nt">&lt;book&gt;</span> <span class="nt">&lt;name&gt;</span>Animal Farm<span class="nt">&lt;/name&gt;</span> <span class="nt">&lt;isbn&gt;</span>104<span class="nt">&lt;/isbn&gt;</span> <span class="nt">&lt;/book&gt;</span> </code></pre> </div> <p>We could represent the same structured data using a smaller footprint with JSON.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight json"><code><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Animal Farm"</span><span class="p">,</span><span class="w"> </span><span class="nl">"isbn"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">104</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre> </div> <p>And if we were to represent this using protocol buffers, it would look something like this.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="o">[</span>10 11 65 110 105 109 97 108 32 70 97 114 109 16 104] </code></pre> </div> <p>If you observe the above wire encoded output closely, you might see that starting from position 2 of the array, the name of the book, "Animal Farm" is spelled out with 'A' = 65, 'n'=110 an so on. The last element is a byte representation of the isbn. While this looks simple there's much more to the encoding than what meets the eye. If you'd like to delve into the details of the encoding format you can read more <a href="proxy.php?url=https://developers.google.com/protocol-buffers/docs/encoding">here</a>. </p> <p>Now, at this scale the size of both the JSON and the Protocol Buffer seem to be quite similar. But as your data increases, a lot of the size and complexity gets shaved off which will lead to smaller and more efficient payloads for your application. Let's see how we can use Protocol Buffers in Go!</p> <h2> Setting Up Protocol Buffers </h2> <p>We're going to cook up a simple example to see how protocol buffers work in Go. Let's get started by initialising a new go module in a new directory. Run the following.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>go mod init protobuf_using_go </code></pre> </div> <p>Now, install the packages required.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>go get <span class="nt">-u</span> github.com/golang/protobuf go get <span class="nt">-u</span> github.com/golang/protobuf/proto go <span class="nb">install </span>google.golang.org/protobuf/cmd/protoc-gen-go </code></pre> </div> <p>Make sure your <code>.bashrc</code> or <code>.zshrc</code> file has the following environment variables.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nb">export </span><span class="nv">GOPATH</span><span class="o">=</span><span class="nv">$HOME</span>/go <span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="nv">$PATH</span>:<span class="nv">$GOPATH</span>/bin </code></pre> </div> <p>You can even use a package manager like homebrew to install the <code>protoc</code> binary. Make sure you have the binary installed by running <code>protoc --version</code>. We're going to be using a version above 3.</p> <p>For any issues related to installation you can refer to the <a href="proxy.php?url=https://github.com/golang/protobuf">official repo.</a></p> <p>Now, we can go about defining our <code>protobuf</code> schema. We'll need to start with a <code>.proto</code> file. Let's define the book structure we saw earlier.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="c">// book.proto</span> <span class="n">syntax</span><span class="o">=</span><span class="s">"proto3"</span><span class="p">;</span> <span class="k">package</span> <span class="n">main</span><span class="p">;</span> <span class="n">message</span> <span class="n">Book</span> <span class="p">{</span> <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="m">1</span><span class="p">;</span> <span class="kt">int32</span> <span class="n">isbn</span> <span class="o">=</span> <span class="m">2</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>Let's understand what we just wrote. At first we specified the syntax we want to use which is <code>"proto3"</code> and specified that we want this to be a part of the <code>main</code> package.</p> <p>Then we specify our schema. The definitions in a <code>.proto</code> file are simple: you add a <code>message</code> for each data structure you want to serialise, then specify a name and a type for each field in the <code>message</code>. Here Book is our data structure which will have two fields, <code>name</code> of type <code>string</code> and <code>isbn</code> of type <code>int32</code>. Keep in mind that the type comes before the variable name unlike Go.</p> <p>Also each field is associated with a unique number. These numbers are used to identify our fields in the encoded message and shouldn't be changed once the message type is in use.</p> <p>Now, let's go ahead and compile this with the protocol buffer compiler using the following command.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>protoc <span class="nt">--go_out</span><span class="o">=</span><span class="nb">.</span> <span class="k">*</span>.proto </code></pre> </div> <p>The <code>--go_out</code> flag specifies which directory the generated Go code for the <code>.proto</code> will be stored in. We're keeping it at the root of the project. The second argument specifies which file to compile. Here, we're compiling all files with a <code>.proto</code> extension. Running this should generate a <code>book.pb.go</code> file with the equivalent Go code that we'll require to use our book protocol buffer. Next, let's write code to read/write data using our protocol buffer.</p> <h2> Specifying Field Rules </h2> <p>We can specify certain rules for our message structure fields as well.</p> <ul> <li> <code>required</code>: a message must have exactly one of this field</li> <li> <code>optional</code>: a message can have zero or just one of this field</li> <li> <code>repeated</code>: this field can be repeated any number of times in the message including zero</li> </ul> <p>For example we can make the <code>isbn</code> field optional by writing<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="n">optional</span> <span class="kt">int32</span> <span class="n">isbn</span> <span class="o">=</span> <span class="m">2</span><span class="p">;</span> </code></pre> </div> <p>For this tutorial we won't be using field rules outside of the default ones.</p> <h2> Working with Protocol Buffers </h2> <p>Let's create a new file <code>main.go</code> with the following code.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="c">// main.go</span> <span class="k">package</span> <span class="n">main</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"fmt"</span> <span class="s">"log"</span> <span class="s">"github.com/golang/protobuf/proto"</span> <span class="p">)</span> <span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span> <span class="n">myBook</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="n">Book</span><span class="p">{</span> <span class="n">Name</span><span class="o">:</span> <span class="s">"Animal Farm"</span><span class="p">,</span> <span class="n">Isbn</span><span class="o">:</span> <span class="m">104</span><span class="p">,</span> <span class="p">}</span> <span class="n">data</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">proto</span><span class="o">.</span><span class="n">Marshal</span><span class="p">(</span><span class="n">myBook</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="s">"marshaling error: "</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span> <span class="p">}</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="n">myNewBook</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="n">Book</span><span class="p">{}</span> <span class="n">err</span> <span class="o">=</span> <span class="n">proto</span><span class="o">.</span><span class="n">Unmarshal</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">myNewBook</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="s">"Unmarshaling error: "</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span> <span class="p">}</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="n">myNewBook</span><span class="o">.</span><span class="n">GetName</span><span class="p">())</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="n">myNewBook</span><span class="o">.</span><span class="n">GetIsbn</span><span class="p">())</span> <span class="p">}</span> </code></pre> </div> <p>That's a lot of new code so let's break it down. </p> <p>First we create a variable <code>myBook</code> , which is a pointer to the <code>Book</code> struct defined in <code>book.pb.go</code>. and we set the fields with our data. Notice that the fields start with a capital letter now. Then we use the <code>Marshal</code> function to serialise our protocol buffer data and store it in the <code>data</code> variable which we display. This will print our encoded data.</p> <p>Now we want to de-serialise the encoded message, i.e, read a protocol buffer. So we declare a new pointer to the <code>Book</code> struct, <code>myNewBook</code>, with empty fields. Then, we use the <code>Unmarshal</code> function to de-serialise the encoded message stored in the <code>data</code>, and store it in <code>myNewBook</code>. Finally we use the getter methods, <code>GetName()</code> and <code>GetIsbn()</code>, provided in our generated code to retrieve and print the fields. Let's go ahead and run this. We need to pass in the generated code file as well.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>go run main.go book.pb.go </code></pre> </div> <p>You should see the following output,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="o">[</span>10 11 65 110 105 109 97 108 32 70 97 114 109 16 104] Animal Farm 104 </code></pre> </div> <h2> Nested Fields </h2> <p>Just like JSON, our Protocol Buffer might contain nested data. So let's go ahead and add nested elements in our <code>book.proto</code> file.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="c">// book.proto</span> <span class="n">syntax</span><span class="o">=</span><span class="s">"proto3"</span><span class="p">;</span> <span class="k">package</span> <span class="n">main</span><span class="p">;</span> <span class="n">message</span> <span class="n">Author</span> <span class="p">{</span> <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="m">1</span><span class="p">;</span> <span class="kt">int32</span> <span class="n">yearOfPublishing</span> <span class="o">=</span> <span class="m">2</span><span class="p">;</span> <span class="p">}</span> <span class="n">message</span> <span class="n">Book</span> <span class="p">{</span> <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="m">1</span><span class="p">;</span> <span class="kt">int32</span> <span class="n">isbn</span> <span class="o">=</span> <span class="m">2</span><span class="p">;</span> <span class="n">Author</span> <span class="n">author</span> <span class="o">=</span> <span class="m">3</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>Here, we've again defined a message structure, <code>Author</code>, with fields <code>name</code> and <code>yearOfPublishing</code>. Then in order to nest this inside <code>Book</code>, we've added a field of type <code>Author</code> and name <code>author</code> to it. Using this we have effectively created a nested structure.</p> <p>Now we simply generate the equivalent Go code for this by running,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="n">protoc</span> <span class="o">--</span><span class="n">go_out</span><span class="o">=.</span> <span class="o">*.</span><span class="n">proto</span> </code></pre> </div> <p>Now, let's make the changes in our main file!<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight go"><code><span class="c">// main.go</span> <span class="k">package</span> <span class="n">main</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"fmt"</span> <span class="s">"log"</span> <span class="s">"github.com/golang/protobuf/proto"</span> <span class="p">)</span> <span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span> <span class="n">myBook</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="n">Book</span><span class="p">{</span> <span class="n">Name</span><span class="o">:</span> <span class="s">"Animal Farm"</span><span class="p">,</span> <span class="n">Isbn</span><span class="o">:</span> <span class="m">104</span><span class="p">,</span> <span class="n">Author</span><span class="o">:</span> <span class="o">&amp;</span><span class="n">Author</span><span class="p">{</span> <span class="n">Name</span><span class="o">:</span> <span class="s">"George Orwell"</span><span class="p">,</span> <span class="n">YearOfPublishing</span><span class="o">:</span> <span class="m">1945</span><span class="p">,</span> <span class="p">},</span> <span class="p">}</span> <span class="n">data</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">proto</span><span class="o">.</span><span class="n">Marshal</span><span class="p">(</span><span class="n">myBook</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="s">"marshaling error: "</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span> <span class="p">}</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="n">myNewBook</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="n">Book</span><span class="p">{}</span> <span class="n">err</span> <span class="o">=</span> <span class="n">proto</span><span class="o">.</span><span class="n">Unmarshal</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">myNewBook</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="s">"Unmarshaling error: "</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span> <span class="p">}</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="n">myNewBook</span><span class="o">.</span><span class="n">GetName</span><span class="p">())</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="n">myNewBook</span><span class="o">.</span><span class="n">GetIsbn</span><span class="p">())</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="n">myNewBook</span><span class="o">.</span><span class="n">Author</span><span class="o">.</span><span class="n">GetName</span><span class="p">())</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="n">myNewBook</span><span class="o">.</span><span class="n">Author</span><span class="o">.</span><span class="n">GetYearOfPublishing</span><span class="p">())</span> <span class="p">}</span> </code></pre> </div> <p>If you see the code above, you'll notice that we've added a new field, <code>Author</code> to <code>myBook</code>, which points to the <code>Author</code> type in our generated code. We set the fields in <code>Author</code> with the relevant data. </p> <p>There's no change in our marshalling and un-marshalling methods. Finally, we access the newly set fields via getter functions, <code>Author.GetName()</code> and <code>Author.GetYearOfPublishing()</code>, of the nested message structure and display it. Run it with the command:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>go run main.go book.pb.go </code></pre> </div> <p>You should see the following output.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="o">[</span>10 11 65 110 105 109 97 108 32 70 97 114 109 16 104 26 18 10 13 71 101 111 114 103 101 32 79 114 119 101 108 108 16 153 15] Animal Farm 104 George Orwell 1945 </code></pre> </div> <h2> Conclusion </h2> <p>If you'd like to dig deeper into Protocol Buffers and discover all the cool stuff you can do with it, read the official docs,</p> <p><a href="proxy.php?url=https://developers.google.com/protocol-buffers">Protocol Buffer Docs</a></p> <p>If you get stuck here's the <a href="proxy.php?url=https://github.com/saswatamcode/protobuf_using_go">repo</a> with all the code!</p> <p>For any queries reach out to my <a href="proxy.php?url=https://twitter.com/saswatamcode">socials</a> or <a href="proxy.php?url=https://github.com/saswatamcode">GitHub</a>!</p> go protobuf webdev tutorial Dynamic Pages using React Router Saswata Mukherjee Fri, 03 Jul 2020 08:24:35 +0000 https://dev.to/gdsckiitdev/dynamic-pages-using-react-router-2pm https://dev.to/gdsckiitdev/dynamic-pages-using-react-router-2pm <p>Hey there! </p> <p>If you've ever visited a site with a bunch of different users with different content from each user such as a blogging site, social media or even <a href="proxy.php?url=http://dev.to">dev.to</a>, you've probably noticed that each page of a particular user has a route along the lines of <code>/username</code> or if you visit a particular article of the user on the site then a route like <code>/username/article</code>. You'll even notice that while all the pages have similar structure, their content is different.</p> <p>This is what's known as dynamic pages and routes.</p> <p>Let's see how we can implement this in React. We'll be using the Star Wars API to get a list of users and we'll generate separate pages and routes for all of them.</p> <p>Keep in mind that this tutorial focusses on dynamic routing in React with React Router. For achieving the same results in Gatsby or Next.js the procedure will be different and will rely on their custom routing APIs.</p> <h2> Get Started </h2> <p>With that out of the way let's get started. Create a new React project using <code>create-react-app</code>. Also install React Router by running <code>yarn add/npm i react-router-dom</code>. The <code>react-router-dom</code> module brings over the core functionality of the React Router to browser based applications.</p> <p>Now open up your <code>App.js</code> and remove the default code and add the following import statements.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="k">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">useState</span><span class="p">,</span> <span class="nx">useEffect</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">Link</span><span class="p">,</span> <span class="nx">BrowserRouter</span> <span class="k">as</span> <span class="nx">Router</span><span class="p">,</span> <span class="nx">Route</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react-router-dom</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="p">&lt;&gt;&lt;/&gt;;</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span> </code></pre> </div> <p>For this tutorial we'll be using React Hooks to make our code simpler. In the same <code>App.js</code> file let's add another component called <code>HomePage</code> . Like the name suggests this will be our homepage from where we'll call the Star Wars API to get person data. We'll also define a route for this component.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="k">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">useState</span><span class="p">,</span> <span class="nx">useEffect</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">Link</span><span class="p">,</span> <span class="nx">BrowserRouter</span> <span class="k">as</span> <span class="nx">Router</span><span class="p">,</span> <span class="nx">Route</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react-router-dom</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">HomePage</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="p">&lt;&gt;</span>Home Page<span class="p">&lt;/&gt;;</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;&gt;</span> <span class="p">&lt;</span><span class="nc">Router</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">Route</span> <span class="na">exact</span> <span class="na">path</span><span class="p">=</span><span class="s">"/"</span> <span class="na">component</span><span class="p">=</span><span class="si">{</span><span class="nx">HomePage</span><span class="si">}</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nc">Router</span><span class="p">&gt;</span> <span class="p">&lt;/&gt;</span> <span class="p">);</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span> </code></pre> </div> <p>Great! So now if you run <code>yarn start/npm start</code> and visit <code>[localhost:3000/](http://localhost:3000/)</code> in your browser you should see "Home Page" written on your screen. Now let's go ahead and add in our API call.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="k">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">useState</span><span class="p">,</span> <span class="nx">useEffect</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">Link</span><span class="p">,</span> <span class="nx">BrowserRouter</span> <span class="k">as</span> <span class="nx">Router</span><span class="p">,</span> <span class="nx">Route</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react-router-dom</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">HomePage</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">isLoading</span><span class="p">,</span> <span class="nx">setIsLoading</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">data</span><span class="p">,</span> <span class="nx">setData</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">();</span> <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://swapi.dev/api/people/</span><span class="dl">"</span><span class="p">,</span> <span class="p">{})</span> <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span> <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">setData</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">results</span><span class="p">);</span> <span class="nx">setIsLoading</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span> <span class="p">})</span> <span class="p">.</span><span class="k">catch</span><span class="p">((</span><span class="nx">error</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">error</span><span class="p">));</span> <span class="p">},</span> <span class="p">[]);</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;&gt;</span> <span class="si">{</span><span class="o">!</span><span class="nx">isLoading</span> <span class="o">&amp;&amp;</span> <span class="nx">data</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">person</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="p">&lt;</span><span class="nt">h5</span> <span class="na">key</span><span class="p">=</span><span class="si">{</span><span class="nx">index</span><span class="si">}</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">person</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h5</span><span class="p">&gt;;</span> <span class="p">})</span><span class="si">}</span> <span class="p">&lt;/&gt;</span> <span class="p">);</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;&gt;</span> <span class="p">&lt;</span><span class="nc">Router</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">Route</span> <span class="na">exact</span> <span class="na">path</span><span class="p">=</span><span class="s">"/"</span> <span class="na">component</span><span class="p">=</span><span class="si">{</span><span class="nx">HomePage</span><span class="si">}</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nc">Router</span><span class="p">&gt;</span> <span class="p">&lt;/&gt;</span> <span class="p">);</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span> </code></pre> </div> <p>Now that's a lot of new code. Let's break down what we wrote.</p> <p>We have two states, <code>isLoading</code> which is a boolean which tells us whether we have received data from our API or not and <code>data</code> which contains the JSON that we'll receive from the API call. </p> <p>We use a <code>useEffect</code> hook to fetch data when the <code>HomePage</code> component loads. When we get data from the API we set the value of <code>isLoading</code> to false and <code>data</code> to whatever JSON we get.</p> <p>Now, if you look at the <code>jsx</code> inside the <code>HomePage</code> component you'll see that we check the value of <code>isLoading</code> and if it's false, we map through <code>data</code> to render the names of the Star Wars characters. </p> <p>If you run your app now you should see the names pop up one after the other. </p> <p>You can check out the Swapi documentation <a href="proxy.php?url=https://swapi.dev/documentation">here</a>.</p> <h2> Making a dynamic component </h2> <p>But, we don't want a list of people, we also want separate pages at dynamic routes for each of them!</p> <p>So let's create another component called <code>PersonPage</code> which will fetch data from an API getting the details of each person.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="k">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">useState</span><span class="p">,</span> <span class="nx">useEffect</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">Link</span><span class="p">,</span> <span class="nx">BrowserRouter</span> <span class="k">as</span> <span class="nx">Router</span><span class="p">,</span> <span class="nx">Route</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react-router-dom</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">PersonPage</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">isLoading</span><span class="p">,</span> <span class="nx">setIsLoading</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">data</span><span class="p">,</span> <span class="nx">setData</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">();</span> <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">fetch</span><span class="p">(</span><span class="s2">`https://swapi.dev/api/people/</span><span class="p">${</span><span class="nx">personId</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="p">{})</span> <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span> <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">setData</span><span class="p">(</span><span class="nx">response</span><span class="p">);</span> <span class="nx">setIsLoading</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`https://swapi.dev/api/people/</span><span class="p">${</span><span class="nx">personId</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span> <span class="p">})</span> <span class="p">.</span><span class="k">catch</span><span class="p">((</span><span class="nx">error</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">error</span><span class="p">));</span> <span class="p">},</span> <span class="p">[</span><span class="nx">personId</span><span class="p">]);</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;&gt;</span> <span class="si">{</span><span class="o">!</span><span class="nx">isLoading</span> <span class="o">&amp;&amp;</span> <span class="p">(</span> <span class="p">&lt;&gt;</span> <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>Name: <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Height: <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">height</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Mass: <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">mass</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Hair Color: <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">hair_color</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Skin Color: <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">skin_color</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Eye Color: <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">eye_color</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Birth Year: <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">birth_year</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Gender: <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">gender</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">Link</span> <span class="na">to</span><span class="p">=</span><span class="s">"/"</span><span class="p">&gt;</span>Back to homepage<span class="p">&lt;/</span><span class="nc">Link</span><span class="p">&gt;</span> <span class="p">&lt;/&gt;</span> <span class="p">)</span><span class="si">}</span> <span class="p">&lt;/&gt;</span> <span class="p">);</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">HomePage</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">isLoading</span><span class="p">,</span> <span class="nx">setIsLoading</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">data</span><span class="p">,</span> <span class="nx">setData</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">();</span> <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://swapi.dev/api/people/</span><span class="dl">"</span><span class="p">,</span> <span class="p">{})</span> <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span> <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">setData</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">results</span><span class="p">);</span> <span class="nx">setIsLoading</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span> <span class="p">})</span> <span class="p">.</span><span class="k">catch</span><span class="p">((</span><span class="nx">error</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">error</span><span class="p">));</span> <span class="p">},</span> <span class="p">[]);</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;&gt;</span> <span class="si">{</span><span class="o">!</span><span class="nx">isLoading</span> <span class="o">&amp;&amp;</span> <span class="nx">data</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">person</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">h5</span> <span class="na">key</span><span class="p">=</span><span class="si">{</span><span class="nx">index</span><span class="si">}</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">Link</span> <span class="na">to</span><span class="p">=</span><span class="si">{</span><span class="s2">`/person/</span><span class="p">${</span><span class="nx">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">}</span><span class="s2">`</span><span class="si">}</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">person</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span>'s Page<span class="p">&lt;/</span><span class="nc">Link</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">h5</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">})</span><span class="si">}</span> <span class="p">&lt;/&gt;</span> <span class="p">);</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;&gt;</span> <span class="p">&lt;</span><span class="nc">Router</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">Route</span> <span class="na">exact</span> <span class="na">path</span><span class="p">=</span><span class="s">"/"</span> <span class="na">component</span><span class="p">=</span><span class="si">{</span><span class="nx">HomePage</span><span class="si">}</span> <span class="p">/&gt;</span> <span class="p">&lt;</span><span class="nc">Route</span> <span class="na">path</span><span class="p">=</span><span class="s">"/person/:personId"</span> <span class="na">component</span><span class="p">=</span><span class="si">{</span><span class="nx">PersonPage</span><span class="si">}</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nc">Router</span><span class="p">&gt;</span> <span class="p">&lt;/&gt;</span> <span class="p">);</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span> </code></pre> </div> <p>Again that's a lot of changes to our code.</p> <p>We have defined a <code>PersonPage</code> component which lists details about each person by getting data from the API in the same fashion as <code>HomePage</code>. We have also defined a new route for this component i.e, <code>person/:personId</code>. This is a bit different compared to our regular routes. Here we pass a parameter <code>personId</code> through the route. That way a single component at that route can be dynamic based on that parameter.</p> <p><code>HomePage</code> has also changed and now returns links to this dynamic route with <code>index</code> as the route parameter. </p> <p>If you observe <code>PersonPage</code> closely, you'll realise that the while the structure of it stays the same, all the content on the page is dependent on <code>personId</code> i.e, the component is fully dynamic. But <code>PersonPage</code> hasn't accessed this parameter yet. This is where we'll use a little bit of React Router magic.</p> <h2> React Router magic </h2> <p>React Router passes two props to all of its routed components:</p> <ul> <li> <code>match</code> props</li> <li> <code>location</code> props</li> </ul> <p>You can log them out in the console if you want to see them in their entirety. We'll be using the <code>match</code> prop to access the route parameters from the <code>PersonPage</code> component. The <code>match</code> prop has a property called <code>params</code> which will have the <code>personId</code> parameter. Let's modify our code!<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="k">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">useState</span><span class="p">,</span> <span class="nx">useEffect</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">Link</span><span class="p">,</span> <span class="nx">BrowserRouter</span> <span class="k">as</span> <span class="nx">Router</span><span class="p">,</span> <span class="nx">Route</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react-router-dom</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="dl">"</span><span class="s2">./App.css</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">PersonPage</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">match</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">{</span> <span class="na">params</span><span class="p">:</span> <span class="p">{</span> <span class="nx">personId</span> <span class="p">},</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">match</span><span class="p">;</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">isLoading</span><span class="p">,</span> <span class="nx">setIsLoading</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">data</span><span class="p">,</span> <span class="nx">setData</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">();</span> <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">fetch</span><span class="p">(</span><span class="s2">`https://swapi.dev/api/people/</span><span class="p">${</span><span class="nx">personId</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="p">{})</span> <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span> <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">setData</span><span class="p">(</span><span class="nx">response</span><span class="p">);</span> <span class="nx">setIsLoading</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`https://swapi.dev/api/people/</span><span class="p">${</span><span class="nx">personId</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span> <span class="p">})</span> <span class="p">.</span><span class="k">catch</span><span class="p">((</span><span class="nx">error</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">error</span><span class="p">));</span> <span class="p">},</span> <span class="p">[</span><span class="nx">personId</span><span class="p">]);</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;&gt;</span> <span class="si">{</span><span class="o">!</span><span class="nx">isLoading</span> <span class="o">&amp;&amp;</span> <span class="p">(</span> <span class="p">&lt;&gt;</span> <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>Name: <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Height: <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">height</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Mass: <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">mass</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Hair Color: <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">hair_color</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Skin Color: <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">skin_color</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Eye Color: <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">eye_color</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Birth Year: <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">birth_year</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>Gender: <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">gender</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">Link</span> <span class="na">to</span><span class="p">=</span><span class="s">"/"</span><span class="p">&gt;</span>Back to homepage<span class="p">&lt;/</span><span class="nc">Link</span><span class="p">&gt;</span> <span class="p">&lt;/&gt;</span> <span class="p">)</span><span class="si">}</span> <span class="p">&lt;/&gt;</span> <span class="p">);</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">HomePage</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">isLoading</span><span class="p">,</span> <span class="nx">setIsLoading</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">data</span><span class="p">,</span> <span class="nx">setData</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">();</span> <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://swapi.dev/api/people/</span><span class="dl">"</span><span class="p">,</span> <span class="p">{})</span> <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span> <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">setData</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">results</span><span class="p">);</span> <span class="nx">setIsLoading</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span> <span class="p">})</span> <span class="p">.</span><span class="k">catch</span><span class="p">((</span><span class="nx">error</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">error</span><span class="p">));</span> <span class="p">},</span> <span class="p">[]);</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;&gt;</span> <span class="si">{</span><span class="o">!</span><span class="nx">isLoading</span> <span class="o">&amp;&amp;</span> <span class="nx">data</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">person</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">h5</span> <span class="na">key</span><span class="p">=</span><span class="si">{</span><span class="nx">index</span><span class="si">}</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">Link</span> <span class="na">to</span><span class="p">=</span><span class="si">{</span><span class="s2">`/person/</span><span class="p">${</span><span class="nx">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">}</span><span class="s2">`</span><span class="si">}</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">person</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span>'s Page<span class="p">&lt;/</span><span class="nc">Link</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">h5</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">})</span><span class="si">}</span> <span class="p">&lt;/&gt;</span> <span class="p">);</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;&gt;</span> <span class="p">&lt;</span><span class="nc">Router</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">Route</span> <span class="na">exact</span> <span class="na">path</span><span class="p">=</span><span class="s">"/"</span> <span class="na">component</span><span class="p">=</span><span class="si">{</span><span class="nx">HomePage</span><span class="si">}</span> <span class="p">/&gt;</span> <span class="p">&lt;</span><span class="nc">Route</span> <span class="na">path</span><span class="p">=</span><span class="s">"/person/:personId"</span> <span class="na">component</span><span class="p">=</span><span class="si">{</span><span class="nx">PersonPage</span><span class="si">}</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nc">Router</span><span class="p">&gt;</span> <span class="p">&lt;/&gt;</span> <span class="p">);</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span> </code></pre> </div> <p>There you go!</p> <p><code>PersonPage</code> now accesses the <code>personId</code> parameter via ES6 destructuring and uses it for the API call. Run your React app and you'll see <code>HomePage</code> populate itself with links and clicking on any person's link will lead you to a dynamic page consisting of all the details of that person. The route will also be dynamic in the form of <code>/person/{number}</code>.</p> <h2> Conclusion </h2> <p>If you'd like to dig deeper into React Router and discover all the cool stuff you can do with it, read the official docs,</p> <p><a href="proxy.php?url=https://reactrouter.com/web/guides/quick-start">React Router Docs</a></p> <p>For any queries reach out to my <a href="proxy.php?url=https://twitter.com/saswatamcode">socials</a> or <a href="proxy.php?url=https://github.com/saswatamcode">GitHub</a>!</p> react javascript webdev tutorial Discuss: What's your preferred way of creating a React app? Saswata Mukherjee Thu, 11 Jun 2020 05:52:31 +0000 https://dev.to/gdsckiitdev/discuss-what-s-your-preferred-way-of-creating-a-react-app-40n2 https://dev.to/gdsckiitdev/discuss-what-s-your-preferred-way-of-creating-a-react-app-40n2 <p>Whenever I start a new React project I mostly use to <code>create-react-app</code>. But I've seen a lot of people default to Gatsby or Next.js.</p> <p>If you do use something other than cra what is your motivation and what tools make your life easier? Any best practices you think React devs should follow while using them?</p> discuss javascript react webdev Write your first API with Deno Saswata Mukherjee Tue, 19 May 2020 11:05:00 +0000 https://dev.to/gdsckiitdev/write-your-first-api-with-deno-1llh https://dev.to/gdsckiitdev/write-your-first-api-with-deno-1llh <p>ICYMI, Deno v1.0 has been released!</p> <h2> But what is Deno? </h2> <blockquote> <p>Deno is a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust.</p> </blockquote> <p>That's according to the official <a href="proxy.php?url=https://deno.land/">website</a>.</p> <p>​Ryan Dahl the original creator of Node.js (the popular server-side JavaScript runtime) announced Deno at JSConf EU 2018 in <a href="proxy.php?url=https://youtu.be/M3BM9TB-8yA">his talk</a> "10 Things I Regret About Node.js". Deno is pretty similar to Node. Except that it's improved in many ways, since it was created to be a better implementation of Node.js. It has a ton of great features like security by default, TypeScript by default, ES modules and Golang-like package management.</p> <p>If you're on twitter, you're probably already seen the influx of "x years of Deno experience" and "node, deno, oden, done..." jokes.</p> <h2> Getting Started </h2> <p>Alright, enough said, let's get playing with Deno.</p> <p>We're going to be building a really simple REST API which lets us perform CRUD operations on a database of dogs!</p> <p>Make sure you've <a href="proxy.php?url=https://deno.land/manual/getting_started/installation">installed</a> deno correctly.</p> <p>We're going to be using the <a href="proxy.php?url=https://deno.land/x/abc">Abc</a> deno web framework along with <a href="proxy.php?url=https://deno.land/x/mongo">MongoDB</a>. We'll also be using <a href="proxy.php?url=https://deno.land/x/denv">Denv</a> to manage our environment variables. Keep in mind, that there are a ton of other web frameworks like <a href="proxy.php?url=https://github.com/alosaur/alosaur">alosaur</a>, <a href="proxy.php?url=https://github.com/oakserver/oak">oak</a>, <a href="proxy.php?url=https://github.com/NMathar/deno-express">deno-express</a>, <a href="proxy.php?url=https://github.com/sholladay/pogo">pogo</a>, <a href="proxy.php?url=https://github.com/keroxp/servest">servest</a> that we can use but since deno is pretty new and I don't really have much of a preference yet, I'm using this one.</p> <p>Make a new directory and create a file named <code>server.ts</code>. This will be our main file which will contain our router. We'll also import Denv and Abc and initialise an application.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Application</span><span class="p">,</span> <span class="nx">Context</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">https://deno.land/x/[email protected]/mod.ts</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="dl">"</span><span class="s2">https://deno.land/x/denv/mod.ts</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Application</span><span class="p">();</span> <span class="nx">app</span> <span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">/hello</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">c</span><span class="p">:</span> <span class="nx">Context</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="dl">"</span><span class="s2">Hello, Abc!</span><span class="dl">"</span><span class="p">;</span> <span class="p">})</span> <span class="p">.</span><span class="nx">start</span><span class="p">({</span> <span class="na">port</span><span class="p">:</span> <span class="mi">8000</span> <span class="p">});</span> </code></pre> </div> <p>Now, if you've worked with node before, this'll look pretty familiar. Initially, we're importing <code>Application</code> and <code>Context</code> from the Abc module. We are basically initialising a new Abc application and then we're defining a route <code>/hello</code> with a callback function which will return "Hello, Abc!". The <code>start</code> method directs the application to start listening for requests at port 8000. Instead of request and response we have single argument <code>c</code> which is of type <code>Context</code> . Let's see this in action. To run our file we need to use the command <code>deno run server.ts</code> but if you run the file you're gonna get a bunch of errors. That's because deno is secure by default. It won't allow the application to access your system in any way. To allow it we need to add the <code>--allow-read</code> flag to allow Denv to read our <code>.env</code> file and <code>--allow-net</code> flag to give Abc access to our ports. Hence the command would be:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>deno run <span class="nt">--allow-read</span> <span class="nt">--allow-net</span> server.ts </code></pre> </div> <p>Now if you visit, <a href="proxy.php?url=http://localhost:8000">localhost:8000</a> you should see "Hello, Abc!" printed on your screen.</p> <p>Great! So let's add our database next.</p> <h2> Database(MongoDB) </h2> <p>We're going to be getting our database url and name from environment variables. So in your <code>.env</code> file add the following.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="nv">DB_NAME</span><span class="o">=</span>deno_dogs <span class="nv">DB_HOST_URL</span><span class="o">=</span>mongodb://localhost:27017 </code></pre> </div> <p>Now add the following in your <code>config/db.ts</code> file<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">init</span><span class="p">,</span> <span class="nx">MongoClient</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">https://deno.land/x/[email protected]/mod.ts</span><span class="dl">"</span><span class="p">;</span> <span class="c1">// Initialize the plugin</span> <span class="k">await</span> <span class="nx">init</span><span class="p">();</span> <span class="kd">class</span> <span class="nx">Database</span> <span class="p">{</span> <span class="k">public</span> <span class="nx">client</span><span class="p">:</span> <span class="nx">MongoClient</span><span class="p">;</span> <span class="kd">constructor</span><span class="p">(</span><span class="k">public</span> <span class="nx">dbName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="k">public</span> <span class="nx">url</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">dbName</span> <span class="o">=</span> <span class="nx">dbName</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="nx">url</span> <span class="o">=</span> <span class="nx">url</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="nx">client</span> <span class="o">=</span> <span class="p">{}</span> <span class="k">as</span> <span class="nx">MongoClient</span><span class="p">;</span> <span class="p">}</span> <span class="nx">connect</span><span class="p">()</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MongoClient</span><span class="p">();</span> <span class="nx">client</span><span class="p">.</span><span class="nx">connectWithUri</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">url</span><span class="p">);</span> <span class="k">this</span><span class="p">.</span><span class="nx">client</span> <span class="o">=</span> <span class="nx">client</span><span class="p">;</span> <span class="p">}</span> <span class="kd">get</span> <span class="nx">getDatabase</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">client</span><span class="p">.</span><span class="nx">database</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">dbName</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">dbName</span> <span class="o">=</span> <span class="nx">Deno</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">DB_NAME</span><span class="dl">"</span><span class="p">)</span> <span class="o">||</span> <span class="dl">"</span><span class="s2">deno_dogs</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">dbHostUrl</span> <span class="o">=</span> <span class="nx">Deno</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">DB_HOST_URL</span><span class="dl">"</span><span class="p">)</span> <span class="o">||</span> <span class="dl">"</span><span class="s2">mongodb://localhost:27017</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">db</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Database</span><span class="p">(</span><span class="nx">dbName</span><span class="p">,</span> <span class="nx">dbHostUrl</span><span class="p">);</span> <span class="nx">db</span><span class="p">.</span><span class="nx">connect</span><span class="p">();</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">db</span><span class="p">;</span> </code></pre> </div> <p>Let's break down what we wrote. Fortunately deno works with mongoDB and thus we can import that module. This will download a mongoDB plugin. The <code>init()</code> method initialises the plugin and we define our <code>Database</code> class. The class has a constructor which takes in the url and name of the db. The <code>connect()</code> method connects to the mongoDB instance and the <code>getDatabase()</code> method is a getter function. At the bottom of the file we define an instance of the class, <code>db</code>, and initialise it with the dbName and dbHostUrl which we fetch from the <code>.env</code> file. We also call the <code>connect()</code> method and export <code>db</code>.</p> <p>Cool! Now let's write the controllers which will let us perform CRUD operations on our db.</p> <h2> Controllers </h2> <p>Inside the <code>controllers/dogs.ts</code> file add the following.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">HandlerFunc</span><span class="p">,</span> <span class="nx">Context</span><span class="p">,</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">https://deno.land/x/[email protected]/mod.ts</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="nx">db</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../config/db.ts</span><span class="dl">"</span><span class="p">;</span> <span class="c1">// DB collection made</span> <span class="kd">const</span> <span class="nx">database</span> <span class="o">=</span> <span class="nx">db</span><span class="p">.</span><span class="nx">getDatabase</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">dogs</span> <span class="o">=</span> <span class="nx">database</span><span class="p">.</span><span class="nx">collection</span><span class="p">(</span><span class="dl">"</span><span class="s2">dogs</span><span class="dl">"</span><span class="p">);</span> <span class="c1">// Dog type defined</span> <span class="kr">interface</span> <span class="nx">Dog</span> <span class="p">{</span> <span class="nl">_id</span><span class="p">:</span> <span class="p">{</span> <span class="na">$oid</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="p">};</span> <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">breed</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">age</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="p">}</span> <span class="k">export</span> <span class="kd">const</span> <span class="nx">createDog</span><span class="p">:</span> <span class="nx">HandlerFunc</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">c</span><span class="p">:</span> <span class="nx">Context</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">body</span> <span class="o">=</span> <span class="k">await</span> <span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">body</span><span class="p">());</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">body</span><span class="p">).</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="kr">string</span><span class="p">(</span><span class="dl">"</span><span class="s2">Request can't be empty</span><span class="dl">"</span><span class="p">,</span> <span class="mi">400</span><span class="p">);</span> <span class="p">}</span> <span class="kd">const</span> <span class="p">{</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">breed</span><span class="p">,</span> <span class="nx">age</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">body</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">insertedDog</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">dogs</span><span class="p">.</span><span class="nx">insertOne</span><span class="p">({</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">breed</span><span class="p">,</span> <span class="nx">age</span><span class="p">,</span> <span class="p">});</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="nx">insertedDog</span><span class="p">,</span> <span class="mi">201</span><span class="p">);</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="mi">500</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> <span class="k">export</span> <span class="kd">const</span> <span class="nx">fetchAllDogs</span><span class="p">:</span> <span class="nx">HandlerFunc</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">c</span><span class="p">:</span> <span class="nx">Context</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="na">fetchedDogs</span><span class="p">:</span> <span class="nx">Dog</span><span class="p">[]</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">dogs</span><span class="p">.</span><span class="nx">find</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="nx">fetchedDogs</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">fetchedDogsList</span> <span class="o">=</span> <span class="nx">fetchedDogs</span><span class="p">.</span><span class="nx">length</span> <span class="p">?</span> <span class="nx">fetchedDogs</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">dog</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">{</span> <span class="na">_id</span><span class="p">:</span> <span class="p">{</span> <span class="nx">$oid</span> <span class="p">},</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">breed</span><span class="p">,</span> <span class="nx">age</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">dog</span><span class="p">;</span> <span class="k">return</span> <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="nx">$oid</span><span class="p">,</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">breed</span><span class="p">,</span> <span class="nx">age</span> <span class="p">};</span> <span class="p">})</span> <span class="p">:</span> <span class="p">[];</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="nx">fetchedDogsList</span><span class="p">,</span> <span class="mi">200</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="mi">500</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> <span class="k">export</span> <span class="kd">const</span> <span class="nx">fetchOneDog</span><span class="p">:</span> <span class="nx">HandlerFunc</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">c</span><span class="p">:</span> <span class="nx">Context</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">{</span> <span class="nx">id</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">c</span><span class="p">.</span><span class="nx">params</span> <span class="k">as</span> <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="kr">string</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">fetchedDog</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">dogs</span><span class="p">.</span><span class="nx">findOne</span><span class="p">({</span> <span class="na">_id</span><span class="p">:</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">$oid</span><span class="dl">"</span><span class="p">:</span> <span class="nx">id</span> <span class="p">}</span> <span class="p">});</span> <span class="k">if</span> <span class="p">(</span><span class="nx">fetchedDog</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">{</span> <span class="na">_id</span><span class="p">:</span> <span class="p">{</span> <span class="nx">$oid</span> <span class="p">},</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">breed</span><span class="p">,</span> <span class="nx">age</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">fetchedDog</span><span class="p">;</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">json</span><span class="p">({</span> <span class="na">id</span><span class="p">:</span> <span class="nx">$oid</span><span class="p">,</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">breed</span><span class="p">,</span> <span class="nx">age</span> <span class="p">},</span> <span class="mi">200</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="kr">string</span><span class="p">(</span><span class="dl">"</span><span class="s2">Dog not found</span><span class="dl">"</span><span class="p">,</span> <span class="mi">404</span><span class="p">);</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="mi">500</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> <span class="k">export</span> <span class="kd">const</span> <span class="nx">updateDog</span><span class="p">:</span> <span class="nx">HandlerFunc</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">c</span><span class="p">:</span> <span class="nx">Context</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">{</span> <span class="nx">id</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">c</span><span class="p">.</span><span class="nx">params</span> <span class="k">as</span> <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="kr">string</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">body</span> <span class="o">=</span> <span class="k">await</span> <span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">body</span><span class="p">())</span> <span class="k">as</span> <span class="p">{</span> <span class="nx">name</span><span class="p">?:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">breed</span><span class="p">?:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">age</span><span class="p">?:</span> <span class="kr">string</span><span class="p">;</span> <span class="p">};</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">body</span><span class="p">).</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="kr">string</span><span class="p">(</span><span class="dl">"</span><span class="s2">Request can't be empty</span><span class="dl">"</span><span class="p">,</span> <span class="mi">400</span><span class="p">);</span> <span class="p">}</span> <span class="kd">const</span> <span class="nx">fetchedDog</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">dogs</span><span class="p">.</span><span class="nx">findOne</span><span class="p">({</span> <span class="na">_id</span><span class="p">:</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">$oid</span><span class="dl">"</span><span class="p">:</span> <span class="nx">id</span> <span class="p">}</span> <span class="p">});</span> <span class="k">if</span> <span class="p">(</span><span class="nx">fetchedDog</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">{</span> <span class="nx">matchedCount</span> <span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">dogs</span><span class="p">.</span><span class="nx">updateOne</span><span class="p">(</span> <span class="p">{</span> <span class="na">_id</span><span class="p">:</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">$oid</span><span class="dl">"</span><span class="p">:</span> <span class="nx">id</span> <span class="p">}</span> <span class="p">},</span> <span class="p">{</span> <span class="na">$set</span><span class="p">:</span> <span class="nx">body</span> <span class="p">},</span> <span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="nx">matchedCount</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="kr">string</span><span class="p">(</span><span class="dl">"</span><span class="s2">Dog updated successfully!</span><span class="dl">"</span><span class="p">,</span> <span class="mi">204</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="kr">string</span><span class="p">(</span><span class="dl">"</span><span class="s2">Unable to update dog</span><span class="dl">"</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="kr">string</span><span class="p">(</span><span class="dl">"</span><span class="s2">Dog not found</span><span class="dl">"</span><span class="p">,</span> <span class="mi">404</span><span class="p">);</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="mi">500</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> <span class="k">export</span> <span class="kd">const</span> <span class="nx">deleteDog</span><span class="p">:</span> <span class="nx">HandlerFunc</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">c</span><span class="p">:</span> <span class="nx">Context</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">{</span> <span class="nx">id</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">c</span><span class="p">.</span><span class="nx">params</span> <span class="k">as</span> <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="kr">string</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">fetchedDog</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">dogs</span><span class="p">.</span><span class="nx">findOne</span><span class="p">({</span> <span class="na">_id</span><span class="p">:</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">$oid</span><span class="dl">"</span><span class="p">:</span> <span class="nx">id</span> <span class="p">}</span> <span class="p">});</span> <span class="k">if</span> <span class="p">(</span><span class="nx">fetchedDog</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">deleteCount</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">dogs</span><span class="p">.</span><span class="nx">deleteOne</span><span class="p">({</span> <span class="na">_id</span><span class="p">:</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">$oid</span><span class="dl">"</span><span class="p">:</span> <span class="nx">id</span> <span class="p">}</span> <span class="p">});</span> <span class="k">if</span> <span class="p">(</span><span class="nx">deleteCount</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="kr">string</span><span class="p">(</span><span class="dl">"</span><span class="s2">Dog deleted successfully!</span><span class="dl">"</span><span class="p">,</span> <span class="mi">204</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="kr">string</span><span class="p">(</span><span class="dl">"</span><span class="s2">Unable to delete dog</span><span class="dl">"</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="kr">string</span><span class="p">(</span><span class="dl">"</span><span class="s2">Dog not found</span><span class="dl">"</span><span class="p">,</span> <span class="mi">404</span><span class="p">);</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="mi">500</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> </code></pre> </div> <p>Alright so there's a lot happening here. First we're importing <code>HandlerFunc</code> and <code>Context</code> from the Abc module and <code>db</code> from our <code>config/db.ts</code> file. Then we call <code>getDatabase()</code> to get our "deno_dogs" db and define a collection "dogs" inside it. Next we define an interface <code>Dog</code> which has the properties of name, breed and age. With all that out of the way, let's move on to the functions. </p> <p>Each function has a type of <code>HandlerFunc</code> and argument <code>c</code> which is of type <code>Context</code> . This lets us use this function as a callback for our routes. All the functions are almost similar so there isn't much to explain. We use <code>c.body()</code> to access our request body in case of <code>createDog</code> and <code>updateDog</code>. We return a json object or string via <code>c.json()</code> or <code>c.string()</code> along with HTTP codes in our return statements in all the above methods. We access url parameters via <code>c.params</code> in case of <code>fetchOneDog, updateDog</code> and <code>deleteDog</code>. </p> <p>We also use the <code>dogs</code> object in our functions to manipulate our collection via methods like <code>.insertOne({}), .find({}), .findOne({}), .updateOne({})</code> and <code>deleteOne({})</code>. All of these methods are wrapped in try-catch blocks for error handling.</p> <p>Now with our controllers defined we can proceed to defining our routes.</p> <h2> Routes </h2> <p>In your <code>server.ts</code> file write the following.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Application</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">https://deno.land/x/[email protected]/mod.ts</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="dl">"</span><span class="s2">https://deno.land/x/denv/mod.ts</span><span class="dl">"</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">createDog</span><span class="p">,</span> <span class="nx">fetchAllDogs</span><span class="p">,</span> <span class="nx">fetchOneDog</span><span class="p">,</span> <span class="nx">updateDog</span><span class="p">,</span> <span class="nx">deleteDog</span><span class="p">,</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./controllers/dogs.ts</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Application</span><span class="p">();</span> <span class="nx">app</span> <span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">/dogs</span><span class="dl">"</span><span class="p">,</span> <span class="nx">fetchAllDogs</span><span class="p">)</span> <span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">"</span><span class="s2">/dogs</span><span class="dl">"</span><span class="p">,</span> <span class="nx">createDog</span><span class="p">)</span> <span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">/dogs/:id</span><span class="dl">"</span><span class="p">,</span> <span class="nx">fetchOneDog</span><span class="p">)</span> <span class="p">.</span><span class="nx">put</span><span class="p">(</span><span class="dl">"</span><span class="s2">/dogs/:id</span><span class="dl">"</span><span class="p">,</span> <span class="nx">updateDog</span><span class="p">)</span> <span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="dl">"</span><span class="s2">/dogs/:id</span><span class="dl">"</span><span class="p">,</span> <span class="nx">deleteDog</span><span class="p">)</span> <span class="p">.</span><span class="nx">start</span><span class="p">({</span> <span class="na">port</span><span class="p">:</span> <span class="mi">8000</span> <span class="p">});</span> </code></pre> </div> <p>As you can see, we've imported all our controller functions and assigned each of them a route and an HTTP method. Plain and simple.</p> <p>We are done writing our REST API. All that's left is to run it! To do that type in the following into your terminal:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>deno run <span class="nt">--allow-write</span> <span class="nt">--allow-read</span> <span class="nt">--allow-plugin</span> <span class="nt">--allow-net</span> <span class="nt">--allow-env</span> <span class="nt">--unstable</span> server.ts </code></pre> </div> <p>We have a few new flags this time. The <code>--allow-read/write</code> flags are for Denv and mongoDB, as they need read/write access to your filesystem. The <code>--allow-plugin</code> flag allows the use of the mongoDB plugin and the <code>--allow-env</code> is for allowing usage of environment variables. </p> <p>A lot of Deno APIs are not stable yet so some of them are marked as unstable. To use these "unstable" APIs we need to add the <code>--unstable</code> flag.</p> <p>Use a tool like <a href="proxy.php?url=https://www.postman.com/">Postman</a> and send a POST request to <a href="proxy.php?url=http://localhost:8000/dogs">localhost:8000/dogs</a> with the body as<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight tsx"><code><span class="p">{</span> <span class="dl">"</span><span class="s2">name</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Cheddar</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">breed</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Corgi</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">age</span><span class="dl">"</span><span class="p">:</span> <span class="mi">11</span> <span class="p">}</span> </code></pre> </div> <p>Send a GET request to the same url to see your dogs! Similarly try out all the other routes.</p> <p>So there you go! Now you know how to write a REST API with Deno. </p> <p>Here's the <a href="proxy.php?url=https://github.com/saswatamcode/deno_dogs_api">GitHub</a> repo of the code.</p> <h2> Conclusion </h2> <p>Since there are a few bugs and also no explicit Code of Conduct for the project yet, I don't recommend using it for production just now. A CoC is an essential part of any open source project. However development is moving forward pretty quickly and this is one project to definitely keep an eye on!</p> <p>For any queries reach out to my <a href="proxy.php?url=https://twitter.com/saswatamcode">socials</a> or <a href="proxy.php?url=https://github.com/saswatamcode">GitHub</a>!</p> deno node javascript webdev React Context+Hooks API=> Ideal State Management Saswata Mukherjee Thu, 07 May 2020 18:22:19 +0000 https://dev.to/gdsckiitdev/react-context-hooks-api-ideal-state-management-23ag https://dev.to/gdsckiitdev/react-context-hooks-api-ideal-state-management-23ag <p>Hey there! React is pretty great with a ton of stuff. But sometimes we need global states, such as a UI theme or locale preferences. Now ordinarily, to pass states down to child components what we do is pass down props. But with global states, we have to pass down props several times down the component tree or the roots of a potato if you've seen <a href="proxy.php?url=https://www.youtube.com/watch?v=K8MF3aDg-bM">Women Of React 2020</a>. This creates a cumbersome phenomenon known as "prop drilling". This means that we are passing down the props from grandparent to parent to child and so on.</p> <p>Now to solve this issue, you can use something like Redux, which is a completely fine solution, but restructures your entire code and necessitates a ton of boilerplate code. This makes it unsuitable for lightweight implementations. Keep in mind though that it doesn't affect performance.</p> <h2> So what do we do? </h2> <p>Enter React Context API.</p> <blockquote> <p>Context provides a way to pass data through the component tree without having to pass props down manually at every level.</p> </blockquote> <p>That's the official React docs intro. It was introduced in React 16.3. It solves the global state management problem. Context is often touted as a lightweight alternative to Redux and provides much cleaner and simpler code. So let's get started with it!</p> <p>So let's make a simple React app. Use <code>create-react-app</code> to generate one. And write the following in <code>App.js</code><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"App"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">AppBar</span> <span class="na">theme</span><span class="p">=</span><span class="s">"white"</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">}</span> <span class="kd">function</span> <span class="nx">AppBar</span><span class="p">({</span><span class="nx">theme</span><span class="p">})</span> <span class="p">{</span> <span class="k">return</span><span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"AppBar"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">ThemedButton</span> <span class="na">theme</span><span class="p">=</span><span class="si">{</span><span class="nx">theme</span><span class="si">}</span><span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">}</span> <span class="kd">function</span> <span class="nx">ThemedButton</span><span class="p">({</span><span class="nx">theme</span><span class="p">})</span> <span class="p">{</span> <span class="k">return</span><span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="na">style</span><span class="p">=</span><span class="si">{</span><span class="p">{</span><span class="na">backgroundColor</span><span class="p">:</span> <span class="nx">theme</span><span class="p">}</span><span class="si">}</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">)</span> <span class="p">}</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span> </code></pre> </div> <p>Well, as you can see above, we have to thread the theme property through all the components, so that we can apply it to child elements. This is great for three components maybe, but imagine a full dynamic website, where the component tree might be huge and deep.</p> <p>Let's try the same thing with React Context then. Now before you use Context you should keep in mind that this isn't meant for small number of props for a small number of components. For that, simple prop threading and component composition would be much simpler. So use it wisely.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="kd">const</span> <span class="nx">ThemeContext</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">createContext</span><span class="p">(</span><span class="dl">'</span><span class="s1">white</span><span class="dl">'</span><span class="p">);</span> <span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"App"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">ThemeContext</span><span class="p">.</span><span class="nc">Provider</span> <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="dl">"</span><span class="s2">black</span><span class="dl">"</span><span class="si">}</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">AppBar</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nc">ThemeContext</span><span class="p">.</span><span class="nc">Provider</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">}</span> <span class="kd">function</span> <span class="nx">AppBar</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span><span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"AppBar"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">ThemedButton</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">}</span> <span class="kd">function</span> <span class="nx">ThemedButton</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span><span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">ThemeContext</span><span class="p">.</span><span class="nc">Consumer</span><span class="p">&gt;</span> <span class="si">{</span><span class="nx">value</span> <span class="o">=&gt;</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="na">style</span><span class="p">=</span><span class="si">{</span><span class="p">{</span><span class="na">backgroundColor</span><span class="p">:</span> <span class="nx">value</span><span class="p">}</span><span class="si">}</span> <span class="p">/&gt;</span><span class="si">}</span> <span class="p">&lt;/</span><span class="nc">ThemeContext</span><span class="p">.</span><span class="nc">Consumer</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">)</span> <span class="p">}</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span> </code></pre> </div> <p>Okay, that's a lot of new code and if you look closely you'll see that our props in the <code>AppBar</code> and <code>ThemedButton</code> component have disappeared. So what happened? Let's break it all down .</p> <p>So notice at the top of the code snippet I have the line,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="kd">const</span> <span class="nx">ThemeContext</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">createContext</span><span class="p">(</span><span class="dl">'</span><span class="s1">white</span><span class="dl">'</span><span class="p">);</span> </code></pre> </div> <p>This is what creates the React Context object. Now, every Context object comes with a Provider and a consumer. Again if you refer to the above code you'll see them.</p> <h2> Provider and Consumer </h2> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="c1">//Provider</span> <span class="p">&lt;</span><span class="nc">ThemeContext</span><span class="p">.</span><span class="nc">Provider</span> <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="dl">"</span><span class="s2">black</span><span class="dl">"</span><span class="si">}</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nc">ThemeContext</span><span class="p">.</span><span class="nc">Provider</span><span class="p">&gt;</span> <span class="c1">//Consumer</span> <span class="p">&lt;</span><span class="nc">ThemeContext</span><span class="p">.</span><span class="nc">Consumer</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nc">ThemeContext</span><span class="p">.</span><span class="nc">Consumer</span><span class="p">&gt;</span> </code></pre> </div> <p>The Provider component allows consuming components to subscribe to context changes.</p> <p>It accepts a <code>value</code> prop to be passed to consuming components that are descendants of this Provider. Thus, one Provider can be connected to many consumers. Providers can even be nested to override values deeper within the component tree.</p> <p>All consumers that are descendants of a Provider will re-render whenever the Provider’s <code>value</code> prop changes. </p> <p>The Consumer component is the component which subscribes to the context changes. The Consumer component however requires a function as a child like <a href="proxy.php?url=https://reactjs.org/docs/render-props.html">render props</a>. The function receives the current context value and returns a React node. </p> <p>The value argument passed to the function will be equal to the value prop of the closest Provider for this context above in the tree. Thus, in the code above I have used the value to colour the button,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code> <span class="p">&lt;</span><span class="nc">ThemeContext</span><span class="p">.</span><span class="nc">Consumer</span><span class="p">&gt;</span> <span class="si">{</span><span class="nx">value</span> <span class="o">=&gt;</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="na">style</span><span class="p">=</span><span class="si">{</span><span class="p">{</span><span class="na">backgroundColor</span><span class="p">:</span> <span class="nx">value</span><span class="p">}</span><span class="si">}</span> <span class="p">/&gt;</span><span class="si">}</span> <span class="p">&lt;/</span><span class="nc">ThemeContext</span><span class="p">.</span><span class="nc">Consumer</span><span class="p">&gt;</span> </code></pre> </div> <h2> Get the value of the Context </h2> <p>So you know how to use the Context API now. But if you look at the Provider and think about the use cases, you'll quickly realise that it's a bit difficult to extract the context from our JSX code for implementing other functionality. Sure, there are workarounds but that isn't really ideal. You may see this somewhere, but it is usually legacy.</p> <p>Now if <code>ThemedButton</code> was a class component we would be able to extract the context with contextType.</p> <p>The <code>contextType property</code> on a class can be assigned a Context object. This lets you consume the nearest current value of that Context type using <code>this.context</code>. You can reference this in any of the lifecycle methods including the render function. So we could implement it like this.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="kd">static</span> <span class="nx">contextType</span> <span class="o">=</span> <span class="nx">ThemeContext</span><span class="p">;</span> <span class="nx">render</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">&lt;</span><span class="nc">Button</span> <span class="na">theme</span><span class="p">=</span><span class="si">{</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="si">}</span> <span class="p">/&gt;;</span> <span class="p">}</span> </code></pre> </div> <p>However, we are using lightweight functional components and Hooks! So let's refactor our existing code a bit,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="k">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">useContext</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">ThemeContext</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">createContext</span><span class="p">(</span><span class="dl">'</span><span class="s1">white</span><span class="dl">'</span><span class="p">);</span> <span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"App"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">ThemeContext</span><span class="p">.</span><span class="nc">Provider</span> <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="dl">"</span><span class="s2">black</span><span class="dl">"</span><span class="si">}</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">AppBar</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nc">ThemeContext</span><span class="p">.</span><span class="nc">Provider</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">}</span> <span class="kd">function</span> <span class="nx">AppBar</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span><span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"AppBar"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">ThemedButton</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">}</span> <span class="kd">function</span> <span class="nx">ThemedButton</span><span class="p">()</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">theme</span> <span class="o">=</span> <span class="nx">useContext</span><span class="p">(</span><span class="nx">ThemeContext</span><span class="p">)</span> <span class="k">return</span><span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="na">style</span><span class="p">=</span><span class="si">{</span><span class="p">{</span><span class="na">backgroundColor</span><span class="p">:</span> <span class="nx">theme</span><span class="p">}</span><span class="si">}</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">)</span> <span class="p">}</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span> </code></pre> </div> <p>Over here, we've used the <code>useContext</code> hook which is the functional component equivalent <code>contextType</code> . With <code>useContext</code> we can do away with the provider, and get the current context value outside of our JSX code.</p> <h2> Updating Our Context </h2> <p>Updating our Context is as simple as updating a state. With functional components, we can use the <code>useState</code> hook to achieve this by passing down a function which will update out context,<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="k">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">useState</span><span class="p">,</span> <span class="nx">useContext</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">ThemeContext</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">createContext</span><span class="p">({</span> <span class="na">theme</span><span class="p">:</span> <span class="dl">"</span><span class="s2">white</span><span class="dl">"</span><span class="p">,</span> <span class="na">toggler</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{}</span> <span class="p">});</span> <span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">color</span><span class="p">,</span> <span class="nx">setColor</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">"</span><span class="s2">white</span><span class="dl">"</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">toPass</span> <span class="o">=</span> <span class="p">{</span> <span class="na">theme</span><span class="p">:</span> <span class="nx">color</span><span class="p">,</span> <span class="na">toggler</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">color</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">white</span><span class="dl">"</span> <span class="p">?</span> <span class="nx">setColor</span><span class="p">(</span><span class="dl">"</span><span class="s2">black</span><span class="dl">"</span><span class="p">)</span> <span class="p">:</span> <span class="nx">setColor</span><span class="p">(</span><span class="dl">"</span><span class="s2">white</span><span class="dl">"</span><span class="p">);</span> <span class="p">},</span> <span class="p">};</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"App"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">ThemeContext</span><span class="p">.</span><span class="nc">Provider</span> <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">toPass</span><span class="si">}</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">AppBar</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nc">ThemeContext</span><span class="p">.</span><span class="nc">Provider</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">}</span> <span class="kd">function</span> <span class="nx">AppBar</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"AppBar"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">ThemedButton</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">}</span> <span class="kd">function</span> <span class="nx">ThemedButton</span><span class="p">()</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">context</span> <span class="o">=</span> <span class="nx">useContext</span><span class="p">(</span><span class="nx">ThemeContext</span><span class="p">);</span> <span class="k">return</span> <span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="na">style</span><span class="p">=</span><span class="si">{</span><span class="p">{</span> <span class="na">backgroundColor</span><span class="p">:</span> <span class="nx">context</span><span class="p">.</span><span class="nx">theme</span> <span class="p">}</span><span class="si">}</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">context</span><span class="p">.</span><span class="nx">toggler</span><span class="si">}</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">}</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span> </code></pre> </div> <p>As you see above the <code>color</code> state is manipulated by a toggle function which we passed down via our Context. The toggle function from the global Context is then called by the button in a child component, which thus updates the global Context.</p> <p>So there you go! You now know how to use Context and Hooks to maintain a global state throughout your component tree.</p> <p>If you'd like to dig deeper into Context, read the official docs,</p> <p><a href="proxy.php?url=https://reactjs.org/docs/context.html">Context - React</a></p> <p>For any queries reach out to my <a href="proxy.php?url=https://twitter.com/saswatamcode">socials</a> or <a href="proxy.php?url=https://github.com/saswatamcode">GitHub</a>!</p> react webdev javascript html