Skip to content
Colin Wren
Twitter

Build a Solid App in React with Tripledoc

JavaScript, Technology9 min read

three shapes
Photo by Andrej Lišakov on Unsplash

On the internet your data is most likely your most valuable asset. With it, companies can deduce all aspects of what drives you and target their advertising efforts to change you into a consumer of their products or to change your opinions on subjects that matter to them.

You don’t own this data though. It exists in a multitude of silos across the various services, websites and apps you use and while you can access this data via a GDPR (or non-EU equivalent) request you won’t get all the data that company holds on you, just the data that falls within the legal obligation the companies have.

There are a number of projects and services available that look to gain back some of the control users have on their data and to build a decentralised version of the services, websites and apps that give users the same experience they love, while making it easier to control where their data goes and what information they see.

Mastodon is probably the most well-known of these decentralised services. Anyone can join a Mastodon instance (or run their own) and post to and receive posts from the ‘fediverse’, which is a federated stream of updates from other mastodon instances the server’s maintainer has curated for their community.

The technology that powers Mastodon is called ActivityPub, a W3C standard for federating content across a social network. It works really well for this purpose but what if you want a decentralised version of something more personal like the data your apps use? This is the problem that Solid is looking to solve.

A quick intro to Solid

Solid is led by Tim Berners-Lee (you may know him as the person who invented the World Wide Web) and aims to decentralise the storage of a user’s data.

This decentralisation isn’t just limited to where the data is stored but makes it possible for the user to allow multiple apps to access the same data, giving the user a high level of freedom on how that data is stored and used.

The user’s data is stored in a pod which is accessed via a WebID, essentially a document that holds the user’s information and their sharing preferences.

The pod where the user’s data is stored and the WebID can be separate services (for even more decentralisation gains) but if you’re first starting out with Solid it’s likely you’ll use the same provider for both.

When a user uses a Solid app they login with their WebID and grant the app access to the user’s data (different access permissions are available) and if the app is able to, share that user’s data.

The app, once authorised to access the user’s data in their pod, can then use a RESTful interface to read, write and delete data to the pod (depending on the level of access granted).

How Solid stores data

The data in a pod is stored as Linked Data (Solid being a portmanteau of Social Linked Data), admittedly I have limited knowledge on Linked Data and RDF but it’s an easy enough format to pick up.

The resource(s) used by apps in Solid are stored in Turtle documents which have one or more subjects.

The subjects in a document have properties (called literals in the Turtle spec) that define that subject. These properties can be simple attributes like a name or they can be relationships between different subjects across the same or multiple documents.

The format for defining both subjects is a subject, predicate, object triple, for instance if the subject is defined as :colin a foaf:Person this means that the subject is a Person as defined by the Friend of a Friend schema.

Properties of a subject are defined in the format property value , for instance foaf:name Colin which at first can feel a little bit weird but there are certain classes of property that make it easier to perform graph-based calculations, such as those that generate a list of people a subject knows using the foaf:knows property.

Building an app with Solid

When I first found Solid I was really pleased to see there were numerous apps that published their source code, which makes for an excellent starting point for understanding the authentication flow and how to create, read, update and delete (CRUD) resources with Solid.

There is even a set of React components and hooks available to make development easier for someone like myself who knows React but knows next to nothing about Linked Data.

I found the implementation of the @solid/react library a little confusing however, as it makes use of JavaScript Proxys and LDFlex expressions (something I struggled to use custom objects with) which felt a little more geared towards just reading a document than reading and writing a document and it’s subject(s).

I eventually found Tripledoc which I found really simple to use as it follows a document -> subject -> property structure that allows CRUD operations at each level of that hierarchy.

I’ve created a demo project using Solid that you can use to get a better understanding of Solid and Tripledoc and I’ll go into more detail below.

Basic React based Solid app to track your Doggos

Authentication

The authentication flow for a Solid app is to ask the user to login with their WebID by entering the host for the WebID provider and redirecting them to that provider to login, asking them to configure the app’s access if it’s the user’s first time using the app; after which they are taken back to the app as an authenticated user.

This authentication flow is normally carried out in a pop-up in the web browser. Solid provides an excellent library for authenticating a user in this way called solid-auth-client which has a handy CLI tool used to generate a HTML file to display the user to do this.

