So, we've talked about Solid already I think. It's Tim Berners-Lee's latest project, an undertaking towards re-decentralizing the web and ensuring privacy. Somewhat similar to Urbit, it's a personal data/cloud server called a pod (you can get one here) — with access control permissions and transparency in how things work. It's one of those projects that are loosely in the same category as Holochain, Urbit, Scutterbutt, etc.
Upon signing up for a pod, remember your Solid Pod URL and WebID, which you will use to log in the various Solid apps and authorize their access control privileges.
But what makes Solid special is its extensive use of Linked Data (also covered more extensively in a previous recent post on web ontologies). What Solid implements as a built-in capability with semantic technologies like Linked Data and JSON-LD is the faculty to translate raw data into meaningful information. And the way it makes sense of it is by organizing and cataloging it into subject-predicate-object statements about the world (called triples in RDF). And the scope/reach of a globally distributed web of actor-networks with near-instant speed of communication in this case would enable unprecedented acquisition of knowledge beyond the limits of our human horizons.
Linked data is represented in RDF using the Turtle syntax to formulate triples. Basically, writing Turtle comes down to writing down the three components of a Linked Data link: the subject (source of the link), the predicate (the link type), and the object (target of the link). Those three together are called a triple. Here's an example of a triple:
<https://janespod.solid/profile/card#me> <http://xmlns.com/foaf/0.1/name> "Jane Doe"@en.
We bracket URLs with <> and literal values with quotation marks "", the @en tag indicates the literal use of English language and the dot ends the triple. The above triple translates as "Jane's full name is Jane Doe."
Manipulating Linked Data with rdflib.js
The easiest way to work with linked data in Solid is by using a library called rdflib.js — a toolbox for doing most things related to linked data (e.g., store, parse, serialize into different formats, keep track of changes coming from an app or a server, etc.)
To install rdflib, use the npm package manager:
npm install rdflib --save
Afterwards you'll need to include the following line in your code:
const $rdf = require(‘rdflib’)
A store is a data structure for storing graph data (linked data) and that queries can be performed against. To set up a store:
const store = $rdf.graph();
And these are the types of nodes in the RDF graph (called terms as they are like terms in a language, assuming RDF to be a language).
What How Term type Node identified by a URI x = $rdf.sym(uri) symbol Blank node x = $rdf.bnode() 'bnode' Untyped Literal x = $rdf.literal('abc') literal Typed Literal x = $rdf.literal('8080', undefined, XSD('int')) literal Literal with language x = $rdf.literal('car', 'en') literal Ordered list x = $rdf.list([node1, node2]) collection
There are two ways to look at RDF data in a store — either synchronously use the each() and any() methods and statementsMatching(), or do a query (returning results asynchronously). The each(), any() and statementsMatching() take a pattern of subject, predate, object and source, where for each() and any() one of the components in the triple are undefined and source may be undefined or not. For example, using $rdf.sym() to make an object for an RDF node (symbol),
var me = $rdf.sym('https://www.w3.org/People/John-Doe/card#i'); var knows = FOAF('knows') var friend = store.any(me, knows) // Any one person
The add(s, p, o, w) method allows a statement to be added to a formula. The optional w argument can be used to keep track of which resource was the source (URI) for each triple.
A full tutorial of rdflib.js is found here.
Writing a Solid App
Writing a Solid app involves the authentication of the user's WebID, the setting up a data model and the reading and writing of data on a user's pod. There's sufficient tooling and Solid-specific libraries available which make the process fairly easy. And, of course, it all involves a lot of handling of linked data and RDF triples. Solid apps are usually written in either Angular or React (Javascript) web frameworks.
The easiest way to get started is to use the solid-angular yeoman generator. Yeoman is a scaffolding tool that will install all the basic files, folders, and dependencies that you will need to start coding right away. The solid-angular generator for that purpose can be found here.
In a command line window, follow these steps:
- $ npm install -g install @inrupt/generator-solid-angular
- Go to the root project folder the app is to live in
- $ yo @inrupt/solid-angular
- Set an application name / folder name
- Angular files and dependencies are installed with a sample application ready to go
Once these steps are complete, you will have a sample application showing the basics of a Solid app. It will be able to login users in via Solid, and authenticate, fetch data from a pod, and update or delete data from a pod. You can start the application using angular-cli as usual, simply by using ng serve.
1. Authentication
The starting point for any Solid Web App is to obtain the user’s WebID. The WebID is a URL at which one can find information about the user, and where to read or write data. The WebID will be provided by the user’s Identity Provider (which is usually their Pod Provider), but any interaction with a user’s Pod will require explicit permission to be granted by the Pod owner.
To obtain this permission, we will be using solid-auth-client. Its usage is relatively straightforward:
- Check if the user has already logged in.
- If not, ask the user for their Identity Provider.
- Then call
auth.login()with the Identity Provider as the first argument.
You can ask the user for their Identity Provider using a regular <input type="url">, or using a convenience library that suggests the most commonly used Providers — at the time of writing, solid.community and inrupt.net.
In code, that process would look roughly as follows:
import auth from 'solid-auth-client';
async function getWebId() {
/* 1. Check if we've already got the user's WebID and access to their Pod: */
let session = await auth.currentSession();
if (session) {
return session.webId;
}
/* 2. User has not logged in; ask for their Identity Provider: */
// Implement `getIdentityProvider` to get a string with the user's Identity Provider (e.g.
`https:inrupt.net` or `https://solid.community`) using a method of your choice.
const identityProvider = await getIdentityProvider();
/* 3. Initiate the login process - this will redirect the user to their Identity Provider: */
auth.login(identityProvider);
}
2. Understanding Solid
Now that we’ve got the user’s WebID, we have a starting point for fetching data from the user’s Pod.
What’s needed here is unique terms that have an agreed-upon definition. And just like we can have a Document describing me, we could also make Documents describing a term. And in fact, many people have done exactly that, for many different terms you might want to use. These Documents are called Vocabularies, and there’s one for things you might want to put on a business card at http://www.w3.org/2006/vcard/ns — the vCard Vocabulary. It contains Statements along the lines of:
Subject Predicate Object #role label Role #role comment To specify the function or part played in a particular situation #organization-name label Organization name #organization-name comment To specify the organizational name
(As a shorthand for http://www.w3.org/2006/vcard/ns#role, we will use vcard:role.)
So now we can use vcard:role, and be relatively confident that every other app using it will use it in the way described at that URL. We can combine terms from different Vocabularies, e.g. the FOAF (“Friend of a friend”) vocabulary has a term to refer to a person’s name at http://xmlns.com/foaf/0.1/name. My Document could thus look something like this:
Subject Predicate Object #me foaf:name Vincent #me vcard:organization-name inrupt #me vcard:role Developer
Everything that needs to be uniquely defined has a URL, with some Literal values for the rest (“Vincent”, “inrupt”, and “Developer”). You could imagine “inrupt” to be replaced by a URL as well, pointing to a Document describing the organisation itself.
3. Reading Data
Having the user's WebID and knowing it pointing to a Document, let's proceed reading data from that Document. We'll use Tripledoc for the purpose (although there are other libraries too,such as ldflex, rdf-ext and rdflib), which is intentionally limited in scope and designed to aid "thinking in Solid".
We’ll attempt getting the user’s name like that:
- Fetching the Document that lives at their WebID.
- From that Document, reading the Subject representing the user’s profile.
- Getting the
foaf:nameof that Subject, if such is set.
In code, the above looks like this:
import { fetchDocument } from 'tripledoc';
async function getName(webId) {
/* 1. Fetch the Document at `webId`: */
const webIdDoc = await fetchDocument(webId);
/* 2. Read the Subject representing the current user's profile: */
const profile = webIdDoc.getSubject(webId);
/* 3. Get their foaf:name: */
return profile.getString('http://xmlns.com/foaf/0.1/name')
}
To avoid typing the full ‘http://xmlns.com/foaf/0.1/name’ every time one can use the library rdf-namespaces. It exports strings for the URLs of the terms in common Vocabularies, turning the above into:
import { fetchDocument } from 'tripledoc';
async function getName(webId) {
/* 1. Fetch the Document at `webId`: */
const webIdDoc = await fetchDocument(webId);
/* 2. Read the Subject representing the current user's profile: */
const profile = webIdDoc.getSubject(webId);
/* 3. Get their foaf:name: */
return profile.getString(foaf.name)
}
Two things to note here. First, we call getString to indicate that we are looking for an actual value (i.e. a Literal), rather than a URL. (Likewise, we could use getInteger or getDecimal if we expected a number instead.) However, the value could also have been a URL pointing to a different Subject, in which case we could in turn fetch that Document. If that was what we expected, we could have used the method getRef instead.
The second thing to consider is that we cannot make any assumptions about what data is, or is not, present in the user’s Pod. Thus, profile.getString(foaf.name) might also return null. This could happen if the Document does not include the user’s name, if the name is stored differently (e.g. using foaf:firstName and foaf:familyName), or the value of foaf:name is not a literal string.
Now that we’re able to read data from the user’s WebID, let’s find out how we can read arbitrary other data.
4. Setting up a Data Model
Now it’s time to start working on some actual functionality: we’ll be making an app that allows people to keep notes. In this step, we’ll prepare their Pod for our data model — much as you might prepare database tables in a traditional application with a relational back-end.
At a high level, we’ll set up the data model as follows:
- Check if a Document tracking our notes already exists.
- If it doesn’t exist, create it.
- Fetch that Document.
So to start with the first step: in which Document should we track notes? The answer can be found in the concept of the Public Type Index.
The Public Type Index is itself a publicly accessible Document stored in the user’s Pod. This Document contains a list of links to other Documents, along with the type of data that is to be included in those Documents. To store notes, the data type we will use is the TextDigitalDocument, defined by Schema.org. Every time the user saves a note, we will store it as a TextDigitalDocument.
If the Document containing these notes was located in the user’s Pod at /public/notes.ttl, their Public Type Index could refer to it like this:
Subject Predicate Object #notes rdf:type solid:TypeRegistration #notes solid:forClass schema:TextDigitalDocument #notes solid:instance /public/notes.ttl
The above Type Index includes one Type Registration, identified by #notes, that registers /public/notes.ttl for the data type schema:TextDigitalDocument.
So how do we find the user’s Public Type Index? It’s usually listed in their profile, i.e. the Document accessible at their WebID:
Subject Predicate Object #me solid:publicTypeIndex /settings/publicTypeIndex.ttl
This is a pattern you’ll often encounter when writing Solid apps: you start with the user’s WebID, read the profile Document located there, and from there you can find the data you need.
So let’s see what this looks like in code:
import { fetchDocument } from 'tripledoc';
import { solid, schema } from 'rdf-namespaces';
async function getNotesList(profile) {
/* 1. Check if a Document tracking our notes already exists. */
const publicTypeIndexRef = profile.getRef(solid.publicTypeIndex);
const publicTypeIndex = await fetchDocument(publicTypeIndexRef);
const notesListEntry = publicTypeIndex.findSubject(solid.forClass, schema.TextDigitalDocument);
/* 2. If it doesn't exist, create it. */
if (notesListEntry === null) {
// We will define this function later:
return initialiseNotesList(profile, publicTypeIndex);
}
/* 3. If it does exist, fetch that Document. */
const notesListRef = notesListEntry.getRef(solid.instance);
return await fetchDocument(notesListRef);
}
Given the profile we fetched before, we can find a reference to the public Type Index under solid:publicTypeIndex. Then in the public Type Index, we can find the #notes entry by looking for the Subject that has a Statement saying it is for the class schema:TextDigitalDocument.
If that Subject is not found, we initialise a new Document to contain the notes — more on that later. If it is found, we find the URL of the Document that should contain notes under the solid:instance key. All we then have to do is to call fetchDocument to retrieve the Document at that URL.
One thing to keep in mind, again, is that you can make no assumptions about the data in the user’s Pod. Thus, to be safe you should always check whether the return value of getRef() is null. Consider using TypeScript to get a warning when you forget to do so.
That leaves us with one loose thread: the function initialiseNotesList(), which creates a new Document that will contain the notes, and adds it to the Public Type Index. Let’s take a look:
import { createDocument } from 'tripledoc';
import { space, rdf, solid, schema } from 'rdf-namespaces';
async function initialiseNotesList(profile, typeIndex) {
// Get the root URL of the user's Pod:
const storage = profile.getRef(space.storage);
// Decide at what URL within the user's Pod the new Document should be stored:
const notesListRef = storage + 'public/notes.ttl';
// Create the new Document:
const notesList = createDocument(notesListRef);
await notesList.save();
// Store a reference to that Document in the public Type Index for `schema:TextDigitalDocument`:
const typeRegistration = typeIndex.addSubject();
typeRegistration.addRef(rdf.type, solid.TypeRegistration)
typeRegistration.addRef(solid.instance, notesList.asRef())
typeRegistration.addRef(solid.forClass, schema.TextDigitalDocument)
await typeIndex.save([ typeRegistration ]);
// And finally, return our newly created (currently empty) notes Document:
return notesList;
}
(Keep in mind that, in order to write to the user’s Pod, the user will need to have given your app explicit permission to write when they signed in.)
If all the above looks terribly complicated: that’s because it is. Work is underway to make this easier in the future, allowing you to point a library at any data model, and having the library make sure that the user’s Pod is automatically prepared to handle that model. For now though, you’ll have to work through these step-by-step instructions.
5. Writing Data
Now that the user’s Pod is all set up, it’s time to store some actual notes. Luckily, now that we already have our notesList, most of the heavy lifting is already done:
async function addNote(note, notesList) {
// Initialise the new Subject:
const newNote = notesList.addSubject();
// Indicate that the Subject is a schema:TextDigitalDocument:
newNote.addRef(rdf.type, schema.TextDigitalDocument);
// Set the Subject's `schema:text` to the actual note contents:
newNote.addString(schema.text, note);
// Store the date the note was created (i.e. now):
newNote.addDateTime(schema.dateCreated, new Date(Date.now()))
const success = await notesList.save([newNote]);
return success;
}
With the user’s Pod fully set up, the above is all there is to it.
Solid Apps (List)
A list of Solid apps can be found here. These include:
- Solid Chess decentralised chess game Source code MIT License Copyright (c) 2018 Pieter Heyvaert
- Dokieli is a client side editor for decentralised article publishing, annotations, and social interactions. Dokieli Apache License v2.0 2012 Sarven Capadisli
- Notepod is a simple note-taking app that stores notes in your Solid Pod. It was created as a demonstration of how to create Solid apps — inspect its commit messages for more detailed guidance on reading data, authenticating, and more.
- Poddit. 2019. Vincent Tunru.
- Solid Focus keep track of your to do list. Source code GNU General Public License v3.0 (c) 2018 Noel de Martin
- Mark Book is for creating bookmarks. Source code MIT License Copyright (c) 2018 Melvin Carvalho
- Tadanime helps you rate your anime series and movies. Tadanime MIT License Copyright (c) 2018 Pieter Heyvaert
- SNS A social Network based on SOLID build on core JavaScript.
- Concept (GitHub) is an embarrassingly simple clone of notion.so built on Solid, currently in early alpha.
- Form Integration is an app to submit online form data to your Pod. Using a FormRouter Account, it is now possible to setup an online form that can route directly into a Solid Pod. This allows any person with a Solid Pod to get a copy of the form data they filled out. The submission may include a copy of the form as a PDF if so configured. This currently supports PDF, HTML and Excel form formats. (c) 2019 FormRouter Inc
- Launcher Exploration (code on https://github.com/inrupt/launcher-exploration)
- Friend Requests Exploration (code on https://github.com/inrupt/friend-requests-exploration)
- Solid LBS (deployed as Sigmafied). 2019. Sharon Statsianis.
- Spoggy
- Linked Pipes. 2019. @aorumbayev
- Linked Beer search for and rate beers listed by brewery. 2019. Özcan Seker. source code
- OEdit an editor app for raw files using the vs interface. 2019. Jackson Morgan.
- OChat a chat app. 2019. Jackson Morgan.
- Solid Weather uses the national weather service API so it is currently only able to fetch weather in the United States.2019. Dylan Martin.
- SolidVC is a Verifiable Credentials framework developed within the context of Solid.
- Solid Calendar is a way to create, store, and query events using a user’s card. 2019. Dylan Martin.
- RiseFor Mobilisation (deployed as Referendum Signons) is an app for organizing referenda; it uses Solid under the hood.
- ODS-Briefcase – a module of OpenLink Data Spaces (ODS), ODS-Briefcase includes Dynamic
- OpenLink Smart Data Bot (OSDB) distills actions from API documentation constructed using RDF or OpenAPI; supports WebID-OIDC for authentication (c) 2019 OpenLink Software
- Solid Shell command-line tool and interactive shell. Source code MIT License Copyright (c) 2019 Jeff Zucker
- OpenLink Structured Data Editor (OSDE) RDF editor that can save content to any Solid Pod. (c) 2019 OpenLink Software
- OpenLink Structured Data Sniffer (OSDS) extracts Metadata in a variety of notations from HTML docs and enables storage to any Solid Pod via “Save As” feature. (c) 2019 OpenLink Software
- URIBurner SPARQL Query Service Endpoint that supports WebID-OIDC for authenticating WebIDs en route to functionality that isn’t granted to the un-authenticated Public; e.g., “generating descriptions of any Web-Accessible Document in RDF, and publishing said description in 5-Star Linked Data form” (c) 2019 OpenLink Software
- File Extractor is an app to sign into your Pod and extract all data. An HTML page allows your to sign in to a Solid Pod and extract a HexBin file from an RDF file there. Source code MIT License Copyright (c) 2019 David Conorozzo
- Tiddlywiki is a Pod store. Source code MIT License Copyright (c) 2019 Alain Bourgeois
- File Extractor for Pods extract files from Pods. Source code MIT License Copyright (c) 2019 FormRouter, Inc.
- Solid IDE file manager and IDE. Source code MIT License Copyright (c) 2018 Jeff Zucker
- SPARQL Fiddle online fiddle to run SPARQL against Pods. Source code MIT License Copyright (c) 2018 Jeff Zucker
- graphMetrix allows you to browse your Solid Pod offering multiple views of information including overview, graph, doc, gallery and grid as well as easy to use Solid collaboration control and file management. 30 day free trial followed by a $10 per user/per month. 2018 graphMetrix
- Twee-Fi helps you review claims and rate trustworthiness of tweets. Twee-Fi MIT License Copyright (c) 2018 FactsMission
- Solid Profile Viewer is a react app to view and browse Solid WebID profiles. Source code MIT License Copyright (c) 2018Angelo Veltens
- LinkedPipes Applications create your own visualisers based on linked data. Source code Apache License 2.0 (c) 2018 Jakub Klimek
- Taisukef add friends. Source code. 2018. Taisuke Fukuno.
- Solidbase agricultural project management. Souce code GNU AGPL3 (c) 2017. Allmende Lab
- OpenLink YouID identity and credentials document generator that creates X.509 certificates and saves WebID-Profile documents in any Solid Pod and sets up the relations required for WebID-TLS and WebID-TLS+Delegation. Copyright © 2013-2016 OpenLink Software Inc.
- 2048 is a tile based game. 2048 MIT License Copyright (c) 2016 Webize
- Solid Signup app for creating WebID accounts with Solid compatible providers. Source code MIT License Copyright (c) 2015 Andrei Sambra
- WebID Profile editor Source code MIT License Copyright (c) 2015Andrei Sambra
- Hello World Source code MIT License Copyright (c) 2015 Melvin Carvalho
- Plume blogging platform. Source code MIT License Copyright (c) 2015 Andrei Sambra
- Inbox processes notifications. Source codeMIT License Copyright (c) 2015Andrei Sambra
- Contacts mobile friendly vCard contact management. Source code MIT License Copyright (c) 2015 Andrei Sambra
- Warp file browser. Source code MIT License Copyright (c) 2014](https://github.com/linkeddata/warp/blob/gh-pages/LICENSE) Andrei Samba
- A-PHEVOS (derived from Photo, Event, Organise and Share) is an alpha version of a Pod manager created by WEBIND
- Solid Notify sends you a desktop notification when a Solid resource changes.
- Find Solid Pods. James Cole.
Example of a Solid App: Plume, a Solid-based Blogging Platform
Plume is a client-side blogging platform built around the Solid standards. Plume makes use of Markdown and doesn't currently support dynamic configuration of data spaces, meaning it must be run from one's own Web server or manually uploaded to one's account (can use solidtest.space as storage).
Configuration
To use Plume you have to first manually set some config values in config.json (copy/rename the config-example.json file). Set the postsURL value to have it point to an existing container on a Solid-friendly server storing your blog posts. Finally, you should also set the owners variable by adding your own WebID (a minimal identity spec that defines the use of URIs as identity tokens for recognizing users, while also, importantly, being machine-readable syntax), in order to be able to access the editor interface and create new posts.
Here is an example of the configuration file:
{
"owners": ["https://example.org/profile#me"],
"title": "Plume",
"tagline": "Light as a feather",
"picture": "img/logo.svg",
"fadeText": true,
"showSources": true,
"cacheUnit": "days",
"defaultPath": "posts",
"postsURL": "https://account.databox.me/Public/blog/posts/"
}
And here's what each config parameter means:
owner: a list of URLs (WebIDs) of the people who can post on the blogtitle: the title of the blogtagline: tagline/subtitlepicture: the picture to display on the blog's headerfadeText: true/false - shortens the posts length when viewing the full blogshowSources: true/false - it will add a button/link that points to the source of the blog post (the actual resource)cacheUnit: minutes/hours/days/ - validity of certain cached data (you shouldn't really need to change it)defaultPath: this value will be suggested to the user if the blog needs to be initializedpostsURL: the URL of the folder (container) holding the posts for the blog
Solid-specific Libraries and Tooling
An assorted list of libraries, tools, clients, etc. by category.
Authentication and file access
- Solid auth client: A browser library for reading and writing to Solid pods - by Ruben Verborgh
- Solid CLI: A utility to facilitate command-line interaction with Solid servers - by Ruben Verborgh
- Solid Auth CLI: A node/command-line Solid client with persistent login - by Jeff Zucker
- Solid File Client: Create and manage files and folders in Solid Pods - by Jeff Zucker
- Solid-Rest: Treat any storage backend as a minimal Solid server - by Jeff Zucker
- Solid-Local-Pod-Manager: Serve parts of your file system as local solid pods - by Otto-AA
- Solid authorization Widgetcomponent for webapp Source code MIT License Copyright (c) 2019 Bourgeoa
- Solid File Widget widget authorisation. Source code MIT License Copyright (c) 2019 Alain Bourgeois
Manipulating data
- LDflex for Solid: Simple access to data in Solid pods through LDflex expressions - by Ruben Verborgh
- GraphQL-LD for Solid: A GraphQL-LD engine with Solid authentication - by Ruben Taelman
- RDF-Easy: easy JavaScript access to RDF from Solid pods - by Jeff Zucker
- Soukai-solid: A Soukai ODM extension adapting Solid to the engine - by Noel De Martin
Managing access control
Build interfaces
- Mashlib, for data mashup
- Solid UI, and the associated Solid panes
React components
- Solid React Components: Basic React components for building your own Solid components and apps - by Ruben Verborgh
- Solid React SDK: Libraries, components, documentation, best practices, and an application generator to accelerate development of high-quality Solid applications - by inrupt
- GraphQL-LD Solid React Components: A GraphQL-LD engine with Solid authentication - by Ruben Taelman
Angular SDK
WebSocket
RDF manipulation and querying libraries
JavaScript
- Tripledoc: A library for easy manipulation on RDF
- rdflib: A library for advanced manipulation of RDF
- LDflex: A library for making RDF querying easy
- rdf-ext: An implementation of RDFJS specifications
- SPARQL Fiddle: A JavaScript SPARQL API - by Jeff Zucker
- Comunica: A highly modular and flexible meta query engine for the Web - by Ruben Taelman
Python
Java
PHP
RDF tools
- EasyRDF converter: Convert RDF from a syntax to another
- prefix.cc: Dereference prefixes into their full domain name
- Protégé: An ontology editor
Identity management libraries
JavaScript
- Node-WebID: A node module to verify and create WebIDs
- Keychain: for use with Web Cryptography API in Node.js
- OIDC resource server auth
- OIDC auth client for Web browsers
- OIDC relying party
- OIDC provider
- OIDC decentralized auth manager