Getting Started: Part 1

This tutorial creates an introductory application that uses Inrupt’s JavaScript client libraries to read your user profile data. 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.

Locally Run Getting Started Application

Prerequisites

Register Your Pod and Create Your Profile

Note

The tutorial uses either https://pod.inrupt.com or https://inrupt.net/ for your example Pod Server. If your example Pod is not on either of those 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.

To create a Pod at https://pod.inrupt.com (Currently in Alpha),

  1. Go to https://signup.pod.inrupt.com/.

  2. To login or sign up, you must agree to the Inrupt terms of use.

  3. Click Login/Sign Up. You are shown the Login screen.

  4. Click the Sign up link at the bottom to switch to the Sign up screen.

  5. Fill in your username, email, and password.

    Click Sign up. You are sent a verification code to the entered email.

  6. Retrieve the code from the email. Enter the code and click Confirm Account.

  7. Click Authorize to allow “Inrupt Pod Space Registration” to create your account.

    Once authorized, your Pod is created.

    WebId: https://pod.inrupt.com/<username>/profile/card#me

    Pod URL: https://pod.inrupt.com/<username>/

  8. Click on the link to Go to PodBrowser.

  9. At the Pod Browser login screen, enter https://broker.pod.inrupt.com.

  10. Click Authorize to allow PodBrowser to access your Pod.

  11. Once logged into PodBrowser, click on the top right person icon and click Profile.

  12. Edit your public profile.

    Important

    Your profile information is public.

    For this tutorial:

    1. Enter a name in the Name field.

    2. Enter a role in the Role field.

    Your data is saved automatically.

  13. Log out. Move your mouse to the image in the upper-right corner image to display additional menu items and click Log out.

To create a Pod at https://inrupt.net/:

  1. Open a browser to https://inrupt.net/.

  2. Click Register.

  3. Fill in the form and click Register.

    You are redirected to your public Solid Pod URL: https://<yourusername>.inrupt.net.

  4. Click Log in on the upper right corner.

  5. From the list of identity providers, click on your Solid Pod URL.

  6. Enter your Username and Password and click Log in.

  7. Click on the Edit your profile tab.

    Tip

    You may need to reload the page.

  8. Edit your public profile.

    Important

    Your profile information is public.

    For this tutorial:

    1. Enter a name in the Name field.

    2. Enter a role in the Role field.

    Your data is saved automatically.

  9. Log out. Move your mouse to the image in the upper-right corner image to display additional menu items and click Log out.

Install npm

If you do not already have npm installed, install npm. npm is installed as part of the Node.js installation.

Please make sure that you use a version of Node that is supported by Webpack. At the time of writing, that is version 10 or later.

Build the Application

1. Initialize the Application

  1. Create the directory structure for your Webpack project:

    mkdir -p  my-demo-app my-demo-app/src my-demo-app/dist
    
  2. Go to the newly created my-demo-app directory.

    cd my-demo-app
    
  3. 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
      
      1. You can either hit return to accept the default values (including empty values) or supply your own values.

      2. When prompted Is this OK? (yes), enter to accept yes.

2. Install the Client Libraries

  1. Use npm to install the solid-client, solid-client-authn-browser, and vocab-common-rdf libraries:

    npm install @inrupt/solid-client @inrupt/solid-client-authn-browser @inrupt/vocab-common-rdf
    