1import auth from 'solid-auth-client';
2const popupUri = 'http://localhost:3000/popup.html';
3
4export async function login() {
5 const webId = await getWebId();
6 if (webId === null) {
7 const session = await auth.popupLogin({ popupUri });
8 return session.webId;
9 }
10 return webId;
11}
Once the user has logged in the WebID for the session can be used

Once the user is authenticated you can then access their pod’s resources thanks to the cookie stored on login.

Working with documents

Reading a document with tripledoc is handled by the fetchDocument function.

This function takes a path to a resource (e.g. https\://host.tld/public/file) and will return the document if found.

It will throw an error if the document is not found so I used the createDocument function of tripledoc to return an empty document for the same path if no document existed already.

1import { fetchDocument, createDocument } from 'tripledoc';
2import { schema, rdf } from 'rdf-namespaces';
3
4const docPath = 'private/example';
5
6//Pod is the user's WebID's protocol and hostname
7
8async function getDoc(pod) {
9 const docPath = `${pod}/${docPath}`;
10 try {
11 const doc = await fetchDocument(docPath);
12 return doc;
13 } catch (err) {
14 const doc = await createDocument(docPath);
15 return doc;
16 }
17}
Grab the document from the user’s pod and create a new document if unable to find and existing one

There isn’t a means of deleting a document in the tripledoc library but you can send a HTTP DELETE request to the resources URI which will delete the resource.

Working with a document’s subject(s)

Once you’ve got a document you can get a subject to work using a number of different methods.

If you want to get all subjects in the document of a particular object class you can use document’s getAllSubjectsOfType method which will return a list of subjects you can iterate over.

1export async function getDoggos(pod) {
2 try {
3 const doggosDoc = await getDoc(pod);
4 const doggos = doggosDoc.getAllSubjectsOfType(schema.Thing);
5 return doggos.map((doggo) => ({
6 doggoID: doggo.asRef(),
7 name: doggo.getString(schema.name),
8 dob: doggo.getString(schema.Date__workaround),
9 goodBoiLevel: doggo.getInteger(schema.Integer),
10 }));
11 } catch (err) {
12 return [];
13 }
14}
Getting a list of subjects from a Solid document

If you know the identifier for a subject you can use the document’s getSubject method which takes the identifier including the # prefix.

1// doggoID is the subject identifier
2export async function getDoggo(pod, doggoID) {
3 try {
4 const doggosDoc = await getDoc(pod);
5 const doggo = doggosDoc.getSubject(doggoID)
6 return {
7 doggoID: doggo.asRef(),
8 name: doggo.getString(schema.name),
9 dob: doggo.getString(schema.Date__workaround),
10 goodBoiLevel: doggo.getInteger(schema.Integer),
11 };
12 } catch (err) {
13 return {};
14 }
15}
Getting a subject via it’s identifier

If you want to use one of the graph based predicates to find subjects (such a finding subjects that know a given subject), you can do so with the findSubject and findSubjects methods respectively.

You can create a subject using the document’s addSubject method which takes an options object, with the most important property to set being the identifier property as this will determine the identifier the new subject is stored under. If no identifier is passed, the subject will use a random UUID as a identifier.

1// Values would be something like { nameVal: 'Prince', dobVal: '1997-03-05', levelVal: 13 }
2export async function addDoggo(pod, values) {
3 const doggosDoc = await getDoc(pod);
4 const identifier = values.nameVal.toLowerCase().replace(' ', '-');
5 const newDoggo = doggosDoc.addSubject({ identifier });
6 newDoggo.addRef(rdf.type, schema.Thing);
7 newDoggo.addString(schema.name, values.nameVal);
8 newDoggo.addString(schema.Date__workaround, values.dobVal);
9 newDoggo.addInteger(schema.Integer, parseInt(values.levelVal, 10));
10 await doggosDoc.save([newDoggo]);
11}
Adding a new subject with data

Unlike documents you can use tripledoc to delete a subject from the document using the document’s removeSubject function, which takes the subjects identifier (with # prefix) as an argument.

1export async function removeDoggo(pod, doggoId) {
2 const doggosDoc = await getDoc(pod);
3 doggosDoc.removeSubject(doggoId);
4 await doggosDoc.save();
5}
Removing a subject from a document

In order to push any subject based changes to the user’s pod you need to call the save method of the document.

Working with a subject’s properties

In order to access the subject’s properties you need to know what data type the property will return and the predicate the property is stored under.

For working with a subject’s properties it’s useful to think of the predicate as the key for which you’ll use to read, write and delete the property’s value with.

The subject object supports the following data types:

  • DateTime
  • Decimal
  • Integer
  • LocaleString
  • Ref
  • String

And the following operations per data type

  • add — Creates a new instance of that data type for the supplied predicate
  • get — Gets an instance of that data type under the supplied predicate
  • remove — Removes an instance of that data type under the supplied predicate
  • set — Removes the existing instance of that data type under supplied predicate and adds a new one

As with the subject, the changes to these properties won’t be pushed to the user’s pod until the save method on the document is called.

1export async function saveDoggo(pod, doggoId, values) {
2 const doggosDoc = await getDoc(pod);
3 const doggo = doggosDoc.getSubject(doggoId);
4 doggo.removeAll(schema.name);
5 doggo.addString(schema.name, values.nameVal);
6 doggo.removeAll(schema.Date__workaround);
7 doggo.addString(schema.Date__workaround, values.dobVal);
8 doggo.removeAll(schema.Integer);
9 doggo.addInteger(schema.Integer, parseInt(values.levelVal, 10));
10 await doggosDoc.save([doggo]);
11}
An example of removing and adding properties to a subject

Using Tripledoc in a React app

This is just an example of how I used tripledoc in my React app, you might find the tripledoc-react library easier to work with but I like to separate my logic functions from my components so I’ve opted to just usetripledoc .

In the demo app I built I have two elements to track the state of — the user’s WebID and the subjects in the resource my app uses.

I handle these via a React context provider as this allows me to access the state and dispatch actions to update that state from within components in the app.

To handle authentication I use the solid-auth-client ‘s popupLogin function to load the popup.html that the library provided me with, by using it’s CLI command. On the user logging in and the user’s WebID being retrieved this is stored in the app’s state.

Once the user is logged in, the list of subjects is fetched from the resource and the properties used to render the items in the app are read and this list of objects is stored in the app’s state also.

In order to create, edit or remove items in the app I grab the document from the solid pod (to ensure I’m working with the most up-to-date data) and carry out the respective operation against the subject or the subject’s properties before saving that document.

On saving the document I then dispatch and update the subjects stored in the app’s state.

Thanks to Tripledoc, using a Solid pod in your React app is no different than using any other REST api.

The only differences to the development flow are:

  • You’ll probably want to register for a pod with a cloud based pod provider instead of running your own pod locally, there’s a bunch of networking and security hoops you need to jump through to have a local app work with a local pod
  • You will need to know the data types and the ontology of your data ahead of time as there’s a bit more effort to be put into architecting the data the app uses over something like a JSON based store

Summary

I think decentralisation will be a growing theme in web and app development over the next 10 years as legislation finally catches up with the technology and we see companies like Facebook being forced to store data in the user’s territory.

A technology like Solid means that app developers don’t need to worry about storing data and the data privacy laws that apply to it, instead they would only need to worry about the laws around processing information.

The portability of data that Solid gives the user is of great interest to me as I’ve always felt that opening up APIs for developers to use allows for ecosystems to spring up around products (it’s one of the things I think Pokemon Go really missed out on when it first launched).

I do however have concerns about how multiple apps using the same resources will work as I can imagine there being incompatibilities between apps and it wouldn’t be too hard for an app developer to make the resource unusable by other apps or, worse case scenario, even deleting data for other apps.

I also think the access controls that Solid provides might be a little too lenient as you’re essentially granting access to all documents on a pod to an app. An argument could be made that people will use different pods to separate private and public information but this is inconvenient for users (and therefore unlikely to happen) and this will ultimately get exploited by bad actors.

If as an app developer I have access to all your data from other apps as well as my own I could potentially build an app that offers something trivial like giving you the ability to save recipes while also in the background reading your health information, social interactions and anything else that would allow me to build a good profile of you that I can sell to advertisers or worse.

We already see this type of behaviour in the app stores where a developer will build a ‘torch’ app that turns out to require every permission the developer can ask for access to and then collects the data and sends it off to be sold on.

If Solid was able to offer this type of fine grained access control for the documents it stores and grant these on an app by app basis this would make me feel more confident that my data is safe using Solid.

I think that while it’s technically possible to host your own Solid server we’ll likely see one or two main Solid providers that will ultimately make these servers targets for hackers who will not only be able to access one set of data per user but multiple sets, making it far more worth the effort.

Security worries aside though I really do think that Solid is a great piece of technology and I really look forward to seeing how it develops and gets adopted over the next decade and how it shapes the internet.