<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pieter Heyvaert</title>
    <description>The latest articles on DEV Community by Pieter Heyvaert (@heypieter).</description>
    <link>https://dev.to/heypieter</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F141056%2Fc44273f8-8459-46e8-b75d-3b0bc0bfcad7.jpg</url>
      <title>DEV Community: Pieter Heyvaert</title>
      <link>https://dev.to/heypieter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/heypieter"/>
    <language>en</language>
    <item>
      <title>Learn how to create a website on top of decentralized data</title>
      <dc:creator>Pieter Heyvaert</dc:creator>
      <pubDate>Wed, 02 Sep 2020 11:44:41 +0000</pubDate>
      <link>https://dev.to/heypieter/getting-started-with-walder-3coa</link>
      <guid>https://dev.to/heypieter/getting-started-with-walder-3coa</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/KNowledgeOnWebScale/walder"&gt;Walder&lt;/a&gt; offers an easy way to set up a website or Web API on top of decentralized knowledge graphs. Knowledge graphs incorporate data together with the meaning of that data. This makes it possible to combine data from multiple knowledge graphs, even if different, independent parties maintain or host them. Knowledge graphs can be hosted via &lt;a href="https://solidproject.org/"&gt;Solid&lt;/a&gt; PODs, &lt;a href="https://www.w3.org/TR/sparql11-query/"&gt;SPARQL&lt;/a&gt; endpoints, &lt;a href="https://linkeddatafragments.org/"&gt;Triple Pattern Fragments&lt;/a&gt; interfaces, &lt;a href="https://www.w3.org/TR/rdf11-concepts/"&gt;RDF&lt;/a&gt; files, and so on. Using content negotiation, Walder makes the data in these knowledge graphs available to clients via HTML, RDF, and JSON-LD. Users define in a configuration file which data Walder uses and how it processes this data. This tutorial introduces you to Walder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table of contents&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Before we start the tutorial&lt;/li&gt;
&lt;li&gt;Example&lt;/li&gt;
&lt;li&gt;How to start a configuration file&lt;/li&gt;
&lt;li&gt;How to add knowledge graphs&lt;/li&gt;
&lt;li&gt;How to add paths&lt;/li&gt;
&lt;li&gt;How to add views&lt;/li&gt;
&lt;li&gt;How to run Walder&lt;/li&gt;
&lt;li&gt;How to process query results&lt;/li&gt;
&lt;li&gt;How to add parameters to paths&lt;/li&gt;
&lt;li&gt;Bonus: content negotiation&lt;/li&gt;
&lt;li&gt;Wrapping up&lt;/li&gt;
&lt;li&gt;More information&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Before we start the tutorial
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1. Learning objective
&lt;/h3&gt;

&lt;p&gt;At the end of the tutorial you will be able to set up a website on top of decentralized knowledge graphs by using Walder.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2. Prerequisites
&lt;/h3&gt;

&lt;p&gt;We assume that you are familiar with &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;knowledge graphs and more specific the Resource Description Framework (RDF)&lt;/li&gt;
&lt;li&gt;bash&lt;/li&gt;
&lt;li&gt;Node.js (v12 or higher) and npm&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1.3. How to use the tutorial
&lt;/h3&gt;

&lt;p&gt;There are two ways to complete this tutorial: you read the explanations and either&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read the examples, or&lt;/li&gt;
&lt;li&gt;try out the examples yourself using your computer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the files of this tutorial in &lt;a href="https://github.com/pheyvaer/walder-tutorial"&gt;this repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Example
&lt;/h2&gt;

&lt;p&gt;For this tutorial we want to create a website that displays our ratings of the tv shows we watched. Our website is getting the required data from two knowledge graphs containing the following data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;our tv show ratings&lt;/li&gt;
&lt;li&gt;details about these tv shows, such as title, release year and so on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will combine the data in both knowledge graphs to create these two Web pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;list of tv shows with their ratings at the path &lt;code&gt;/ratings&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;list of tv shows with "x"-star ratings at the path &lt;code&gt;/rating/{x}&lt;/code&gt;, where "x" is a value between 1 and 5.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. How to start a configuration file
&lt;/h2&gt;

&lt;p&gt;A configuration file for Walder is a valid &lt;a href="https://swagger.io/docs/specification/basic-structure/"&gt;OpenAPI 3.0&lt;/a&gt; file with Walder-specific extensions. These extensions start with &lt;code&gt;x-walder-&lt;/code&gt; in the configuration file.&lt;/p&gt;

&lt;p&gt;We do the following steps&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a config file called &lt;code&gt;config.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add the following content to the file:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.0.2&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Ratings&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;website'&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's have a look at this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;openapi&lt;/code&gt; states the OpenAPI version we are using.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;info&lt;/code&gt; contains the title and version of our website.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. How to add knowledge graphs
&lt;/h2&gt;

&lt;p&gt;You add knowledge graphs, also called data sources, to Walder by listing them under the section &lt;code&gt;x-walder-datasources&lt;/code&gt;. For the two aforementioned knowledge graphs this results in&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;x-walder-datasources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;https://pieterheyvaert.com/data/example/walder/ratings.ttl&lt;/span&gt; &lt;span class="c1"&gt;# Turtle file&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;https://data.betweenourworlds.org/latest&lt;/span&gt; &lt;span class="c1"&gt;# Triple Pattern Fragments server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Add this section to the root of &lt;code&gt;config.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Although both data sources are hosted using different technologies, you do not need to specify these technologies &lt;br&gt;
as Walder determines the correct way to query a data source &lt;br&gt;
on the fly. If interested, you can open both data sources in your browser and explore them.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. How to add paths
&lt;/h2&gt;

&lt;p&gt;You add paths under the section &lt;code&gt;paths&lt;/code&gt;, which is &lt;a href="https://swagger.io/docs/specification/paths-and-operations/"&gt;defined&lt;/a&gt; in the OpenAPI specification. The following is an entry for the path &lt;code&gt;ratings&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;/ratings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Returns a list of all tv shows and their ratings.&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;200&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List of all tv shows and their ratings.&lt;/span&gt;
          &lt;span class="s"&gt;x-walder-input-text/html&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ratings.handlebars&lt;/span&gt;
      &lt;span class="na"&gt;x-walder-query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;graphql-query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="s"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;id @single&lt;/span&gt;
            &lt;span class="s"&gt;title @single&lt;/span&gt;
            &lt;span class="s"&gt;review @single {&lt;/span&gt;
              &lt;span class="s"&gt;rating @single {&lt;/span&gt;
                &lt;span class="s"&gt;value @single&lt;/span&gt;
              &lt;span class="s"&gt;}&lt;/span&gt;
            &lt;span class="s"&gt;}&lt;/span&gt;
          &lt;span class="s"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;json-ld-context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="s"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"schema": "http://schema.org/",&lt;/span&gt;
            &lt;span class="s"&gt;"review": "schema:review",&lt;/span&gt;
            &lt;span class="s"&gt;"rating": "schema:reviewRating",&lt;/span&gt;
            &lt;span class="s"&gt;"value": "schema:ratingValue",&lt;/span&gt;
            &lt;span class="s"&gt;"title": "schema:name"&lt;/span&gt;
          &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Add this section to the root of &lt;code&gt;config.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's have a closer look at this entry.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/ratings&lt;/code&gt; is the path we want and  is the section where we add the details about that path.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get&lt;/code&gt; says that for this path we want to support an HTTP GET and it is the section where we add the details about the GET.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;summary&lt;/code&gt; contains a summary of the request.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;responses&lt;/code&gt; is a section that contains all supported responses based on their HTTP status code.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;200&lt;/code&gt; is a section that contains all the information to handle a response with status code 200.&lt;/li&gt;
