Read/Write Structured Data

This page provides information on reading and writing structured data stored in Solid Pods. For information on reading and writing files (e.g., .jpg or .json) stored in Solid Pods, see Read/Write Files.

Structured data, in this context, refer to data structured as Things in SolidDatasets. A Thing is saved as part of a SolidDataset, where a SolidDataset contains a set of Things. That is, you do not save a Thing independently from its SolidDataset.

For example, if you want to save information (such as the required books, etc.) about a course, you can have a SolidDataset that corresponds to the course, and within that SolidDataset, contain the Things that correspond to various data about the course, such as the books.

A Pod represented as a directory structure, containing a folder "profile" with a document "card", and a folder "fall2021", which in turn contains a folder "courses", which contains a document "Writing101". Alongside the directory structure, the "Writing101" document is also shown individually, containing separate areas labelled "#book1", "#book2" and "#otherThing".

For more information, see Structured Data.

Required Access

By default, solid-client functions make unauthenticated requests. To perform read/write operations on restricted Resources (i.e., not open to the general public), the user must first authenticate as a user who has appropriate access to that Resource. Then, to make authenticated requests, pass to the various read/write functions the authenticated Session’s fetch function.

Action

Required Access

Read a Resource.

Read access to a Resource.

Write a new SolidDataset to a Container.

Either Append or Write access to the Container, depending on the library’s function:

If the user only has Append access to the Container, you can only use saveSolidDatasetInContainer() to save a new SolidDataset.

If the user has Write access to the Container, you can use either saveSolidDatasetInContainer() or saveSolidDatasetAt() to save a new SolidDataset.

Add to an existing SolidDataset.

Either Append or Write access to the SolidDataset.

For example, to add new Thing(s) to an existing SolidDataset or to add new properties to the existing Things in a SolidDataset, the user needs either Append or Write access to the SolidDataset.

Replace an existing SolidDataset.

Write access to the SolidDataset.

For example, to overwrite/delete existing Thing(s) in an existing SolidDataset or to overwrite/delete existing properties of an existing Thing(s) in a SolidDataset, the user needs Write access to the SolidDataset.

Delete an existing SolidDataset.

Write access to the SolidDataset.

For more information on access control, see Manage Access to Data. For more information on authentication, see Authentication.

Prerequisite for Restricted Data

To make authenticated requests, you can use one of Inrupt’s authentication libraries to login and pass the fetch() function as an option to solid-client functions. Inrupt provides the following libraries for authentication:

  • solid-client-authn-browser library to authenticate in a browser.

  • solid-client-authn-node library to authenticate in Node.js.

The following example uses solid-client-authn-browser to authenticate a user and use the authenticated Session’s fetch() function to make authenticated requests:

import { handleIncomingRedirect, login, fetch, getDefaultSession } from '@inrupt/solid-client-authn-browser'
import { getSolidDataset, saveSolidDatasetAt } from "@inrupt/solid-client";

async function loginAndFetch() {
  // 1. Call the handleIncomingRedirect() function,
  //    - Which completes the login flow if redirected back to this page as part of login; or
  //    - Which is a No-op if not part of login.
  await handleIncomingRedirect();

  // 2. Start the Login Process if not already logged in.
  if (!getDefaultSession().info.isLoggedIn) {
    await login({
      oidcIssuer: "https://broker.pod.inrupt.com",
      redirectUrl: window.location.href,
      clientName: "My application"
    });
  }

  // 3. Make authenticated requests by passing `fetch` to the solid-client functions.
  // For example, the user must be someone with Read access to the specified URL.
  const myDataset = await getSolidDataset(
    "https://pod.inrupt.com/docsteam/profile/card", {
    fetch: fetch
  });

  // ...
  
  // For example, the user must be someone with Write access to the specified URL.
  const savedSolidDataset = await saveSolidDatasetAt(
    "https://pod.inrupt.com/docsteam/profile/card",
    myChangedDataset, {
    fetch: fetch
  });
}

loginAndFetch();

For more information on authentication, see Authentication.

Read Data

Read Access

To read private data (i.e., it is not configured for public read access), the user must first authenticate as someone with Read access to the resource. Then, the user can pass in the authenticated Session’s fetch function to the various read/write functions. For an example, see the Prerequisite section.

