Getting Started: Part 2¶
This tutorial creates an introductory application that uses Inrupt’s JavaScript client libraries to write to your Pod. Alternatively, to create a sample application using Inrupt’s Solid React SDK, refer to Solid React SDK documentation.
The tutorial uses npm and Webpack to run the application locally on
localhost:8080
.

Prerequisites¶
The tutorial follows Getting Started: Part 1. As such, the tutorial assumes that you have:
Registered a Pod.
Installed npm.
If you do not have the prerequisites, refer to Prerequisites in Getting Started: Part 1.
Note
The tutorial uses either https://pod.inrupt.com
or
https://inrupt.net/
as the example Pod Server. If your
example Pod is not on either of these servers, you must change the
oidcIssuer
value in the application’s index.js
file.
For more information on these Pod servers, see Inrupt Pod Spaces.
Build the Application¶
1. Initialize the Application¶
Create the directory structure for your Webpack project:
mkdir -p my-demo-app2 my-demo-app2/src my-demo-app2/dist
Go to the newly created
my-demo-app2
directory.cd my-demo-app2
Initialize the application.
To accept the default values for the application without prompts:
npm init -y
- or -
To be prompted to enter values for the application:
npm init
You can either hit return to accept the default values (including empty values) or supply your own values.
When prompted
Is this OK? (yes)
, enter to acceptyes
.
2. Install the Client Libraries¶
Use
npm
to install thesolid-client
,solid-client-authn-browser
, andvocab-common-rdf
libraries:npm install @inrupt/solid-client @inrupt/solid-client-authn-browser @inrupt/vocab-common-rdf
3. Install Webpack¶
Use
npm
to install Webpack packages:npm install webpack webpack-cli webpack-dev-server css-loader style-loader --save-dev
In
my-demo-app2
directory, create awebpack.config.js
file with the following content:const path = require("path"); module.exports = { mode: "development", entry: "./src/index.js", output: { path: path.resolve(__dirname, "public"), filename: "index.js" }, module: { rules: [ { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader" } ], }, ] }, devServer: { contentBase: "./dist" }, resolve: { fallback: { stream: require.resolve("stream-browserify") , crypto: require.resolve("crypto-browserify") } } };
In
my-demo-app2
directory, edit thepackage.json
file to addbuild
andstart
script fields toscripts
:Tip
Be sure to add the comma after the preceding field value before adding the
build
andstart
fields."scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack", "start": "webpack serve --open true" },
4. Create the Application¶
In the my-demo-app2
directory, create the files for the application.
The tutorial provides an explanation of the JavaScript code at the end of the
tutorial.
In the
my-demo-app2/dist
folder, create amy-demo.css
file with the following content:h2,h3 { margin: 1rem 1.2rem 1rem 1.4rem; } body * { margin-left: .5rem; margin-right: 1rem; } header { border-bottom: #5795b9 solid; padding-left: .5rem; } .panel { border: 1px solid #005b81; border-radius: 4px; box-shadow: rgb(184, 196, 194) 0px 4px 10px -4px; box-sizing: border-box; padding: 1rem 1.5rem; margin: 1rem 1.2rem 1rem 1.2rem; } #login { background: white; } #labelStatus[role="alert"] { padding-left: 1rem; color: purple; } #write { background: #e6f4f9; } #titles { margin-left: 1.3rem; } label { vertical-align: top; } #savedtitles { border: none; background: #f8fbfd; margin-left: 6rem; } #labelRetrieved { margin-left: 1.2rem; }
In the
my-demo-app2/dist
, create anindex.html
file with the following content:<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Getting Started: Inrupt JavaScript Client Libraries</title> <script defer src="./index.js" ></script> <link rel="stylesheet" href="my-demo.css" /> </head> <body> <header> <h2>Getting Started</h2> <h3>with Inrupt JavaScript Client Libraries: Write Operation</h3> </header> <section id="login" class="panel"> <div class="row"> <label id="labelLogin" for="btnLogin">1. Click the button to log in: </label> <button name="btnLogin" id="btnLogin">Login</button> <p id="labelStatus"></p> </div> </section> <div id="write" class="panel" > <div class="row"> <label id="writelabel">2. Create a private reading list in your Pod.</label> </div> <div class="row"> <label id="podlabel" for="PodURL">a. Enter your Pod URL: </label> <input type="url" id="PodURL" name="PodURL" size="50" pattern="https://.*" placeholder="e.g., https://pod.inrupt.com/<username>">/getting-started/readingList/myList </div> <div class="row"> <label id="listLabel" for="titles">b. Enter items to read: </label> <textarea id="titles" name="titles" rows="5" cols="42" > Leaves of Grass RDF 1.1 Primer</textarea> <button name="btnCreate" id="btnCreate">Create</button> <span id="labelCreateStatus"></span> </div> <div class="row"></div> <label id="labelRetrieved" for="savedtitles">Retrieved:</label> <textarea id="savedtitles" name="savedtitles" rows="5" cols="42" readonly></textarea> </div> </div> </body> </html>
In the
my-demo-app2/src
, create anindex.js
file with the following content:Note
If your Pod is not on
https://pod.inrupt.com
, modify theoidcIssuer
value.import { createSolidDataset, createThing, setThing, addUrl, addStringNoLocale, saveSolidDatasetAt, getSolidDataset, getThingAll, getStringNoLocale } from "@inrupt/solid-client"; import { login, handleIncomingRedirect, getDefaultSession, fetch } from "@inrupt/solid-client-authn-browser"; import { SCHEMA_INRUPT_EXT, RDF, AS } from "@inrupt/vocab-common-rdf"; const buttonLogin = document.querySelector("#btnLogin"); const buttonCreate = document.querySelector("#btnCreate"); buttonCreate.disabled=true; const labelCreateStatus = document.querySelector("#labelCreateStatus"); // 1a. Start Login Process. Call login() function. function loginToInruptDotCom() { return login({ oidcIssuer: "https://broker.pod.inrupt.com", redirectUrl: window.location.href, }); } // 1b. Login Redirect. Call handleIncomingRedirect() function. // When redirected after login, finish the process by retrieving session information. async function handleRedirectAfterLogin() { await handleIncomingRedirect(); const session = getDefaultSession(); if (session.info.isLoggedIn) { // Update the page with the status. document.getElementById("labelStatus").textContent = "Your session is logged in."; document.getElementById("labelStatus").setAttribute("role", "alert"); // Enable Create button buttonCreate.disabled=false; } } // The example has the login redirect back to the index.html. // This calls the function to process login information. // If the function is called when not part of the login redirect, the function is a no-op. handleRedirectAfterLogin(); // 2. Create the Reading List async function createList() { labelCreateStatus.textContent = ""; const podUrl = document.getElementById("PodURL").value; // For simplicity and brevity, this tutorial hardcodes the SolidDataset URL. // In practice, you should add a link to this resource in your profile that applications can follow. const readingListUrl = `${podUrl}/getting-started/readingList/myList`; let titles = document.getElementById("titles").value.split("\n"); let myReadingList = createSolidDataset(); // Add titles to the Dataset for (let i = 0; i < titles.length; i++) { let title = createThing({name: "title" + i}); title = addUrl(title, RDF.type, AS.Article); title = addStringNoLocale(title, SCHEMA_INRUPT_EXT.name, titles[i]); myReadingList = setThing(myReadingList, title); } try { let savedReadingList = await saveSolidDatasetAt( readingListUrl, myReadingList, { fetch: fetch } ); labelCreateStatus.textContent = "Saved"; // Disable Create button buttonCreate.disabled=true; // Refetch the Reading List savedReadingList = await getSolidDataset( readingListUrl, { fetch: fetch } ); let items = getThingAll(savedReadingList); let listcontent=""; for (let i = 0; i < items.length; i++) { let item = getStringNoLocale(items[i], SCHEMA_INRUPT_EXT.name); if (item != null) { listcontent += item + "\n"; } } document.getElementById("savedtitles").value = listcontent; } catch (error) { console.log(error); labelCreateStatus.textContent = "Error" + error; labelCreateStatus.setAttribute("role", "alert"); } } buttonLogin.onclick = function() { loginToInruptDotCom(); }; buttonCreate.onclick = function() { createList(); };
For details about the JavaScript code, see Examination of the Code.
In the
my-demo-app2/dist
folder, create amy-demo.css
file with the following content:h2,h3 { margin: 1rem 1.2rem 1rem 1.4rem; } body * { margin-left: .5rem; margin-right: 1rem; } header { border-bottom: #5795b9 solid; padding-left: .5rem; } .panel { border: 1px solid #005b81; border-radius: 4px; box-shadow: rgb(184, 196, 194) 0px 4px 10px -4px; box-sizing: border-box; padding: 1rem 1.5rem; margin: 1rem 1.2rem 1rem 1.2rem; } #login { background: white; } #labelStatus[role="alert"] { padding-left: 1rem; color: purple; } #write { background: #e6f4f9; } #titles { margin-left: 1.3rem; } label { vertical-align: top; } #savedtitles { border: none; background: #f8fbfd; margin-left: 6rem; } #labelRetrieved { margin-left: 1.2rem; }
In the
my-demo-app2/dist
, create anindex.html
file with the following content:<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Getting Started: Inrupt JavaScript Client Libraries</title> <script defer src="./index.js" ></script> <link rel="stylesheet" href="my-demo.css" /> </head> <body> <header> <h2>Getting Started</h2> <h3>with Inrupt JavaScript Client Libraries: Write Operation</h3> </header> <section id="login" class="panel"> <div class="row"> <label id="labelLogin" for="btnLogin">1. Click the button to log in: </label> <button name="btnLogin" id="btnLogin">Login</button> <p id="labelStatus"></p> </div> </section> <div id="write" class="panel" > <div class="row"> <label id="writelabel">2. Create a private reading list in your Pod.</label> </div> <div class="row"> <label id="podlabel" for="PodURL">a. Enter your Pod URL: </label> <input type="url" id="PodURL" name="PodURL" size="50" pattern="https://.*" placeholder="e.g., https://<username>.inrupt.net">/getting-started/readingList/myList </div> <div class="row"> <label id="listLabel" for="titles">b. Enter items to read: </label> <textarea id="titles" name="titles" rows="5" cols="42" > Leaves of Grass RDF 1.1 Primer</textarea> <button name="btnCreate" id="btnCreate">Create</button> <span id="labelCreateStatus"></span> </div> <div class="row"></div> <label id="labelRetrieved" for="savedtitles">Retrieved:</label> <textarea id="savedtitles" name="savedtitles" rows="5" cols="42" readonly></textarea> </div> </div> </body> </html>
In the
my-demo-app2/src
, create anindex.js
file with the following content:Note
If your Pod is not on
https://inrupt.net
, modify theoidcIssuer
value.import { createSolidDataset, createThing, setThing, addUrl, addStringNoLocale, saveSolidDatasetAt, getSolidDataset, getThingAll, getStringNoLocale } from "@inrupt/solid-client"; import { login, handleIncomingRedirect, getDefaultSession, fetch } from "@inrupt/solid-client-authn-browser"; import { SCHEMA_INRUPT_EXT, RDF, AS } from "@inrupt/vocab-common-rdf"; const buttonLogin = document.querySelector("#btnLogin"); const buttonCreate = document.querySelector("#btnCreate"); buttonCreate.disabled=true; const labelCreateStatus = document.querySelector("#labelCreateStatus"); // 1a. Start Login Process. Call login() function. function loginToInruptDotNet() { return login({ oidcIssuer: "https://inrupt.net", redirectUrl: window.location.href, }); } // 1b. Login Redirect. Call handleIncomingRedirect() function. // When redirected after login, finish the process by retrieving session information. async function handleRedirectAfterLogin() { await handleIncomingRedirect(); const session = getDefaultSession(); if (session.info.isLoggedIn) { // Update the page with the status. document.getElementById("labelStatus").textContent = "Your session is logged in."; document.getElementById("labelStatus").setAttribute("role", "alert"); // Enable Create button buttonCreate.disabled=false; } } // The example has the login redirect back to the index.html. // This calls the function to process login information. // If the function is called when not part of the login redirect, the function is a no-op. handleRedirectAfterLogin(); // 2. Create the Reading List async function createList() { labelCreateStatus.textContent = ""; const podUrl = document.getElementById("PodURL").value; // For simplicity and brevity, this tutorial hardcodes the SolidDataset URL. // In practice, you should add a link to this resource in your profile that applications can follow. const readingListUrl = `${podUrl}/getting-started/readingList/myList`; let titles = document.getElementById("titles").value.split("\n"); let myReadingList = createSolidDataset(); // Add titles to the Dataset for (let i = 0; i < titles.length; i++) { let title = createThing({name: "title" + i}); title = addUrl(title, RDF.type, AS.Article); title = addStringNoLocale(title, SCHEMA_INRUPT_EXT.name, titles[i]); myReadingList = setThing(myReadingList, title); } try { let savedReadingList = await saveSolidDatasetAt( readingListUrl, myReadingList, { fetch: fetch } ); labelCreateStatus.textContent = "Saved"; // Disable Create button buttonCreate.disabled=true; // Refetch the Reading List savedReadingList = await getSolidDataset( readingListUrl, { fetch: fetch } ); let items = getThingAll(savedReadingList); let listcontent=""; for (let i = 0; i < items.length; i++) { let item = getStringNoLocale(items[i], SCHEMA_INRUPT_EXT.name); if (item != null) { listcontent += item + "\n"; } } document.getElementById("savedtitles").value = listcontent; } catch (error) { console.log(error); labelCreateStatus.textContent = "Error" + error; labelCreateStatus.setAttribute("role", "alert"); } } buttonLogin.onclick = function() { loginToInruptDotNet(); }; buttonCreate.onclick = function() { createList(); };
For details about the JavaScript code, see Examination of the Code.
5. Run the Application¶
In the
my-demo-app2
directory, run:npm run build && npm run start
The output resembles the following:
ℹ 「wds」: Project is running at http://localhost:8080/ ... ℹ 「wdm」: Compiled successfully.
Open
localhost:8080
in a browser.Login.
Click Login to login.
If you have logged out of your Pod, you are prompted to log in. Enter your username and password and log in.
For pod.inrupt.com, you will be prompted to accept Inrupt Terms and Conditions and authorize this application. To continue, click Authorize.
For inrupt.net, you will be prompted to authorize applications from
http://localhost:8080
to access your Pod, if you have not done so already.You are redirected back to your page.
Tip
You must be logged in to enable the Create button.
Write your reading list.
Enter your Pod URL; e.g.,
https://pod.inrupt.com/<username>
https://<username>.inrupt.net
.
Add your reading list.
Click Create to create.
The reading list is created in your
<PodURL>/getting-started/readingList/myList
.
6. Exit the Application¶
To exit the application, stop the npm run start
process; e.g.,
Ctrl-C
.
Examination of the Code¶
Login Code¶
The example uses the solid-client-authn-browser
library to log in.
solid-client-authn-browser
is for client-side code only.
Start the login process by calling login():
To login to a Pod on
https://pod.inrupt.com
:// 1a. Start Login Process. Call login() function. function loginToInruptDotCom() { return login({ oidcIssuer: "https://broker.pod.inrupt.com", redirectUrl: window.location.href, }); } // 1b. Login Redirect. Call handleIncomingRedirect() function.
This function sends the user to the identity provider
oidcIssuer
, and once logged in, redirects back to the specifiedredirectURL
.To login to a Pod on
https://inrupt.net
:// 1a. Start Login Process. Call login() function. function loginToInruptDotNet() { return login({ oidcIssuer: "https://inrupt.net", redirectUrl: window.location.href, }); } // 1b. Login Redirect. Call handleIncomingRedirect() function.
This function sends the user to the identity provider
oidcIssuer
, and once logged in, redirects back to the specifiedredirectURL
.
Note
If your Pod is not on either https://pod.inrupt.com
or
https://inrupt.net
, modify the oidcIssuer
value.
When redirected back from the identity provider, use handleIncomingRedirect() to complete the login process:
// 1b. Login Redirect. Call handleIncomingRedirect() function.
// When redirected after login, finish the process by retrieving session information.
async function handleRedirectAfterLogin() {
await handleIncomingRedirect();
const session = getDefaultSession();
if (session.info.isLoggedIn) {
// Update the page with the status.
document.getElementById("labelStatus").textContent = "Your session is logged in.";
document.getElementById("labelStatus").setAttribute("role", "alert");
// Enable Create button
buttonCreate.disabled=false;
}
}
// The example has the login redirect back to the index.html.
// This calls the function to process login information.
// If the function is called when not part of the login redirect, the function is a no-op.
The function collects and verifies the information provided by the identity provider.
For more information on using the library to authenticate, see Authenticate.
Write Reading List¶
The example uses the solid-client
and vocab-common-rdf
libraries to write data to your Pod.
Tip
For the sake of simplicity and brevity, this getting started guide hardcodes the SolidDataset URL. In practice, you should add a link to this resource in your profile that applications can follow.
From @inrupt/solid-client
and @inrupt/vocab-common-rdf
,
import the objects used in the application:
import {
createSolidDataset,
createThing,
setThing,
addUrl,
addStringNoLocale,
saveSolidDatasetAt,
getSolidDataset,
getThingAll,
getStringNoLocale
} from "@inrupt/solid-client";
} from "@inrupt/solid-client-authn-browser";
import { SCHEMA_INRUPT_EXT, RDF, AS } from "@inrupt/vocab-common-rdf";
Use createSolidDataset to create a new SolidDataset (i.e., the reading list).
let myReadingList = createSolidDataset();
The application uses createThing to create a new data entity (i.e., Thing) for each title to save. 1
To the Thing, the application uses the following functions to add data to the Thing:
addUrl to add the
RDF.type
property withAS.Article
as its value.addStringNoLocale to add the
SCHEMA_INRUPT_EXT.name
property with the title as its value.
Then, the application uses setThing to add the Thing to the SolidDataset (i.e., the reading list).
// Add titles to the Dataset
for (let i = 0; i < titles.length; i++) {
let title = createThing({name: "title" + i});
title = addUrl(title, RDF.type, AS.Article);
title = addStringNoLocale(title, SCHEMA_INRUPT_EXT.name, titles[i]);
myReadingList = setThing(myReadingList, title);
}
RDF
, SCHEMA_INRUPT_EXT
, and AS
provide convenience
objects for many Solid-related terms:
RDF
provides convenience objects for the RDF Vocabulary. For example, theRDF.type
is a convenience object that includes thehttp://www.w3.org/1999/02/22-rdf-syntax-ns#type
IRI.SCHEMA_INRUPT_EXT
is Inrupt’s extension of the schema.org Vocabulary. It provides convenience objects for a subset of terms from the schema.org Vocabulary, adding language tags/translations to labels and comments if missing from schema.org.By limiting the number of terms,
SCHEMA_INRUPT_EXT
aims to make working with select terms from Schema.org easier as Schema.org currently defines over 2,500 terms. For more information, see SCHEMA_INRUPT_EXT.AS
provides convenience objects for the ActivityStreams Vocabulary. For example, theAS.Article
is a convenience object that includes thehttps://www.w3.org/ns/activitystreams#Article
IRI.
Although you can use the IRI string instead of the convenience objects, these objects represent many of the ideas and concepts that are useful in Solid itself as well as in Solid applications.
The identifiers RDF.type
, SCHEMA_INRUPT_EXT.name
, and
AS.Article
(or their IRI equivalents) impose no restrictions
on the data saved for the titles. For example, adding the
RDF.type
property with value AS.Article
to the data
entity imposes no conditions about the data properties of the
entity. See Vocabularies vs. Data Schemas for more information.
The solid-client
library’s functions (such as the various
add/set 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.
- 1
The application specifies the Thing’s name (optional) during its instantiation. A Thing’s URL is its Dataset URL appended with
#
and the Thing’s name; in this case:${podURL}/getting-started/readingList/myList#title1
,${podURL}/getting-started/readingList/myList#title2
, etc.
Use saveSolidDatasetAt to
save the SolidDataset with the titles data to
<PodURL>/getting-started/readingList/myList
.
saveSolidDatasetAt
creates any intermediate folders/containers as needed. 2
let savedReadingList = await saveSolidDatasetAt(
readingListUrl,
myReadingList,
{ fetch: fetch }
);
Upon successful save, saveSolidDatasetAt returns a SolidDataset whose state reflect the data that was sent to be saved.
- 2
The
solid-client
library also provides the saveSolidDatasetInContainer. However, unlike saveSolidDatasetAt which creates any intermediate folders/containers as needed, saveSolidDatasetInContainer requires that the specified destination container already exists.
Upon save, the application returns the SolidDataset (the reading
list) whose state reflect the data that was sent to be saved. The
savedReadingList
may not accurately reflect the saved
state of the data if concurrent operations have modified
additional fields.
To retrieve the saved reading list, the tutorial uses getSolidDataset.
// Refetch the Reading List
savedReadingList = await getSolidDataset(
readingListUrl,
{ fetch: fetch }
);
let items = getThingAll(savedReadingList);
let listcontent="";
for (let i = 0; i < items.length; i++) {
let item = getStringNoLocale(items[i], SCHEMA_INRUPT_EXT.name);
if (item != null) {
listcontent += item + "\n";
}
}
document.getElementById("savedtitles").value = listcontent;
The application uses SCHEMA_INRUPT_EXT.name
convenience object
from the vocab-common-rdf
library to specify the property to
retrieve.
For more information on property identifiers and vocabularies, see Vocabulary.
For more information on write operations, see Write Data.
Additional Information¶
API Documentation¶
React SDK Availability¶
To create a sample application using Inrupt’s Solid React SDK, refer to the Solid React SDK documentation.