&lt;li&gt; &lt;code&gt;description&lt;/code&gt; contains a description of the response.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;x-walder-input-text/html&lt;/code&gt; points to a template-engine file that renders an HTML file, which is send back to the client as response. Walder supports different template engines, 
such as &lt;a href="https://pugjs.org"&gt;Pug&lt;/a&gt; and &lt;a href="https://handlebarsjs.com/"&gt;Handlebars&lt;/a&gt;.
In our example, we have a Handlebars file called &lt;code&gt;ratings.handlebars&lt;/code&gt; (which we will create later).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;x-walder-query&lt;/code&gt; is a section that contains information about query that is executed over the knowledge graphs. Walder gives the result of this query to the template engine, 
which uses this result during the rendering of the HTML file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;graphql-query&lt;/code&gt; contains the &lt;a href="https://github.com/rubensworks/GraphQL-LD.js"&gt;GraphQL-LD&lt;/a&gt; query 
that is executed over the knowledge graphs. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;json-ld-context&lt;/code&gt; contains the &lt;a href="https://json-ld.org/spec/latest/json-ld/#the-context"&gt;JSON-LD context&lt;/a&gt; that is needed for the GraphQL-LD query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's have a closer look at the query and the context. The GraphQL-LD query is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  id @single
  title @single
  review @single {
    rating @single {
      value @single
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This query returns the URLs, titles, and reviews of the shows. The variable &lt;code&gt;id&lt;/code&gt; contains the URL. The variable &lt;code&gt;title&lt;/code&gt; is a string with the title. The variable &lt;code&gt;review&lt;/code&gt; is an object with a rating, which is also an object having the variable &lt;code&gt;value&lt;/code&gt; with the actual rating value. We use &lt;code&gt;@single&lt;/code&gt; to get strings and objects instead of arrays of strings and objects, which is the default.&lt;/p&gt;

&lt;p&gt;The query in itself is not enough,because these variables are not defined across all data sources. To tackle this, we use this JSON-LD context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "schema": "http://schema.org/",
  "review": "schema:review",
  "rating": "schema:reviewRating",
  "value": "schema:ratingValue",
  "title": "schema:name"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The context defines the prefix for &lt;code&gt;http://schema.org/&lt;/code&gt; and the URLs for all the variables. For example, &lt;code&gt;review&lt;/code&gt; has as URL &lt;code&gt;http://schema.org/review&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. How to add views
&lt;/h2&gt;

&lt;p&gt;In our path entry, we have &lt;code&gt;x-walder-input-text/html: ratings.handlebars&lt;/code&gt;. Walder will look for the file &lt;code&gt;ratings.handlebars&lt;/code&gt; in the folder &lt;code&gt;views&lt;/code&gt; relative to the location of the config file. We do the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a folder &lt;code&gt;views&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add a file &lt;code&gt;ratings.handlebars&lt;/code&gt; with the following content:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight handlebars"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Ratings&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
       &lt;span class="k"&gt;{{#&lt;/span&gt;&lt;span class="nn"&gt;each&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;: &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;review&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;/5 &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;{{/&lt;/span&gt;&lt;span class="nn"&gt;each&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It is important to note here that the results of the query are accessed via the variable &lt;code&gt;data&lt;/code&gt;, as done on the line 10 with &lt;code&gt;{{#each data}}&lt;/code&gt; in the above code.&lt;/p&gt;

&lt;p&gt;You can find the files we created up until this point &lt;a href="https://github.com/pheyvaer/walder-tutorial/tree/master/part-1"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. How to run Walder
&lt;/h2&gt;

&lt;p&gt;We now have all the files in place to use Walder for the first time. If you do not have Walder installed, you can do so via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i -g walder
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You run Walder via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;walder -c config.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You will see this in the output&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;info 2020-08-26T13:37:03.152Z : Starting static server...
info 2020-08-26T13:37:03.153Z : Static server running.
info 2020-08-26T13:37:03.154Z : Parsing route /ratings
info 2020-08-26T13:37:03.154Z :     - Parsing method get
info 2020-08-26T13:37:03.209Z : Starting server...
info 2020-08-26T13:37:03.212Z : Started listening on port: 3000.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The output is configurable via the &lt;code&gt;-l, --log&lt;/code&gt; option. See &lt;code&gt;walder -h&lt;/code&gt; for more information and other options.&lt;/p&gt;

&lt;p&gt;Next, open a browser and navigate to &lt;code&gt;http://localhost:3000/ratings&lt;/code&gt;. The result is a list of tv shows together with their ratings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lKZWHbMQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3k83wym2tae3m9h1tvu1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lKZWHbMQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3k83wym2tae3m9h1tvu1.png" alt="List of tv shows together with their ratings, but contains duplicates."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8. How to process query results
&lt;/h2&gt;

&lt;p&gt;When checking the results on &lt;code&gt;http://localhost:3000/ratings&lt;/code&gt;&lt;br&gt;
we see that there are duplicate results: certain tv shows are shown multiple times. This is due to the fact that tv shows have different titles, possibly in different languages.&lt;/p&gt;

&lt;p&gt;Walder offers the functionality to process the results of a query. You do this via the &lt;code&gt;x-walder-postprocessing&lt;/code&gt; section, which has a section for every function that needs to be executed on the results.&lt;/p&gt;

&lt;p&gt;In our example this is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;x-walder-postprocessing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;keepSingleENTitle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;utils.js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This means that we want to use the function &lt;code&gt;keepSingleENTitle&lt;/code&gt; which can be found in the file &lt;code&gt;utils.js&lt;/code&gt;. Walder will look for this file in the folder &lt;code&gt;pipe-modules&lt;/code&gt; relative to the location of the config file.  We do the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a folder &lt;code&gt;pipe-modules&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add a file &lt;code&gt;utils.js&lt;/code&gt; with the following content:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;keepSingleENTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryResults&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;queryResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Keep only English titles.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;english&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Za-z0-9 .&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showsWithEnglishTitles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;originalData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;english&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Filter duplicate shows.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="nx"&gt;showsWithEnglishTitles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Only add show when the id hasn't been encountered yet.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;shows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;queryResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;shows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;queryResults&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The resulting config file is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.0.2&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Ratings&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;website'&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;
&lt;span class="na"&gt;x-walder-datasources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;https://data.betweenourworlds.org/latest&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;https://pieterheyvaert.com/data/example/walder/ratings.ttl&lt;/span&gt;
&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;/ratings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Returns a list of all tv shows and their ratings.&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;200&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List of all tv shows and their ratings.&lt;/span&gt;
          &lt;span class="s"&gt;x-walder-input-text/html&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ratings.handlebars&lt;/span&gt;
      &lt;span class="na"&gt;x-walder-query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;graphql-query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="s"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;id @single&lt;/span&gt;
            &lt;span class="s"&gt;title @single&lt;/span&gt;
            &lt;span class="s"&gt;review @single {&lt;/span&gt;
              &lt;span class="s"&gt;rating @single {&lt;/span&gt;
                &lt;span class="s"&gt;value @single&lt;/span&gt;
              &lt;span class="s"&gt;}&lt;/span&gt;
            &lt;span class="s"&gt;}&lt;/span&gt;
          &lt;span class="s"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;json-ld-context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="s"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"schema": "http://schema.org/",&lt;/span&gt;
            &lt;span class="s"&gt;"review": "schema:review",&lt;/span&gt;
            &lt;span class="s"&gt;"rating": "schema:reviewRating",&lt;/span&gt;
            &lt;span class="s"&gt;"value": "schema:ratingValue",&lt;/span&gt;
            &lt;span class="s"&gt;"title": "schema:name"&lt;/span&gt;
          &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;x-walder-postprocessing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;keepSingleENTitle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;utils.js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you run Walder again and browse to &lt;code&gt;http://localhost:3000/ratings&lt;/code&gt; we see every show only once with an English title:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SN-7l00n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/egugmhlfjmjxyujyz1f5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SN-7l00n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/egugmhlfjmjxyujyz1f5.png" alt="List of tv shows together with their ratings, without duplicates."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can find the files we created up until this point &lt;a href="https://github.com/pheyvaer/walder-tutorial/tree/master/part-2"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. How to add parameters to paths
&lt;/h2&gt;

&lt;p&gt;The second Web page of our website lists all tv shows with "x" star ratings at the path &lt;code&gt;/rating/{x}&lt;/code&gt;, where "x" is a value between 1 and 5. The corresponding path entry is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;/rating/{value}&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Returns a list of all tv shows with a given rating.&lt;/span&gt;
  &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;value&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;integer&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Rating of the tv shows.&lt;/span&gt;
  &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;200&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List of all tv shows with a given rating.&lt;/span&gt;
      &lt;span class="s"&gt;x-walder-input-text/html&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;selected-rating.handlebars&lt;/span&gt;
  &lt;span class="na"&gt;x-walder-query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;graphql-query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;id @single&lt;/span&gt;
        &lt;span class="s"&gt;title @single&lt;/span&gt;
        &lt;span class="s"&gt;review @single {&lt;/span&gt;
          &lt;span class="s"&gt;rating(value: $value)&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;json-ld-context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"schema": "http://schema.org/",&lt;/span&gt;
        &lt;span class="s"&gt;"review": "schema:review",&lt;/span&gt;
        &lt;span class="s"&gt;"rating": "schema:reviewRating",&lt;/span&gt;
        &lt;span class="s"&gt;"value": "schema:ratingValue",&lt;/span&gt;
        &lt;span class="s"&gt;"title": "schema:name"&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;x-walder-postprocessing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;keepSingleENTitle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;utils.js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The differences with the first path entry are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the addition of a &lt;code&gt;parameters&lt;/code&gt; section which contains all information about the supported parameters, and&lt;/li&gt;
&lt;li&gt;the use of a parameter value in the GraphQL-LD query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;parameters&lt;/code&gt; section contains an entry for every parameter. This path entry only has one parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;value&lt;/span&gt;
&lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;integer&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Rating of the tv shows.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;in&lt;/code&gt; defines where the parameter can be found. In this case that is the path.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; defines the name of the parameter. In this case the name is &lt;code&gt;value&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;required&lt;/code&gt; defines whether a parameter is required or not. In this case the parameter is required.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;schema&lt;/code&gt; defines the schema of the parameter. In this case we only make us of the &lt;code&gt;type&lt;/code&gt; key, which states the data type of the parameter. The type is &lt;code&gt;integer&lt;/code&gt;, because a rating is an integer between 1 and 5.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;description&lt;/code&gt; contains the description of the parameter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To use the value of the parameter in our query, we use &lt;code&gt;$value&lt;/code&gt;. That is &lt;code&gt;$&lt;/code&gt; and the name of the parameter. Our query is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  id @single
  title @single
  review @single {
    rating(value: $value)
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;rating(value: $value)&lt;/code&gt; means that we only want the ratings that have values that match &lt;code&gt;$value&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The view for this path is in the file &lt;code&gt;selected-rating.handlebars&lt;/code&gt; with this content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight handlebars"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;TV shows&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;{{#&lt;/span&gt;&lt;span class="nn"&gt;each&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;{{/&lt;/span&gt;&lt;span class="nn"&gt;each&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The only difference with the previous view is that in this one the ratings are not shown, because they are the same for all tv shows on the Web page and determined by the parameter in the path.&lt;/p&gt;

&lt;p&gt;Do the following steps to update our files:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add the aforementioned path in &lt;code&gt;config.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add the file &lt;code&gt;selected-rating.handlebars&lt;/code&gt; to the folder &lt;code&gt;views&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you run Walder again and browse to &lt;code&gt;http://localhost:3000/rating/2&lt;/code&gt; we only see one show with a two-star rating:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R2rwcTsm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sdj0kcaofnhuvklx3rxx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R2rwcTsm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sdj0kcaofnhuvklx3rxx.png" alt="List of tv shows that have a 2-star rating, without dupblicates."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can find all files &lt;a href="https://github.com/pheyvaer/walder-tutorial/tree/master/part-3"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Bonus: content negotiation
&lt;/h2&gt;

&lt;p&gt;Besides offering HTML pages, Walder can also offer RDF and JSON-LD. This is supported through the use of &lt;a href="https://en.wikipedia.org/wiki/Content_negotiation"&gt;content negotiation&lt;/a&gt;. You use the &lt;code&gt;Accept&lt;/code&gt; header to achieve this. Let's have a look at the following cURL commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -H 'accept: text/turtle' http://localhost:3000/rating/2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This command returns all shows with a two-star rating in the Turtle serialization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight turtle"&gt;&lt;code&gt;&lt;span class="nl"&gt;&amp;lt;https://betweenourworlds.org/anime/bakuman&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;&amp;lt;http://schema.org/name&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Bakuman."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;&amp;lt;http://schema.org/review&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;_:&lt;/span&gt;&lt;span class="n"&gt;b1_b0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nn"&gt;_:&lt;/span&gt;&lt;span class="n"&gt;b1_b0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;&amp;lt;http://schema.org/reviewRating&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"bc_0_n3-71"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -H 'accept: application/ld+json' http://localhost:3000/rating/2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This command returns the same data, but uses JSON-LD instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http://schema.org/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"review"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"schema:review"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"schema:reviewRating"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"schema:ratingValue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"schema:name"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@graph"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Bakuman."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"review"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"rating"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"bc_0_n3-71"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"https://betweenourworlds.org/anime/bakuman"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -H 'accept: text/html' http://localhost:3000/rating/2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This command returns the HTML version of the data, which is what is shown in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Wrapping up
&lt;/h2&gt;

&lt;p&gt;Congratulations! You have created your first website using Walder that&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;offers two Web pages&lt;/li&gt;
&lt;li&gt;is based on two different, decentralized knowledge graphs&lt;/li&gt;
&lt;li&gt;supports content negotiation to return the data in different formats and serializations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nice work! We hope you now feel like you have a decent grasp on how Walder works.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. More information
&lt;/h2&gt;

&lt;p&gt;You can find more information in the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/KNowledgeOnWebScale/walder"&gt;Walder repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rubensworks/GraphQL-LD.js"&gt;GraphQL-LD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://swagger.io/docs/specification/basic-structure/"&gt;OpenAPI 3.0 specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>decentralized</category>
      <category>knowledgegraph</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Generate RDF from an XML file</title>
      <dc:creator>Pieter Heyvaert</dc:creator>
      <pubDate>Wed, 22 Apr 2020 06:57:26 +0000</pubDate>
      <link>https://dev.to/rmlio/generate-rdf-from-an-xml-file-5b33</link>
      <guid>https://dev.to/rmlio/generate-rdf-from-an-xml-file-5b33</guid>
      <description>&lt;p&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Before we start the tutorial&lt;/li&gt;
&lt;li&gt;Example&lt;/li&gt;
&lt;li&gt;What rules are needed&lt;/li&gt;
&lt;li&gt;How to start a document with RML rules&lt;/li&gt;
&lt;li&gt;What data to use&lt;/li&gt;
&lt;li&gt;How to generate subjects&lt;/li&gt;
&lt;li&gt;How to generate predicates and objects&lt;/li&gt;
&lt;li&gt;Complete Turtle document with RML rules&lt;/li&gt;
&lt;li&gt;Wrapping up&lt;/li&gt;
&lt;li&gt;More information&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id="before-we-start"&gt;1 Before we start the tutorial&lt;/h1&gt;

&lt;h2 id="what-you-learn"&gt;1.1 What you learn&lt;/h2&gt;

&lt;p&gt;At the end of the tutorial you will be able to generate RDF from an XML file using RML rules.&lt;/p&gt;

&lt;h2 id="what-you-need"&gt;What you need&lt;/h2&gt;

&lt;p&gt;We assume that you understand &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/TR/rdf11-concepts/"&gt;RDF&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/TR/xml/"&gt;XML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;vocabularies and ontologies, such as classes, properties, and datatypes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="how-to-use"&gt;How you use the tutorial&lt;/h2&gt;

&lt;p&gt;There are two ways to complete this tutorial: you read the explanations and either&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read the examples.&lt;/li&gt;
&lt;li&gt;Try out the examples yourself by writing and executing RML rules on your computer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the second option you need a tool that executes RML rules. Suggestions are the &lt;a href="https://github.com/RMLio/rmlmapper-java"&gt;RMLMapper&lt;/a&gt; and the &lt;a href="https://github.com/RMLio/RMLStreamer"&gt;RMLStreamer&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id="example"&gt;2 Example&lt;/h1&gt;

&lt;p&gt;Consider the following XML file called "&lt;a href="https://rml.io/docs/rml/tutorials/xml/characters.xml"&gt;characters.xml&lt;/a&gt;":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;characters&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;character&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;firstname&amp;gt;&lt;/span&gt;Ash&lt;span class="nt"&gt;&amp;lt;/firstname&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;lastname&amp;gt;&lt;/span&gt;Ketchum&lt;span class="nt"&gt;&amp;lt;/lastname&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;hair&amp;gt;&lt;/span&gt;black&lt;span class="nt"&gt;&amp;lt;/hair&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/character&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;character&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;firstname&amp;gt;&lt;/span&gt;Misty&lt;span class="nt"&gt;&amp;lt;/firstname&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;hair&amp;gt;&lt;/span&gt;orange&lt;span class="nt"&gt;&amp;lt;/hair&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/character&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/characters&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It contains the information about two different characters. The id, first name, last name, and hair color are included. The latter two are optional. We want to annotate every character and generate the corresponding RDF triples.&lt;/p&gt;

&lt;p&gt;For example, consider the character described by the first XML element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;character&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;firstname&amp;gt;&lt;/span&gt;Ash&lt;span class="nt"&gt;&amp;lt;/firstname&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;lastname&amp;gt;&lt;/span&gt;Ketchum&lt;span class="nt"&gt;&amp;lt;/lastname&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;hair&amp;gt;&lt;/span&gt;black&lt;span class="nt"&gt;&amp;lt;/hair&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/character&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We want to generate the corresponding RDF triples for this element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix schema: &amp;lt;http://schema.org/&amp;gt; .
@prefix dbo: &amp;lt;http://dbpedia.org/ontology/&amp;gt; .
@prefix character: &amp;lt;http://example.org/character/&amp;gt; .

character:0 a schema:Person;
  schema:givenName "Ash";
  schema:lastName "Ketchum";
  dbo:hairColor "black".
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the following sections we explain &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;what rules you need to generate these triples, and &lt;/li&gt;
&lt;li&gt;how you write them using RML.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id="what-rules"&gt;3 What rules are needed&lt;/h1&gt;

&lt;p&gt;Two sets of rules are needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rules that describe the XML file&lt;/li&gt;
&lt;li&gt;rules that define how the RDF terms are generated from the XML file, and how these terms are used to generate triples.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our example we need rules that define that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The IRI representing a character is generated by concatenating &lt;code&gt;http://example.org/character/&lt;/code&gt; with the character's id.&lt;/li&gt;
&lt;li&gt;This IRI is used as subject of the triples.&lt;/li&gt;
&lt;li&gt;A character is annotated with the class &lt;a href="http://schema.org/Person"&gt;&lt;code&gt;schema:Person&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The first name is annotated with the property &lt;a href="http://schema.org/givenName"&gt;&lt;code&gt;schema:givenName&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The last name is annotated with the property &lt;a href="http://schema.org/lastName"&gt;&lt;code&gt;schema:lastName&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The hair color is annotated with the property &lt;a href="http://schema.org/hairColor"&gt;&lt;code&gt;dbo:hairColor&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id="how-to-start"&gt;4 How to start a document with RML rules&lt;/h1&gt;

&lt;p&gt;We write the RML rules in a &lt;a href="https://www.w3.org/TR/turtle/"&gt;Turtle&lt;/a&gt; document. RML rules are RDF themselves.&lt;/p&gt;

&lt;p&gt;We add the following prefixes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prefix&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;RML ontology&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The R2RML ontology, which is extended by RML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ql&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The Query Language vocabulary, which is used together with RML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rdf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The RDF Concepts Vocabulary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;empty&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;The prefix used for our RML rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;schema&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The schema.org vocabulary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dbo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The DBpedia ontology&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last two are added because they are used for the classes and properties.&lt;/p&gt;

&lt;p&gt;The prefixes are added in Turtle like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix rml: &amp;lt;http://semweb.mmlab.be/ns/rml#&amp;gt; .
@prefix rr: &amp;lt;http://www.w3.org/ns/r2rml#&amp;gt; .
@prefix ql: &amp;lt;http://semweb.mmlab.be/ns/ql#&amp;gt; .
@prefix rdf: &amp;lt;http://www.w3.org/1999/02/22-rdf-syntax-ns#&amp;gt; .
@prefix : &amp;lt;http://example.org/rules/&amp;gt; .
@prefix schema: &amp;lt;http://schema.org/&amp;gt; .
@prefix dbo: &amp;lt;http://dbpedia.org/ontology/&amp;gt; .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1 id="what-data"&gt;5 What data to use&lt;/h1&gt;

&lt;p&gt;In our example the data of the characters is stored in an XML file. We add the following RML rules that define what XML file is used and how we iterate over the elements in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:TriplesMap a rr:TriplesMap;
  rml:logicalSource [
    rml:source "characters.xml";
    rml:referenceFormulation ql:XPath;
    rml:iterator "/characters/character"
  ].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The different rules work as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:TriplesMap a rr:TriplesMap;&lt;/code&gt; 
defines the &lt;a href="https://www.w3.org/TR/r2rml/#dfn-triples-map"&gt;Triples Map&lt;/a&gt;  that groups all rules for the characters.&lt;/li&gt;
&lt;li&gt;The blank node of &lt;code&gt;:TriplesMap rml:logicalSource [ ... ]&lt;/code&gt; contains
all rules about the XML file. The class of the blank node is implicitly of the class &lt;a href="https://rml.io/specs/rml/#logical-source"&gt;&lt;code&gt;rml:LogicalSource&lt;/code&gt;&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[rml:source "characters.xml"]&lt;/code&gt; says that we access the XML file &lt;code&gt;characters.xml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[rml:referenceFormulation ql:XPath]&lt;/code&gt; says that we use XPath the access the data in the XML file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[rml:iterator "/characters/character"]&lt;/code&gt; says that we iterate over all elements  that match the XPath expression &lt;code&gt;/characters/character&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id="generate-subjects"&gt;6 How to generate subjects&lt;/h1&gt;

&lt;p&gt;We add the following rules that define how the subject IRI of a character is generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:TriplesMap rr:subjectMap [
  rr:template "http://example.org/character/{@id}"
].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The different rules work as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:TriplesMap rr:subjectMap [ ... ]&lt;/code&gt; contains all the rules about the subject of a triple. The class of the blank node is implicitly of the class &lt;a href="https://www.w3.org/TR/r2rml/#dfn-subject-map"&gt;&lt;code&gt;rr:SubjectMap&lt;/code&gt;&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[rr:template "http://example.org/character/{@id}"]&lt;/code&gt; says that the IRI of the subject is generated by concatenating &lt;code&gt;http://example.org/character/&lt;/code&gt; with the attribute &lt;code&gt;id&lt;/code&gt; of the character element.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id="generate-pos"&gt;7 How to generate predicates and objects&lt;/h1&gt;

&lt;h2 id="class"&gt;7.1 How to annotate with a class&lt;/h2&gt;

&lt;p&gt;In our example we need to annotate every character with the class &lt;code&gt;schema:Person&lt;/code&gt;. We add the following RML rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:TriplesMap rr:predicateObjectMap [
  rr:predicate rdf:type;
  rr:constant schema:Person
].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The different rules work as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:TriplesMap rr:predicateObjectMap [ ... ]&lt;/code&gt; contains all the rules about a specific predicate of a triple. The class of the blank node is implicitly of the class &lt;a href="https://www.w3.org/TR/r2rml/#dfn-predicate-object-map"&gt;&lt;code&gt;rr:PredicateObjectMap&lt;/code&gt;&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[rr:predicate rdf:type]&lt;/code&gt; says that we use the predicate &lt;code&gt;rdf:type&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[rr:objectMap [ ... ]]&lt;/code&gt; contains all the rules about the object of a triple. The class of the blank node is implicitly of the class &lt;a href="https://www.w3.org/TR/r2rml/#dfn-object-map"&gt;&lt;code&gt;rr:ObjectMap&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[rr:constant schema:Person]&lt;/code&gt; says that the object of the triple is &lt;code&gt;schema:Person&lt;/code&gt; for every character.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Putting all rules we have so far together results in&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix rml: &amp;lt;http://semweb.mmlab.be/ns/rml#&amp;gt; .
@prefix rr: &amp;lt;http://www.w3.org/ns/r2rml#&amp;gt; .
@prefix ql: &amp;lt;http://semweb.mmlab.be/ns/ql#&amp;gt; .
@prefix rdf: &amp;lt;http://www.w3.org/1999/02/22-rdf-syntax-ns#&amp;gt; .
@prefix : &amp;lt;http://example.org/rules/&amp;gt; .
@prefix schema: &amp;lt;http://schema.org/&amp;gt; .
@prefix dbo: &amp;lt;http://dbpedia.org/ontology/&amp;gt; .

:TriplesMap a rr:TriplesMap;
  rml:logicalSource [
    rml:source "characters.xml";
    rml:referenceFormulation ql:XPath;
    rml:iterator "/characters/character"
  ].

:TriplesMap rr:subjectMap [
  rr:template "http://example.org/character/{@id}"
].

:TriplesMap rr:predicateObjectMap [
  rr:predicate rdf:type;
  rr:objectMap [
    rr:constant schema:Person
  ]
].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can download the Turtle file &lt;a href="https://rml.io/docs/rml/tutorials/xml/rules-1.rml.ttl"&gt;here&lt;/a&gt;. If we execute these rules, the following triples are generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix schema: &amp;lt;http://schema.org/&amp;gt; .

&amp;lt;http://example.org/character/0&amp;gt; a schema:Person .
&amp;lt;http://example.org/character/1&amp;gt; a schema:Person .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Two triples are generated: one for each character. There is a unique subject IRI for each character and each character is annotated with the class &lt;code&gt;schema:Person&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id="property"&gt;7.2 How to annotate with a property&lt;/h2&gt;
 

&lt;p&gt;In our example we need to annotate the values in the tags &lt;code&gt;firstname&lt;/code&gt; &lt;br&gt;
with the property &lt;code&gt;schema:givenName&lt;/code&gt;. We add the following rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:TriplesMap rr:predicateObjectMap [
  rr:predicate schema:givenName;
  rr:objectMap [
    rml:reference "firstname"
  ]
].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The rules are different from when annotating with a class: &lt;code&gt;rml:reference&lt;/code&gt; is used instead of &lt;code&gt;rr:constant&lt;/code&gt; because the object is not the same for every character. More specific, &lt;code&gt;[rml:reference "firstname"]&lt;/code&gt; says that the data in the tag &lt;code&gt;firstname&lt;/code&gt; is used for the object.&lt;/p&gt;

&lt;p&gt;Putting all rules we have so far together results in&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix rml: &amp;lt;http://semweb.mmlab.be/ns/rml#&amp;gt; .
@prefix rr: &amp;lt;http://www.w3.org/ns/r2rml#&amp;gt; .
@prefix ql: &amp;lt;http://semweb.mmlab.be/ns/ql#&amp;gt; .
@prefix rdf: &amp;lt;http://www.w3.org/1999/02/22-rdf-syntax-ns#&amp;gt; .
@prefix : &amp;lt;http://example.org/rules/&amp;gt; .
@prefix schema: &amp;lt;http://schema.org/&amp;gt; .
@prefix dbo: &amp;lt;http://dbpedia.org/ontology/&amp;gt; .

:TriplesMap a rr:TriplesMap;
  rml:logicalSource [
    rml:source "characters.xml";
    rml:referenceFormulation ql:XPath;
    rml:iterator "/characters/character"
  ].

:TriplesMap rr:subjectMap [
  rr:template "http://example.org/character/{@id}"
].

:TriplesMap rr:predicateObjectMap [
  rr:predicate rdf:type;
  rr:objectMap [
   rr:constant schema:Person
 ]
].

:TriplesMap rr:predicateObjectMap [
  rr:predicate schema:givenName;
  rr:objectMap [
    rml:reference "firstname"
  ]
].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can download the Turtle file &lt;a href="https://rml.io/docs/rml/tutorials/xml/rules-2.rml.ttl"&gt;here&lt;/a&gt;. If we execute these rules, the following triples are generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix schema: &amp;lt;http://schema.org/&amp;gt; .

&amp;lt;http://example.org/character/0&amp;gt; a schema:Person;
  schema:givenName "Ash" .

&amp;lt;http://example.org/character/1&amp;gt; a schema:Person;
  schema:givenName "Misty" .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Two triples are added: one for the first name of each character.&lt;/p&gt;

&lt;p&gt;We add the following rules to annotate the last name and the hair color in the same way as the first name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:TriplesMap rr:predicateObjectMap [
  rr:predicate schema:lastName;
  rr:objectMap [
    rml:reference "lastname"
  ]
].

:TriplesMap rr:predicateObjectMap [
  rr:predicate dbo:hairColor;
  rr:objectMap [
    rml:reference "hair"
  ]
].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1 id="complete-turtle"&gt;8 Complete Turtle document with RML rules&lt;/h1&gt;

&lt;p&gt;The complete Turtle document with RML rules is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix rml: &amp;lt;http://semweb.mmlab.be/ns/rml#&amp;gt; .
@prefix rr: &amp;lt;http://www.w3.org/ns/r2rml#&amp;gt; .
@prefix ql: &amp;lt;http://semweb.mmlab.be/ns/ql#&amp;gt; .
@prefix rdf: &amp;lt;http://www.w3.org/1999/02/22-rdf-syntax-ns#&amp;gt; .
@prefix : &amp;lt;http://example.org/rules/&amp;gt; .
@prefix schema: &amp;lt;http://schema.org/&amp;gt; .
@prefix dbo: &amp;lt;http://dbpedia.org/ontology/&amp;gt; .

:TriplesMap a rr:TriplesMap;
  rml:logicalSource [
    rml:source "characters.xml";
    rml:referenceFormulation ql:XPath;
    rml:iterator "/characters/character"
  ].

:TriplesMap rr:subjectMap [
  rr:template "http://example.org/character/{@id}"
].

:TriplesMap rr:predicateObjectMap [
  rr:predicate rdf:type;
  rr:objectMap [
   rr:constant schema:Person
 ]
].

:TriplesMap rr:predicateObjectMap [
  rr:predicate schema:givenName;
  rr:objectMap [
    rml:reference "firstname"
  ]
].

:TriplesMap rr:predicateObjectMap [
  rr:predicate schema:lastName;
  rr:objectMap [
    rml:reference "lastname"
  ]
].

:TriplesMap rr:predicateObjectMap [
  rr:predicate dbo:hairColor;
  rr:objectMap [
    rml:reference "hair"
  ]
].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can download the Turtle file &lt;a href="https://rml.io/docs/rml/tutorials/xml/rules-3.rml.ttl"&gt;here&lt;/a&gt;. If we execute these rules, the final triples are generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix dbo: &amp;lt;http://dbpedia.org/ontology/&amp;gt; .
@prefix schema: &amp;lt;http://schema.org/&amp;gt; .

&amp;lt;http://example.org/character/0&amp;gt; a schema:Person;
  dbo:hairColor "black";
  schema:givenName "Ash";
  schema:lastName "Ketchum" .

&amp;lt;http://example.org/character/1&amp;gt; a schema:Person;
  dbo:hairColor "orange";
  schema:givenName "Misty" .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1 id="wrapping-up"&gt;9 Wrapping up&lt;/h1&gt;

&lt;p&gt;Congratulations! You have created your own RML rules that generate RDF from data in an XML file. Nice work! We hope you now feel like you have a decent grasp on how RML rules work.&lt;/p&gt;

&lt;h1 id="more-info"&gt;10 More information&lt;/h1&gt;

&lt;p&gt;You can find more information about RML in its &lt;a href="https://rml.io/specs/rml"&gt;specification&lt;/a&gt;. There is also a human readable text-based representation available for RML rules called &lt;a href="https://w3id.org/yarrrml"&gt;YARRRML&lt;/a&gt;. It is a subset of &lt;a href="https://yaml.org/"&gt;YAML&lt;/a&gt;, a widely used data serialization language designed to be human-friendly.&lt;/p&gt;

&lt;p&gt;If you have any questions or remarks, don't hesitate to contact me via &lt;a href="mailto:pieter.heyvaert@ugent.be"&gt;email&lt;/a&gt; or via &lt;a href="https://twitter.com/HeyPieter"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>xml</category>
      <category>rdf</category>
      <category>rml</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Generate RDF from TSV file</title>
      <dc:creator>Pieter Heyvaert</dc:creator>
      <pubDate>Tue, 07 Apr 2020 13:55:54 +0000</pubDate>
      <link>https://dev.to/rmlio/tutorial-generate-rdf-from-tsv-file-18mf</link>
      <guid>https://dev.to/rmlio/tutorial-generate-rdf-from-tsv-file-18mf</guid>
      <description>&lt;p&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Before we start the tutorial&lt;/li&gt;
&lt;li&gt;Example&lt;/li&gt;
&lt;li&gt;What rules are needed&lt;/li&gt;
&lt;li&gt;How to start a document with RML rules&lt;/li&gt;
&lt;li&gt;What data to use&lt;/li&gt;
&lt;li&gt;How to generate subjects&lt;/li&gt;
&lt;li&gt;How to generate predicates and objects&lt;/li&gt;
&lt;li&gt;Complete Turtle document with RML rules&lt;/li&gt;
&lt;li&gt;Wrapping up&lt;/li&gt;
&lt;li&gt;More information&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  1 Before we start the tutorial &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1.1 What you learn
&lt;/h2&gt;

&lt;p&gt;At the end of the tutorial you will be able to generate RDF from a TSV file using RML rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;p&gt;We assume that you understand &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/TR/rdf11-concepts/"&gt;RDF&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iana.org/assignments/media-types/text/tab-separated-values"&gt;TSV&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;vocabularies and ontologies, such as classes, properties, and datatypes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How you use the tutorial
&lt;/h2&gt;

&lt;p&gt;There are two ways to complete this tutorial: you read the explanations and either&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read the examples.&lt;/li&gt;
&lt;li&gt;Try out the examples yourself by writing and executing RML rules on your computer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the second option you need a tool that executes RML rules. &lt;br&gt;
Suggestions are the &lt;a href="https://github.com/RMLio/rmlmapper-java"&gt;RMLMapper&lt;/a&gt; and the &lt;a href="https://github.com/RMLio/RMLStreamer"&gt;RMLStreamer&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;
  
  
  2 Example &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Consider the following TSV file called "&lt;a href="https://rml.io/docs/rml/tutorials/tsv/characters.tsv"&gt;characters.tsv&lt;/a&gt;":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id firstname lastname hair
0 Ash Ketchum black
1 Misty  orange
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It contains the information about two different characters. The id, first name, last name, and hair color are included. The latter two are optional. We want to annotate every character and generate the corresponding RDF triples.&lt;/p&gt;

&lt;p&gt;For example, consider the character described by the first row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 Ash Ketchum black
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We want to generate the corresponding RDF triples for this row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix schema: &amp;lt;http://schema.org/&amp;gt; .
@prefix dbo: &amp;lt;http://dbpedia.org/ontology/&amp;gt; .
@prefix character: &amp;lt;http://example.org/character/&amp;gt; .

character:0 a schema:Person;
  schema:givenName "Ash";
  schema:lastName "Ketchum";
  dbo:hairColor "black".
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the following sections we explain &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;what rules you need to generate these triples, and &lt;/li&gt;
&lt;li&gt;how you write them using RML.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  3 What rules are needed &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Two sets of rules are needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rules that describe the TSV file&lt;/li&gt;
&lt;li&gt;rules that define how the RDF terms are generated from the TSV file, and how these terms are used to generate triples.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our example we need rules that define that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The IRI representing a character is generated by concatenating &lt;code&gt;http://example.org/character/&lt;/code&gt; 
with the character's id.&lt;/li&gt;
&lt;li&gt;This IRI is used as subject of the triples.&lt;/li&gt;
&lt;li&gt;A character is annotated with the class &lt;a href="http://schema.org/Person"&gt;&lt;code&gt;schema:Person&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The first name is annotated with the property &lt;a href="http://schema.org/givenName"&gt;&lt;code&gt;schema:givenName&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The last name is annotated with the property &lt;a href="http://schema.org/lastName"&gt;&lt;code&gt;schema:lastName&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The hair color is annotated with the property &lt;a href="http://schema.org/hairColor"&gt;&lt;code&gt;dbo:hairColor&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  4 How to start a document with RML rules &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;We write the RML rules in a &lt;a href="https://www.w3.org/TR/turtle/"&gt;Turtle&lt;/a&gt; document. RML rules are RDF themselves.&lt;/p&gt;

&lt;p&gt;We add the following prefixes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prefix&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;RML ontology&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The R2RML ontology, which is extended by RML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ql&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The Query Language vocabulary, which is used together with RML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;csvw&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The CSVW Vocabulary, which is used to describe the TSV file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rdf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The RDF Concepts Vocabulary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;empty&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;The prefix used for our RML rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;schema&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The schema.org vocabulary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dbo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The DBpedia ontology&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;{.table .table-responsive}&lt;/p&gt;

&lt;p&gt;The last two are added because they are used for the classes and properties.&lt;/p&gt;

&lt;p&gt;The prefixes are added in Turtle like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix rml: &amp;lt;http://semweb.mmlab.be/ns/rml#&amp;gt; .
@prefix rr: &amp;lt;http://www.w3.org/ns/r2rml#&amp;gt; .
@prefix ql: &amp;lt;http://semweb.mmlab.be/ns/ql#&amp;gt; .
@prefix csvw: &amp;lt;http://www.w3.org/ns/csvw#&amp;gt; .
@prefix rdf: &amp;lt;http://www.w3.org/1999/02/22-rdf-syntax-ns#&amp;gt; .
@prefix : &amp;lt;http://example.org/rules/&amp;gt; .
@prefix schema: &amp;lt;http://schema.org/&amp;gt; .
@prefix dbo: &amp;lt;http://dbpedia.org/ontology/&amp;gt; .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  5 What data to use &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;In our example the data of the characters is stored in a TSV file. We add the following RML rules that define what TSV file is used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:TriplesMap a rr:TriplesMap;
  rml:logicalSource [
    rml:source [
      a csvw:Table;
      csvw:url "characters.tsv";
      csvw:dialect [
        a csvw:Dialect;
        csvw:delimiter "\t"
      ]
    ];
    rml:referenceFormulation ql:CSV
  ].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The different rules work as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:TriplesMap a rr:TriplesMap;&lt;/code&gt; 
defines the &lt;a href="https://www.w3.org/TR/r2rml/#dfn-triples-map"&gt;Triples Map&lt;/a&gt; that groups all rules for the characters.&lt;/li&gt;
&lt;li&gt;The blank node of &lt;code&gt;:TriplesMap rml:logicalSource [ ... ]&lt;/code&gt; contains all rules about the TSV file and how to access the data in the file. The class of the blank node is implicitly of the class &lt;a href="https://rml.io/specs/rml/#logical-source"&gt;&lt;code&gt;rml:LogicalSource&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The blank node in &lt;code&gt;[rml:source [ ... ]&lt;/code&gt; contains all rules about the TSV file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[csvw:url "characters.tsv"]&lt;/code&gt; says that we access the TSV file &lt;code&gt;characters.tsv&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[csvw:dialect [csvw:delimiter "\t"]]&lt;/code&gt; says that the delimiter used in this file is a tab (&lt;code&gt;\t&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[rml:referenceFormulation ql:CSV]&lt;/code&gt; says that we use the column names to access the data in the TSV file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that we access a TSV file as if it is a CSV file because we consider it a CSV file with a different delimiter.&lt;/p&gt;

&lt;h1&gt;
  
  
  6 How to generate subjects &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;We add the following rules that define how the subject IRI of a character is generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:TriplesMap rr:subjectMap [
  rr:template "http://example.org/character/{id}"
].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The different rules work as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:TriplesMap rr:subjectMap [ ... ]&lt;/code&gt; contains all the rules about the subject of a triple. The class of the blank node is implicitly of the class &lt;a href="https://www.w3.org/TR/r2rml/#dfn-subject-map"&gt;&lt;code&gt;rr:SubjectMap&lt;/code&gt;&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[rr:template "http://example.org/character/{id}"]&lt;/code&gt; says that the IRI of the subject is generated by concatenating &lt;code&gt;http://example.org/character/&lt;/code&gt; with the value in the column &lt;code&gt;id&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  7 How to generate predicates and objects &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;h2&gt;
  
  
  7.1 How to annotate with a class
&lt;/h2&gt;

&lt;p&gt;In our example we need to annotate every character with the class &lt;code&gt;schema:Person&lt;/code&gt;. We add the following RML rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:TriplesMap rr:predicateObjectMap [
  rr:predicate rdf:type;
  rr:constant schema:Person
].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The different rules work as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:TriplesMap rr:predicateObjectMap [ ... ]&lt;/code&gt; contains all the rules about a specific predicate of a triple.
The class of the blank node is implicitly of the class &lt;a href="https://www.w3.org/TR/r2rml/#dfn-predicate-object-map"&gt;&lt;code&gt;rr:PredicateObjectMap&lt;/code&gt;&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[rr:predicate rdf:type]&lt;/code&gt; says that we use the predicate &lt;code&gt;rdf:type&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[rr:objectMap [ ... ]]&lt;/code&gt; contains all the rules about the object of a triple.
The class of the blank node is implicitly of the class &lt;a href="https://www.w3.org/TR/r2rml/#dfn-object-map"&gt;&lt;code&gt;rr:ObjectMap&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[rr:constant schema:Person]&lt;/code&gt; says that the object of the triple is &lt;code&gt;schema:Person&lt;/code&gt; for every character.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Putting all rules we have so far together results in&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix rml: &amp;lt;http://semweb.mmlab.be/ns/rml#&amp;gt; .
@prefix rr: &amp;lt;http://www.w3.org/ns/r2rml#&amp;gt; .
@prefix ql: &amp;lt;http://semweb.mmlab.be/ns/ql#&amp;gt; .
@prefix csvw: &amp;lt;http://www.w3.org/ns/csvw#&amp;gt; .
@prefix rdf: &amp;lt;http://www.w3.org/1999/02/22-rdf-syntax-ns#&amp;gt; .
@prefix : &amp;lt;http://example.org/rules/&amp;gt; .
@prefix schema: &amp;lt;http://schema.org/&amp;gt; .
@prefix dbo: &amp;lt;http://dbpedia.org/ontology/&amp;gt; .

:TriplesMap a rr:TriplesMap;
  rml:logicalSource [
    rml:source [
      a csvw:Table;
      csvw:url "characters.tsv";
      csvw:dialect [ 
        a csvw:Dialect;
        csvw:delimiter "\t"
      ]
    ];
    rml:referenceFormulation ql:CSV
  ].

:TriplesMap rr:subjectMap [
  rr:template "http://example.org/character/{id}"
].

:TriplesMap rr:predicateObjectMap [
  rr:predicate rdf:type;
  rr:objectMap [
    rr:constant schema:Person
  ]
].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can download the Turtle file &lt;a href="https://rml.io/docs/rml/tutorials/tsv/rules-1.rml.ttl"&gt;here&lt;/a&gt;. If we execute these rules, the following triples are generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix schema: &amp;lt;http://schema.org/&amp;gt; .

&amp;lt;http://example.org/character/0&amp;gt; a schema:Person .
&amp;lt;http://example.org/character/1&amp;gt; a schema:Person .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Two triples are generated: one for each character. There is a unique subject IRI for each character and each character is annotated with the class &lt;code&gt;schema:Person&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  7.2 How to annotate with a property
&lt;/h2&gt;

&lt;p&gt;In our example we need to annotate the values in the column &lt;code&gt;firstname&lt;/code&gt; with the property &lt;code&gt;schema:givenName&lt;/code&gt;. We add the following rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:TriplesMap rr:predicateObjectMap [
  rr:predicate schema:givenName;
  rr:objectMap [
    rml:reference "firstname"
  ]
].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The rules are different from when annotating with a class: &lt;code&gt;rml:reference&lt;/code&gt; is used instead of &lt;code&gt;rr:constant&lt;/code&gt; because the object is not the same for every character. More specific, &lt;code&gt;[rml:reference "firstname"]&lt;/code&gt; says that the data in the column &lt;code&gt;firstname&lt;/code&gt; is used for the object.&lt;/p&gt;

&lt;p&gt;Putting all rules we have so far together results in&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix rml: &amp;lt;http://semweb.mmlab.be/ns/rml#&amp;gt; .
@prefix rr: &amp;lt;http://www.w3.org/ns/r2rml#&amp;gt; .
@prefix ql: &amp;lt;http://semweb.mmlab.be/ns/ql#&amp;gt; .
@prefix csvw: &amp;lt;http://www.w3.org/ns/csvw#&amp;gt; .
@prefix rdf: &amp;lt;http://www.w3.org/1999/02/22-rdf-syntax-ns#&amp;gt; .
@prefix : &amp;lt;http://example.org/rules/&amp;gt; .
@prefix schema: &amp;lt;http://schema.org/&amp;gt; .
@prefix dbo: &amp;lt;http://dbpedia.org/ontology/&amp;gt; .

:TriplesMap a rr:TriplesMap;
  rml:logicalSource [
    rml:source [
      a csvw:Table;
      csvw:url "characters.tsv";
      csvw:dialect [ 
        a csvw:Dialect;
        csvw:delimiter "\t"
      ]
    ];
    rml:referenceFormulation ql:CSV
  ].

:TriplesMap rr:subjectMap [
  rr:template "http://example.org/character/{id}"
].

:TriplesMap rr:predicateObjectMap [
  rr:predicate rdf:type;
  rr:objectMap [
   rr:constant schema:Person
 ]
].

:TriplesMap rr:predicateObjectMap [
  rr:predicate schema:givenName;
  rr:objectMap [
    rml:reference "firstname"
  ]
].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can download the Turtle file &lt;a href="https://rml.io/docs/rml/tutorials/tsv/rules-2.rml.ttl"&gt;here&lt;/a&gt;. If we execute these rules, the following triples are generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix schema: &amp;lt;http://schema.org/&amp;gt; .

&amp;lt;http://example.org/character/0&amp;gt; a schema:Person;
  schema:givenName "Ash" .

&amp;lt;http://example.org/character/1&amp;gt; a schema:Person;
  schema:givenName "Misty" .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Two triples are added: one for the first name of each character.&lt;/p&gt;

&lt;p&gt;We add the following rules to annotate the last name and the hair color in the same way as the first name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:TriplesMap rr:predicateObjectMap [
  rr:predicate schema:lastName;
  rr:objectMap [
    rml:reference "lastname"
  ]
].

:TriplesMap rr:predicateObjectMap [
  rr:predicate dbo:hairColor;
  rr:objectMap [
    rml:reference "hair"
  ]
].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  8 Complete Turtle document with RML rules &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;The complete Turtle document with RML rules is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix rml: &amp;lt;http://semweb.mmlab.be/ns/rml#&amp;gt; .
@prefix rr: &amp;lt;http://www.w3.org/ns/r2rml#&amp;gt; .
@prefix ql: &amp;lt;http://semweb.mmlab.be/ns/ql#&amp;gt; .
@prefix csvw: &amp;lt;http://www.w3.org/ns/csvw#&amp;gt; .
@prefix rdf: &amp;lt;http://www.w3.org/1999/02/22-rdf-syntax-ns#&amp;gt; .
@prefix : &amp;lt;http://example.org/rules/&amp;gt; .
@prefix schema: &amp;lt;http://schema.org/&amp;gt; .
@prefix dbo: &amp;lt;http://dbpedia.org/ontology/&amp;gt; .

:TriplesMap a rr:TriplesMap;
  rml:logicalSource [
    rml:source [
      a csvw:Table;
      csvw:url "characters.tsv";
      csvw:dialect [ 
        a csvw:Dialect;
        csvw:delimiter "\t"
      ]
    ];
    rml:referenceFormulation ql:CSV
  ].

:TriplesMap rr:subjectMap [
  rr:template "http://example.org/character/{id}"
].

:TriplesMap rr:predicateObjectMap [
  rr:predicate rdf:type;
  rr:objectMap [
   rr:constant schema:Person
 ]
].

:TriplesMap rr:predicateObjectMap [
  rr:predicate schema:givenName;
  rr:objectMap [
    rml:reference "firstname"
  ]
].

:TriplesMap rr:predicateObjectMap [
  rr:predicate schema:lastName;
  rr:objectMap [
    rml:reference "lastname"
  ]
].

:TriplesMap rr:predicateObjectMap [
  rr:predicate dbo:hairColor;
  rr:objectMap [
    rml:reference "hair"
  ]
].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can download the Turtle file &lt;a href="https://rml.io/docs/rml/tutorials/tsv/rules-3.rml.ttl"&gt;here&lt;/a&gt;. If we execute these rules, the final triples are generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix dbo: &amp;lt;http://dbpedia.org/ontology/&amp;gt; .
@prefix schema: &amp;lt;http://schema.org/&amp;gt; .

&amp;lt;http://example.org/character/0&amp;gt; a schema:Person;
  dbo:hairColor "black";
  schema:givenName "Ash";
  schema:lastName "Ketchum" .

&amp;lt;http://example.org/character/1&amp;gt; a schema:Person;
  dbo:hairColor "orange";
  schema:givenName "Misty";
  schema:lastName "" .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  9 Wrapping up &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Congratulations! You have created your own RML rules that generate RDF from data in a TSV file. Nice work! We hope you now feel like you have a decent grasp on how RML rules work.&lt;/p&gt;

&lt;h1&gt;
  
  
  10 More information &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;You can find more information about RML in its &lt;a href="https://dev.to/specs/rml"&gt;specification&lt;/a&gt;. There is also a human readable text-based representation available for RML rules called &lt;a href="https://w3id.org/yarrrml"&gt;YARRRML&lt;/a&gt;. It is a subset of &lt;a href="https://yaml.org/"&gt;YAML&lt;/a&gt;, a widely used data serialization language designed to be human-friendly.&lt;/p&gt;

&lt;p&gt;If you have any questions or remarks, don't hesitate to contact me via &lt;a href="mailto:pieter.heyvaert@ugent.be"&gt;email&lt;/a&gt; or via &lt;a href="https://twitter.com/HeyPieter"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>tsv</category>
      <category>rdf</category>
      <category>rml</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What is a knowledge graph - Pokémon edition</title>
      <dc:creator>Pieter Heyvaert</dc:creator>
      <pubDate>Thu, 16 Jan 2020 10:54:33 +0000</pubDate>
      <link>https://dev.to/heypieter/what-is-a-knowledge-graph-pokemon-edition-42hn</link>
      <guid>https://dev.to/heypieter/what-is-a-knowledge-graph-pokemon-edition-42hn</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally posted on &lt;a href="https://pieterheyvaert.com/blog/2019/12/27/kg-pkmn/" rel="noopener noreferrer"&gt;my website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The word "knowledge graph" is used more and more, but what exactly is a knowledge graph? In this blog post I will explain what it is through an example, based on the approach of the &lt;a href="https://www.w3.org/TR/rdf11-concepts/" rel="noopener noreferrer"&gt;Resource Description Framework&lt;/a&gt;. And, as title suggests, the example will have as theme: Pokémon! Let's jump right in it!&lt;/p&gt;

&lt;p&gt;But before we can really get started you need to pick your starter, trainer! Who will it be?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Charmander: a bipedal, reptilian fire-type Pokémon with a primarily orange body and blue eyes.&lt;/li&gt;
&lt;li&gt;Bulbasaur: a small, quadruped grass/poison-type Pokémon that has blue-green skin with darker patches.&lt;/li&gt;
&lt;li&gt;Squirtle: a small water-type Pokémon that resembles a light blue turtle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which one do you want?&lt;br&gt;
I choose Charmander!&lt;br&gt;
I choose Bulbasaur!&lt;br&gt;
I choose Squirtle!&lt;/p&gt;

&lt;h2 id="charmander"&gt;Charmander&lt;/h2&gt;

&lt;p&gt;Great! You picked the fire-type Pokémon. Here are some facts about Charmander:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Charmander is a fire-type Pokémon introduced in Generation I.&lt;br&gt;
  It evolves into Charmeleon starting at level 16, which evolves into Charizard starting at level 36. &lt;a href="https://bulbapedia.bulbagarden.net/wiki/Charmander_(Pok%C3%A9mon)" rel="noopener noreferrer"&gt;source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's see how we can represent the information in this text as a knowledge graph. At the centre of our graph is Charmander, as you can see below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fcharmander-0.jpg" class="article-body-image-wrapper"&gt;&lt;img id="charmander-0" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fcharmander-0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Graphs, including knowledge graphs, are formed of &lt;a href="https://en.wikipedia.org/wiki/Vertex_(graph_theory)" rel="noopener noreferrer"&gt;nodes&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Glossary_of_graph_theory_terms#edge" rel="noopener noreferrer"&gt;edges&lt;/a&gt;. Our graph contains one &lt;em&gt;node&lt;/em&gt;: Charmander. Now let's have a look at the first sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Charmander is a fire-type Pokémon introduced in Generation I.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sentence can be split up in two bits of information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Charmander is a fire-type Pokémon.&lt;/li&gt;
&lt;li&gt;Charmander is introduced in Generation &lt;span&gt;I&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We represent a fire-type Pokémon by the word "FIRE" in an orange rectangle:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fcharmander-1.svg" class="article-body-image-wrapper"&gt;&lt;img id="charmander-1" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fcharmander-1.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now our graph contains two nodes: Charmander and the fire-type Pokémon. What is the relationship between these two? "Charmander is a fire-type Pokémon" means that there is a relationship between our two nodes. We add that information to our graph by drawing an &lt;em&gt;edge&lt;/em&gt; that connects the two nodes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fcharmander-2a.svg" class="article-body-image-wrapper"&gt;&lt;img id="charmander-2a" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fcharmander-2a.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are not done yet though. We know that there is a relationship, but what kind of relationship is it? "Charmander is a fire-type Pokémon" means that Charmander belongs to the group of fire-type Pokémon, or that Charmander is of the type fire. Thus, we add "type" to the relationship.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fcharmander-2b.svg" class="article-body-image-wrapper"&gt;&lt;img id="charmander-2b" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fcharmander-2b.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that the direction of the arrow is from Charmander to fire and not the other way around. If that were the case then our sentence would have been "A fire-type Pokémon is Charmander", or something like that. What doesn't make sense.&lt;/p&gt;

&lt;p&gt;To add the second bit of information from the first sentence to the graph, we add a "&lt;span&gt;I&lt;/span&gt;" to the graph that represents Generation &lt;span&gt;I&lt;/span&gt;. Now there are three nodes in our graph.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fcharmander-3a.svg" class="article-body-image-wrapper"&gt;&lt;img id="charmander-3a" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fcharmander-3a.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we draw an edge between Charmander and I to show that there is a relationship between the nodes. Finally, we also add what kind of relationship it is. Here we add "generation" to the relationship.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fk1v6tx49xlvaqyqe5zu7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fk1v6tx49xlvaqyqe5zu7.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second sentence is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It evolves into Charmeleon starting at level 16, which evolves into Charizard starting at level 36.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sentence can be split up in two bits of information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Charmander evolves into Charmeleon, starting at level 16.&lt;/li&gt;
&lt;li&gt;Charmeleon evolves into Charizard, starting at level 36.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We add a new node for Charmeleon and add an "evolves" relationship between Charmander and Charmeleon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Far410y948knt7xdv2016.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Far410y948knt7xdv2016.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we want to add the fact that the evolution can only happen at level 16 or higher. Again, we add a node. This time for "16", but where do we add the relationship? Do we add it between Charmander and 16? Do we add it between Charmeleon and 16? This is possible, but what happens if a Pokémon can evolve into two different Pokémon? How do you know to which evolution the level belongs? Actually, we want to say that that specific &lt;code&gt;evolve&lt;/code&gt; relationship only happens starting at level 16. Thus, we want to add "16" to the relationship between Charmander and Charmeleon. But that is not possible: extra information cannot be added to a relationship. But don't despair trainer! If we alter our current graph we can still add that information where we want it.&lt;/p&gt;

&lt;p&gt;We introduce a dedicated node for a single evolution. In our case the evolution from Charmander to Charmeleon. But instead of a relationship between Charmander and Charmeleon directly, we now have a relationship between Charmander and the evolution, and a relationship between the evolution and Charmeleon, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fcharmander-5.svg" class="article-body-image-wrapper"&gt;&lt;img id="charmander-5" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fcharmander-5.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can add as much information to that specific evolution as we want. Charmeleon is already one of them, and the level, via the &lt;code&gt;level&lt;/code&gt; relationship, is the second one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fcharmander-6.svg" class="article-body-image-wrapper"&gt;&lt;img id="charmander-6" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fcharmander-6.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can do the same for the fact that Charmeleon evolves into Charizard starting at level 36:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fcharmander-final.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fcharmander-final.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nice job! We already have a pretty decent knowledge graph. But we are not done yet: we still need to add semantics to the graph. We do this by explicitly defining what each relationship means, so that there is no doubt when others try to understand the data in our knowledge graph. For the relationships we used we can define the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;type&lt;/code&gt;: Pokémon belong to one or more types. When there is a &lt;code&gt;type&lt;/code&gt; relationship between a subject and object, then this means that the subject is of the type represented by the object. The subject is always a Pokémon, such as Charmander, and the object is always a type, such as Fire.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;generation&lt;/code&gt;: Pokémon are introduced in a specific generation. When there is a &lt;code&gt;generation&lt;/code&gt; relationship between a subject and object, then this means that the subject is introduced in the generation represented by the object. The subject is always a Pokémon, such as Charmander, and the object is a always a generation, such as &lt;span&gt;I&lt;/span&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;evolves&lt;/code&gt;: Some Pokémon can evolve.  This relationship is used between a Pokémon, the subject, and a representation of a specific evolution, the object. The subject can be for example Charmander, and the object is then an evolution that is linked to Charmeleon. Note that the object of the &lt;code&gt;evolves&lt;/code&gt; relationship is not a Pokémon.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;into&lt;/code&gt;: When a Pokémon evolves, they evolve into another Pokémon. This relationship says into which Pokémon another Pokémon evolves. The subject of this relationship is an evolution, and the object is the Pokémon into which the original Pokémon evolves. The subject is for example an evolution for Charmander, and the object is Charmeleon.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;level&lt;/code&gt;: When a Pokémon evolves is often determined by the level they should have. The subject of this relationship is the representation of an evolution, and the object is the level that is required before an evolution can take place. The subject is for example an evolution for Charmander, and then the object is the 16.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By incorporating this information in the graph, we now have a real knowledge graph: we have the data together with the meaning of the data. Note that in practice the meaning of the data is not described in plain text, but through the use of &lt;a href="https://en.wikipedia.org/wiki/Ontology" rel="noopener noreferrer"&gt;ontologies&lt;/a&gt; (which are knowledge graphs themselves).&lt;/p&gt;

&lt;p&gt;Great work trainer! This is the end of our example with Charmander. I hope you now have a better understanding of what knowledge graphs actually are. If you are eager to see more &lt;del&gt;Pokémon&lt;/del&gt; knowledge graphs, do not hesitate to check the examples with Bulbasaur and Squirtle.&lt;/p&gt;

&lt;p&gt;If you have any questions or remarks, don’t hesitate to contact me via &lt;a href="mailto:pieter.heyvaert@ugent.be"&gt;email&lt;/a&gt; or via &lt;a href="https://twitter.com/HeyPieter" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="bulbasaur"&gt;Bulbasaur&lt;/h2&gt;

&lt;p&gt;Great! You picked the grass/poison-type Pokémon. Here are some facts about Bulbasaur:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Bulbasaur is a grass/poison-type Pokémon introduced in Generation I.&lt;br&gt;
  It learns Leech Seed at level 7 and Vine Whip at level 13. &lt;a href="https://bulbapedia.bulbagarden.net/wiki/Bulbasaur_(Pok%C3%A9mon)" rel="noopener noreferrer"&gt;source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's see how we can represent the information in this text as a knowledge graph. At the centre of our graph is Bulbasaur, as you can see below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-0.jpg" class="article-body-image-wrapper"&gt;&lt;img id="bulbasaur-0" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Graphs, including knowledge graphs, are formed of  &lt;a href="https://en.wikipedia.org/wiki/Vertex_(graph_theory)" rel="noopener noreferrer"&gt;nodes&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Glossary_of_graph_theory_terms#edge" rel="noopener noreferrer"&gt;edges&lt;/a&gt;. Our graph contains one &lt;em&gt;node&lt;/em&gt;: Bulbasaur. Now let's have a look at the first sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Bulbasaur is a grass/poison-type Pokémon introduced in Generation I.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sentence can be split up in three bits of information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bulbasaur is a grass-type Pokémon.&lt;/li&gt;
&lt;li&gt;Bulbasaur is a poison-type Pokémon.&lt;/li&gt;
&lt;li&gt;Bulbasaur is introduced in Generation &lt;span&gt;I&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We represent a grass-type Pokémon by the word "GRASS" in a green rectangle:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-1.svg" class="article-body-image-wrapper"&gt;&lt;img id="bulbasaur-1" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-1.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now our graph contains two nodes: Bulbasaur and the grass-type Pokémon. What is the relationship between these two? "Bulbasaur is a grass-type Pokémon" means that there is a relationship between our two nodes. We add that information to our graph by drawing an &lt;em&gt;edge&lt;/em&gt; that connects the two nodes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-2a.svg" class="article-body-image-wrapper"&gt;&lt;img id="bulbasaur-2a" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-2a.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are not done yet though. We know that there is a relationship, but what kind of relationship is it? "Bulbasaur is a grass-type Pokémon" means that Bulbasaur belongs to the group of grass-type Pokémon, or that Bulbasaur is of the type grass. Thus, we add "type" to the relationship.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-2b.svg" class="article-body-image-wrapper"&gt;&lt;img id="bulbasaur-2b" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-2b.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that the direction of the arrow is from Bulbasaur to grass and not the other way around. If that were the case then our sentence would have been  "A grass-type Pokémon is Bulbasaur", or something like that. What doesn't make sense.&lt;/p&gt;

&lt;p&gt;We do the same for the fact that Bulbasaur is a poison-type Pokémon:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-3.svg" class="article-body-image-wrapper"&gt;&lt;img id="bulbasaur-3" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-3.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To add the third bit of information from the first sentence to the graph, we add a "&lt;span&gt;I&lt;/span&gt;" to the graph that represents Generation &lt;span&gt;I&lt;/span&gt;. Now there are four nodes in our graph.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-4a.svg" class="article-body-image-wrapper"&gt;&lt;img id="bulbasaur-4a" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-4a.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we draw an edge between Bulbasaur and &lt;span&gt;I&lt;/span&gt; to show that there is a relationship between the nodes. Finally, we also add what kind of relationship it is. Here we add "generation" to the relationship.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-4b.svg" class="article-body-image-wrapper"&gt;&lt;img id="bulbasaur-4b" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-4b.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second sentence is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It learns Leech Seed at level 7 and Vine Whip at level 13.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sentence can be split up in two bits of information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bulbasaur learns Leech Seed at level 7.&lt;/li&gt;
&lt;li&gt;Bulbasaur learns Wine Whip at level 13.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We add a new node for the move "Leech Seed" and add a &lt;code&gt;learns&lt;/code&gt; relationship between Bulbasaur and Leech Seed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-5.svg" class="article-body-image-wrapper"&gt;&lt;img id="bulbasaur-5" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-5.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we want to add the fact that Leech Seed is learned at level 7. Again, we add a node. This time for "7", but where do we add the relationship? Do we add it between Bulbasaur and 7? Do we add it between Leech Seed and 7?&lt;br&gt;
This is possible, but what happens if Leech Seed can be learned by multiple Pokémon at different levels? How do you know to which Pokémon the level belongs? Actually, we want to say that that specific &lt;code&gt;learns&lt;/code&gt; relationship only happens at level 7 (for Bulbasaur). Thus, we want to add "7" to the relationship between Bulbasaur and Leech Seed. But that is not possible: extra information cannot be added to a relationship. But don't despair trainer! If we alter our current graph we can still add that information where we want it.&lt;/p&gt;

&lt;p&gt;We introduce a dedicated node for the learning of a single move. In our case the learning of Leech Seed by Bulbasaur. But instead of a relationship between Bulbasaur and Leech Seed directly, we now have a relationship between Bulbasaur and the learning of a move, and a relationship between the learning of a move and Leech Seed, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-6a.svg" class="article-body-image-wrapper"&gt;&lt;img id="bulbasaur-6a" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-6a.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can add as much information to that specific learning of a move as we want. Leech Seed is already one of them, and the level, via the &lt;code&gt;level&lt;/code&gt; relationship,  is the second one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-6b.svg" class="article-body-image-wrapper"&gt;&lt;img id="bulbasaur-6b" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-6b.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can do the same for the fact that Bulbasaur learns Vine Whip at level 13:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-final.svg" class="article-body-image-wrapper"&gt;&lt;img id="bulbasaur-final" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fbulbasaur-final.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nice job! We already have a pretty decent knowledge graph. But we are not done yet: we still need to add semantics to the graph. We do this by explicitly defining what each relationship means, so that there is no doubt when others try to understand the data in our knowledge graph. For the relationships we used we can define the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;type&lt;/code&gt;: Pokémon belong to one or more types. When there is a &lt;code&gt;type&lt;/code&gt; relationship between a subject and object, then this means that the subject is of the type represented by the object. The subject is always a Pokémon, such as Bulbasaur, and the object is always a type, such as Grass.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;generation&lt;/code&gt;: Pokémon are introduced in a specific generation. When there is a &lt;code&gt;generation&lt;/code&gt; relationship between a subject and object, then this means that the subject is introduced in the generation represented by the object. The subject is always a Pokémon, such as Bulbasaur, and the object is a always a generation, such as &lt;span&gt;I&lt;/span&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;learns&lt;/code&gt;: Pokémon can learn new moves. This relationship is used between a Pokémon, the subject, and the representation of learning a specific move, the object. The subject can be for example Bulbasaur, and the object the learning of a move that is linked to Leech Seed. Note that the object of the &lt;code&gt;learns&lt;/code&gt; relationship is not a move.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;which move&lt;/code&gt;: &lt;br&gt;
This relationship says which moves a Pokémon learns. The subject of this relationship is the learning of a specific move, and the object is that move which the Pokémon learns. The subject is for example the learning of a specific move by Bulbasaur, and the object is Leech Seed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;level&lt;/code&gt;: &lt;br&gt;
When a Pokémon learns a specific move is often determined by the level they should have. The subject of this relationship is the representation of learning a specific move, and the object is the level that is required before that move can be learned. The subject is for example the learning of a specific move by Bulbasaur, and then the object is the 7.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By incorporating this information in the graph, we now have a real knowledge graph: we have the data together with the meaning of the data.&lt;br&gt;
Note that in practice the meaning of the data is not described in plain text, but through the use of &lt;a href="https://en.wikipedia.org/wiki/Ontology" rel="noopener noreferrer"&gt;ontologies&lt;/a&gt; (which are knowledge graphs themselves).&lt;/p&gt;

&lt;p&gt;Great work trainer! This is the end of our example with Bulbasaur.&lt;br&gt;
I hope you now have a better understanding of what knowledge graphs actually are. If you are eager to see more &lt;del&gt;Pokémon&lt;/del&gt; knowledge graphs,&lt;br&gt;
do not hesitate to check the examples with Charmander and Squirtle.&lt;/p&gt;

&lt;p&gt;If you have any questions or remarks, don’t hesitate to contact me via &lt;a href="mailto:pieter.heyvaert@ugent.be"&gt;email&lt;/a&gt; or via &lt;a href="https://twitter.com/HeyPieter" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="squirtle"&gt;Squirtle&lt;/h2&gt;

&lt;p&gt;Great! You picked the water-type Pokémon. Here are some facts about Squirtle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Squirtle is a water-type Pokémon introduced in Generation I.&lt;br&gt;
  It is a starter in Pokémon Blue and Red and&lt;br&gt;
  can be received in Pokémon Yellow from Officer Jenny after receiving the Thunder Badge. &lt;a href="https://bulbapedia.bulbagarden.net/wiki/Squirtle_(Pok%C3%A9mon)" rel="noopener noreferrer"&gt;source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's see how we can represent the information in this text as a knowledge graph. At the centre of our graph is Squirtle, as you can see below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-0.svg" class="article-body-image-wrapper"&gt;&lt;img id="squirtle-0" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-0.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Graphs, including knowledge graphs, are formed of  &lt;a href="https://en.wikipedia.org/wiki/Vertex_(graph_theory)" rel="noopener noreferrer"&gt;nodes&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Glossary_of_graph_theory_terms#edge" rel="noopener noreferrer"&gt;edges&lt;/a&gt;. Our graph contains one &lt;em&gt;node&lt;/em&gt;: Squirtle. Now let's have a look at the first sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Squirtle is a water-type Pokémon introduced in Generation I.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sentence can be split up in two bits of information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Squirtle is a water-type Pokémon.&lt;/li&gt;
&lt;li&gt;Squirtle is introduced in Generation &lt;span&gt;I&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We represent a water-type Pokémon by the word "WATER" in a blue rectangle:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-1.svg" class="article-body-image-wrapper"&gt;&lt;img id="squirtle-1" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-1.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now our graph contains two nodes: Squirtle and the water-type Pokémon. What is the relationship between these two? "Squirtle is a water-type Pokémon" means that there is a relationship between our two nodes. We add that information to our graph by drawing an &lt;em&gt;edge&lt;/em&gt; that connects the two nodes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-2.svg" class="article-body-image-wrapper"&gt;&lt;img id="squirtle-2" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-2.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are not done yet though. We know that there is a relationship,&lt;br&gt;
but what kind of relationship is it? "Squirtle is a water-type Pokémon" means that Squirtle belongs to the group of water-type Pokémon, or that Squirtle is of the type water. Thus, we add "type" to the relationship.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-3.svg" class="article-body-image-wrapper"&gt;&lt;img id="squirtle-3" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-3.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that the direction of the arrow is from Squirtle to water and&lt;br&gt;
not the other way around. If that were the case then our sentence would have been "A water-type Pokémon is Squirtle", or something like that. What doesn't make sense.&lt;/p&gt;

&lt;p&gt;To add the second bit of information from the first sentence to the graph, we add a "&lt;span&gt;I&lt;/span&gt;" to the graph that represents Generation &lt;span&gt;I&lt;/span&gt;. Now there are three nodes in our graph.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-4.svg" class="article-body-image-wrapper"&gt;&lt;img id="squirtle-4" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-4.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we draw an edge between Squirtle and &lt;span&gt;I&lt;/span&gt; to show that there is a relationship between the nodes. Finally, we also add what kind of relationship it is. Here we add "generation" to the relationship.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-5.svg" class="article-body-image-wrapper"&gt;&lt;img id="squirtle-5" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-5.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second sentence is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It is a starter in Pokémon Blue and Red and&lt;br&gt;
can be received in Pokémon Yellow from Officer Jenny after receiving the Thunder Badge.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sentence can be split up in three bits of information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Squirtle is a starter in Pokémon Blue.&lt;/li&gt;
&lt;li&gt;Squirtle is a starter in Pokémon Red.&lt;/li&gt;
&lt;li&gt;Squirtle can be received in Pokémon Yellow from Officer Jenny after receiving the Thunder Badge.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the first bit of information, we add a new node for the game "Pokémon Blue" and add a &lt;code&gt;starter&lt;/code&gt; relationship between Squirtle and Pokémon Blue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-6.svg" class="article-body-image-wrapper"&gt;&lt;img id="squirtle-6" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-6.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We do the same for the second bit: a node for Pokémon Red and a &lt;code&gt;starter&lt;/code&gt; relationship between Squirtle and that node.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-7.svg" class="article-body-image-wrapper"&gt;&lt;img id="squirtle-7" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-7.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we add a new node for Pokémon Yellow too and a &lt;code&gt;receive&lt;/code&gt; relationship between Squirtle and that node for the third bit for information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-8.svg" class="article-body-image-wrapper"&gt;&lt;img id="squirtle-8" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-8.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we want to add the fact that Squirtle is received from Officer Jenny in Pokémon Yellow. Again, we add a node for Officer Jenny, but where do we add the relationship? Do we add it between Squirtle and Officer Jenny?&lt;br&gt;
Do we add it between Pokémon Yellow and Officer Jenny? This is possible, but what happens if a Squirtle can be received from different people in different games? How do you know from whom in which game? Actually, we want to say that that specific &lt;code&gt;receive&lt;/code&gt; relationship only happens  in a specific game from a specific person (for Squirtle). Thus, we want to add Officer Jenny to the relationship between Squirtle and Pokémon Yellow. But that is not possible: extra information cannot be added to a relationship.&lt;br&gt;
But don't despair trainer! If we alter our current graph we can still add that information where we want it.&lt;/p&gt;

&lt;p&gt;We introduce a dedicated node for receiving something called "give away".&lt;br&gt;
In our case the receiving of Squirtle from Officer Jenny in Pokémon Yellow. But instead of a relationship between Squirtle and Pokémon Yellow directly, we now have a relationship between Squirtle and the give away, and a relationship between the give away and Pokémon Yellow, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-9.svg" class="article-body-image-wrapper"&gt;&lt;img id="squirtle-9" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-9.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can add as much information to that give away as we want. Pokémon Yellow is already one of them, and Officer Jenny, via the &lt;code&gt;from&lt;/code&gt; relationship, is the second one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-10.svg" class="article-body-image-wrapper"&gt;&lt;img id="squirtle-10" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-10.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, we can also add the fact that the Squirtle can only received if you have the Thunder Badge:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-final.svg" class="article-body-image-wrapper"&gt;&lt;img id="squirtle-final" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpieterheyvaert.com%2Fimg%2Fblog%2Fkg-pkmn%2Fsquirtle-final.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nice job! We already have a pretty decent knowledge graph. But we are not done yet: we still need to add semantics to the graph. We do this by explicitly defining what each relationship means, so that there is no doubt when others try to understand the data in our knowledge graph. For the relationships we used we can define the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;type&lt;/code&gt;: Pokémon belong to one or more types. When there is a &lt;code&gt;type&lt;/code&gt; relationship between a subject and object, then this means that the subject is of the type represented by the object. The subject is always a Pokémon, such as Squirtle, and the object is always a type, such as Grass.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;generation&lt;/code&gt;: Pokémon are introduced in a specific generation. When there is a &lt;code&gt;generation&lt;/code&gt; relationship between a subject and object, then this means that the subject is introduced in the generation represented by the object. The subject is always a Pokémon, such as Squirtle, and the object is always a generation, such as &lt;span&gt;I&lt;/span&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;starter&lt;/code&gt;: Certain Pokémon can be picked as a starter in the games. When there is a &lt;code&gt;starter&lt;/code&gt; relationship between a subject and object, then this means that the subject can be picked as a starter in the object. The subject is always a Pokémon, such as Squirtle, and the object is always a game, such as Pokémon Blue.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;receive&lt;/code&gt;: Certain Pokémon can be received from characters in the games. When there is a &lt;code&gt;receive&lt;/code&gt; relationship between a subject and object, then this means that the subject can be received according to the details described by the object. The subject is always a Pokémon, such as Squirtle, and the object is a representation of giving away a Pokémon.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;game&lt;/code&gt;: Pokémon can be received in specific games. This relationship is used between representation of giving away a Pokémon, the subject, and a game, the object. The subject can be for example the representation of giving away Squirtle, and the object Pokémon Yellow. Note that the subject of the &lt;code&gt;game&lt;/code&gt; relationship is not a Pokémon.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;from&lt;/code&gt;: Pokémon can be received from specific characters in the games. &lt;br&gt;
This relationship is used between representation of giving away a Pokémon, the subject, and a character from which the Pokémon is received, the object. The subject can be for example the representation of giving away Squirtle, and the object Officer Jenny. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;requires&lt;/code&gt;: Sometimes requirements have to fulfilled before Pokémon can be received in games. This relationship is used between representation of giving away a Pokémon, the subject, and a requirement that needs to be fulfilled, the object. The subject can be for example the representation of giving away Squirtle, and the object the Thunder Badge.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Great work trainer! This is the end of our example with Squirtle. I hope you now have a better understanding of what knowledge graphs actually are. If you are eager to see more &lt;del&gt;Pokémon&lt;/del&gt; knowledge graphs, do not hesitate to check the examples with Charmander and Bulbasaur.&lt;/p&gt;

&lt;p&gt;If you have any questions or remarks, don’t hesitate to contact me via &lt;a href="mailto:pieter.heyvaert@ugent.be"&gt;email&lt;/a&gt; or via &lt;a href="https://twitter.com/HeyPieter" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Pokémon and Pokémon character names are trademarks of Nintendo.&lt;/p&gt;

</description>
      <category>knowledgegraph</category>
      <category>rdf</category>
      <category>linkeddata</category>
      <category>pokemon</category>
    </item>
    <item>
      <title>Caching JavaScript data file results when using Eleventy</title>
      <dc:creator>Pieter Heyvaert</dc:creator>
      <pubDate>Fri, 27 Sep 2019 19:14:56 +0000</pubDate>
      <link>https://dev.to/heypieter/caching-javascript-data-file-results-when-using-eleventy-38ch</link>
      <guid>https://dev.to/heypieter/caching-javascript-data-file-results-when-using-eleventy-38ch</guid>
      <description>&lt;p&gt;&lt;a href="https://www.11ty.io"&gt;Eleventy&lt;/a&gt; by &lt;a href="https://twitter.com/zachleat/"&gt;Zach Leatherman&lt;/a&gt; has become my default static site generator. It is simple, uses JavaScript, and is easy to extend. It allows me to include custom code to access additional data sources, &lt;br&gt;
such as RDF datasets.&lt;/p&gt;

&lt;p&gt;Querying data can take up some time, for example, when using an external Web API. During deployment of a website this is not a big deal, as this probably doesn't happen every minute. But when you are developing then it might become an issue: you don't want to wait for query results every time you make a change that doesn't affect the results, such as updating a CSS property, which only affects how the results are visualized. Ideally, you want to reuse these results without querying the data over and over again. I explain in this blog post how that can be done by introducing a cache.&lt;/p&gt;

&lt;p&gt;The cache has the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The cache is only used when the website is locally served (&lt;code&gt;eleventy --serve&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The cached data is written to and read from the filesystem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is done by using the following two files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;serve.sh&lt;/code&gt;: a Bash script that runs Eleventy.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cache.js&lt;/code&gt;: a JavaScript file that defines the cache method.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An example Eleventy website using these two files is available on &lt;a href="https://github.com/pheyvaer/eleventy-cache-example"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serve.sh
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c"&gt;# trap ctrl-c and call ctrl_c()&lt;/span&gt;
&lt;span class="nb"&gt;trap &lt;/span&gt;ctrl_c INT

&lt;span class="k"&gt;function &lt;/span&gt;ctrl_c&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; _data/_cache
  &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Remove old folders&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; _data/_cache &lt;span class="c"&gt;# Should already be removed, but just in case&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; _site

&lt;span class="c"&gt;# Create needed folders&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;_data/_cache

&lt;span class="nv"&gt;ELEVENTY_SERVE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;npx eleventy &lt;span class="nt"&gt;--serve&lt;/span&gt; &lt;span class="nt"&gt;--port&lt;/span&gt; 8080
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This Bash script creates the folder for the cached data and serves the website locally. First, we remove the cache folder and the files generated by Eleventy, which might still be there from before. Strictly speaking removing the latter is not necessary, but I have noticed that removed files are not removed from &lt;code&gt;_site&lt;/code&gt;, which might result in unexpected behaviour. Second, we create the cache folder again, which of course is now empty. Finally, we set the environment variable &lt;code&gt;ELEVENTY_SERVE&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; and start Eleventy: we serve the website locally on port 8080. The environment variable is used by &lt;code&gt;cache.js&lt;/code&gt; to check if the website is being served, because currently this information can't be extracted from Eleventy directly. Note that I have only tested this on macOS 10.12.6 and 10.14.6, and Ubuntu 16.04.6. Changes might be required for other OSs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache.js
&lt;/h2&gt;



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

&lt;span class="cm"&gt;/**
 * This method returns a cached version if available, else it will get the data via the provided function.
 * @param getData The function that needs to be called when no cached version is available.
 * @param cacheFilename The filename of the file that contains the cached version.
 * @returns the data either from the cache or from the geData function.
 */&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cacheFilename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Check if the environment variable is set.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isServing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ELEVENTY_SERVE&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cacheFilePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_data/_cache/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cacheFilename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;dataInCache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Check if the website is being served and that a cached version is available.&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isServing&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathExists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheFilePath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Read file from cache.&lt;/span&gt;
    &lt;span class="nx"&gt;dataInCache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheFilePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Using from cache: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cacheFilename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// If no cached version is available, we execute the function.&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;dataInCache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// If the website is being served, then we write the data to the cache.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isServing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Write data to cache.&lt;/span&gt;
      &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;dataInCache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;dataInCache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The method defined by the JavaScript file above takes two parameters: &lt;code&gt;getData&lt;/code&gt; and &lt;code&gt;cacheFilename&lt;/code&gt;. The former is the expensive function that you don't want to repeat over and over again. The latter is the filename of the file with the cached version. The file will be put in the folder &lt;code&gt;_data/_cache&lt;/code&gt; relative to the location of &lt;code&gt;cache.js&lt;/code&gt;. The environment variable used in &lt;code&gt;serve.sh&lt;/code&gt; is checked here to see if the website is being served. Note that the script requires the package &lt;code&gt;fs-extra&lt;/code&gt;, which adds extra methods to &lt;code&gt;fs&lt;/code&gt; and is not available by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;To get it all running, we put both files in our Eleventy project root folder. Do not forget to make the script executable and run &lt;code&gt;serve.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When executing the &lt;a href="https://github.com/pheyvaer/eleventy-cache-example"&gt;aforementioned example&lt;/a&gt;, we see that the first time to build the website it takes 10.14 seconds (see screencast below). No cached version of the query results is available at this point and thus the Web API has to be queried. But the second time, when we update the template, it only takes 0.03 seconds. This is because the cached version of the query results is used instead of querying the Web API again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Sp3gdCNJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/7uneee04nq49jzqe773r.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Sp3gdCNJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/7uneee04nq49jzqe773r.gif" alt="Screencast"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p class="caption"&gt;Screencast: When the Web API is queried it takes 10.14 seconds. When the cached version of the query results is used it takes 0.03 seconds.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>eleventy</category>
      <category>cache</category>
    </item>
    <item>
      <title>How a chess app interacts with Solid</title>
      <dc:creator>Pieter Heyvaert</dc:creator>
      <pubDate>Thu, 04 Apr 2019 10:32:15 +0000</pubDate>
      <link>https://dev.to/heypieter/how-a-chess-app-interacts-with-solid-409a</link>
      <guid>https://dev.to/heypieter/how-a-chess-app-interacts-with-solid-409a</guid>
      <description>&lt;p&gt;Last year I started playing around with the &lt;a href="https://solid.inrupt.com" rel="noopener noreferrer"&gt;Solid platform&lt;/a&gt;, introduced by Tim Berners-Lee. In order to get more familiar with the different concepts and technologies used, I created a proof-of-concept app: a &lt;a href="https://github.com/pheyvaer/solid-chess" rel="noopener noreferrer"&gt;browser chess game&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The main concept of the Solid platform is the use of Solid PODs, which provide personal online data storage. The idea of Solid is that a user has one or more PODs to store their data and different apps are able to interact with these PODs, but these apps can only read and write the data to which the user has granted them access. So, instead of having to store all data of all users on a single server per app, users now store and control (!) their own data, leading to a decentralized approach. The focus of this blog post is on the interaction between the app and the Solid PODs.&lt;/p&gt;

&lt;p&gt;First, we list the different features of the chess app. Second, we elaborate on the three high-level components of the app. Third, we list the different steps that are taken when certain actions are done by a player. Finally, we discuss how the data generated by this app can be used by other apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;p&gt;The app provides the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authenticate a player&lt;/li&gt;
&lt;li&gt;start a new chess game and invite your opponent&lt;/li&gt;
&lt;li&gt;join a chess game you are invited to&lt;/li&gt;
&lt;li&gt;do moves in a chess game&lt;/li&gt;
&lt;li&gt;continue an existing chess game&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  High-level components
&lt;/h2&gt;

&lt;p&gt;The app consists of three high-level components: the GUI, the chess game engine, and the interaction with the POD. The GUI uses the &lt;a href="https://github.com/oakmac/chessboardjs" rel="noopener noreferrer"&gt;chessboard.js&lt;/a&gt; library, which offers the chessboard and the interaction of the players with the board, such as the moving of the pieces. It does not provide a chess engine, i.e., it will not check whether the moves are valid or not and keep track of which player's turn it is. To accomplish this we make use of the &lt;a href="https://github.com/jhlywa/chess.js" rel="noopener noreferrer"&gt;chess.js&lt;/a&gt; library. Although it provides the required information to play a chess game, the data is not presented as Linked Data, which is preferred by the Solid platform. &lt;a href="https://en.wikipedia.org/wiki/Linked_data" rel="noopener noreferrer"&gt;Linked Data&lt;/a&gt; is a method of publishing structured data so that it can be interlinked and become more useful through semantic queries. Therefore, I created a "wrapper" library around chess.js called &lt;a href="https://github.com/pheyvaer/solid-chess" rel="noopener noreferrer"&gt;semantic-chess&lt;/a&gt; that outputs RDF, a technology used to materialize Linked Data, which can be used during the interaction of the app with the POD. The focus of this blog post is on this interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Actions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;p&gt;When the app is launched, one of the first actions a player does is logging in. For this we use the &lt;a href="https://github.com/solid/solid-auth-client" rel="noopener noreferrer"&gt;solid-auth-client&lt;/a&gt; library. It keeps track of the session and provides a fetch method that is used whenever we want to interact with the POD. Whenever a player is logged in the fetch method is able to store and read data from PODs to which the logged-in player has access.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fuc6ivzlttvm39ghx0zud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fuc6ivzlttvm39ghx0zud.png" alt="Figure 1: pop-up window that appears when clicking the "&gt;&lt;/a&gt;&lt;br&gt;
Figure 1: pop-up window that appears when clicking the "Log in" button in the app.&lt;/p&gt;

&lt;p&gt;In the GUI there is a classic log-in button. When clicked, a pop-up appears that allows selecting your &lt;a href="https://en.wikipedia.org/wiki/Identity_provider" rel="noopener noreferrer"&gt;identify provider&lt;/a&gt; to which you want to authenticate (see Figure 1). You can provide your own HTML file to render the pop-up or you can use the default one provided by the library. After successfully logging in, the pop-up is closed and the focus is back on the app. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;solid-auth-client&lt;/code&gt; library has tracked the fact that a player has logged in and now a session object is available. This session object contains, for example, the Web ID of the logged in player. A Web ID is an HTTP URI that denotes an agent on an HTTP-based network. In line with the Linked Data principles, when a Web ID is de-referenced, it resolves to a profile document that describes its referent, i.e., the player. Furthermore, the fetch method provided by the library will now act on behalf of the player.  &lt;/p&gt;
&lt;h3&gt;
  
  
  Start new game
&lt;/h3&gt;

&lt;p&gt;When a player starts a new game he needs to select an opponent, the app needs to store the game data on the player's POD, and invite the opponent.&lt;/p&gt;
&lt;h4&gt;
  
  
  Select opponent
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fc7q1mnetu4uw5ppkuxb5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fc7q1mnetu4uw5ppkuxb5.png" alt="Figure 2: sequence diagram of the steps taken when a player selects an opponent."&gt;&lt;/a&gt;&lt;br&gt;
Figure 2: sequence diagram of the steps taken when a player selects an opponent.&lt;/p&gt;

&lt;p&gt;With this action, the player selects an opponent for a new game. In Figure 2, we describe the corresponding steps between the player, the player's app, the player's POD, and the PODs of his friends. The steps are:&lt;/p&gt;

&lt;p&gt;1) The app does a GET to the player's Web ID. If the Web ID of the player is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://player.solid.community/profile/card#me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then the corresponding &lt;code&gt;curl&lt;/code&gt; command is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl https://player.solid.community/profile/card#me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) The player's POD returns RDF describing the player. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix : &amp;lt;https://player.solid.community/profile/card#&amp;gt;.
@prefix inbox: &amp;lt;https://player.solid.community/inbox/&amp;gt;.
@prefix ldp: &amp;lt;http://www.w3.org/ns/ldp#&amp;gt;.
@prefix foaf: &amp;lt;http://xmlns.com/foaf/0.1/&amp;gt;.
@prefix c0: &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;.

:me ldp:inbox inbox:;
  foaf:knows c0:me;
  foaf:name "Player".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) The app queries the RDF to determine the player's friends, via the predicate &lt;code&gt;foaf:knows&lt;/code&gt;. An example of SPARQL query using this predicate is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PREFIX foaf: &amp;lt;http://xmlns.com/foaf/0.1/&amp;gt;
PREFIX player: &amp;lt;https://player.solid.community/profile/card#&amp;gt;

SELECT ?friend 
WHERE {
  player:me foaf:knows ?friend.
} 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For every friend the following three steps are taken.&lt;/p&gt;

&lt;p&gt;4) The app does a GET to the friend's Web ID. If the Web ID of the player is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://opponent.solid.community/profile/card#me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then the corresponding &lt;code&gt;curl&lt;/code&gt; command is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl https://opponent.solid.community/profile/card#me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5) The friend's POD returns RDF describing the friend. This RDF is similar to the RDF of step 2. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix : &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;.
@prefix inbox: &amp;lt;https://opponent.solid.community/inbox/&amp;gt;.
@prefix ldp: &amp;lt;http://www.w3.org/ns/ldp#&amp;gt;.
@prefix foaf: &amp;lt;http://xmlns.com/foaf/0.1/&amp;gt;.
@prefix player: &amp;lt;https://player.solid.community/profile/card#&amp;gt;.

:me ldp:inbox inbox:;
  foaf:knows player:me;
  foaf:name "Opponent".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;6) The app queries the RDF to determine the friend's name, via the predicate &lt;code&gt;foaf:name&lt;/code&gt;. An example of SPARQL query using this predicate for an friend with the Web ID&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://opponent.solid.community/profile/card#me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PREFIX foaf: &amp;lt;http://xmlns.com/foaf/0.1/&amp;gt;
PREFIX opponent: &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;

SELECT ?name 
WHERE {
  opponent:me foaf:name ?name.
} 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;7) The app shows a list of friends to the player.&lt;br&gt;
8) The player selects the desired friend as his opponent. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remarks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SPARQL is used to query the RDF to find friends and their names, because it is the default query language for RDF. However, alternatives such as &lt;a href="https://github.com/solid/query-ldflex" rel="noopener noreferrer"&gt;LDFlex&lt;/a&gt; and &lt;a href="https://github.com/rubensworks/graphql-to-sparql.js" rel="noopener noreferrer"&gt;GraphQL-LD&lt;/a&gt; are also available.&lt;/li&gt;
&lt;li&gt;The FOAF ontology, with prefix &lt;code&gt;foaf&lt;/code&gt;, is one of several ontologies, such as &lt;a href="http://schema.org" rel="noopener noreferrer"&gt;Schema.org&lt;/a&gt; and &lt;a href="https://www.w3.org/TR/vcard-rdf/" rel="noopener noreferrer"&gt;vCard&lt;/a&gt;, 
that can be used to describe the basic information of a person. At the time of writing, when creating a default profile with Solid mostly FOAF is used. However, bear in mind that other Solid PODs might use (a combination of) other ontologies.&lt;/li&gt;
&lt;li&gt;We only store the Web IDs of a player's friends on the player's POD, because additional information about a friend can be acquired by downloading data from the friend's POD.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Store game data on POD
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1oh0lvfv4q1z7bcv76x8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1oh0lvfv4q1z7bcv76x8.png" alt="Figure 3: sequence diagram of the steps taken when storing game data on the POD."&gt;&lt;/a&gt;&lt;br&gt;
Figure 3: sequence diagram of the steps taken when storing game data on the POD.&lt;/p&gt;

&lt;p&gt;With this action, the game data of a new game is stored on the player's POD. In Figure 3, we describe the corresponding steps between the player, the player's app, and the player's POD. The steps are:&lt;/p&gt;

&lt;p&gt;1) The player starts the game.&lt;br&gt;
2) The player's app generates RDF that describing this specific instance of a chess game. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; @prefix : &amp;lt;https://player.solid.community/public/chess.ttl#&amp;gt;.
 @prefix chess: &amp;lt;http://purl.org/NET/rdfchess/ontology/&amp;gt;.
 @prefix stor: &amp;lt;http://example.org/storage/&amp;gt;.
 @prefix schema: &amp;lt;http://schema.org/&amp;gt;.
 @prefix player: &amp;lt;https://player.solid.community/profile/card#&amp;gt;.
 @prefix opponent: &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;.

 :game
   a chess:ChessGame;
   stor:storeIn :;
   chess:providesAgentRole :jo8deywv, :jo8deyww;
   chess:starts :jo8deywv;
   schema:name "Test game".

 :jo8deywv a chess:WhitePlayerRole;
   chess:performedBy player:me.

 :jo8deyww a chess:BlackPlayerRole;
   chess:performedBy opponent:me.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It includes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the unique URL of the game: uniquely identifies this specific chess game on the Web&lt;/li&gt;
&lt;li&gt;the name of the game: displayed in the app&lt;/li&gt;
&lt;li&gt;the Web IDs of the two players: uniquely identifies the players of this specific chess game on the Web&lt;/li&gt;
&lt;li&gt;the color of each player&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3) The player's app does a PATCH with a SPARQL UPDATE query to the player's POD with the RDF. A PATCH is used because the file where we want to store the RDF, chosen by the player, might already contain data, which we do not want to overwrite. An example of such a PATCH is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PREFIX : &amp;lt;https://player.solid.community/public/chess.ttl#&amp;gt;
PREFIX chess: &amp;lt;http://purl.org/NET/rdfchess/ontology/&amp;gt;
PREFIX stor: &amp;lt;http://example.org/storage/&amp;gt;
PREFIX schema: &amp;lt;http://schema.org/&amp;gt;
PREFIX player: &amp;lt;https://player.solid.community/profile/card#&amp;gt;
PREFIX opponent: &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;