To read data with solid-client,

  1. You first use getSolidDataset to fetch the SolidDataset that contains the data you want to read. For authenticated reads, include the fetch function in the function call.

  2. Then, use either:

    • getThing to get a single data entity from the fetched SolidDataset, or

    • getThingAll to get all data entities from the fetched SolidDataset.

  3. Then, from the data entity (i.e., Thing), you can get specific data. For a list of the get functions, see thing/get module.

0. Import

For the examples, import the following objects from the client libraries:

import {
  getSolidDataset,
  getThing,
  getStringNoLocale,
  getUrlAll
} from "@inrupt/solid-client";

import { FOAF, VCARD } from "@inrupt/vocab-common-rdf";

1. Fetch the SolidDataset

To access data, first use getSolidDataset to fetch the SolidDataset, such as a profile document, that contains the data.

Use getSolidDataset() to get the Profile Document

To fetch a SolidDataset, pass its URL to getSolidDataset as in the following example:

const myDataset = await getSolidDataset(
  "https://pod.inrupt.com/docsteam/profile/card",
  { fetch: fetch }          // fetch from authenticated session
);

Although the example retrieves a SolidDataset that is accessible by the public, for illustrative purposes, the example includes a fetch function. If the fetch is associated with a logged in Session, the getSolidDataset() operation is an authenticated request.

2. Get the Data Entity Thing

From the fetched SolidDataset, you can use either:

  • getThing with the Thing’s URL to get a single data entity, or

  • getThingAll to get all data entities from the dataset.

Use getThing() to get profile data associated with WebID from SolidDataset

The following example passes in the WebID to the getThing to retrieve the profile (i.e., the Thing) from the previously fetched SolidDataset (i.e., the profile document).

// 2. Get the data entity/Thing, specified by the URL, from the SolidDataset.

const profile = getThing(
  myDataset,
  "https://pod.inrupt.com/docsteam/profile/card#me"
);

3. Read Data Attribute of a Thing

Like the Thing and SolidDataset, each property (such as name, etc.) of a Thing is also identified by a URL. That is, when storing the name of a book, you do not store the data under the string identifier name. Instead, you use a URL identifier, such as https://schema.org/name from the https://schema.org Vocabulary (or some other URL).

A property can have zero, one or more values, and the value is typed; e.g., a string, an integer, or a URL if pointing to other Things. To retrieve the data, use the appropriate get function depending on the data type and the number of values for the property.

Note

These types are specific to Solid, i.e. they are not JavaScript types. As such, there is a distinction between integers and decimals as well as a separate type for URLs.

To the get function, pass the URL that identifies the property to fetch. To encourage interoperability, various Vocabularies exist to identify common data. For example:

Tip

Inrupt’s vocab-common-rdf library bundles constants that refer to terms in popular pre-existing vocabularies. Applications can use these constants instead of specifying the identifier’s URL. For more information on Vocabulary usage, see Vocabulary.

Use various get() functions to get properties/data from the profile.

From the retrieved profile (i.e. the Thing), the following operations gets the following properties:

  • http://xmlns.com/foaf/0.1/name

  • http://xmlns.com/foaf/0.1/knows.

For simplicity, the example uses the corresponding constants (FOAF.name and FOAF.knows) from the @inrupt/vocab-common-rdf library for the URLs.

// 3a. Get a single string data value from the profile.
// Specifically, the operation returns the profile's FOAF.name 
// (i.e., "http://xmlns.com/foaf/0.1/name") value as a string.
// Depending on your profile document, the name may be stored under a different identifier.

const fn = getStringNoLocale(profile, FOAF.name);

// 3b. Get multiple URL data from the profile.
// Specifically , the operation returns the profile's FOAF.knows
// (i.e., "http://xmlns.com/foaf/0.1/knows") values as an array of URLs. 
// The URLs point to the Things representing Persons.

const acquaintances = getUrlAll(profile, FOAF.knows);

Write Data

Write Access

Writing data to a Pod requires that the user have appropriate access. That is, a user must first authenticate as someone with the appropriate access. Then, the user can pass in the authenticated Session’s fetch function to the various read/write functions. For an example, see the Prerequisite section.

Assumptions

The following examples assumes:

  • The application has used the solid-client-authn-browser library to handle login and has an authenticated fetch function. See the Prerequisite section for details on authenticating the user’s session.

  • The logged-in user has permission to read and write the specified SolidDataset. See the Required Access for the access required for specific read/write actions.

Write a New SolidDataset

1. Create a new SolidDataset

You can use createSolidDataset to create a new SolidDataset. For example:

// Create a new SolidDataset for Writing 101
let courseSolidDataset = createSolidDataset();

2. Create a new Thing with Data (Fluent API)

First, use createThing to create a new Thing (i.e., data entity) for the SolidDataset. Pass the function a name for the Thing. Typically, a Thing’s URL is the SolidDataset’s URL appended with a # hash fragment. Upon save, the specified name becomes the # hash fragment; i.e., the Thing’s URL becomes <SolidDatasetURL>#<name>.

Then, you can use the buildThing() and the ThingBuilder functions (Fluent API) to generate the Thing with the data modifications.

For example:

// Create a new Thing for "book1"; Thing's URL will include the hash #book1.
// Use Fluent API to add properties to the new Thing, and 
// build a new Thing with the properties.
// Note: solid-client functions do not modify objects passed in as arguments. 
// Instead the functions return new objects with the modifications.
const newBookThing1 = buildThing(createThing({ name: "book1" }))
  .addStringNoLocale(SCHEMA_INRUPT.name, "ABC123 of Example Literature")
  .addUrl(RDF.type, "https://schema.org/Book")
  .build();           

The example creates a new Thing with name book1 (which will be part of its URL), and using the Fluent API, adds the following Thing properties and value:

SCHEMA_INRUPT.name

"ABC123 of Example Literature"

RDF.type

"https://schema.org/Book"

Note

The solid-client library’s functions (such as the various add/set/remove functions) do not modify the objects that are passed in as arguments. Instead, the library’s functions return a new object with the requested changes, and do not modify the passed-in objects.

3. Create a new Thing with Data (Alternative)

First, use createThing to create a new Thing (i.e., data entity) for the SolidDataset. Pass the function a name for the Thing. Typically, a Thing’s URL is the SolidDataset’s URL appended with a # hash fragment. Upon save, the specified name becomes the # hash fragment; i.e., the Thing’s URL becomes <SolidDatasetURL>#<name>.

Then, use the series of add/set/remove functions to create a new Thing with the data modifications:

  • thing/add functions that return a new Thing modified with new data added,

  • thing/set functions that return a new Thing modified with the specified data, and

  • thing/remove functions that return a new Thing modified with specified data removed.

// Create a new Thing for "book2"; Thing's URL will include the hash #book2.
// Use various add functions to add properties to the Thing
// Note: solid-client functions do not modify objects passed in as arguments. 
// Instead the functions return new objects with the modifications.
let newBookThing2 = createThing({ name: "book2" });
newBookThing2 = addStringNoLocale(newBookThing2, SCHEMA_INRUPT.name, "ZYX987 of Example Poetry");
newBookThing2 = addUrl(newBookThing2, RDF.type, "https://schema.org/Book");

The example creates a new Thing with name book2 (which will be part of its URL), and using a sequence of thing/add functions to add the following Thing properties:

SCHEMA_INRUPT.name

"ZYX987 of Example Poetry"

RDF.type

"https://schema.org/Book"

Note

The solid-client library’s functions (such as the various add/set/remove functions) do not modify the objects that are passed in as arguments. Instead, the library’s functions return a new object with the requested changes, and do not modify the passed-in objects.

3. Insert Things into SolidDataset

Use setThing to create a SolidDataset that contains the Things.

For example:

// Update SolidDataset with the book1 and book2 Things.
// Note: solid-client functions do not modify objects passed in as arguments. 
// Instead the functions return new objects with the modifications.
courseSolidDataset = setThing(courseSolidDataset, newBookThing1);
courseSolidDataset = setThing(courseSolidDataset, newBookThing2);

The solid-client library’s functions (such as the various add/set/remove functions) do not modify the objects that are passed in as arguments. Instead, the library’s functions return a new object with the requested changes, and do not modify the passed-in objects.

4. Save the SolidDataset

Use saveSolidDataSetAt to save the SolidDataset to the Pod and return the SolidDataset. For example:

  // Save the SolidDataset at the specified URL.
  // The function returns a SolidDataset that reflects your sent data
  const savedSolidDataset = await saveSolidDatasetAt(
    "https://pod.example.com/universityZ/fall2021/courses/Writing101",
    courseSolidDataset,
    { fetch: fetch }             // fetch from authenticated Session
  );

When using saveSolidDataSetAt, the Solid server creates any intermediary Containers as needed.

See also Save Considerations.

Modify an Existing SolidDataset

1. Retrieve the SolidDataset.