3. Install Webpack

  1. Use npm to install Webpack packages:

    npm install webpack webpack-cli webpack-dev-server css-loader style-loader --save-dev
    
  2. In my-demo-app directory, create a webpack.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")
          }
      }
    };
    
  3. In my-demo-app directory, edit the package.json file to add build and start script fields to scripts:

    Tip

    Be sure to add the comma after the preceding field value before adding the build and start 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-app directory, create the files for the application. The tutorial provides an explanation of the JavaScript code at the end of the tutorial.

  1. In the my-demo-app/dist folder, create a my-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;
    }
    
    #read {
       background: #e6f4f9;
    }
    
    #labelStatus[role="alert"] {
       padding-left: 1rem;
       color: purple;
    }
    
    .display {
        margin-left: 1rem;
        color: gray;
    }
    
    dl {
      display: grid;
      grid-template-columns:  max-content auto;
    }
    
    dt {
      grid-column-start: 1;
    }
    
    dd {
      grid-column-start: 2;
    }
    
  2. In the my-demo-app/dist, create an index.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</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="read" class="panel" >
          <div class="row">
            <label id="readlabel" for="webID">2. Enter a WebID: </label>
            <input type="url" id="webID" name="webID"  size="50" pattern="https://.*"  value="https://pod.inrupt.com/<username>/profile/card#me">
            <button name="btnRead" id="btnRead" >Read Profile</button>
          </div>
          <dl class="display"> 
              <dt>Formatted Name (FN): </dt>
              <dd id="labelFN"></dd>
              <dt>Role: </dt>
              <dd id="labelRole"></dd>
          </dl>
        </div>
      </body>
    </html>
    
  3. In the my-demo-app/src, create an index.js file with the following content:

    Note

    If your Pod is not on https://pod.inrupt.com, modify the oidcIssuer value.

    import {
      getSolidDataset,
      getThing,
      getStringNoLocale
    } from "@inrupt/solid-client";
    
    import { Session } from "@inrupt/solid-client-authn-browser";
    
    import { VCARD } from "@inrupt/vocab-common-rdf";
    
    
    const session = new Session();
    
    const buttonLogin = document.querySelector("#btnLogin");
    const buttonRead = document.querySelector("#btnRead");
    
    // 1a. Start Login Process. Call session.login() function.
    async function login() {
      if ( !session.info.isLoggedIn ) {
        await session.login({
          oidcIssuer: "https://broker.pod.inrupt.com",
          redirectUrl: window.location.href,
        });
      }
    }
    
    // 1b. Login Redirect. Call session.handleIncomingRedirect() function.
    // When redirected after login, finish the process by retrieving session information.
    async function handleRedirectAfterLogin() {
       await session.handleIncomingRedirect(window.location.href);
    
      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");
      }
    }
    
    // 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. Read profile
    async function readProfile() {
      const webID = document.getElementById("webID").value;
    
      // Profile is public data; i.e., you do not need to be logged in to read the data.
      // For illustrative purposes, shows both an authenticated and non-authenticated reads.
    
      let myDataset;
      if (session.info.isLoggedIn){
        myDataset = await getSolidDataset(webID, { fetch: session.fetch });
      } else {
        myDataset = await getSolidDataset(webID);
      }
    
      const profile = getThing(myDataset, webID);
    
      // Get the formatted name (fn) using the property identifier "http://www.w3.org/2006/vcard/ns#fn".
      // VCARD.fn object is a convenience object that includes the identifier string "http://www.w3.org/2006/vcard/ns#fn".
      // As an alternative, you can pass in the "http://www.w3.org/2006/vcard/ns#fn" string instead of VCARD.fn.
     
      const fn = getStringNoLocale(profile, VCARD.fn);
    
      // VCARD.role obect is a convenience object that includes the identifier string "http://www.w3.org/2006/vcard/ns#role"
      // As an alternative, you can pass in the "http://www.w3.org/2006/vcard/ns#role" string instead of VCARD.role.
    
      const role = getStringNoLocale(profile, VCARD.role);
    
      // Update the page with the retrieved values.
      document.getElementById("labelFN").textContent = fn;
      document.getElementById("labelRole").textContent = role;
    }
    
    buttonLogin.onclick = function() {  
      login();
    };
    
    buttonRead.onclick = function() {  
      readProfile();
    };
    
  1. In the my-demo-app/dist folder, create a my-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;
    }
    
    #read {
       background: #e6f4f9;
    }
    
    #labelStatus[role="alert"] {
       padding-left: 1rem;
       color: purple;
    }
    
    .display {
        margin-left: 1rem;
        color: gray;
    }
    
    dl {
      display: grid;
      grid-template-columns:  max-content auto;
    }
    
    dt {
      grid-column-start: 1;
    }
    
    dd {
      grid-column-start: 2;
    }
    
  2. In the my-demo-app/dist, create an index.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</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="read" class="panel" >
          <div class="row">
            <label id="readlabel" for="webID">2. Enter a WebID: </label>
            <input type="url" id="webID" name="webID"  size="50" pattern="https://.*" value="https://docs-example.inrupt.net/profile/card#me">
            <button name="btnRead" id="btnRead" >Read Profile</button>
          </div>
          <dl class="display"> 
              <dt>Formatted Name (FN): </dt>
              <dd id="labelFN"></dd>
              <dt>Role: </dt>
              <dd id="labelRole"></dd>
          </dl>
        </div>
      </body>
    </html>
    
  3. In the my-demo-app/src, create an index.js file with the following content:

    Note

    If your Pod is not on https://inrupt.net, modify the oidcIssuer value.

    import {
      getSolidDataset,
      getThing,
      getStringNoLocale
    } from "@inrupt/solid-client";
    
    import { Session } from "@inrupt/solid-client-authn-browser";
    
    import { VCARD } from "@inrupt/vocab-common-rdf";
    
    
    const session = new Session();
    
    const buttonLogin = document.querySelector("#btnLogin");
    const buttonRead = document.querySelector("#btnRead");
    
    // 1a. Start Login Process. Call session.login() function.
    async function login() {
      if ( !session.info.isLoggedIn ) {
        await session.login({
          oidcIssuer: "https://inrupt.net",
          redirectUrl: window.location.href,
        });
      }
    }
    
    // 1b. Login Redirect. Call session.handleIncomingRedirect() function.
    // When redirected after login, finish the process by retrieving session information.
    async function handleRedirectAfterLogin() {
    
      await session.handleIncomingRedirect(window.location.href);
    
      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");
      }
    }
    
    // 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. Read profile
    async function readProfile() {
      const webID = document.getElementById("webID").value;
    
      // Profile is public data; i.e., you do not need to be logged in to read the data.
      // For illustrative purposes, shows both an authenticated and non-authenticated reads.
    
      let myDataset;
      if (session.info.isLoggedIn){
        myDataset = await getSolidDataset(webID, { fetch: session.fetch });
      } else {
        myDataset = await getSolidDataset(webID);
      }
    
      const profile = getThing(myDataset, webID);
    
      // Get the formatted name (fn) using the property identifier "http://www.w3.org/2006/vcard/ns#fn".
      // VCARD.fn object is a convenience object that includes the identifier string "http://www.w3.org/2006/vcard/ns#fn".
      // As an alternative, you can pass in the "http://www.w3.org/2006/vcard/ns#fn" string instead of VCARD.fn.
     
      const fn = getStringNoLocale(profile, VCARD.fn);
    
      // VCARD.role obect is a convenience object that includes the identifier string "http://www.w3.org/2006/vcard/ns#role"
      // As an alternative, you can pass in the "http://www.w3.org/2006/vcard/ns#role" string instead of VCARD.role.
    
      const role = getStringNoLocale(profile, VCARD.role);
    
      // Update the page with the retrieved values.
      document.getElementById("labelFN").textContent = fn;
      document.getElementById("labelRole").textContent = role;
    }
    
    buttonLogin.onclick = function() {  
      login();
    };
    
    buttonRead.onclick = function() {  
      readProfile();
    };
    