INSERT DATA {
  :game a chess:ChessGame;
    stor:storeIn &amp;lt;&amp;gt;;
    chess:providesAgentRole :jo8deywv, :jo8deyww;
    chess:starts :jo8deywv;
    schema:name "Test game".

    :jo8deywv a chess:WhitePlayerRole; 
      chess:performedBy player:me.

    :jo8deyww a chess:BlackPlayerRole; 
      chess:performedBy opponent:me.
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Invite opponent
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fpysxqun1hsekzqtuqgyo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fpysxqun1hsekzqtuqgyo.png" alt="&amp;lt;br&amp;gt;
Figure 4: sequence diagram of the steps taken when an opponent is invited."&gt;&lt;/a&gt;&lt;br&gt;
Figure 4: sequence diagram of the steps taken when an opponent is invited.&lt;/p&gt;

&lt;p&gt;With this action, an invitation is sent to the opponent to join the newly created game. In Figure 4, we describe the corresponding steps between&lt;br&gt;
the player's app and the opponent's POD. The steps are:&lt;/p&gt;

&lt;p&gt;1) The player's app generates RDF describing the invitation. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix : &amp;lt;https://player.solid.community/public/chess.ttl#&amp;gt;.
@prefix schema: &amp;lt;http://schema.org/&amp;gt;.
@prefix player: &amp;lt;https://player.solid.community/profile/card#&amp;gt;.
@prefix opponent: &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;.

