Building Solid Apps with LDO
WARNING: This tutorial is out of date. For a more up-to-date tutorial see the official LDO documentation.
Solid is a decentralized web framework that lets users keep their own data on their own Personal Online Datastore (Pod). Solid Apps let these users interact with their Solid Pods, allowing them to use unique experiences without having to hand over their own data to an app.
While Solid is conceptually very exciting, it has traditionally been unapproachable to new developers. For example, in the past, simply modifying a user’s name required this complicated code:
I built LDO (Linked Data Objects) to make developing a Solid application easy. And we’re partially there! The example above can be created in a more readable manner with LDO:
While I admit that this example is still more complex than what I ultimately want to accomplish, it is a large improvement over the previous example. You’re now able to change values naturally with code like profile.name = "A Cooler Name"
rather than the confusing code of like 15–18 of the first example.
There are still improvements to make in LDO. Asking users to understand Solid’s “Fetch” API is a big ask. But, if this interface for interacting with Solid looks good to you, then this tutorial will teach you how to use it while you wait for the full version of LDO to be developed.
Step 1: Setup
For this tutorial, we’ll be creating a simple Solid app the lets the user log in, see the name currently in their profile, and modify that name.
Our first step is to setup the project. We’ll be building the project in React, but any front-end library works to LDO as well.
LDO is designed to work with TypeScript, so we’ll include the typescript template when we run the create-react-app script.
npx create-react-app ldo-solid-tutorial --template typescript
cd ldo-solid-tutorial
create-react-app generates CSS files and test files the we don’t need, so I eliminated them. Throughout this tutorial, you can follow along in github at https://github.com/jaxoncreed/ldo-react-tutorial-1. There’s a commit for each step and I’ll be posting the changes made in each commit in each section.
Here’s the commit for setup: https://github.com/jaxoncreed/ldo-react-tutorial-1/commit/a597cf1bc4a18ac537cd61930b2efbf0a4e63e4b, leaving us with a blank site that looks like this:
Step 2: Solid Login
Changes on GitHub: https://github.com/jaxoncreed/ldo-react-tutorial-1/commit/55b8e7c34fdd5a027f3dfb079f236efa79a3534c
Before we can get to LDO, we want to let the user log into their Solid Pod. To do this, we’ll use @inrupt/solid-client-authn-browser. I’m not going to go into a deep explanation of this section, but you can find an explanation here https://docs.inrupt.com/developer-tools/javascript/client-libraries/tutorial/authenticate-browser/.
By the end of this section, you’ll be able to Log In as shown below:
Install @inrupt/solid-client-authn-browser
npm install @inrupt/solid-client-authn-browser
Create a file called ProfilePanel.tsx
. This panel will only show up when the user is logged in and will include the work we do with LDO, but for now, it’s a placeholder.
Update App.tsx
to handle the authentication callback, render a LoginHeader
, and display ProfilePanel
only when logged in.
Finally, create the LoginHeader.tsx
component.
Step 3: Initialize LDO
Changes on GitHub: https://github.com/jaxoncreed/ldo-react-tutorial-1/commit/9e701e500ed4a76e209d6f527bc50e4f3ba81546
Now that we have all the boilerplate stuff out of the way, we can begin working with LDO. The first step is to initialize it by running the following command:
npx ldo-cli init
This command will install all the the required dependencies and set up your project for LDO.
At the core of LDO are Shape Expressions (also known at ShEx). They define the data schemas for your project. You can learn more about ShEx here http://shex.io/shex-primer/index.html, but we’ll do a simple run down in this tutorial as well.
The ldo-cli init
script created a default shape for your project called foafProfile.shex
you can find it in the shapes
folder. The shapes
folder is the place to put any Shape Expression for your project.
Let’s walk through the default ShEx file that init generated. This Shape Expression defines what a Profile Object should look like when you use foaf (friend of a friend).
The Profile defined here is actually a subset of what’s in the official Solid Profile. You can see the full Solid Profile here https://shaperepo.com/shapes?id=https%3A%2F%2Fshaperepo.com%2Fschemas%2FsolidProfile
If you’re familiar with turtle, the RDF serialization, some of this might look familiar. If not, I’ll explain. Solid’s main data-structure is RDF. You can represent almost anything in RDF and every “thing” is identified by a URL. The relationships between “things” are called triples. For example, the statement “Jackson knows Michiel” can be represented in RDF as
<https://jackson.inrupt.net/profile/card#me>
<http://xmlns.com/foaf/0.1/knows>
<https://michielbdejong.inrupt.net/profile/card#me>
Notice that all elements of the triple, including the concept of “knowing” someone is a URL.
But, typing out a full URL is annoying. That’s why ShEx uses PREFIX
. When you add PREFIX foaf: <http://xmlns.com/foaf/0.1/>
it means that every time foaf:
is typed, it should be replaced with http://xmlns.com/foaf/0.1/
by the ShEx interpreter. So, foaf:name
becomes http://xmlns.com/foaf/0.1/name
.
Once we write our prefixes, we want to define a shape. In this example, we’re defining a FoafProfile as data that contains the following fields:
a [ foaf:Person ]
means that the data must have a type offoaf:Person
.foaf:name xsd:string ?
means that a profile should have between 0 and 1 (? means between 0 and 1) “names” and that name should be of type “string.”foaf:image xsd:string ?
means that a profile should have between 0 and 1 “images” and that name should be of type “string.”foaf:knows @ex:FoafProfile *
means that a profile should have between 0 and infinity (that’s what * means) “FoafProfiles” associated with it.
You may notice that ShEx doesn’t look like TypeScript, so it would be hard to use it in a TypeScript situation. That’s where LDO takes over. Run:
npm run build:ldo
This kicks off a script that takes the ShEx you defined in the shapes
folder and turns them into files you can use in the ldo
folder. 5 files were generated:
- foafProfile.typings.ts: This file contains a TypeScript typings that represent the shape you just defined.
- foafProfile.ldoFactory.ts: This file contains an LDO Factory. This is a class that will be useful to us in the coming steps.
- foafProfile.context.ts: This file contains a JSON-LD context generated from your ShEx. For the most part, you won’t need to touch this file unless you’re doing something more advanced.
- foafProfile.schema.ts: This file contains a machine-readable serialization of your ShEx shape. Again, you don’t need to touch this unless you’re doing advanced stuff.
- foafProfile.shapeTypes.ts: This file contains ShapeTypes, a grouping of Typings, Context, and Schema. This is another document that’s for advanced purposes.
Step 4: Fetch and Visualize the Profile
Changes on GitHub: https://github.com/jaxoncreed/ldo-react-tutorial-1/commit/f079bbc80d1ac93f58393807db024843d2b4090e
In this step, we’ll fetch RDF data from the user’s profile, parse it, and display the results. By the end, our app will look like this:
The great thing about LDO is that it converts the ShEx shapes you made into typescript typings. This means that whenever I use a Linked Data Object, I can get intellisense:
Let’s look at the code that achieves this:
Much of this code is just React stuff, but let’s focus on the useEffect
method. Here, we’re fetching the profile and parsing it.
Fetch the Profile
const rawProfile = await (await fetch(webId)).text();
In the above example, we fetch the profile which comes back as raw turtle
text. turtle
is a serialization of RDF that looks like this:
You can clearly see that this represents a person who has an image at foaf:img
, knows 7 people at foaf:knows
, and has the name “Jackson” at foaf:name
.
But, while we can read this, using the data in this form is a bit difficult. That’s where LDO comes in. We can convert this data into a linked data object as follows:
const foafProfile = await FoafProfileFactory.parse(
webId,
rawProfile,
{ baseIRI: webId }
);
When you use the parse
method, you include three parameters.
- The
id
of the node or subject within the dataset you want to extract. In this case we want to extract the webId. - The raw data to be parsed.
- Some parser options that identify things about the data.
Now, foafProfile
is a Linked Data Object you can use to traverse the data. Calling foafProfile.name
will get you the name listed at foaf:name
.
Step 5: Modifying and Saving Data
Changes on GitHub: https://github.com/jaxoncreed/ldo-react-tutorial-1/commit/a2e22d55dbdeabab8db492b2c0de41be5d8557d6
Now onto the final step. In this one, we’ll modify the data in the profile and save it to the Pod. We’ll add a text box at the bottom of the screen that let’s you change the name.
React-wise, we can do this by making a new form and a callback method called onSubmitNameChange
.
But, you’re not here for a tutorial on React. Let’s look at the LDO portions of this code.
const modifiedProfile = profile.$clone();
Remember, profile
is the Linked Data Object we use to render our profile. If we call the $clone
method, we can duplicate the profile
object. This way we can modify the object without worrying about it affecting our render.
modifiedProfile.name = nameField;
That’s really it! LDO lets you modify a value by simply using the =
operator.
const response = await fetch(webId, {
method: "PATCH",
body: await modifiedProfile.$toSparqlUpdate(),
headers: {
"Content-Type": "application/sparql-update"
}
});
And finally, we update the document. Solid lets you update documents via the PATCH
http method. Solid servers expect a content type of application/sparql-update
. Fortunately, LDO keeps track of all the changes you made and can generate the update body using the method $toSparqlUpdate
.
Conclusion
LDO offers an RDF development experience with Shape Expressions at the center. This makes developing apps with RDF easier as users can modify datasets as if they were typical TypeScript objects.
In the future, I plan to expand LDO to simplify the development experience even more, but for now, I hope you enjoy the devtool.
If you have any questions, feel free to comment or reach out to me on twitter at https://twitter.com/otherJackson.