For details about the JavaScript code, see Examination of the Code.

5. Run the Application

  1. In the my-demo-app directory, run:

    npm run build && npm run start
    

    The output resembles the following:

    ℹ 「wds」: Project is running at http://localhost:8080/
    ...
    ℹ 「wdm」: Compiled successfully.
    
  2. Open localhost:8080 in a browser.

  3. Login.

    1. Click Login to login.

    2. 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.

    3. You are redirected back to your page.

  4. Read public profile data.

    Note

    Because the profile data is public, you do not need to log in to read the data. The application does so to show how to make authenticated read requests.

    1. Enter your WebID.

    2. Click Read Profile.

    3. The page should display your formatted name and role.

    You can read public profiles of others by entering in their WebID; e.g., https://docs-example.inrupt.net/profile/card#me.

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.

From @inrupt/solid-client-authn-browser, import Session:

import { Session } from "@inrupt/solid-client-authn-browser";

Create a new Session:

const session = new Session();

Start the login process by calling Session.login():

  • To login to a Pod on https://pod.inrupt.com:

    // 1a. Start Login Process. Call session.login() function.
    async function login() {
      if ( !session.info.isLoggedIn ) {
        await session.login({
          oidcIssuer: "https://broker.pod.inrupt.com",
          redirectUrl: window.location.href,
        });
      }
    }
    
  • To login to a Pod on https://inrupt.net:

    // 1a. Start Login Process. Call session.login() function.
    async function login() {
      if ( !session.info.isLoggedIn ) {
        await session.login({
          oidcIssuer: "https://inrupt.net",
          redirectUrl: window.location.href,
        });
      }
    }
    

    This function sends the user to the identity provider oidcIssuer, and once logged in, redirects back to the specified redirectURL.

    Note

    If your Pod is not on pod.inrupt.net or https://inrupt.net, modify the oidcIssuer value accordingly.