:invitation a schema:InviteAction&amp;gt;;
  schema:event :game;
  schema:agent player:me;
  schema:recipient opponent:me.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) The player's app does PATCH with a SPARQL UPDATE query to store the invitation on the player's POD. An example of such a PATCH is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; PREFIX schema: http://schema.org/&amp;gt;
 PREFIX : &amp;lt;https://player.solid.community/public/chess.ttl#&amp;gt;
 PREFIX player: &amp;lt;https://player.solid.community/profile/card#&amp;gt;
 PREFIX opponent: &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;

 INSERT DATA {
   :invitation a schema:InviteAction;
     schema:event :game;
     schema:agent player:me;
     schema:recipient opponent:me.
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) The player's app generates RDF for a notification of the invitation. An RDF example of such an notification is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix schema: &amp;lt;http://schema.org/&amp;gt; .
@prefix : &amp;lt;https://player.solid.community/public/chess.ttl#&amp;gt;.

:invitation a schema:InviteAction .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4) The player's app does a GET to the Web ID of the opponent. If the Web ID of the player is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://player.solid.community/profile/card#me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then the corresponding &lt;code&gt;curl&lt;/code&gt; command is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl https://player.solid.community/profile/card#me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5) The opponent's POD returns RDF describing the opponent. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix : &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;.
@prefix inbox: &amp;lt;https://opponent.solid.community/inbox/&amp;gt;.
@prefix ldp: &amp;lt;http://www.w3.org/ns/ldp#&amp;gt;.
@prefix foaf: &amp;lt;http://xmlns.com/foaf/0.1/&amp;gt;.
@prefix c0: &amp;lt;https://player.solid.community/profile/card#&amp;gt;.

