{"id":13892633,"url":"https://github.com/solid-contrib/solid-tutorial-intro","last_synced_at":"2025-07-17T05:31:52.404Z","repository":{"id":114034619,"uuid":"49514041","full_name":"solid-contrib/solid-tutorial-intro","owner":"solid-contrib","description":"Introductory tutorial to Solid","archived":true,"fork":false,"pushed_at":"2019-10-03T11:14:04.000Z","size":26,"stargazers_count":175,"open_issues_count":2,"forks_count":16,"subscribers_count":32,"default_branch":"master","last_synced_at":"2024-05-21T20:12:44.619Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/solid-contrib.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-01-12T16:41:22.000Z","updated_at":"2024-07-26T04:53:42.990Z","dependencies_parsed_at":null,"dependency_job_id":"d26d5d62-31eb-43db-9ac2-97a0416e04a4","html_url":"https://github.com/solid-contrib/solid-tutorial-intro","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solid-contrib%2Fsolid-tutorial-intro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solid-contrib%2Fsolid-tutorial-intro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solid-contrib%2Fsolid-tutorial-intro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/solid-contrib%2Fsolid-tutorial-intro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/solid-contrib","download_url":"https://codeload.github.com/solid-contrib/solid-tutorial-intro/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226185769,"owners_count":17587032,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-06T17:01:07.196Z","updated_at":"2024-11-24T20:30:49.569Z","avatar_url":"https://github.com/solid-contrib.png","language":null,"readme":"# Building Decentralized Applications for the Social Web\n\n## WWW 2016 Tutorial, Tuesday 12 April\n\n### Introduction\n\nThis morning we will introduce you to the ideas behind personal datastores and\nhow to build applications that use data in people's personal datastores using\nthe Solid protocols. After the coffee break we'll do a walkthrough of an example\napplication, and this afternoon you're free to build your own applications.\n\n### Decentralization: Motivation and Principles\n\nDeveloping a technology stack that enables truly *user-centric* de-centralized\napplications can drastically improve the life of both users and app developers:\n\n* Avoids vendor lock in (and the tendencies towards monopolies, walled gardens,\n  surveillance, and other abuses of privilege that go along with that)\n\n* Promotes healthy competition and diversity in app development --\n  because the user's data is separated from a particular service provider or\n  application, users are free to choose their preferred way of interacting with\n  their data\n\n* The user, instead of the service provider, owns all of their own data\n\n* Helps prevent data loss (in case a provider gets acquired, or decides to\n  discontinue a particular service)\n\n* Encourages the use of standard inter-operable data formats, which\n  provides a richer user experience\n\n#### Logic Separated from Data\n\nConsider the flowering of innovative and interesting features that resulted from\nthe various [mashups](http://www.programmableweb.com/mashups) and the general\n\"Web 2.0\" trend of having service providers offer access to *some* subset of\ntheir users' data as simple REST APIs. And that was with non-standard APIs,\nproprietary and frequently incompatible data formats, using only the tightly\ncontrolled and metered functionality that the service providers allowed.\n\nNow imagine instead, if *users* had full control over their data (all of the\ndata that currently belongs to siloed service providers), and could select which\napps were allowed to access it (using standardized APIs). This is one of the\ncore goals of the Solid project.\n\n#### Interoperability\n\nOf course, separating applications from data (with the aim of giving users more\nchoices in terms of which apps they can use) is only possible when that data\nis written in standard, mutually-intelligible formats.\n\nBy focusing on the re-use of standard protocols, formats and vocabularies, and\nby providing a common data access API (as well as an Access Control List\nformat), Solid enables and encourages interoperability.\n\n### Protocols and APIs\n\nWe mentioned that applications that read, create and manipulate data are\ncompletely decoupled from where the data is stored. This means that applications\nand datastores (servers) need a common protocol to speak to each other.\n\nDatastores use a RESTful API, and store data as RDF. Every *resource* in the\ndata has a URL, whether this is a document, an image, a person, an abstract\nconcept, or a relationship between two resources. Applications can use HTTP to\ncreate, read, update and delete resources in a datastore. Resources can be\norganized hierarchically in *containers*, which can be thought of as\nfolders/directories.\n\nSimply, to get the data in any resource (the attributes and their values),\nan application does a GET request on the URL of the resource. For all of the\nresources in a container, a GET request on the container returns a list. For an\napplication to create a new resource in a container, make an HTTP POST request\nto the container itself with the attributes of that resource, and an optional\nslug. To update a resource (or a container) use PUT or PATCH and to delete use\nDELETE.\n\n| Action | Path | Description |\n|--------|------|------------------------|\n| `GET` | `/path/to/resource` | Retrieve a resource |\n| `GET` | `/path/to/container/` | List of resources in a container |\n| `POST` | `/path/to/parent/` | Create a new resource in container |\n| `PUT` | `/path/to/resource` | Replace a resource |\n| `PATCH` | `/path/to/resource` | Update a resource |\n| `DELETE` | `/path/to/resource` | Delete a resource |\n\nThe data that is sent and retrieved is RDF; the RDF serialization we use by\ndefault is Turtle. Don't worry if you're not familiar with this format, we're\ngoing to show you handy libraries for working with it. If you want to find out\nmore about Linked Data principles you can see **[Linked Data\n101](http://rhiaro.github.io/sws/)** for a quick run down.\n\nThis API is a W3C standard called [Linked Data Platform\n(LDP)](http://www.w3.org/TR/ldp/) (see also the\n[LDP Primer](http://www.w3.org/TR/ldp-primer/)).\n\n### Schemas and Vocabularies\n\nYou're used to describing data in a database with a schema, a set of terms that\ndescribe attributes of a data item for which you can set values. For global\ninteroperability on the Web, we all need to find a way to cooperate when\nchoosing schemas to represent our data.\n\nFortunately many people publish RDF vocabularies which cover different domains,\nand the idea is to reuse existing ones as much as possible. All terms in a\nvocabulary (just like any other data item) have their own URL. This ensures that\nwhen two people use the same term they can use a globally unique identifier to\nshow they mean the same thing.\n\n* **Vocabularies** or ontologies are a set of words and phrases that someone has\ndecided to put together for the purposes of describing a particular topic. They\nusually consist of *classes* and *properties*.\n\n* **Classes** are used to say that `\u003csome-thing\u003e` is of type `\u003cother-thing\u003e`.\nFor example: `\u003chttp://rhiaro.co.uk/about#me\u003e type \u003cPerson\u003e`. (Person is the\nclass).\n\n* **Properties** are the attributes. In the previous example `type` is\nthe property.\n\n* **Anyone can publish** a vocabulary, and you can use vocabularies that anyone\nelse has published. There are lots out there on the web, some better than\nothers.\n\nThere are well-known, stable vocabularies. Examples include: RDF and RDFS\n(which provide the basics for describing anything), FOAF (for describing people\nand their friends), Dublin Core (for describing publications, mostly),\n[schema.org](https://schema.org) (covers a broad variety of things).\n\nA good place to start looking for domain-specific terms to reuse is\n[LOV](http://lov.okfn.org).\n\nVocabularies specifically for social applications you might be interested in\ninclude:\n\n* [FOAF](http://xmlns.com/foaf/0.1/) (friend-of-a-friend) - people and the\n  relations between them).\n* SIOC (semantically-interlinked online communities) - some basic social stuff,\n  simple but a little dated and based on forums / message board kinds of data.\n* [ActivityStreams 2.0](https://www.w3.org/TR/activitystreams-core/) - an emerging W3C\n  standard for describing most of the kinds of social data we see on the web\n  today.\n\n### Users, Authentication and Access Control\n\nSolid uses WebID URLs as unique user IDs. This means that no matter where on the\nweb a user is interacting, they have one identifier which they can use to sign\ninto their personal datastore.\n\n* Not just humans allowed: Apps and services can have their own WebIDs, or\n  borrow a user's WebID when needed (through delegation)\n\n* A WebID URL, when fetched, must return a valid WebID User Profile\n  (a FOAF document in RDF format)\n\n* Browser-side certificates are currently used for authentication, instead of\n  passwords\n\n* You need a WebID profile and related certificate on a Solid-compatible storage\n  provider to use any Solid-based apps.\n\nHow can we decide who has access to a particular resource? Since we have\nidentities, we could use them to define who has read or write access to\nresources. These lists are called ACL (Access Control List), and they follow the\n[Web Access Control spec](https://github.com/solid/web-access-control-spec)\n\n## Pastebin Example\n\nThis example will walk you through the steps required to build a typical Solid\napp. You will learn how to:\n\n- create new resources (pastebins)\n- view created bins\n- update existing bins\n\n### Preparing the App\n\nBefore we start writing the functions that create and view bins, we need to\ndecide how the app will work. For instance, we know that we will use the app to\ncreate new bins, to view existing ones and also to update them.\n\nThe easiest way to separate these different app functionalities, is to separate\nthe states by using query parameters -- i.e. `?view=...`, `?edit=...`.\n\nAnother important step we need to do right now is to add the required\ndependencies. These are [rdflib.js](https://github.com/linkeddata/rdflib.js) and\n[solid.js](https://github.com/solid/solid.js).\n\n### Local Data Structure\n\nNext, we can define how we want to structure our bins. For example, a bin could\nhave a title and body (content), but also a URI (useful later for updates).\n\n```js\n// Bin structure\nvar bin = {\n    url: '',\n    title: '',\n    body: ''\n}\n```\n\n### Picking the Vocabularies\n\nFor this particular app, we can go with two very common vocabularies, `SIOC` and\n`Dublin Core Terms`. You can see their usage in the `load()` function, for\nexample. Specifically, we'll be using the `title` predicate (referred to as\n`vocab.dct('title')` in the code), and the `content` predicate (as\n`vocab.sioc('content')`).\n\n### Deciding Where to Store New Bins\n\nTo create bins we have to define a default container to which we POST the bin.\nFor now we can create a global variable called `defaultContainer`. The value of\nthis variable can be a `bins`container on your account, which you have created\nwith Warp.\n\n```js\nvar defaultContainer = 'https://user.databox.me/Public/bins/'\n```\n\n### UI/HTML\n\nThe UI is quite minimalistic. A couple of divs, one for the viewer and one for\nthe editor. The current example lacks CSS definitions for some classes (e.g.\n`hidden`), which you can get from the Github repo (link below).\n\n```html\n\u003cdiv class=\"content center-text hidden\" id=\"view\"\u003e\n    \u003ch1 id=\"view-title\"\u003e\u003c/h1\u003e\n    \u003cbr\u003e\n    \u003cdiv id=\"view-body\"\u003e\u003c/div\u003e\n\u003c/div\u003e\n\n\u003cdiv class=\"content center-text hidden\" id=\"edit\"\u003e\n    \u003cinput type=\"text\" id=\"edit-title\" class=\"block\" placeholder=\"Title...\"\u003e\n    \u003cbr\u003e\n    \u003ctextarea id=\"edit-body\" class=\"block\" placeholder=\"Paste text here...\"\u003e\u003c/textarea\u003e\n    \u003cbr\u003e\n    \u003cbutton id=\"submit\" class=\"btn\"\u003ePublish\u003c/button\u003e\n\u003c/div\u003e\n```\n\nNow let's prepare a few functions that make the bread and butter of our app.\n\n### Creating new bins\n\nCreating new bins is easy. It involves using `solid.js` to post the bin data to\nthe defaultContainer. We can create a `publish()` function which will handle\nthis step. This function will read the value of the title and body elements from\nthe editor.\n\n```js\nfunction publish () {\n    bin.title = document.getElementById('edit-title').value\n    bin.body = document.getElementById('edit-body').value\n}\n```\n\nNext, we will create a new graph object, in which we'll add the two triples, one\nfor the title and one for the body. We will also store the serialized value of\nthe graph in a new `data` variable.\n\n```js\nfunction publish () {\n    bin.title = document.getElementById('edit-title').value\n    bin.body = document.getElementById('edit-body').value\n\n    var graph = $rdf.graph()\n    var thisResource = $rdf.sym('')\n    graph.add(thisResource, vocab.dct('title'), $rdf.lit(bin.title))\n    graph.add(thisResource, vocab.sioc('content'), $rdf.lit(bin.body))\n    var data = new $rdf.Serializer(graph).toN3(graph)\n}\n```\n\nFinally, we will use `solid.js` to POST the new bin to the server. If the POST\nsucceeded, we will update the URL bar to the viewer. Else, we will have to deal\nwith the error.\n\nHere is the final function.\n\n```js\nfunction publish () {\n    bin.title = document.getElementById('edit-title').value\n    bin.body = document.getElementById('edit-body').value\n\n    var graph = $rdf.graph()\n    var thisResource = $rdf.sym('')\n    graph.add(thisResource, vocab.dct('title'), $rdf.lit(bin.title))\n    graph.add(thisResource, vocab.sioc('content'), $rdf.lit(bin.body))\n    var data = new $rdf.Serializer(graph).toN3(graph)\n\n    solid.web.post(defaultContainer, data).then(function(meta) {\n        // view\n        window.location.search = \"?view=\"+encodeURIComponent(meta.url)\n    }).catch(function(err) {\n        // do something with the error\n        console.log(err)\n    });\n}\n```\n\n### Reading/viewing bins\n\nNow that we have a way to create new bins, let's create a corresponding function\nthat reads and displays bins.\n\nWe can use `solid.js` to fetch the contents of a bin. Let's call this fetching\nfunction `load`. Our function will take two parameters - one is the URL of the\nbin, and the other one is a flag that indicates whether we need to display the\neditor or not.\n\n```js\nfunction load (url, showEditor) {\n    solid.web.get(url).then(function(response) {\n\n    }).catch(function(err) {\n        // do something with the error\n        console.log(err)\n    });\n}\n```\n\nThe function `solid.web.get()` returns a response on success, from which we\ncan get a parsed graph object (parsed via\n[rdflib.js](https://github.com/linkeddata/rdflib.js/)). This graph object has a\nfew methods that allow us to query the graph for specific triples - i.e.\n`graph.any()`. We'll use this method to get the title and body of a bin.\n\n```js\nfunction load (url, showEditor) {\n    solid.web.get(url).then(function(response) {\n        var graph = response.parsedGraph();\n        // set url\n        bin.url = response.url;\n        var subject = $rdf.sym(response.url);\n        // add title\n        var title = graph.any(subject, vocab.dct('title'));\n        if (title) {\n            bin.title = title.value;\n        }\n        // add body\n        var body = graph.any(subject, vocab.sioc('content'));\n        if (body) {\n            bin.body = body.value;\n        }\n    }).catch(function(err) {\n        // do something with the error\n        console.log(err)\n    });\n}\n```\n\nNow that we have set our `bin` object with the right values, we can update the\nview, depending on whether or not we have to display the editor.\n\nHere is the final version of our `load` function.\n\n```js\nfunction load (url, showEditor) {\n    solid.web.get(url).then(function(g) {\n        var graph = response.parsedGraph();\n        // set url\n        bin.url = response.url;\n        var subject = $rdf.sym(response.url);\n        // add title\n        var title = graph.any(subject, vocab.dct('title'));\n        if (title) {\n            bin.title = title.value;\n        }\n        // add body\n        var body = graph.any(subject, vocab.sioc('content'));\n        if (body) {\n            bin.body = body.value;\n        }\n\n        if (showEditor) {\n            document.getElementById('edit-title').value = bin.title\n            document.getElementById('edit-body').innerHTML = bin.body\n            document.getElementById('submit').setAttribute('onclick', 'update()')\n            document.getElementById('edit').classList.remove('hidden')\n        } else {\n            document.getElementById('view-title').innerHTML = bin.title\n            document.getElementById('view-body').innerHTML = bin.body\n            document.getElementById('view').classList.remove('hidden')\n            document.getElementById('edit').classList.add('hidden')\n        }\n    }).catch(function(err) {\n        // do something with the error\n        console.log(err);\n    });\n}\n```\n\n### Updating bins\n\nUpdating bins is very similar to creating new ones. The only difference is that we use the URI of an existing bin, which we then pass to `solid.js`' `solid.web.put()` function.\n\n```js\nfunction update () {\n    bin.title = document.getElementById('edit-title').value\n    bin.body = document.getElementById('edit-body').value\n\n    var graph = $rdf.graph();\n    var thisResource = $rdf.sym('');\n    graph.add(thisResource, vocab.dct('title'), bin.title);\n    graph.add(thisResource, vocab.sioc('content'), bin.body);\n    var data = new $rdf.Serializer(graph).toN3(graph);\n\n    solid.web.put(bin.url, data).then(function(meta) {\n        // view\n        window.location.search = \"?view=\"+encodeURIComponent(meta.url)\n    }).catch(function(err) {\n        // do something with the error\n        console.log(err)\n    });\n}\n```\n\n### Routing different app states\n\nGreat, so now we have all the important functions in place. Let's put everything\ntogether! We know we can use the app to create and also to view bins. The final\nstep now is to add the app logic which handles these different states. For that\nwe need a utility function that parses the current URL and returns different\nquery parameters.\n\nYou can go ahead and just paste the following function in your app.\n\n```js\n// Utility function to parse URL query string values\nvar queryVals = (function(a) {\n    if (a == \"\") return {}\n    var b = {}\n    for (var i = 0; i \u003c a.length; ++i)\n    {\n        var p=a[i].split('=', 2)\n        if (p.length == 1) {\n            b[p[0]] = \"\"\n        } else {\n            b[p[0]] = decodeURIComponent(p[1].replace(/\\+/g, \" \"))\n        }\n    }\n    return b\n})(window.location.search.substr(1).split('\u0026'))\n```\n\nYou can call this function every time you need to check that a query parameter\nis present and to get its value.\n\nNext we can go ahead and create an `init()` function where we handle our\n\"routing\". This function is in charge of parsing the current URL and either\ndisplay the editor or load the viewer. It also sets the `onclick()` event on the\nsubmit button, depending on whether the editor is used to create a new bin or to\nupdate an old one.\n\n```js\nfunction init() {\n    if (queryVals['view'] \u0026\u0026 queryVals['view'].length \u003e 0) {\n        load(queryVals['view'])\n    } else if (queryVals['edit'] \u0026\u0026 queryVals['edit'].length \u003e 0) {\n        load(queryVals['edit'], true)\n    } else {\n        document.getElementById('submit').setAttribute('onclick', 'publish()')\n        document.getElementById('edit').classList.remove('hidden')\n    }\n}\n```\n\nThis function will be called at the bottom of our app, after all the other\nfunctions have been defined.\n\nThis is it, you're all set! You can use the following link for the source files\nof the full example app:\n[solid-tutorial-pastebin](https://github.com/solid/solid-tutorial-pastebin/)\n\nSee also [\"user posts a note\"\nexample](https://github.com/solid/solid-spec#example).\n","funding_links":[],"categories":["Others"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsolid-contrib%2Fsolid-tutorial-intro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsolid-contrib%2Fsolid-tutorial-intro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsolid-contrib%2Fsolid-tutorial-intro/lists"}