<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Nader Dabit on Medium]]></title>
        <description><![CDATA[Stories by Nader Dabit on Medium]]></description>
        <link>https://medium.com/@dabit3?source=rss-695782484bda------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*lcQ4wcaeYxV64gemIuJ0lQ.jpeg</url>
            <title>Stories by Nader Dabit on Medium</title>
            <link>https://medium.com/@dabit3?source=rss-695782484bda------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 13 Nov 2026 21:07:49 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@dabit3/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[The Cloud is Your Development Environment]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://dabit3.medium.com/the-cloud-is-your-development-environment-6c5fd9f044c1?source=rss-695782484bda------2"><img src="https://cdn-images-1.medium.com/max/2400/1*e3AXf-kzErY1Te4EEd88EQ.png" width="2400"></a></p><p class="medium-feed-snippet">Provisioning ready-to-code developer environments in the cloud</p><p class="medium-feed-link"><a href="https://dabit3.medium.com/the-cloud-is-your-development-environment-6c5fd9f044c1?source=rss-695782484bda------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://dabit3.medium.com/the-cloud-is-your-development-environment-6c5fd9f044c1?source=rss-695782484bda------2</link>
            <guid isPermaLink="false">https://medium.com/p/6c5fd9f044c1</guid>
            <category><![CDATA[vscode]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[cloud]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[nextjs]]></category>
            <dc:creator><![CDATA[Nader Dabit]]></dc:creator>
            <pubDate>Tue, 05 Oct 2021 17:58:53 GMT</pubDate>
            <atom:updated>2021-10-05T17:58:53.594Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Uploading Files to IPFS from a Web Application]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-snippet">One of the most common interactions with IPFS is uploading files like images and videos from a client-side application, so I found it&#x2026;</p><p class="medium-feed-link"><a href="https://dabit3.medium.com/uploading-files-to-ipfs-from-a-web-application-37d75fb326ed?source=rss-695782484bda------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://dabit3.medium.com/uploading-files-to-ipfs-from-a-web-application-37d75fb326ed?source=rss-695782484bda------2</link>
            <guid isPermaLink="false">https://medium.com/p/37d75fb326ed</guid>
            <category><![CDATA[web3]]></category>
            <category><![CDATA[ipfs]]></category>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Nader Dabit]]></dc:creator>
            <pubDate>Thu, 05 Aug 2021 16:41:00 GMT</pubDate>
            <atom:updated>2021-08-05T16:41:00.070Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Magic Link Authentication and Route Controls with Supabase and Next.js]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://dabit3.medium.com/magic-link-authentication-and-route-controls-with-supabase-and-next-js-19b874020550?source=rss-695782484bda------2"><img src="https://cdn-images-1.medium.com/max/600/1*fjktVCUErK5GfF1wUra5sA.png" width="600"></a></p><p class="medium-feed-snippet">While Supabase is widely known for their real-time database and API layer, one of the things I like about it is the number of easy to set&#x2026;</p><p class="medium-feed-link"><a href="https://dabit3.medium.com/magic-link-authentication-and-route-controls-with-supabase-and-next-js-19b874020550?source=rss-695782484bda------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://dabit3.medium.com/magic-link-authentication-and-route-controls-with-supabase-and-next-js-19b874020550?source=rss-695782484bda------2</link>
            <guid isPermaLink="false">https://medium.com/p/19b874020550</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[supabase]]></category>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Nader Dabit]]></dc:creator>
            <pubDate>Tue, 13 Jul 2021 13:17:15 GMT</pubDate>
            <atom:updated>2021-07-13T13:17:15.865Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[How to Get Into Ethereum | Crypto | Web3 as a Developer]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://dabit3.medium.com/how-to-get-into-ethereum-crypto-web3-as-a-developer-4723d8ddaa6b?source=rss-695782484bda------2"><img src="https://cdn-images-1.medium.com/max/2000/1*fajNvbdbx8iut_aDRvMjnQ.png" width="2000"></a></p><p class="medium-feed-snippet">I have been talking about my move into the Web3 / Ethereum / crypto space since making the switch from a traditional web, mobile, and&#x2026;</p><p class="medium-feed-link"><a href="https://dabit3.medium.com/how-to-get-into-ethereum-crypto-web3-as-a-developer-4723d8ddaa6b?source=rss-695782484bda------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://dabit3.medium.com/how-to-get-into-ethereum-crypto-web3-as-a-developer-4723d8ddaa6b?source=rss-695782484bda------2</link>
            <guid isPermaLink="false">https://medium.com/p/4723d8ddaa6b</guid>
            <category><![CDATA[blockchain]]></category>
            <category><![CDATA[crypto]]></category>
            <category><![CDATA[ethereum]]></category>
            <category><![CDATA[webdev]]></category>
            <dc:creator><![CDATA[Nader Dabit]]></dc:creator>
            <pubDate>Wed, 12 May 2021 14:53:07 GMT</pubDate>
            <atom:updated>2021-05-12T14:53:07.425Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Building GraphQL APIs on Ethereum]]></title>
            <link>https://dabit3.medium.com/building-graphql-apis-on-ethereum-427ed363df17?source=rss-695782484bda------2</link>
            <guid isPermaLink="false">https://medium.com/p/427ed363df17</guid>
            <category><![CDATA[graphql]]></category>
            <category><![CDATA[ethereum]]></category>
            <category><![CDATA[solidity]]></category>
            <category><![CDATA[webdev]]></category>
            <category><![CDATA[blockchain]]></category>
            <dc:creator><![CDATA[Nader Dabit]]></dc:creator>
            <pubDate>Tue, 04 May 2021 13:22:53 GMT</pubDate>
            <atom:updated>2021-05-04T19:13:12.487Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dpYXU_Rxr1AOwfxID4rpiw.png" /><figcaption>Building GraphQL APIs on Ethereum</figcaption></figure><p>The number of production <a href="https://ethereum.org/en/dapps/">Dapps</a> continues to explode and <a href="https://twitter.com/FurqanR/status/1389393957126246403?s=20">the demand</a> for <a href="https://twitter.com/CryptoCobain/status/1371901082113351680?s=20">developers building with Solidity</a> and other blockchain languages continues to outpace supply, <a href="https://cryptocurrencyjobs.co/salaries/solidity-developer/">moving wages higher and higher</a>.</p><p>As a developer <a href="https://twitter.com/dabit3/status/1379157277660299264">getting into this space</a>, I quickly learned there are a lot of differences in the way that we interact with and build on top of blockchains versus what I was used to in the traditional web. With Ethereum (and other blockchains), data isn’t stored in a format that can efficiently or easily be consumed directly from other applications or front ends. The problem is that you need to have the data indexed and organized for efficient retrieval.</p><p>Traditionally, that’s the work that databases do in the centralized tech stack, but that indexing layer was missing in the <a href="https://beta.web3index.org/blog/introducing-the-web3-index">Web3 stack</a>.</p><p>In the traditional web stack, databases, servers, and APIs query, filter, sort, paginate, group, and join data before it is returned to an application usually via some type of http request. These types of data transformations are not possible when reading data directly from Ethereum or other blockchains.</p><p>In the past, developers were getting around this by building their own centralized indexing servers — pulling data from blockchains, storing it in a database, and exposing it over an API. This required significant engineering and hardware resources and broke the important security properties required for decentralization.</p><p>How can we build an API on top of blockchain data that can easily be deployed to a decentralized web infrastructure? Let’s find out.</p><h3>Decentralizing web infrastructure</h3><p>The vision of and movement towards a decentralized internet is commonly referred to as Web3. Web3 enhances the internet as we know it today with these added characteristics:</p><ul><li>Decentralized</li><li>Verifiable</li><li>Trustless</li><li>Self governing</li></ul><blockquote><em>For a great overview of what Web3 is, check out </em><a href="https://www.youtube.com/watch?v=KHwVljhq7NQ"><em>this video</em></a><em> by </em><a href="https://twitter.com/juanbenet"><em>Juan Benet</em></a></blockquote><p>To achieve decentralization, protocols define networks that offer a range of digital services such as compute, storage, bandwidth, identity, and other pieces of web infrastructure with no intermediaries. These <a href="https://www.youtube.com/watch?v=j2rXJLW_93o">protocols</a> are usually distributed across multiple nodes (servers) that enable the participation of anyone looking to be part of the network and provide a service.</p><p>The network participants are <a href="https://www.youtube.com/watch?v=Nurp3Foqf2w">incentivized</a> to provide the highest quality services to anyone consuming them. Rules are also put in place to ensure the security and the integrity of the network itself. This is usually accomplished using a combination of consensus mechanisms programmed into smart contracts implementing various types of game theory and <a href="https://thegraph.com/blog/modeling-cryptoeconomic-protocols-as-complex-systems-part-1">cryptoeconomic design</a>.</p><h3>Building on The Graph</h3><p>In this post, we will be looking at one such protocol, <a href="https://thegraph.com/">The Graph</a>, and how to build and deploy our own GraphQL API using data stored in the Ethereum blockchain.</p><p>The Graph is an indexing protocol for querying blockchains like Ethereum and networks like <a href="https://ipfs.io/">IPFS</a>. Anyone can build and publish open APIs, called subgraphs, making data easily accessible.</p><p>Subgraphs define the data, data sources, and data access patterns that you would like to have made available via a GraphQL API. You as a developer can choose either to use a subgraph <a href="https://thegraph.com/explorer/">already deployed</a> by another developer, or define and deploy your own subgraph and use it.</p><p>Developers can create open APIs by deploying their subgraphs either to the hosted service or to the network, enabling them to earn money based on the amount of usage of their API.</p><p>Subgraphs are made up of a few main parts:</p><h4>1. GraphQL Schema</h4><p>The GraphQL Schema defines the data types / entities you would like to save and query for. You can also define configuration like relationships and full text search capabilities in your schema.</p><h4>2. Subgraph Manifest (yaml configuration)</h4><p>(from the docs) The <a href="https://thegraph.com/docs/define-a-subgraph#the-subgraph-manifest">manifest</a> defines the smart contracts your subgraph indexes, their <a href="https://docs.soliditylang.org/en/v0.8.4/abi-spec.html">ABIs</a>, which events from these contracts to pay attention to, and how to map event data to entities that Graph Node stores and allows to query.</p><h4>3. AssemblyScript Mappings</h4><p>AssemblyScript Mappings allow you to save data to be indexed using the entity types defined in your schema. The <a href="https://github.com/graphprotocol/graph-cli">Graph CLI</a> also generates AssemblyScript types using a combination of the subgraph’s schema along with a smart contract’s ABIs.</p><h3>Let’s start building</h3><p>Now that we have a good understanding of The Graph and how it works, let’s start writing some code.</p><p>In this tutorial we’ll build a subgraph for querying NTF data from the<a href="https://etherscan.io/address/0xabEFBc9fD2F806065b4f3C237d4b59D9A97Bcac7"> Zora smart contract</a>, implementing queries for fetching NFTs as well as their owners, and building relationships between them.</p><h3>Prerequisites</h3><p>To be successful in this tutorial, you should have <a href="https://nodejs.org/en/">Node.js</a> installed on your machine. These days, I recommend using either <a href="https://github.com/nvm-sh/nvm">nvm</a> or <a href="https://github.com/Schniz/fnm/blob/master/docs/commands.md">fnm</a> to manage Node.js versions.</p><h3>Creating the Graph project in the Graph Explorer</h3><p>To get started, open the <a href="https://thegraph.com/explorer/dashboard">Graph Explorer</a> and either sign in or create a new account.</p><p>Next, go to the <a href="https://thegraph.com/explorer/dashboard">dashboard</a> and click on <strong>Add Subgraph</strong> to create a new subgraph.</p><p>Configure your subgraph with the following properties:</p><ul><li>Subgraph Name — <strong>Zoranftsubgraph</strong></li><li>Subtitle — <strong>A subgraph for querying NFTs</strong></li><li>Optional — Fill the description and GITHUB URL properties</li></ul><p>Once the subgraph is created, we will initialize the subgraph locally using the Graph CLI.</p><h3>Initializing a new subgraph using the Graph CLI</h3><p>Next, install the Graph CLI:</p><pre>$ npm install -g @graphprotocol/graph-cli</pre><pre># or</pre><pre>$ yarn global add @graphprotocol/graph-cli</pre><p>Once the Graph CLI has been installed you can initialize a new subgraph with the Graph CLI init command.</p><p>There are two ways to initialize a new subgraph:</p><p>1 — From an example subgraph</p><pre>$ graph init --from-example &lt;GITHUB_USERNAME&gt;/&lt;SUBGRAPH_NAME&gt; [&lt;DIRECTORY&gt;]</pre><p>2 — From an existing smart contract</p><p>If you already have a smart contract deployed to Ethereum mainnet or one of the testnets, initializing a new subgraph from this contract is an easy way to get up and running.</p><pre>$ graph init --from-contract &lt;CONTRACT_ADDRESS&gt; \<br>  [--network &lt;ETHEREUM_NETWORK&gt;] \<br>  [--abi &lt;FILE&gt;] \<br>  &lt;GITHUB_USER&gt;/&lt;SUBGRAPH_NAME&gt; [&lt;DIRECTORY&gt;]</pre><p>In our case we’ll be using the <a href="https://etherscan.io/address/0xabEFBc9fD2F806065b4f3C237d4b59D9A97Bcac7#code">Zora Token Contract</a> so we can initilize from that contract address by passing in the contract address using the --from-contract flag:</p><pre>$ graph init --from-contract 0xabEFBc9fD2F806065b4f3C237d4b59D9A97Bcac7 --network mainnet  \<br>--contract-name Token --index-events</pre><pre>? Subgraph name › your-username/Zoranftsubgraph<br>? Directory to create the subgraph in › Zoranftsubgraph<br>? Ethereum network › Mainnet<br>? Contract address › 0xabEFBc9fD2F806065b4f3C237d4b59D9A97Bcac7<br>? Contract Name · Token</pre><p>This command will generate a basic subgraph based off of the contract address passed in as the argument to --from-contract. By using this contract address, the CLI will initialize a few things in your project to get you started (including fetching the abis and saving them in the <strong>abis</strong> directory).</p><blockquote><em>By passing in </em><em>--index-events the CLI will automatically populate some code for us both in </em><strong><em>schema.graphql</em></strong><em> as well as </em><strong><em>src/mapping.ts</em></strong><em> based on the events emitted from the contract.</em></blockquote><p>The main configuration and definition for the subgraph lives in the <strong>subgraph.yaml</strong> file. The subgraph codebase consists of a few files:</p><ul><li><strong>subgraph.yaml</strong>: a YAML file containing the subgraph manifest</li><li><strong>schema.graphql</strong>: a GraphQL schema that defines what data is stored for your subgraph, and how to query it via GraphQL</li><li><strong>AssemblyScript Mappings</strong>: AssemblyScript code that translates from the event data in Ethereum to the entities defined in your schema (e.g. mapping.ts in this tutorial)</li></ul><p>The entries in <strong>subgraph.yaml</strong> that we will be working with are:</p><ul><li>description (optional): a human-readable description of what the subgraph is. This description is displayed by the Graph Explorer when the subgraph is deployed to the Hosted Service.</li><li>repository (optional): the URL of the repository where the subgraph manifest can be found. This is also displayed by the Graph Explorer.</li><li>dataSources.source: the address of the smart contract the subgraph sources, and the abi of the smart contract to use. The address is optional; omitting it allows to index matching events from all contracts.</li><li>dataSources.source.startBlock (optional): the number of the block that the data source starts indexing from. In most cases we suggest using the block in which the contract was created.</li><li>dataSources.mapping.entities : the entities that the data source writes to the store. The schema for each entity is defined in the the schema.graphql file.</li><li>dataSources.mapping.abis: one or more named ABI files for the source contract as well as any other smart contracts that you interact with from within the mappings.</li><li>dataSources.mapping.eventHandlers: lists the smart contract events this subgraph reacts to and the handlers in the mapping — <strong>./src/mapping.ts</strong> in the example — that transform these events into entities in the store.</li></ul><h3>Defining the entities</h3><p>With The Graph, you define entity types in <strong>schema.graphql</strong>, and Graph Node will generate top level fields for querying single instances and collections of that entity type. Each type that should be an entity is required to be annotated with an @entity directive.</p><p>The entities / data we will be indexing are the Token and User. This way we can index the Tokens created by the users as well as the users themselves.</p><p>To do this, update <strong>schema.graphql</strong> with the following code:</p><pre>type Token @entity {<br>  id: ID!<br>  tokenID: BigInt!<br>  contentURI: String!<br>  metadataURI: String!<br>  creator: User!<br>  owner: User!<br>}</pre><pre>type User @entity {<br>  id: ID!<br>  tokens: [Token!]! @derivedFrom(field: &quot;owner&quot;)<br>  created: [Token!]! @derivedFrom(field: &quot;creator&quot;)<br>}</pre><h3>On Relationships via @derivedFrom (from the docs):</h3><p>Reverse lookups can be defined on an entity through the @derivedFrom field. This creates a virtual field on the entity that may be queried but cannot be set manually through the mappings API. Rather, it is derived from the relationship defined on the other entity. For such relationships, it rarely makes sense to store both sides of the relationship, and both indexing and query performance will be better when only one side is stored and the other is derived.</p><p>For one-to-many relationships, the relationship should always be stored on the ‘one’ side, and the ‘many’ side should always be derived. Storing the relationship this way, rather than storing an array of entities on the ‘many’ side, will result in dramatically better performance for both indexing and querying the subgraph. In general, storing arrays of entities should be avoided as much as is practical.</p><p>Now that we have created the GraphQL schema for our app, we can generate the entities locally to start using in the mappings created by the CLI:</p><pre>graph codegen</pre><p>In order to make working smart contracts, events and entities easy and type-safe, the Graph CLI generates AssemblyScript types from a combination of the subgraph’s GraphQL schema and the contract ABIs included in the data sources.</p><h3>Updating the subgraph with the entities and mappings</h3><p>Now we can configure the <strong>subgraph.yaml</strong> to use the entities that we have just created and configure their mappings.</p><p>To do so, first update the dataSources.mapping.entities field with the User and Token entities:</p><pre>entities:<br>  - Token<br>  - User</pre><p>Next, update the dataSources.mapping.eventHandlers to include only the following two event handlers:</p><pre>eventHandlers:<br>  - event: TokenURIUpdated(indexed uint256,address,string)<br>    handler: handleTokenURIUpdated<br>  - event: Transfer(indexed address,indexed address,indexed uint256)<br>    handler: handleTransfer</pre><p>Finally, update the configuration to add the startBlock:</p><pre>source:<br>  address: &quot;0xabEFBc9fD2F806065b4f3C237d4b59D9A97Bcac7&quot;<br>  abi: Token<br>  startBlock: 11565020</pre><h3>Assemblyscript mappings</h3><p>Next, open <strong>src/mappings.ts</strong> to write the mappings that we defined in our subgraph subgraph eventHandlers.</p><p>Update the file with the following code:</p><pre>import {<br>  TokenURIUpdated as TokenURIUpdatedEvent,<br>  Transfer as TransferEvent,<br>  Token as TokenContract<br>} from &quot;../generated/Token/Token&quot;</pre><pre>import {<br>  Token, User<br>} from &#39;../generated/schema&#39;</pre><pre>export function handleTokenURIUpdated(event: TokenURIUpdatedEvent): void {<br>  let token = Token.load(event.params._tokenId.toString());<br>  token.contentURI = event.params._uri;<br>  token.save();<br>}</pre><pre>export function handleTransfer(event: TransferEvent): void {<br>  let token = Token.load(event.params.tokenId.toString());<br>  if (!token) {<br>    token = new Token(event.params.tokenId.toString());<br>    token.creator = event.params.to.toHexString();<br>    token.tokenID = event.params.tokenId;</pre><pre>    let tokenContract = TokenContract.bind(event.address);<br>    token.contentURI = tokenContract.tokenURI(event.params.tokenId);<br>    token.metadataURI = tokenContract.tokenMetadataURI(event.params.tokenId);<br>  }<br>  token.owner = event.params.to.toHexString();<br>  token.save();</pre><pre>  let user = User.load(event.params.to.toHexString());<br>  if (!user) {<br>    user = new User(event.params.to.toHexString());<br>    user.save();<br>  }<br>}</pre><p>These mappings will handle events for when a new token is created, transfered, or updated. When these events fire, the mappings will save the data into the subgraph.</p><h3>Running a build</h3><p>Next, let’s run a build to make sure that everything is configured properly. To do so, run the build command:</p><pre>$ graph build</pre><p>If the build is successful, you should see a new <strong>build</strong> folder generated in your root directory.</p><h3>Deploying the subgraph</h3><p>To deploy, we can run the deploy command using the Graph CLI. To deploy, you will first need to copy the <strong>Access token</strong> for the subgraph you created in the Graph Explorer:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*nzexWMmePhrQxmo6.png" /></figure><p>Next, run the following command:</p><pre>$ graph auth https://api.thegraph.com/deploy/ &lt;ACCESS_TOKEN&gt;</pre><pre>$ yarn deploy</pre><p>Once the subgraph is deployed, you should see it show up in your dashboard:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*f04FvlXNbtVC53OV.png" /></figure><p>When you click on the subgraph, it should open the Graph explorer:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/880/0*cv-hVNXdtxct9CaW.png" /></figure><h3>Querying for data</h3><p>Now that we are in the dashboard, we should be able to start querying for data. Run the following query to get a list of tokens and their metadata:</p><pre>{<br>  tokens {<br>    id<br>    tokenID<br>    contentURI<br>    metadataURI<br>  }<br>}</pre><p>We can also configure the order direction:</p><pre>{<br>  tokens(<br>    orderBy:id,<br>    orderDirection: desc<br>  ) {<br>    id<br>    tokenID<br>    contentURI<br>    metadataURI<br>  }<br>}</pre><p>Or choose to skip forward a certain number of results to implement some basic pagination:</p><pre>{<br>  tokens(<br>    skip: 100,<br>    orderBy:id,<br>    orderDirection: desc<br>  ) {<br>    id<br>    tokenID<br>    contentURI<br>    metadataURI<br>  }<br>}</pre><p>Or query for users and their associated content:</p><pre>{<br>  users {<br>    id<br>    tokens {<br>      id<br>      contentURI<br>    }<br>  }<br>}</pre><h3>Updating the subgraph</h3><p>What if we want to make some changes to the subgraph and then redeploy? This is pretty easy, so let’s learn how to do it.</p><p>Let’s say that we want to add a new feature to our subgraph. In addition to our existing querying capabilities, let’s say that we wanted to add the capabilities to sort by the timestamp that the NFT was created.</p><p>To do so, we need to first add a new createdAtTimestamp field to the Token entity:</p><pre>type Token @entity {<br>  id: ID!<br>  tokenID: BigInt!<br>  contentURI: String!<br>  metadataURI: String!<br>  creator: User!<br>  owner: User!<br>  &quot;Add new createdAtTimesamp field&quot;<br>  createdAtTimestamp: BigInt!<br>}</pre><p>Now we can re-run the codegen:</p><pre>graph codegen</pre><p>Next, we need to update the mapping to save this new field:</p><pre>// update the handleTransfer function to add the createdAtTimestamp to the token object<br>export function handleTransfer(event: TransferEvent): void {<br>  let token = Token.load(event.params.tokenId.toString());<br>  if (!token) {<br>    token = new Token(event.params.tokenId.toString());<br>    token.creator = event.params.to.toHexString();<br>    token.tokenID = event.params.tokenId;<br>    // Add the createdAtTimestamp to the token object<br>    token.createdAtTimestamp = event.block.timestamp;</pre><pre>    let tokenContract = TokenContract.bind(event.address);<br>    token.contentURI = tokenContract.tokenURI(event.params.tokenId);<br>    token.metadataURI = tokenContract.tokenMetadataURI(event.params.tokenId);<br>  }<br>  token.owner = event.params.to.toHexString();<br>  token.save();</pre><pre>  let user = User.load(event.params.to.toHexString());<br>  if (!user) {<br>    user = new User(event.params.to.toHexString());<br>    user.save();<br>  }<br>}</pre><p>Now we can re-deploy the subgraph:</p><pre>$ yarn deploy</pre><p>Once the subgraph has been redeployed, we can now query by timestamp to view the most recently created NFTS:</p><pre>{<br>  tokens(<br>    orderBy:createdAtTimestamp,<br>    orderDirection: desc<br>  ) {<br>    id<br>    tokenID<br>    contentURI<br>    metadataURI<br>  }<br>}</pre><blockquote><em>The codebase for this project is located </em><a href="https://github.com/dabit3/building-a-subgraph-workshop/tree/main/Zoranftgraph"><em>here</em></a></blockquote><h3>Next steps</h3><p>If you are interested in learning more about Web3, building Dapps, or building subgraphs, check out the following resources:</p><p>The Graph on Twitter — <a href="https://twitter.com/graphprotocol">@graphprotocol</a></p><p><a href="https://dev.to/dabit3/the-complete-guide-to-full-stack-ethereum-development-3j13">The Complete Guide to Full Stack Ethereum Development</a></p><p><a href="https://thegraph.com/discord">The Graph Discord</a></p><p><a href="https://docs.soliditylang.org/">Solidity Docs</a></p><p><a href="https://ethereum.org/en/developers/docs/">Ethereum Developer Documentation</a></p><p>Austin Griffith on Twitter <a href="https://twitter.com/austingriffith">@austingriffith</a> &amp; <a href="https://github.com/austintgriffith/scaffold-eth">Scaffold Eth</a></p><p><a href="https://cryptozombies.io/">Crypto Zombies</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=427ed363df17" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Amplify Console and CLI now work together —  Five new features]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://dabit3.medium.com/amplify-console-and-cli-now-work-together-five-new-features-6a6ee1d45b1c?source=rss-695782484bda------2"><img src="https://cdn-images-1.medium.com/max/2000/1*XdumPk-aWv8ZXD3JK4bF0A.jpeg" width="2000"></a></p><p class="medium-feed-snippet">Today the Amplify Console and Amplify CLI have launched 5 new features for improving how teams manage Amplify backend environments</p><p class="medium-feed-link"><a href="https://dabit3.medium.com/amplify-console-and-cli-now-work-together-five-new-features-6a6ee1d45b1c?source=rss-695782484bda------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://dabit3.medium.com/amplify-console-and-cli-now-work-together-five-new-features-6a6ee1d45b1c?source=rss-695782484bda------2</link>
            <guid isPermaLink="false">https://medium.com/p/6a6ee1d45b1c</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[jamstack]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[aws-amplify]]></category>
            <dc:creator><![CDATA[Nader Dabit]]></dc:creator>
            <pubDate>Tue, 26 Nov 2019 18:02:36 GMT</pubDate>
            <atom:updated>2019-11-26T18:02:36.931Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Open GraphQL Moving to Dev.to]]></title>
            <link>https://medium.com/open-graphql/open-graphql-moving-to-dev-to-20de142a260?source=rss-695782484bda------2</link>
            <guid isPermaLink="false">https://medium.com/p/20de142a260</guid>
            <category><![CDATA[graphql]]></category>
            <dc:creator><![CDATA[Nader Dabit]]></dc:creator>
            <pubDate>Mon, 12 Aug 2019 15:29:59 GMT</pubDate>
            <atom:updated>2019-08-12T15:29:59.519Z</atom:updated>
            <content:encoded><![CDATA[<p>If you’re reading this, chances are you are following either me or the Open GraphQL publication.</p><blockquote>TLDR: Check out our new publication <a href="https://dev.to/open-graphql">here</a>.</blockquote><p>Medium has defaulted all posts to be behind a paywall (meaning you need to opt out, not opt in) which has caused some confusion for people that have published here in the past. This has caused some posts to be behind a paywall that people may not have really wanted to be behind by default.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mG4wdMpNXU9OBYqURF-0Iw.png" /><figcaption>Default setting when publishing.</figcaption></figure><p>Over the past year I’ve seen many people not enjoying the experience of reading on Medium now that many of these articles are behind this paywall so I have been slowly moving my personal as well as new publications writing to Dev.to.</p><p>I’ve created a new Open GraphQL publication there. <a href="https://dev.to/open-graphql">If you’re interested in contributing or writing about GraphQL, come join us there</a>. Dev.to is a great and thriving community of people passionate about what they do. Also, you are able to write in Markdown allowing you to easily port your content to other places with ease.</p><p>Thanks for reading!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=20de142a260" width="1" height="1" alt=""><hr><p><a href="https://medium.com/open-graphql/open-graphql-moving-to-dev-to-20de142a260">Open GraphQL Moving to Dev.to</a> was originally published in <a href="https://medium.com/open-graphql">Open GraphQL</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GraphQLifying REST]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/open-graphql/graphqlifying-rest-5a95d57a04c2?source=rss-695782484bda------2"><img src="https://cdn-images-1.medium.com/max/1800/1*MXi8GpY6jVHwYYOvY3CZmA.jpeg" width="1800"></a></p><p class="medium-feed-snippet">Transforming REST APIs to GraphQL with AWS AppSync</p><p class="medium-feed-link"><a href="https://medium.com/open-graphql/graphqlifying-rest-5a95d57a04c2?source=rss-695782484bda------2">Continue reading on Open GraphQL »</a></p></div>]]></description>
            <link>https://medium.com/open-graphql/graphqlifying-rest-5a95d57a04c2?source=rss-695782484bda------2</link>
            <guid isPermaLink="false">https://medium.com/p/5a95d57a04c2</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[graphql]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[aws-appsync]]></category>
            <dc:creator><![CDATA[Nader Dabit]]></dc:creator>
            <pubDate>Mon, 13 May 2019 13:14:50 GMT</pubDate>
            <atom:updated>2019-05-20T01:12:40.678Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[How to deploy a custom domain with the Amplify Console]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/a-cloud-guru/how-to-deploy-a-custom-domain-with-the-amplify-console-a884b6a3c0fc?source=rss-695782484bda------2"><img src="https://cdn-images-1.medium.com/max/2560/1*W3hYO3Zf0LHF7_rdN9W2bg.png" width="2560"></a></p><p class="medium-feed-snippet">Add a custom domain to an Amplify Console deployment in just a couple of minutes&#x200A;&#x2014;&#x200A;let&#x2019;s take a look at how this works!</p><p class="medium-feed-link"><a href="https://medium.com/a-cloud-guru/how-to-deploy-a-custom-domain-with-the-amplify-console-a884b6a3c0fc?source=rss-695782484bda------2">Continue reading on A Cloud Guru »</a></p></div>]]></description>
            <link>https://medium.com/a-cloud-guru/how-to-deploy-a-custom-domain-with-the-amplify-console-a884b6a3c0fc?source=rss-695782484bda------2</link>
            <guid isPermaLink="false">https://medium.com/p/a884b6a3c0fc</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[cloud-computing]]></category>
            <category><![CDATA[serverless]]></category>
            <dc:creator><![CDATA[Nader Dabit]]></dc:creator>
            <pubDate>Fri, 12 Apr 2019 19:40:04 GMT</pubDate>
            <atom:updated>2019-04-12T19:41:13.766Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[How to Build a Real-time Collaborative Markdown Editor with React Hooks, GraphQL & AWS AppSync]]></title>
            <link>https://medium.com/open-graphql/how-to-build-a-real-time-collaborative-markdown-editor-with-react-hooks-graphql-aws-appsync-dc0c121683f4?source=rss-695782484bda------2</link>
            <guid isPermaLink="false">https://medium.com/p/dc0c121683f4</guid>
            <category><![CDATA[aws-appsync]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[aws-amplify]]></category>
            <category><![CDATA[graphql]]></category>
            <dc:creator><![CDATA[Nader Dabit]]></dc:creator>
            <pubDate>Wed, 03 Apr 2019 11:45:00 GMT</pubDate>
            <atom:updated>2019-04-08T10:59:36.554Z</atom:updated>
            <content:encoded><![CDATA[<blockquote><em>This post will walk through everything from creating the API, writing the client code, &amp; deploying a custom domain.</em><br>Try it out — <a href="https://www.writewithme.dev/#/">writewithme.dev</a><br>Check out the code- <a href="https://github.com/dabit3/write-with-me">https://github.com/dabit3/write-with-me</a></blockquote><p>I’ve been deep-diving into some interesting use-cases showing some of the real-time capabilities of GraphQL subscriptions.</p><p>A couple of weeks ago I built <a href="https://hypebeats.dev/">HypeBeats</a>, a collaborative drum-machine with the help of Ken Wheeler’s <a href="https://github.com/kenwheeler/hooks-drum-machine">drum machine</a>, &amp; before that I built <a href="https://github.com/dabit3/appsync-graphql-real-time-canvas">a real-time drawing canvas</a> with <a href="https://www.google.com/search?q=react+canvas+draw&amp;oq=react+canvas+draw&amp;aqs=chrome..69i57j69i60j0l4.2443j0j4&amp;sourceid=chrome&amp;ie=UTF-8">React Canvas Draw</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4F79aJFC9jfepkILJHCGmQ.gif" /></figure><p>Last week I sent out a tweet to see what other ideas people had, &amp; there were a lot:</p><h3>Nader Dabit on Twitter</h3><p>What crazy 🚫❌❗️❓ do you want you want me to build next with GraphQL? Looking for some useful and or crazy demo ideas. Real-time / collaboration is what I&#39;d like to showcase the most.</p><p>An idea that immediately stood out to me was from <a href="https://twitter.com/NagarajanFs">NagarajanFs</a> on Twitter:</p><h3>Nagarajan on Twitter</h3><p>@dabit3 A collaborative markdown editor with features of real-time comment by participants on different sections. (Google docs, but with markdown)</p><p>Basically his idea was Google Docs but for markdown. I loved it, so I decided to build it! The result was <a href="https://www.writewithme.dev/#/">writewithme.dev</a>.</p><h4>Getting Started</h4><p>The first thing I did was created a new React application:</p><pre>npx create-react-app write-with-me</pre><p>The next thing I needed to do was to find the tool I was going to use to allow markdown in a React App. I stumbled upon <a href="https://github.com/rexxars/react-markdown">react-markdown</a> by <a href="https://medium.com/u/a691c1e5d348">Espen Hovlandsdal</a> (<a href="https://twitter.com/rexxars">rexxars</a> on Twitter).</p><pre>npm install react-markdown</pre><p>React Markdown was super simple to use. You import ReactMarkdown from the package &amp; pass in the markdown you’d like to render as a prop:</p><pre>const ReactMarkdown = require(&#39;react-markdown&#39;)<br><br>const input = &#39;# This is a header\n\nAnd this is a paragraph&#39;<br><br>&lt;ReactMarkdown source={input} /&gt;</pre><h4>Building the API</h4><p>Now that we have the markdown tool the next set was creating the API. I used AWS AppSync &amp; AWS Amplify to do this. With the Amplify CLI, you can set a base type &amp; add a decorator to build out the entire backend by taking advantage of the <a href="https://aws-amplify.github.io/docs/cli/graphql">GraphQL Transform librar</a>y.</p><pre>amplify init</pre><pre>amplify add api</pre><pre>// chose GraphQL<br>// Choose API Key as the authentication</pre><p>The schema I used was this:</p><pre>type Post <a href="http://twitter.com/model">@model</a> {<br>  id: ID!<br>  clientId: ID!<br>  markdown: String!<br>  title: String!<br>  createdAt: String<br>}</pre><blockquote>The @model decorator will tell Amplify to deploy a GraphQL backend complete with a DynamoDB data source &amp; schema (types &amp; operations) for all crud operations &amp; GraphQL subscriptions. It will also create the resolvers for the created GraphQL operations.</blockquote><p>After defining &amp; saving the schema we deploy the AppSync API:</p><pre>amplify push</pre><p>When we run amplify push, we’re also given the option to execute <a href="https://aws-amplify.github.io/docs/cli/codegen?sdk=js"><strong>GraphQL codegen</strong></a> in either TypeScript, JavaScript, or Flow. Doing this will introspect your GraphQL schema &amp; automatically generate the GraphQL code you’ll need on the client in order to execute queries, mutations, &amp; subscriptions.</p><h4>Installing the dependencies</h4><p>Next, we install the other necessary libraries we need for the app:</p><pre>npm install aws-amplify react-router-dom uuid glamor debounce</pre><ul><li><strong>uuid</strong> — to create unique IDs to uniquely identify the client</li><li><strong>react-router-dom</strong> — to add routing</li><li><strong>aws-amplify</strong> — to interact with the AppSync API</li><li><strong>glamor</strong> — for styling</li><li><strong>debounce</strong> — for adding a debounce when user types</li></ul><h3>Writing the code</h3><p>Now that our project is set up &amp; our API has been deployed, we can start writing some code!</p><p><strong>The app has three main files:</strong></p><ul><li><a href="https://github.com/dabit3/write-with-me/blob/master/src/Router.js">Router.js</a> — Defines the routes</li><li><a href="https://github.com/dabit3/write-with-me/blob/master/src/Posts.js">Posts.js</a> — Fetches &amp; renders the posts</li><li><a href="https://github.com/dabit3/write-with-me/blob/master/src/Post.js">Post.js</a> — Fetches &amp; renders a single post</li></ul><h4><a href="https://github.com/dabit3/write-with-me/blob/master/src/Router.js">Router.js</a></h4><p>Setting up navigation was pretty basic. We needed two routes: one for listing all of the posts &amp; one for viewing a single post. I decided to go with the following route scheme:</p><pre>/post/:id/:title</pre><p>When someone hits the above route, we have access to everything we need to display a title for the post as well as the ID for us to fetch the post if it is an existing post. All of this info is available directly in the route parameters.</p><h4>React Hooks with GraphQL</h4><blockquote>If you’ve ever wondered how to implement hooks into a GraphQL application, I recommend also reading my post <a href="https://medium.com/open-graphql/react-hooks-for-graphql-3fa8ebdd6c62">Writing Custom Hooks for GraphQL</a> because that’s exactly what we’ll be doing.</blockquote><p>Instead of going over <em>all</em> of the code for the 2 components (if you’d like to see the code, click on the links above), I’d like to focus on how we implemented the necessary functionality using GraphQL &amp; hooks.</p><p>The main API operations we needed from this app are intended to do the following:</p><ol><li>Load all of the posts from the API</li><li>Subscribe to new posts being created by others</li><li>Load an individual post</li><li>Subscribe to changes within an individual post &amp; re-render the component</li></ol><p>Let’s take a look at each.</p><h4>Loading all of the posts from the API</h4><p>To load the posts I went with a useReducer hook combined with a function call within a useEffect hook. We first define the initial state. When the component renders for the first time we call the API &amp; update the state using the fetchPosts function. This reducer will also handle our subscription:</p><pre>import { listPosts } from &#39;./graphql/queries&#39;<br>import { API, graphqlOperation } from &#39;aws-amplify&#39;</pre><pre>const initialState = {<br>  posts: [],<br>  loading: true,<br>  error: false<br>}</pre><pre>function reducer(state, action) {<br>  switch (action.type) {<br>    case &#39;fetchPostsSuccess&#39;:<br>      return { ...state, posts: action.posts, loading: false }<br>    case &#39;addPostFromSubscription&#39;:<br>      return { ...state, posts: [ action.post, ...state.posts ] }<br>    case &#39;fetchPostsError&#39;:<br>      return { ...state, loading: false, error: true }<br>    default:<br>      throw new Error();<br>  }<br>}</pre><pre>async function fetchPosts(dispatch) {<br>  try {<br>    const postData = await API.graphql(graphqlOperation(listPosts))<br>    dispatch({<br>      type: &#39;fetchPostsSuccess&#39;,<br>      posts: postData.data.listPosts.items<br>    })<br>  } catch (err) {<br>    console.log(&#39;error fetching posts...: &#39;, err)<br>    dispatch({<br>      type: &#39;fetchPostsError&#39;,<br>    })<br>  }<br>}</pre><pre>// in the hook</pre><pre>const [postsState, dispatch] = useReducer(reducer, initialState)</pre><pre>useEffect(() =&gt; {<br>  fetchPosts(dispatch)<br>}, [])</pre><h4>Subscribing to new posts being created</h4><p>We already have our state ready to go from the above code, now we just need to subscribe to the changes in the hook. To do that, we use a useEffect hook &amp; subscribe to the onCreatePost subscription:</p><pre>useEffect(() =&gt; {<br>  const subscriber = API.graphql(graphqlOperation(onCreatePost)).subscribe({<br>    next: data =&gt; {<br>      const postFromSub = data.value.data.onCreatePost<br>      dispatch({<br>        type: &#39;addPostFromSubscription&#39;,<br>        post: postFromSub<br>      })<br>    }<br>  });<br>  return () =&gt; subscriber.unsubscribe()<br>}, [])</pre><p>In this hook, we set up a subscription that will fire when a user creates a new post. When the data comes through, we call the dispatch function &amp; pass in the new post data that was returned from the subscription.</p><h4>Fetching a single post</h4><p>When a user lands on a route, we can use the data from the route params to identify the post name &amp; ID:</p><pre><a href="https://www.writewithme.dev/#/post/9999b0bb-63eb-4f5b-9805-23f6c2661478/%F0%9F%94%A5%20Write%20with%20me">https://www.writewithme.dev/#/post/9999b0bb-63eb-4f5b-9805-23f6c2661478/%F0%9F%94%A5%20Write%20with%20me</a></pre><p>In this route, the ID would be <strong>9999b0bb-63eb-4f5b-9805–23f6c2661478</strong> &amp; the name would be <strong>Write with me</strong>.</p><p>When the component loads, we extract these params &amp; use them.</p><p>The first thing we do is attempt to create a new post. If this is successful, we are done. If this post already exists, we are given the data for this post from the API call.</p><p>This may seem strange at first, right? Why not try to fetch, &amp; if unsuccessful the create? Well, we want to reduce the total number of API calls. If we attempt to create a new post &amp; the post exists, the API will actually return the data from the existing post allowing us to only make a single API call. This data is available in the errors:</p><pre>err.errors[0].data</pre><p>We handle the state in this component using useReducer hook.</p><pre><strong>// initial state</strong><br>const post = {<br>  id: params.id,<br>  title: params.title,<br>  clientId: CLIENTID,<br>  markdown: &#39;# Loading...&#39;<br>}</pre><pre>function reducer(state, action) {<br>  switch (action.type) {<br>    case &#39;updateMarkdown&#39;:<br>      return { ...state, markdown: action.markdown, clientId: CLIENTID };<br>    case &#39;updateTitle&#39;:<br>      return {  ]...state, title: action.title, clientId: CLIENTID };<br>    case &#39;updatePost&#39;:<br>      return action.post<br>    default:<br>      throw new Error();<br>  }<br>}</pre><pre>async function createNewPost(post, dispatch) {<br>  try {<br>    const postData = await API.graphql(graphqlOperation(createPost, { input: post }))<br>    dispatch({<br>      type: &#39;updatePost&#39;,<br>      post: {<br>        ...postData.data.createPost,<br>        clientId: CLIENTID<br>      }<br>    })<br>  } catch(err) {<br>    if (err.errors[0].errorType === &quot;DynamoDB:ConditionalCheckFailedException&quot;) {<br>      const existingPost = err.errors[0].data<br>      dispatch({<br>        type: &#39;updatePost&#39;,<br>        post: {<br>          ...existingPost,<br>          clientId: CLIENTID<br>        }<br>      })<br>    }    <br>  }<br>}</pre><pre>// in the hook, initialize the state<br> const [postState, dispatch] = useReducer(reducer, post)</pre><pre>// fetch post<br>useEffect(() =&gt; {<br>  const post = {<br>    ...postState,<br>    markdown: input<br>  }<br>  createNewPost(post, dispatch)<br>}, [])</pre><h4>Subscribing to a post change</h4><p>The last thing we need to do is subscribe to changes in a post. To do this, we do two things:</p><ol><li>Update the API when the user types (both title or markdown changes)</li><li>Subscribe to changes then the post is updated</li></ol><pre>async function updatePost(post) {<br>  try {<br>    await API.graphql(graphqlOperation(UpdatePost, { input: post }))<br>    console.log(&#39;post has been updated!&#39;)<br>  } catch (err) {<br>    console.log(&#39;error:&#39; , err)<br>  }<br>}</pre><pre>function updateMarkdown(e) {<br>  dispatch({<br>    type: &#39;updateMarkdown&#39;,<br>    markdown: e.target.value,<br>  })<br>  const newPost = {<br>    id: post.id,<br>    markdown: e.target.value,<br>    clientId: CLIENTID,<br>    createdAt: post.createdAt,<br>    title: postState.title<br>  }<br>  updatePost(newPost, dispatch)<br>}</pre><pre>function updatePostTitle (e) {<br>  dispatch({<br>    type: &#39;updateTitle&#39;,<br>    title: e.target.value<br>  })<br>  const newPost = {<br>    id: post.id,<br>    markdown: postState.markdown,<br>    clientId: CLIENTID,<br>    createdAt: post.createdAt,<br>    title: e.target.value<br>  }<br>  updatePost(newPost, dispatch)<br>}</pre><pre>useEffect(() =&gt; {<br>  const subscriber = API.graphql(graphqlOperation(onUpdatePost, {<br>    id: post.id<br>  })).subscribe({<br>    next: data =&gt; {<br>      if (CLIENTID === data.value.data.onUpdatePost.clientId) return<br>      const postFromSub = data.value.data.onUpdatePost<br>      dispatch({<br>        type: &#39;updatePost&#39;,<br>        post: postFromSub<br>      })<br>    }<br>  });<br>  return () =&gt; subscriber.unsubscribe()<br>}, [])</pre><p>When the user types, we update both the local state as well as the API. In the subscription, we first check to see if the subscription data coming in is from the same Client ID. If it is, we do nothing. If it is from another client, we update the state.</p><h3>Deploying the app on a custom domain</h3><p>Now that we’ve built the app, what about deploying it to a custom domain like I did with <a href="https://www.writewithme.dev/#/">writewithme.dev</a>?</p><p>I did this using GoDaddy, Route53 &amp; the Amplify console.</p><p>In the AWS dashboard, go to <a href="https://console.aws.amazon.com/route53/home">Route53</a> &amp; click on Hosted Zones. Choose <strong>Create Hosted Zone</strong>. From there, enter your domain name &amp; click create.</p><blockquote>Be sure to enter your domain name as is, without <strong>www</strong>. E.g. <strong>writewithme.dev</strong></blockquote><p>Now in Route53 dashboard you should be given 4 nameservers. In your hosting account, set these custom nameservers in your DNS setting for the domain you’re using.</p><blockquote>These nameservers should look something like ns-1355.awsdns-41.org, ns-1625.awsdns-11.co.uk, etc…</blockquote><p>Next, in the <a href="https://console.aws.amazon.com/amplify">Amplify Console</a>, click on <strong>Get Started</strong> under the Deploy section.</p><p>Connect your GitHub account &amp; then choose the repo &amp; branch that your project lives in.</p><p>This will walk you through deploying the app in the Amplify Console &amp; making it live. Once complete, you should see some information about the build &amp; some screenshots of the app:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*d1xrb43nRijbnB01m4o-Og.png" /></figure><p>Next, click <strong>Domain Management</strong> in the left menu. Next, click the <strong>Add Domain</strong> button.</p><p>Here, the dropdown menu should show you the domain you have in Route53. Choose this domain &amp; click <strong>Configure domain</strong>.</p><p>This should deploy the app to your domain (this will take between 5–20 minutes). The last thing is to set up redirects. Click on <strong>Rewrites &amp; redirects</strong>.</p><p>Make sure the redirect for the domain looks like this <strong>(i.e. redirect the </strong><a href="https://websitename/"><strong>https://websitename</strong></a><strong> to https://www.websitename)</strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f4uAFsco0iE3Az4XRMWQsQ.png" /></figure><h3>Next Steps</h3><p>The next thing I’d like to do would be to add authentication &amp; commenting functionality (so people can comment on each individual post). This is all doable with Amplify &amp; AppSync. If we wanted to add authentication, we could add it with the CLI:</p><pre>amplify add auth</pre><p>Next, we’d have to write some logic to log people in &amp; out or we can use the withAuthenticator HOC. To learn more about how to do this, check out <a href="https://aws-amplify.github.io/docs/js/authentication">these</a> docs.</p><p>Next, we’d probably want to update the schema to add commenting functionality. To do so, we could update the schema to something like what I created <a href="https://github.com/dabit3/RNAConfApp/blob/master/amplify/backend/api/rna2019/schema.graphql">here</a>. In this schema, we’ve added a field for the Discussion (in our case it would probably be called Comment).</p><p>In the resolver, we could probably also correlate a user’s identity to the message as well but using the $context.identity.sub which would be available after the user is signed in.</p><blockquote>Check out the final code- <a href="https://github.com/dabit3/write-with-me">https://github.com/dabit3/write-with-me</a></blockquote><blockquote><em>My Name is </em><a href="https://twitter.com/dabit3"><em>Nader Dabit </em></a><em>. I am a Developer Advocate at </em><a href="https://aws.amazon.com/"><em>AWS</em></a><em> working with projects like </em><a href="https://aws.amazon.com/appsync/"><em>AWS AppSync</em></a><em> and </em><a href="https://github.com/aws/aws-amplify"><em>AWS </em></a><a href="https://github.com/aws/aws-amplify"><em>Amplify</em></a><em>.</em></blockquote><blockquote><em>I’m also the author of </em><a href="https://www.manning.com/books/react-native-in-action"><em>React Native in Action</em></a><em>, &amp; the editor of </em><a href="https://medium.com/react-native-training"><em>React Native Training</em></a><em> &amp; </em><a href="https://medium.com/open-graphql"><em>OpenGraphQL</em></a><em>.</em></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dc0c121683f4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/open-graphql/how-to-build-a-real-time-collaborative-markdown-editor-with-react-hooks-graphql-aws-appsync-dc0c121683f4">How to Build a Real-time Collaborative Markdown Editor with React Hooks, GraphQL &amp; AWS AppSync</a> was originally published in <a href="https://medium.com/open-graphql">Open GraphQL</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>