:me ldp:inbox inbox:;
  foaf:knows c0:me;
  foaf:name "Opponent".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;6) The player's app queries the RDF to determine the opponent's inbox via the predicate &lt;code&gt;ldp:inbox&lt;/code&gt;. An example of a SPARQL query using this predicate is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PREFIX ldp: &amp;lt;http://www.w3.org/ns/ldp#&amp;gt;

SELECT ?inbox
 HERE {
  &amp;lt;https://opponent.solid.community/profile/card#&amp;gt; ldp:inbox ?inbox.
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;7) The player's app does a POST to the inbox with the notification of the invitation. When we do a POST to an inbox a new file, containing the notification, is created in the inbox.&lt;/p&gt;

&lt;h3&gt;
  
  
  Join game
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fplsg5y0rfgi4hohos8y9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fplsg5y0rfgi4hohos8y9.png"&gt;&lt;/a&gt;&lt;br&gt;
Figure 5: sequence diagram of the steps taken when an opponent joins a game.&lt;/p&gt;

&lt;p&gt;With this action, the opponent joins the game for which he received an invitation. In Figure 5, we describe the corresponding steps between the player, the opponent, the player's app, the player's POD, the opponent's app, and the opponent's POD. The steps are:&lt;/p&gt;

&lt;p&gt;1) The opponent's app does a GET to the Web ID of the the logged-in user, which in this case is the opponent.&lt;br&gt;
2) The opponent's POD returns RDF describing the opponent. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix : &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;.
@prefix inbox: &amp;lt;https://opponent.solid.community/inbox/&amp;gt;.
@prefix ldp: &amp;lt;http://www.w3.org/ns/ldp#&amp;gt;.
@prefix foaf: &amp;lt;http://xmlns.com/foaf/0.1/&amp;gt;.
@prefix player: &amp;lt;https://player.solid.community/profile/card#&amp;gt;.