You can use getSolidDataset to fetch an existing SolidDataset. For example:

// Get the SolidDataset for Writing 101 at the specified URL
const resourceURL = "https://pod.example.com/universityZ/fall2021/courses/Writing101";
let courseSolidDataset = await getSolidDataset(
  resourceURL,
  { fetch: fetch }
);

2. Get the Thing to Modify (Fluent API)

First, you can use getThing to get an existing Thing from the fetched SolidDataset. Pass the function the Thing’s URL. Typically, a Thing’s URL is <SolidDatasetURL>#<name>.

Then, you can use the buildThing() and the ThingBuilder functions (Fluent API) to generate the Thing with the data modifications.

For example:

// Get the "book1" Thing from the retrieved SolidDataset; the Thing's URL will be the SolidDatatset URL with hash #book1.
// Use Fluent API to add a new property to the Thing, and 
// build a new Thing with the added property.
// Note: solid-client functions do not modify objects passed in as arguments. 
// Instead the functions return new objects with the modifications.
let book1Thing = getThing(courseSolidDataset, `${resourceURL}#book1`);
book1Thing = buildThing(book1Thing)
  .addInteger("https://schema.org/numberOfPages", 30)
  .build();

The example gets the book1 Thing, and using the Fluent API, adds the following Thing property and value:

"https://schema.org/numberOfPages"

30

Note

The solid-client library’s functions (such as the various add/set/remove functions) do not modify the objects that are passed in as arguments. Instead, the library’s functions return a new object with the requested changes, and do not modify the passed-in objects.

3. Get the Thing to Modify (Alternative)

First, you can use getThing to get an existing Thing from the fetched SolidDataset. Pass the function the Thing’s URL. Typically, a Thing’s URL is <SolidDatasetURL>#<name>.

Then, use the series of add/set/remove functions to modify the Thing with the data modifications:

  • thing/add functions that return a new Thing modified with new data added,

  • thing/set functions that return a new Thing modified with the specified data, and

  • thing/remove functions that return a new Thing modified with specified data removed.

For example:

// Get the "book2" Thing from the retrieved SolidDataset; the Thing's URL will be the SolidDatatset URL with hash #book2.
// Use setStringNoLocale to return a new Thing with the name property set to "ZYX987 of Example Poesy"
// Note: solid-client functions do not modify objects passed in as arguments. 
// Instead the functions return new objects with the modifications.
let book2Thing = getThing(courseSolidDataset, `${resourceURL}#book2`);
book2Thing = setStringNoLocale(book2Thing, SCHEMA_INRUPT.name, "ZYX987 of Example Poesy");

The example gets the book2 Thing, and uses a setStringNoLocale function to update the Thing’s SCHEMA_INRUPT.name value to "ZYX987 of Example Poesy".

Note

The solid-client library’s functions (such as the various add/set/remove functions) do not modify the objects that are passed in as arguments. Instead, the library’s functions return a new object with the requested changes, and do not modify the passed-in objects.

4. Create a New Thing to Add

In addition to being able to modify existing Things in the SolidDataset, you can add a new Thing to the existing SolidDataset.

First, use createThing to create a new Thing (i.e., data entity) for the SolidDataset. Pass the function a name for the Thing. Typically, a Thing’s URL is the SolidDataset’s URL appended with a # hash fragment. Upon save, the specified name becomes the # hash fragment; i.e., the Thing’s URL becomes <SolidDatasetURL>#<name>.

Then, you can use the buildThing() and the ThingBuilder functions (Fluent API) to generate the Thing with the data modifications.

The following example creates a new Thing with the name location (which will be part of its URL), and uses the Fluent API to add various properties and values.

// Create a new Thing for "location"; Thing's URL will include the hash #location.
// Use Fluent API to add properties to the new Thing, and 
// build a new Thing with the properties.
// Note: solid-client functions do not modify objects passed in as arguments. 
// Instead the functions return new objects with the modifications.
const locationThing = buildThing(createThing({ name: "location" }))
  .addStringNoLocale(SCHEMA_INRUPT.name, "Sample Lecture Hall")
  .addUrl(RDF.type, "https://schema.org/Place")
  .build();

See also 2. Create a new Thing with Data (Fluent API) and 3. Create a new Thing with Data (Alternative).

5. Update the SolidDataset with Modified/New Things

Use setThing to create a SolidDataset with Thing modifications.

For example:

// Update SolidDataset with the book1 and book2 and location Things.
// Note: solid-client functions do not modify objects passed in as arguments. 
// Instead the functions return new objects with the modifications.
courseSolidDataset = setThing(courseSolidDataset, book1Thing);
courseSolidDataset = setThing(courseSolidDataset, book2Thing);
courseSolidDataset = setThing(courseSolidDataset, locationThing);

In the returned SolidDataset, the book1 and book2 Things have been overwritten and the new location Thing has been added.

The solid-client library’s functions (such as the various add/set/remove functions) do not modify the objects that are passed in as arguments. Instead, the library’s functions return a new object with the requested changes, and do not modify the passed-in objects.

6. Save the SolidDataset

Use saveSolidDataSetAt to save the SolidDataset to the Pod and return the SolidDataset. For example:

// Save the SolidDataset at the specified URL.
// The function returns a SolidDataset that reflects your sent data
const savedSolidDataset = await saveSolidDatasetAt(
  resourceURL,
  courseSolidDataset,
  { fetch: fetch }             // fetch from authenticated Session
);

A SolidDataset keeps track of the data changes compared to the data in the Pod. For set operations on existing properties, the changelog tracks both the old value and new values of the property being modified.

The save operation applies the changes from the changelog to the current SolidDataset. If the old value specified in the changelog does not correspond to the value currently in the Pod, the save operation will error. See Save Considerations.

Save Considerations

Create vs. Update Operations

saveSolidDatasetAt can be used to create new Resource or update an existing Resource in a Pod. If the passed-in Resource has an identifying URL, the function performs an update. If the passed-in Resource has no identifying URL, the function attempts to create a new Resource.

When creating a new Resource, the function adds the precondition that the Resource must not already exist. If the Resource already exists, the server will return a 412 Precondition Failed error.

Changelog Considerations

A SolidDataset keeps track of the data changes compared to the data in the Pod. The save operation applies the changes from the changelog to the current SolidDataset:

  • For add operations, the changelog keeps track of additions.

  • For remove operations, the changelog keeps track of the deletions.

  • For set operations, the changelog keeps track of the old and new values. As such, if the old value specified in the changelog does not correspond to the value currently in the Pod, the save operation will error with a 409 Conflict.

In the Modify an Existing SolidDataset example, the locally modified courseSolidDataset has a changelog that reflects:

  • addition of a number of pages property for book1

  • modification of the name property field (from old value to new value) for book2.

  • addition of a new Thing location

Scenario 1: If another operation has modified the name property of book2 after you have retrieved courseSolidDataset but before you save the locally modified courseSolidDataset, the save operation errors with 409 Conflict.

Scenario 2: If another operation has separately added/modified/removed a property from book1 after you have retrieved courseSolidDataset but before you the locally modified courseSolidDataset, the save operation succeeds, but the returned SolidDataset only reflects your changes. To make sure the SolidDataset accurately reflects all changes to date, call getSolidDataset again after saving the data.

Returned SolidDataset

saveSolidDatasetAt returns a SolidDataset that reflects only the sent data. That means, if another process made separate non-conflicting modifications to the SolidDataset after you retrieved the SolidDataset but before you successfully saved, the returned SolidDataset only reflects your changes.

To make sure the SolidDataset reflects not just your changes, call getSolidDataset again after saving the data.

Delete an Existing SolidDataset

To delete a SolidDataset from a Pod, you can use deleteSolidDataset to remove the SolidDataset at the specified URL. To use, pass the function the following parameters:

Parameter

Value

SolidDataset URL

The URL of the SolidDataset to delete.

Options object

An object that includes the following option:

{ fetch: <fetch func> }

fetch

fetch function from an authenticated session if deleting a restricted Resource (i.e., the general public cannot delete the specified SolidDataset).

Optional if the general public can delete the Resource.

Note

To delete a SolidDataset, you must have the Write access to the SolidDataset. For more information, see Required Access.

The following example uses deleteSolidDataset to delete the specified SolidDataset. The example assumes the user has the appropriate access.

import { login, handleIncomingRedirect, getDefaultSession, fetch } from "@inrupt/solid-client-authn-browser";

import {
  deleteSolidDataset
} from "@inrupt/solid-client";


// ... Various logic, including login logic, omitted for brevity.

try {
  await deleteSolidDataset(
    "https://pod.example.com/universityZ/fall2021/courses/Writing101", 
    { fetch: fetch }           // fetch function from authenticated session
  );
} catch (error) {
  //...
}