When redirected back from the identity provider, use Session.handleIncomingRedirect() to complete the login process:

// 1b. Login Redirect. Call session.handleIncomingRedirect() function.
// When redirected after login, finish the process by retrieving session information.
async function handleRedirectAfterLogin() {

  await session.handleIncomingRedirect(window.location.href);

  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");
  }
}

// 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();

The function collects the information provided by the identity provider.

For more information on using the library to authenticate, see Authenticate.

Read Profile Code

The example uses the solid-client and vocab-common-rdf libraries to read profile data from a Pod.

From @inrupt/solid-client and @inrupt/vocab-common-rdf, import the objects used in the application:

import {
  getSolidDataset,
  getThing,
  getStringNoLocale
} from "@inrupt/solid-client";
import { VCARD } from "@inrupt/vocab-common-rdf";

Use getSolidDataset to fetch the SolidDataset from the URI.

  // Profile is public data; i.e., you do not need to be logged in to read the data.
  // For illustrative purposes, shows both an authenticated and non-authenticated reads.

  let myDataset;
  if (session.info.isLoggedIn){
    myDataset = await getSolidDataset(webID, { fetch: session.fetch });
  } else {
    myDataset = await getSolidDataset(webID);
  }
  1. For authenticated reads, pass in the URI and the session’s fetch function as an option.

  2. For unauthenticated reads, pass in the URI.

Although the application uses a URI that contains a fragment (i.e. the URI of a Thing), the returned SolidDataset is the resource located at the URI w/o the fragment. That is, if the URI is https://<username>.inrupt.net/profile/card#me, the returned SolidDataset is the resource at https://<username>.inrupt.net/profile/card. You can modify the application to pass in the resource URI https://<username>.inrupt.net/profile/card instead.

From the fetched SolidDataset, the application uses the getThing to retrieve the profile. A Thing’s URI contains a fragment, as in #me in https://pod.inrupt.com/<username>/profile/card#me or https://<username>.inrupt.net/profile/card#me.

  const profile = getThing(myDataset, webID);

The application uses getStringNoLocale to retrieve:

  • the formatted name (fn) property stored with the identifier http://www.w3.org/2006/vcard/ns#fn, and

  • the role (role) property stored with the identifier http://www.w3.org/2006/vcard/ns#role.

  // Get the formatted name (fn) using the property identifier "http://www.w3.org/2006/vcard/ns#fn".
  // VCARD.fn object is a convenience object that includes the identifier string "http://www.w3.org/2006/vcard/ns#fn".
  // As an alternative, you can pass in the "http://www.w3.org/2006/vcard/ns#fn" string instead of VCARD.fn.
 
  const fn = getStringNoLocale(profile, VCARD.fn);

  // VCARD.role obect is a convenience object that includes the identifier string "http://www.w3.org/2006/vcard/ns#role"
  // As an alternative, you can pass in the "http://www.w3.org/2006/vcard/ns#role" string instead of VCARD.role.

  const role = getStringNoLocale(profile, VCARD.role);

The application uses VCARD.fn and VCARD.role convenience objects from the vocab-common-rdf library to specify the property identifiers.

  • For more information on property identifiers and vocabularies, see Vocabulary.

  • For more information on read operations, see Read/Write Data.

Additional Information

React SDK Availability

To create a sample application using Inrupt’s Solid React SDK, refer to the Solid React SDK documentation.