Building your first Solid app

This tutorial creates an introductory application that uses Inrupt’s JavaScript Client Libraries to read and write your name to your user profile.

Note: Although this tutorial uses Inrupt’s JavaScript libraries, you can use other libraries (be it for JavaScript or other languages) that support Solid. For more information, see the Solid Developer Tools & Libraries.

This tutorial uses npm and Parcel to run the application locally on localhost:1234.

Locally Run Getting Started Application

You can also try out the application in CodeSandbox.

Edit on CodeSandbox

Inside CodeSandbox, rather than running the application in the embedded browser, open the application in a new window to be able to log in.

Prerequisites

Your own Pod and WebID

Before you start, make sure you have your own Pod and WebID.

Install npm

If you don’t already have npm installed, follow the appropriate instructions at the Get npm! site. npm is installed as part of the Node.js installation.

Build the Application

1. Initialize the Application

  1. Create a directory for the application:

     mkdir my-demo-app
    
  2. Go into the newly created directory:

     cd my-demo-app
    
  3. Create a brand new application (Omit the --yes to override the default values):

     npm init --yes
    

2. Install the Inrupt Client Libraries

Use npm to install Inrupt client libraries:

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

3. Install Parcel

  1. Use npm to install Parcel:

     npm install parcel-bundler
    
  2. Edit the package.json file to list the browsers supported by this application. Specifically, add the browserslist field and value:

    TIP: Be sure to add the comma after the preceding field before adding the browserslist field.

     "dependencies": {
        ...
     },
     "browserslist": [
       "last 3 and_chr versions",
       "last 3 chrome versions",
       "last 3 opera versions",
       "last 3 ios_saf versions",
       "last 3 safari versions",
       "last 3 edge versions"
     ]
    

    Without the browserslist specification, Parcel would need to inject a compatibility layer to avoid a regeneratorRuntime reference error since our sample application uses async functions.

    As an alternative to modifying the package.json file, you could import regenerator-runtime/runtime in your JavaScript code, which will inject a suitable compatibility layer.

4. Create the Application