:me ldp:inbox inbox:;
  foaf:knows player:me;
  foaf:name "Opponent".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) The opponent's app queries the RDF to determine the opponent's inbox via the predicate &lt;code&gt;ldp:inbox&lt;/code&gt;. An example of a SPARQL query using this predicate is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PREFIX ldp: &amp;lt;http://www.w3.org/ns/ldp#&amp;gt;
PREFIX opponent: &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;

SELECT ?inbox
WHERE {
  opponent:me ldp:inbox ?inbox.
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4) The opponent's app does a GET to the inbox, which contains links that identify the different notifications.&lt;br&gt;
5) The opponent's POD returns RDF describing the inbox. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix inbox: &amp;lt;https://opponent.solid.community/inbox/&amp;gt;.
@prefix ldp: &amp;lt;http://www.w3.org/ns/ldp#&amp;gt;.
@prefix terms: &amp;lt;http://purl.org/dc/terms/&amp;gt;.
@prefix XML: &amp;lt;http://www.w3.org/2001/XMLSchema#&amp;gt;.
@prefix st: &amp;lt;http://www.w3.org/ns/posix/stat#&amp;gt;.

inbox:
  a ldp:BasicContainer, ldp:Container;
  terms:modified "2019-01-10T15:00:26Z"^^XML:dateTime;
  ldp:contains inbox:765a0510-14e8-11e9-a29e-5d8e3e616ac9, n0:;
  st:mtime 1547132426.207;
  st:size 4096.

inbox:765a0510-14e8-11e9-a29e-5d8e3e616ac9
  a ldp:Resource;
  terms:modified "2019-01-10T15:00:26Z"^^XML:dateTime;
  st:mtime 1547132426.207;
  st:size 1369.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;6) The opponent's app queries the RDF to determine the notifications, i.e., their links, via the class &lt;code&gt;ldp:Resource&lt;/code&gt;. An example of a SPARQL query using this predicate is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PREFIX ldp: &amp;lt;http://www.w3.org/ns/ldp#&amp;gt;

SELECT ?notification
WHERE {
  ?notification a ldp:Resource .
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The opponent's app iterates over all notifications in the inbox.&lt;br&gt;
7) The opponent's app does a GET to the link of the notification.&lt;br&gt;
8) The opponent's POD returns RDF describing the notification.&lt;br&gt;
An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix schema: &amp;lt;http://schema.org/&amp;gt; .
@prefix : &amp;lt;https://player.solid.community/public/chess.ttl#&amp;gt;.

:invitation a schema:InviteAction .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;9) The app queries the RDF to determine if the notification contains an invitation, via the class &lt;code&gt;schema:InviteAction&lt;/code&gt;. An example of a corresponding SPARQL query is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PREFIX schema: &amp;lt;http://schema.org/&amp;gt;

SELECT ?invitation
WHERE {
  ?invitation a schema:InviteAction .
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;10)If the notification contains an invitation, the opponent's app does a GET to the link of the invitation.&lt;br&gt;
11) The player's POD returns RDF describing the invitation. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix : &amp;lt;https://player.solid.community/public/chess.ttl#&amp;gt;.
@prefix schema: &amp;lt;http://schema.org/&amp;gt;.
@prefix player: &amp;lt;https://player.solid.community/profile/card#&amp;gt;.
@prefix opponent: &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;.

:invitation a schema:InviteAction;
  schema:event :game;
  schema:agent player:me;
  schema:recipient opponent:me.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;12) The opponent's app queries the RDF to determine the link of the game and the Web ID of the opponent. A corresponding SPARQL query is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PREFIX : &amp;lt;https://player.solid.community/public/chess.ttl#&amp;gt;
PREFIX schema: &amp;lt;http://schema.org/&amp;gt;   

SELECT ?game ?opponent
  WHERE {
   :invitation schema:event ?game;
     schema:agent ?opponent.
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;13) The opponent's app shows the invitation to the opponent.&lt;br&gt;
14) The opponent accepts the invitation.&lt;br&gt;
15) The opponent's app generates RDF describing the response. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix response: &amp;lt;https://opponent.solid.community/public/chess.ttl#response&amp;gt;.
@prefix invitation: &amp;lt;https://player.solid.community/public/chess.ttl#invitation&amp;gt;.
@prefix schema: &amp;lt;http://schema.org/&amp;gt;.
@prefix player: &amp;lt;https://player.solid.community/profile/card#&amp;gt;.
@prefix opponent: &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;.

response: a schema:RsvpAction;
  schema:rsvpResponse schema:RsvpResponseYes;
  schema:agent opponent:me;
  schema:recipient player:me.

invitation: schema:result response:.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;16) The opponent's app does PATCH with a SPARQL UPDATE query to store the response on the opponent's POD. An example of such a PATCH is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PREFIX response: &amp;lt;https://opponent.solid.community/public/chess.ttl#response&amp;gt;
PREFIX invitation: &amp;lt;https://player.solid.community/public/chess.ttl#invitation&amp;gt;
PREFIX schema: &amp;lt;http://schema.org/&amp;gt;
PREFIX player: &amp;lt;https://player.solid.community/profile/card#&amp;gt;
PREFIX opponent: &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;

INSERT DATA {
  response: a schema:RsvpAction;
    schema:rsvpResponse schema:RsvpResponseYes;
    schema:agent opponent:me;
    schema:recipient player:me.

  invitation: schema:result response:.
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;17) The player's app generates RDF for a notification of the response. An RDF example of such an notification is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix response: &amp;lt;https://opponent.solid.community/public/chess.ttl#response&amp;gt;.
@prefix schema: &amp;lt;http://schema.org/&amp;gt;.

response: a schema:RsvpAction.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;18) The opponent's app does a PATCH to the opponent's POD to store the relevant game data. This is mostly data to know later that the opponent is participating in the game. Details about the game are not stored on the opponent's POD, because they are stored on the player's POD and are retrievable by doing a GET to the link of the game.&lt;br&gt;
19) The opponent's app does a GET to the Web ID of the player. This Web ID is available through the invitation.&lt;br&gt;
20) The player's POD returns RDF describing the player. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix : &amp;lt;https://player.solid.community/profile/card#&amp;gt;.
@prefix inbox: &amp;lt;https://player.solid.community/inbox/&amp;gt;.
@prefix ldp: &amp;lt;http://www.w3.org/ns/ldp#&amp;gt;.
@prefix foaf: &amp;lt;http://xmlns.com/foaf/0.1/&amp;gt;.
@prefix opponent: &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;.

:me ldp:inbox inbox:;
  foaf:knows opponent:me;
  foaf:name "Player".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;21) The app queries the RDF to determine the player's inbox via the predicate &lt;code&gt;ldp:inbox&lt;/code&gt;.&lt;br&gt;
22) The app does a POST to the player's inbox with notification of the response.&lt;br&gt;
23) The player's app does a GET to the notification. For clarity we skip the iteration over the different notifications, because this is the same as earlier.&lt;br&gt;
24) The player's POD returns RDF describing the notification. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix response: &amp;lt;https://opponent.solid.community/public/chess.ttl#response&amp;gt;.
@prefix schema: &amp;lt;http://schema.org/&amp;gt;.

response: a schema:RsvpAction.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;25) The player's app queries the RDF to determine if the notification contains a response, via the class &lt;code&gt;schema:RsvpAction&lt;/code&gt;.&lt;br&gt;
26) If the notification contains a response, the app does a GET to the link of the response.&lt;br&gt;
27) The opponent's POD returns RDF describing the response. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix response: &amp;lt;https://opponent.solid.community/public/chess.ttl#response&amp;gt;.
@prefix invitation: &amp;lt;https://player.solid.community/public/chess.ttl#invitation&amp;gt;.
@prefix schema: &amp;lt;http://schema.org/&amp;gt;.
@prefix player: &amp;lt;https://player.solid.community/profile/card#&amp;gt;.
@prefix opponent: &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;.

response: a schema:RsvpAction;
  schema:rsvpResponse schema:RsvpResponseYes;
  schema:agent opponent:me;
  schema:recipient player:me.

invitation: schema:result response:.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;28) The player's app queries the response to determine the corresponding invitation, from which the corresponding game can be determined. An example of a corresponding SPARQL query is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PREFIX schema: &amp;lt;http://schema.org/&amp;gt;

SELECT ?invitation 
WHERE {
  response: schema:result ?invitation. 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;29) The player's app shows the response to the player.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do move
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F5y9t01fmmfqomgvqbw68.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F5y9t01fmmfqomgvqbw68.png" alt="Figure 6: sequence diagram of the steps taken when a player does a move."&gt;&lt;/a&gt;&lt;br&gt;
Figure 6: sequence diagram of the steps taken when a player does a move.&lt;/p&gt;

&lt;p&gt;With this action, the player does a new move, which is shown to the opponent.&lt;br&gt;
In Figure 6, we describe the corresponding steps between the player, the player's app, the player's POD, the opponent's app, and the opponent's POD. The steps are:&lt;/p&gt;

&lt;p&gt;1) The player does a move.&lt;br&gt;
2) The player's app generates RDF that describes this move. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix : &amp;lt;https://player.solid.community/public/chess.ttl#&amp;gt;.
@prefix schema: &amp;lt;http://schema.org/&amp;gt;.
@prefix chess: &amp;lt;http://purl.org/NET/rdfchess/ontology/&amp;gt;.
@prefix opponent: &amp;lt;https://opponent.solid.community/public/chess.ttl#&amp;gt;.

:game chess:hasHalfMove :move2.

:move2 a chess:HalfMove;
  schema:subEvent :game;
  chess:hasSANRecord "e3"^^xsd:string;
  chess:resultingPosition "rnbqkbnr/pppp1ppp/4p3/8/8/4P3/PPPP1PPP/RNBQKBNR w KQkq -".

opponent:move1 chess:nextHalfMove :move2.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://player.solid.community/public/chess.ttl#move2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is the link of the new move and&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://opponent.solid.community/public/chess.ttl#move1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is the link of the previous move.&lt;br&gt;
3) The player's app does a PATCH to the player's POD to store the move.&lt;br&gt;
4) The player's app generates a notification for the move. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix : &amp;lt;https://player.solid.community/public/chess.ttl#&amp;gt;.
@prefix opponent: &amp;lt;https://opponent.solid.community/public/chess.ttl#&amp;gt;.

opponent:move1 chess:nextHalfMove :move2.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5) The player's app does a POST to the opponent's POD with this notification.&lt;br&gt;
6) The opponent's app does a GET to the link of the notification.&lt;br&gt;
7) The opponent's POD returns RDF describing the move. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix : &amp;lt;https://player.solid.community/public/chess.ttl#&amp;gt;.
@prefix opponent: &amp;lt;https://opponent.solid.community/public/chess.ttl#&amp;gt;.

opponent:move1 chess:nextHalfMove :move2.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;8) The opponent's app queries the RDF to determine if the notification contains a move.&lt;br&gt;
9) The opponent's app does a GET to the link of the move.&lt;br&gt;
10) The player's POD returns RDF describing the move. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix : &amp;lt;https://player.solid.community/public/chess.ttl#&amp;gt;.
@prefix schema: &amp;lt;http://schema.org/&amp;gt;.
@prefix chess: &amp;lt;http://purl.org/NET/rdfchess/ontology/&amp;gt;.

:move2 a chess:HalfMove;
  schema:subEvent :game;
  chess:hasSANRecord "e3"^^xsd:string;
  chess:resultingPosition "rnbqkbnr/pppp1ppp/4p3/8/8/4P3/PPPP1PPP/RNBQKBNR w KQkq -".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;11) The opponent's app queries the RDF to determine the details of the move. A corresponding SPARQL query is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PREFIX : &amp;lt;https://player.solid.community/public/chess.ttl#&amp;gt;
PREFIX schema: &amp;lt;http://schema.org/&amp;gt;
PREFIX chess: &amp;lt;http://purl.org/NET/rdfchess/ontology/&amp;gt;

SELECT ?game ?san
WHERE {
  :move2 schema:subEvent ?game;
  chess:hasSANRecord ?san.
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;12) The opponent's app shows the new move to the opponent.&lt;br&gt;
13) The opponent's app stores the link of the new move on the opponent's POD. This is done to reconstruct the chess game based on the sequence of individual moves.&lt;/p&gt;
&lt;h3&gt;
  
  
  Continue game
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fedk6a9amlhkqk7arzbkd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fedk6a9amlhkqk7arzbkd.png" alt="Figure 7: sequence diagram of the steps taken when a player continues a game."&gt;&lt;/a&gt;&lt;br&gt;
Figure 7: sequence diagram of the steps taken when a player continues a game.&lt;/p&gt;

&lt;p&gt;With this action, the player continues a game that he started before. Chess does not have to be played real time, i.e., it is possible that a player does a move in the morning, but that the opponent does the next move only in the evening. Therefore, players should be able to continue a game at any point in time. In Figure 7, we describe the corresponding steps between the player, the player's app, the player's POD, and the opponent's POD. The steps are:&lt;/p&gt;

&lt;p&gt;1) The player's app does a GET to the player's Web ID.&lt;br&gt;
2) The player's POD returns RDF describing the player. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix : &amp;lt;https://player.solid.community/profile/card#&amp;gt;.
@prefix inbox: &amp;lt;https://player.solid.community/inbox/&amp;gt;.
@prefix ldp: &amp;lt;http://www.w3.org/ns/ldp#&amp;gt;.
@prefix foaf: &amp;lt;http://xmlns.com/foaf/0.1/&amp;gt;.
@prefix c0: &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;.

:me ldp:inbox inbox:;
  foaf:knows c0:me;
  foaf:name "Player".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) The player's app queries the RDF to determine the chess games in which the player participates. The app iterates over all games.&lt;br&gt;
4) The player's app does a GET to the link of each game.&lt;br&gt;
5) The player's POD returns RDF describing the game. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix : &amp;lt;https://player.solid.community/public/chess.ttl#&amp;gt;.
@prefix chess: &amp;lt;http://purl.org/NET/rdfchess/ontology/&amp;gt;.
@prefix stor: &amp;lt;http://example.org/storage/&amp;gt;.
@prefix schema: &amp;lt;http://schema.org/&amp;gt;.
@prefix player: &amp;lt;https://player.solid.community/profile/card#&amp;gt;.
@prefix opponent: &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;.

:game a chess:ChessGame;
  stor:storeIn :;
  chess:providesAgentRole :jo8deywv, :jo8deyww;
  chess:starts :jo8deywv;
  schema:name "Test game".

:jo8deywv a chess:WhitePlayerRole;
  chess:performedBy player:me.

:jo8deyww a chess:BlackPlayerRole; 
  chess:performedBy opponent:me.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;6) The player's app queries the RDF to determine the name of the game and the opponent's Web ID. An example of a corresponding SPARQL query is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PREFIX : &amp;lt;https://player.solid.community/public/chess.ttl#&amp;gt;
PREFIX chess: &amp;lt;http://purl.org/NET/rdfchess/ontology/&amp;gt;
PREFIX schema: &amp;lt;http://schema.org&amp;gt;
PREFIX player: &amp;lt;https://player.solid.community/profile/card#&amp;gt;

SELECT ?name ?opponent
WHERE {
  :game schema:name ?name;
    chess:providesAgentRole ?role.

  ?role chess:performedBy ?opponent.

  MINUS {?role chess:performedBy player:me}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;7) The player's app does a GET to the opponent's Web ID.&lt;br&gt;
8) The opponent's POD returns RDF describing the opponent. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix : &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;.
@prefix inbox: &amp;lt;https://opponent.solid.community/inbox/&amp;gt;.
@prefix ldp: &amp;lt;http://www.w3.org/ns/ldp#&amp;gt;.
@prefix foaf: &amp;lt;http://xmlns.com/foaf/0.1/&amp;gt;.
@prefix player: &amp;lt;https://player.solid.community/profile/card#&amp;gt;.

:me ldp:inbox inbox:;
  foaf:knows player:me;
  foaf:name "Opponent".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;9) The player's app queries the RDF to determine the name of the player via the predicate &lt;code&gt;foaf:name&lt;/code&gt;. An example of SPARQL query using this predicate for an friend with the Web ID&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://opponent.solid.community/profile/card#me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PREFIX foaf: &amp;lt;http://xmlns.com/foaf/0.1/&amp;gt;
PREFIX opponent: &amp;lt;https://opponent.solid.community/profile/card#&amp;gt;

SELECT ?name 
WHERE {
  opponent:me foaf:name ?name.
} 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;10) The player selects the game he wants to continue.&lt;br&gt;
11) The player's app iterates over all moves of the selected game.&lt;br&gt;
12) If the move is from the player then a GET to the link of the move by the player's app goes to the player's POD.&lt;br&gt;
13) The player's POD returns RDF describing the move. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix : &amp;lt;https://player.solid.community/public/chess.ttl#&amp;gt;.
@prefix chess: &amp;lt;http://purl.org/NET/rdfchess/ontology/&amp;gt;.
@prefix schema: &amp;lt;http://schema.org&amp;gt;.

:move2 a chess:HalfMove;
  schema:subEvent :game;
  chess:hasSANRecord "e3"^^xsd:string;
  chess:resultingPosition "rnbqkbnr/pppp1ppp/4p3/8/8/4P3/PPPP1PPP/RNBQKBNR w KQkq -".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;14) If the move is from the opponent then a GET to the link of the move by the player's app goes to the opponent's POD.&lt;br&gt;
15) The opponent's POD returns RDF describing the move. An example of such RDF is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@prefix : &amp;lt;https://opponent.solid.community/public/chess.ttl#&amp;gt;.
@prefix player: &amp;lt;https://opponent.solid.community/public/chess.ttl#&amp;gt;.
@prefix chess: &amp;lt;http://purl.org/NET/rdfchess/ontology/&amp;gt;.
@prefix schema: &amp;lt;http://schema.org&amp;gt;.

:move1 a chess:HalfMove;
  schema:subEvent :game;
  chess:hasSANRecord "e6"^^xsd:string;
  chess:resultingPosition "rnbqkbnr/pppp1ppp/4p3/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -";
  chess:nextHalfMove player:move2.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;16) The player's app adds the move to the instance of the game.&lt;br&gt;
17) The player's app shows the game to the player.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond this app
&lt;/h2&gt;

&lt;p&gt;The data generated by this app is not tied to this specific app, because the data is materialized as RDF and follows the Linked Data principles. Furthermore, it is stored in the PODs of the players, which the players themselves control. As a result, the data can be used by other apps, completely independent of the app described in this blog post. Examples of such other apps are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;other chess apps which can continue an existing game&lt;/li&gt;
&lt;li&gt;leaderboard apps that list the best players&lt;/li&gt;
&lt;li&gt;artificial intelligence apps that analyze the moves of a player with the goal to provide suggestions to the player as how to improve their skills&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you have any questions or remarks, don’t hesitate to contact me via &lt;a href="//mailto:pheyvaer.heyvaert@ugent.be"&gt;email&lt;/a&gt; or via &lt;a href="https://twitter.com/phadventure" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>solid</category>
      <category>decentralization</category>
      <category>interaction</category>
      <category>linkeddata</category>
    </item>
    <item>
      <title>Add content negotiation to website when using NGINX</title>
      <dc:creator>Pieter Heyvaert</dc:creator>
      <pubDate>Fri, 01 Mar 2019 06:51:18 +0000</pubDate>
      <link>https://dev.to/heypieter/add-content-negotiation-to-website-when-using-nginx-4j07</link>
      <guid>https://dev.to/heypieter/add-content-negotiation-to-website-when-using-nginx-4j07</guid>
      <description>&lt;p&gt;co-authored by &lt;a href="https://twitter.com/julianr1987" rel="noopener noreferrer"&gt;Julián Rojas&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Content_negotiation" rel="noopener noreferrer"&gt;Content negotiation&lt;/a&gt; (conneg) is an HTTP mechanism that makes it possible to serve different representations of a resource at the same URI. This allows agents to specify which representation of a resource they prefer.For example, on the one hand, when a browser opens &lt;code&gt;http://example.com/house&lt;/code&gt; it will ask for an HTML document from the server.On the other hand, another application that works with XML documents, can request the same resource at &lt;code&gt;http://example.com/house&lt;/code&gt;, but the application will ask for the resource's XML representation. In this blog post we will explain how you can achieve conneg for your website when using &lt;a href="https://www.nginx.com/" rel="noopener noreferrer"&gt;NGINX&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We want to achieve the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use NGINX as reverse proxy.&lt;/li&gt;
&lt;li&gt;Serve a static website, which has different documents for the same resource.&lt;/li&gt;
&lt;li&gt;Enable conneg for that website.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, we have a look at the tools we use. Second, we discuss the setup and configuration. Finally, we show two live examples and summarize this blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;NGINX does not support conneg out of the box. There are tutorials and blog posts available that explain how to achieve some kind of conneg, but they are heavily tied to a specific use case. Thus, they require significant changes to make it work for your use case and are not easily reusable. If NGINX doesn't support conneg, then why not just pick something else. Well, although NGINX lacks this feature, it does work great for the other things such as high performance, caching, acting as a reverse, and so on.&lt;/p&gt;

&lt;p&gt;So what do we use to enable conneg? We use a &lt;a href="https://github.com/pheyvaer/http-server" rel="noopener noreferrer"&gt;fork&lt;/a&gt; of &lt;a href="https://github.com/indexzero/http-server" rel="noopener noreferrer"&gt;http-server&lt;/a&gt;.&lt;br&gt;
http-sever is "a simple, zero-configuration command-line http server" build using Node.js. We created a fork that supports conneg. In more details: the server looks for the correct file depending on the MIME Types in the Accept header. MIME Types are linked to their corresponding extensions. For example, when you do a request to &lt;code&gt;http://localhost:8080/test&lt;/code&gt; with Accept header &lt;code&gt;text/turtle&lt;/code&gt;, the server looks for the file &lt;code&gt;/test.ttl&lt;/code&gt;. When you do a request to &lt;code&gt;http://localhost:8080/test&lt;/code&gt; with Accept header &lt;code&gt;application/n-triples&lt;/code&gt;, the server looks for the file &lt;code&gt;/test.nt&lt;/code&gt;. &lt;br&gt;
Accept headers with multiple types, optionally weighted with a quality value, are also supported. For example, when you do a request to &lt;code&gt;http://localhost:8080/test&lt;/code&gt; with Accept header &lt;code&gt;text/turtle;q=0.5, application/n-triples&lt;/code&gt;, the server looks for the file &lt;code&gt;/test.nt&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1rgow6gh2ajzrt94ibfu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1rgow6gh2ajzrt94ibfu.jpg" alt="All requests to our website are forwarded to the http-server where conneg is preformed. NGINX is not aware of conneg."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can find an overview of the different components of our setup in the figure above. We have three services running: NGINX, the http-server, and a service X. The latter service can be anything, and there is no limit to the number of services. All requests are received by NGINX and the ones for our website, for which we want conneg,  are redirected to the http-server. NGINX is unaware of any conneg, as all requests are just forwarded to the http-server. Service X is added to show that other services behind NGINX are also unaware of the conneg, as all conneg logic is handled in the http-server. Of course, service X can also support conneg, but that does not influence the (conneg of the) http-server.&lt;/p&gt;
&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;We need to configure both NGINX and the http-server in order for both to work together. The relevant part of the NGINX config looks as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
        proxy_pass http://127.0.0.1:4000/;
        proxy_redirect off;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important line is &lt;code&gt;proxy_pass http://127.0.0.1:4000/;&lt;/code&gt; where it says that requests are forwarded to the http-server, which is running on the same server as NGINX and on port 4000. Of course, you can run it on another server and port.&lt;/p&gt;

&lt;p&gt;When you enable caching via NGINX together with conneg, it is important to add the Accept header to the &lt;a href="http://nginx.org/en/docs/http/ngx_http_proxy_module.html?&amp;amp;_ga=2.191697123.859341525.1551188069-1620149709.1547735724#proxy_cache_key" rel="noopener noreferrer"&gt;&lt;code&gt;proxy_cache_key&lt;/code&gt;&lt;/a&gt;, for example, via &lt;code&gt;proxy_cache_key "$request_uri $http_accept";&lt;/code&gt;. If you don't do this, then the Accept header is not considered for the cache, because the default value for &lt;code&gt;proxy_cache_key&lt;/code&gt; is &lt;code&gt;$scheme$proxy_host$request_uri&lt;/code&gt;. Note that &lt;code&gt;$http_accept&lt;/code&gt; is missing. As a result, once a cached version of a resource is available, that cached version is returned regardless of the value of the Accept header in new requests.&lt;/p&gt;

&lt;p&gt;The http-server is running on port 4000, with conneg and the automatic addition of trailing slashes enabled, in silent mode. This is achieved via &lt;code&gt;http-server -P 4000 --conneg --trailing -s&lt;/code&gt; where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-P 4000&lt;/code&gt;: use port 4000,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--conneg&lt;/code&gt;: enable conneg,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--trailing&lt;/code&gt;: enabled automatic addition of trailing slashes, and&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-s&lt;/code&gt;: enable silent mode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more details on the available parameters, we refer to the &lt;a href="https://github.com/pheyvaer/http-server/blob/master/README.md" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;The setup described above is already available in the wild: the descriptions of &lt;a href="https://pieterheyvaert.com" rel="noopener noreferrer"&gt;Pieter Heyvaert&lt;/a&gt; and the &lt;a href="https://velopark.ilabt.imec.be/openvelopark/vocabulary" rel="noopener noreferrer"&gt;Open Velopark Ontology&lt;/a&gt; are available in different representations through conneg.&lt;/p&gt;

&lt;h3&gt;
  
  
  Describing a person via HTML and RDF
&lt;/h3&gt;

&lt;p&gt;A person's website main goal is to provide information about that person to others. When browsing to that website, an HTML document is downloaded from the server and displayed. However, applications might prefer another representation that is easier for them to process. By offering conneg for a persons's website, applications can decide which representation to request. For example, browsers request an HTML representation to display to users, while Linked Data applications request an RDF representation to easily process the data.&lt;/p&gt;

&lt;p&gt;Basic information about Pieter has been made available in both HTML and RDF (serialized in Turtle and RDF/XML) through conneg, using the setup described above.&lt;/p&gt;

&lt;p&gt;When you do a GET to &lt;code&gt;https://pieterheyvaert.com/#me&lt;/code&gt; without an Accept header, an HTML document is returned that provides information about Pieter, such as topics of interest and ongoing work. Try it out yourself by browsing to &lt;code&gt;https://pieterheyvaert.com/#me&lt;/code&gt; or via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl https://pieterheyvaert.com/#me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you do a GET with the Accept header &lt;code&gt;text/turtle&lt;/code&gt;, RDF in Turtle format that describes Pieter is returned. Try it out yourself via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -H 'accept: text/turtle' https://pieterheyvaert.com/#me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, when you do a GET with the Accept header &lt;code&gt;application/rdf+xml&lt;/code&gt;, RDF in XML format is returned. Try it out yourself via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -H 'accept: application/rdf+xml' https://pieterheyvaert.com/#me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explaining an ontology via HTML and RDF
&lt;/h3&gt;

&lt;p&gt;Ontologies provide structured descriptions of domain models. They define the different concepts and elements of a certain domain and describe how they are related to each other. Take for example the &lt;a href="https://velopark.ilabt.imec.be/openvelopark/vocabulary" rel="noopener noreferrer"&gt;Open Velopark Ontology&lt;/a&gt;. This ontology provides a description of concepts and properties related to bicycle parkings. This description can be used, for example, by people wanting to know what different types of bike parkings are contemplated in the domain model or by machines to automatically discover and reason over data about bike parkings. &lt;/p&gt;

&lt;p&gt;The information contained in the ontology needs to be delivered in the right format for the right audience. For humans using a browser, a HTML representation would be the most appropriate format while for machines, a machine-readable format (e.g., RDF, JSON, XML and so on) would probably be the best fit. This is where conneg comes into play. By exposing different representations of the same resource through its URI, we are saying that anyone and anything can request the information contained in that resource using the format they need (if available of course). &lt;/p&gt;

&lt;p&gt;The Velopark Ontology has been made available in both HTML and RDF (serialized in Turtle) through conneg, using the setup described above.&lt;/p&gt;

&lt;p&gt;When you do a GET to &lt;code&gt;http://velopark.ilabt.imec.be/openvelopark/vocabulary&lt;/code&gt; without an Accept header, an HTML document is returned that provides information about the ontology. Try it out yourself by browsing to &lt;code&gt;http://velopark.ilabt.imec.be/openvelopark/vocabulary&lt;/code&gt; or via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl http://velopark.ilabt.imec.be/openvelopark/vocabulary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you do a GET with the Accept header &lt;code&gt;text/turtle&lt;/code&gt;, RDF in Turtle format that describes the ontology is returned. Try it out yourself via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -H 'accept: text/turtle' http://velopark.ilabt.imec.be/openvelopark/vocabulary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this blog post we show that we can add conneg to a website by using NGINX together with the http-server. Both NGINX and the http-server can be easily configured to support this. Different documents corresponding with the different representations for each resource are served by the http-server depending on the Accept header of the request. NGINX and other services running behind it are unaware of conneg.&lt;/p&gt;




&lt;p&gt;If you have any questions or remarks, don’t hesitate to contact me via &lt;a href="//mailto:pheyvaer.heyvaert@ugent.be"&gt;email&lt;/a&gt; or via &lt;a href="https://twitter.com/PHaDventure" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Originally published at &lt;a href="https://pieterheyvaert.com/blog/2019/02/25/nginx-conneg/" rel="noopener noreferrer"&gt;pieterheyvaert.com&lt;/a&gt; on February 25, 2019.&lt;/p&gt;

</description>
      <category>contentnegotation</category>
      <category>nginx</category>
      <category>httpserver</category>
      <category>acceptheader</category>
    </item>
  </channel>
</rss>