In the my-demo-app directory, create the files for the application.

  1. Create a my-demo.css file with the following content:

    
        
     h2,
     h3 {
       margin: 1rem 1.2rem 1rem 1.4rem;
     }
    
     header {
       border-bottom: #5795b9 solid;
       padding-left: 0.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;
     }
    
     dd {
       font-weight: bold;
     }
    
    
  2. 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 into
               <span id="solid_identity_provider"
                 >...provided by the JavaScript code...</span
               >:
             </label>
             <button name="btnLogin" id="btnLogin">Login</button>
             <p id="labelStatus" class="labelStatus"></p>
           </div>
         </section>
    
         <div id="read" class="panel">
           <div class="row">
             <form id="writeForm">
               <label id="writelabel" for="input_name">2. Write your name: </label>
               <input
                 type="text"
                 id="input_name"
                 name="name"
                 size="50"
                 placeholder="Your name here"
               />
               <button type="submit">
                 Write to Profile
               </button>
             </form>
           </div>
    
           <dl class="display">
             <dt>Writing status:&nbsp</dt>
             <dd id="labelWriteStatus" class="labelStatus">...not written yet...</dd>
           </dl>
         </div>
    
         <div id="read" class="panel">
           <div class="row">
             <form id="readForm">
               <label id="readlabel" for="webID"
                 >3. Read back name (anyone's!) from their WebID:
               </label>
               <input
                 type="url"
                 id="webID"
                 name="webID"
                 size="50"
                 placeholder="...not logged in yet - but enter any WebID to read from its profile..."
               />
               <button type="submit" name="btnRead" id="btnRead">
                 Read Profile
               </button>
             </form>
           </div>
           <dl class="display">
             <dt>Formatted Name (FN) read from Pod:&nbsp</dt>
             <dd id="labelFN">...not read yet...</dd>
           </dl>
         </div>
       </body>
     </html>
    
    
  3. Create an index.js file with the following content:

    NOTE: If your Pod is not on https://solidcommunity.net, modify the value of the SOLID_IDENTITY_PROVIDER variable accordingly.

    
        
     import {
       getSolidDataset,
       getThing,
       setThing,
       getStringNoLocale,
       setStringNoLocale,
       saveSolidDatasetAt
     } from "@inrupt/solid-client";
     import { Session } from "@inrupt/solid-client-authn-browser";
     import { VCARD } from "@inrupt/vocab-common-rdf";
    
     // If your Pod is *not* on `solidcommunity.net`, change this to your identity provider.
     const SOLID_IDENTITY_PROVIDER = "https://solidcommunity.net";
     document.getElementById(
       "solid_identity_provider"
     ).innerHTML = `[<a target="_blank" href="${SOLID_IDENTITY_PROVIDER}">${SOLID_IDENTITY_PROVIDER}</a>]`;
    
     const NOT_ENTERED_WEBID =
       "...not logged in yet - but enter any WebID to read from its profile...";
    
     const session = new Session();
    
     const buttonLogin = document.getElementById("btnLogin");
     const writeForm = document.getElementById("writeForm");
     const readForm = document.getElementById("readForm");
    
     // 1a. Start Login Process. Call session.login() function.
     async function login() {
       if (!session.info.isLoggedIn) {
         await session.login({
           oidcIssuer: SOLID_IDENTITY_PROVIDER,
           clientName: "Inrupt tutorial client app",
           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"
         ).innerHTML = `Your session is logged in with the WebID [<a target="_blank" href="${session.info.webId}">${session.info.webId}</a>].`;
         document.getElementById("labelStatus").setAttribute("role", "alert");
         document.getElementById("webID").value = session.info.webId;
       }
     }
    
     // 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. Write to profile
     async function writeProfile() {
       const name = document.getElementById("input_name").value;
    
       if (!session.info.isLoggedIn) {
         // You must be authenticated to write.
         document.getElementById(
           "labelWriteStatus"
         ).textContent = `...you can't write [${name}] until you first login!`;
         document.getElementById("labelWriteStatus").setAttribute("role", "alert");
         return;
       }
       const webID = session.info.webId;
       // The WebID can contain a hash fragment (e.g. `#me`) to refer to profile data
       // in the profile dataset. If we strip the hash, we get the URL of the full
       // dataset.
       const profileDocumentUrl = new URL(webID);
       profileDocumentUrl.hash = "";
    
       // To write to a profile, you must be authenticated. That is the role of the fetch
       // parameter in the following call.
       let myProfileDataset = await getSolidDataset(profileDocumentUrl.href, {
         fetch: session.fetch
       });
    
       // The profile data is a "Thing" in the profile dataset.
       let profile = getThing(myProfileDataset, webID);
    
       // Using the name provided in text field, update the name in your profile.
       // 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.
       profile = setStringNoLocale(profile, VCARD.fn, name);
    
       // Write back the profile to the dataset.
       myProfileDataset = setThing(myProfileDataset, profile);
    
       // Write back the dataset to your Pod.
       await saveSolidDatasetAt(profileDocumentUrl.href, myProfileDataset, {
         fetch: session.fetch
       });
    
       // Update the page with the retrieved values.
       document.getElementById(
         "labelWriteStatus"
       ).textContent = `Wrote [${name}] as name successfully!`;
       document.getElementById("labelWriteStatus").setAttribute("role", "alert");
       document.getElementById(
         "labelFN"
       ).textContent = `...click the 'Read Profile' button to to see what the name might be now...?!`;
     }
    
     // 3. Read profile
     async function readProfile() {
       const webID = document.getElementById("webID").value;
    
       if (webID === NOT_ENTERED_WEBID) {
         document.getElementById(
           "labelFN"
         ).textContent = `Login first, or enter a WebID (any WebID!) to read from its profile`;
         return false;
       }
    
       try {
         new URL(webID);
       } catch (_) {
         document.getElementById(
           "labelFN"
         ).textContent = `Provided WebID [${webID}] is not a valid URL - please try again`;
         return false;
       }
    
       const profileDocumentUrl = new URL(webID);
       profileDocumentUrl.hash = "";
    
       // 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;
       try {
         if (session.info.isLoggedIn) {
           myDataset = await getSolidDataset(profileDocumentUrl.href, { fetch: session.fetch });
         } else {
           myDataset = await getSolidDataset(profileDocumentUrl.href);
         }
       } catch (error) {
         document.getElementById(
           "labelFN"
         ).textContent = `Entered value [${webID}] does not appear to be a WebID. Error: [${error}]`;
         return false;
       }
    
       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 formattedName = getStringNoLocale(profile, VCARD.fn);
    
       // Update the page with the retrieved values.
       document.getElementById("labelFN").textContent = `[${formattedName}]`;
     }
    
     buttonLogin.onclick = function () {
       login();
     };
    
     writeForm.addEventListener("submit", (event) => {
       event.preventDefault();
       writeProfile();
     });
    
     readForm.addEventListener("submit", (event) => {
       event.preventDefault();
       readProfile();
     });
    
    

Run the Application

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

     npx parcel index.html
    

    The output should resemble the following:

       Server running at http://localhost:1234
       ...
       Built in 2ms.
    
  2. Open localhost:1234 in a browser.

  3. Click the Login button.

    • The first time you log into your Pod with this application (it’ll be named http://localhost:1234), you’ll be prompted to authorize it to access your Pod . To allow the application to read and write to your Pod, click Authorize.

    • If you have logged out of your Pod, you are prompted to log in. Enter your username and password to log in.

    • Once logged in, you should be redirected back to the client application.

      Back in the application, you should see a message stating Your session is logged in with the WebID [<your WebID>]. and the WebID textfield should display your WebID.

  4. Now that you’re logged in, you can read and write the information in your Solid profile.

    1. First, click the Read Profile button.

      You should see the name you entered when you registered your Pod.

    2. To update your name, enter a new name in the 2. Write your name textfield, and click the Write to Profile button. You should see the message:

        Wrote [<your new name>] as name successfully!
      
    3. Verify that your profile was updated by clicking on the Read Profile button again. You should see the updated name displayed!

    You can also read the public profiles of anyone else in the world with a Solid Pod. Enter the WebID of the person whose profile you wish to read; e.g., https://docs-example.inrupt.net/profile/card#me.

  5. Exit the Application. To exit the application, stop the npx parcel process; e.g., Ctrl-C.

Additional Information

API Documentation

For more information on the functions: