Skip to main content

rdflib.js

rdflib.js is the foundation of SolidOS — a JavaScript library for working with RDF (Resource Description Framework) data.

Installation

npm install rdflib

Or via CDN:

<script src="https://cdn.jsdelivr.net/npm/rdflib/dist/rdflib.min.js"></script>

Core Concepts

The Store (IndexedFormula)

The store is an in-memory RDF graph database:

import { graph } from 'rdflib'

const store = graph()

// The store holds triples (subject, predicate, object, graph)
// Also called "quads" when including the graph component

Nodes

RDF uses different node types:

import { sym, lit, blankNode, variable } from 'rdflib'

// NamedNode - a URI reference
const alice = sym('https://alice.example/profile/card#me')

// Literal - a value with optional language or datatype
const name = lit('Alice')
const age = lit(30, null, sym('http://www.w3.org/2001/XMLSchema#integer'))
const greeting = lit('Bonjour', 'fr')

// BlankNode - anonymous node
const address = blankNode()

// Variable - for queries
const x = variable('x')

Namespaces

Create namespace helpers for common vocabularies:

import { Namespace } from 'rdflib'

const FOAF = Namespace('http://xmlns.com/foaf/0.1/')
const VCARD = Namespace('http://www.w3.org/2006/vcard/ns#')
const RDF = Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
const RDFS = Namespace('http://www.w3.org/2000/01/rdf-schema#')
const LDP = Namespace('http://www.w3.org/ns/ldp#')
const SOLID = Namespace('http://www.w3.org/ns/solid/terms#')

// Usage
const nameProperty = FOAF('name') // http://xmlns.com/foaf/0.1/name
const Person = FOAF('Person') // http://xmlns.com/foaf/0.1/Person

Working with the Store

Adding Data

import { graph, sym, lit, st } from 'rdflib'

const store = graph()
const alice = sym('https://alice.example/profile/card#me')
const doc = sym('https://alice.example/profile/card')

// Add a single statement
store.add(alice, FOAF('name'), lit('Alice'), doc)

// Using st() helper
const statement = st(alice, FOAF('age'), lit(30), doc)
store.add(statement)

// Add multiple statements
store.add(alice, RDF('type'), FOAF('Person'), doc)
store.add(alice, FOAF('knows'), sym('https://bob.example/#me'), doc)

Querying Data

// Get a single value
const name = store.any(alice, FOAF('name'), null, null)
console.log(name?.value) // "Alice"

// Get all values for a predicate
const friends = store.each(alice, FOAF('knows'), null, null)
friends.forEach(friend => console.log(friend.uri))

// Check if a statement exists
const isPerson = store.holds(alice, RDF('type'), FOAF('Person'), null)

// Get all statements matching a pattern
const statements = store.match(alice, null, null, null)
statements.forEach(st => {
console.log(`${st.predicate.uri}: ${st.object.value}`)
})

// Get statements where alice is the object
const whoKnowsAlice = store.match(null, FOAF('knows'), alice, null)

Removing Data

// Remove a specific statement
store.remove(st(alice, FOAF('name'), lit('Alice'), doc))

// Remove all statements matching a pattern
store.removeMatches(alice, FOAF('knows'), null, null)

Fetcher

The Fetcher loads RDF data from URIs into the store:

import { graph, Fetcher } from 'rdflib'

const store = graph()
const fetcher = new Fetcher(store)

// Load a resource
await fetcher.load('https://alice.example/profile/card')

// Now query the loaded data
const name = store.any(
sym('https://alice.example/profile/card#me'),
FOAF('name'),
null,
null
)

// Load multiple resources
await fetcher.load([
'https://alice.example/profile/card',
'https://bob.example/profile/card'
])

// Check if a resource is loaded
if (fetcher.requested['https://alice.example/profile/card']) {
console.log('Already loaded')
}

// Force reload
await fetcher.load('https://alice.example/profile/card', { force: true })

Fetcher Options

const fetcher = new Fetcher(store, {
timeout: 30000, // Request timeout in ms
withCredentials: true, // Include cookies for CORS
fetch: customFetch, // Custom fetch implementation
})

Authenticated and Alternate Fetches

By default, rdflib's Fetcher uses cross-fetch.fetch() — a plain fetch without authentication. To work with private Solid data, you need to pass an authenticated fetch.

Using solidFetch

The simplest approach is to set a global solidFetch variable:

  1. Load your authentication library
  2. Log in to your identity provider
  3. Set global.solidFetch (Node) or window.solidFetch (browser) to the auth library's fetch
  4. All rdflib fetches will then be authenticated
// Node.js example
const auth = new (require("solid-node-client").SolidNodeClient)();
await auth.login(credentials);
global.solidFetch = auth.fetch;

const store = graph();
const fetcher = new Fetcher(store);
await fetcher.load(privateUrl); // authenticated
note

Prior to rdflib version 2.2.9, this variable was named solidFetcher. Use solidFetch going forward.

Using a Custom Fetcher

To avoid global variables, pass the fetch directly when creating the Fetcher:

const fetcher = new Fetcher(store, {
fetch: auth.fetch.bind(auth)
});

This means changes need to be added every time you create a new Fetcher, but avoids polluting the global scope.

Authentication Libraries

LibraryEnvironmentNotes
solid-client-authn-browserBrowserInrupt's browser auth
solid-client-authn-nodeNode.jsWhen you don't need local filesystem access
solid-node-clientNode.jsWhen you need full filesystem access

UpdateManager

The UpdateManager writes changes back to Solid pods:

import { graph, Fetcher, UpdateManager, sym, lit, st } from 'rdflib'

const store = graph()
const fetcher = new Fetcher(store)
const updater = new UpdateManager(store)

// Load the document first
const doc = sym('https://alice.example/profile/card')
await fetcher.load(doc)

const alice = sym('https://alice.example/profile/card#me')

// Update: delete old statements, insert new ones
const oldName = store.any(alice, FOAF('name'), null, doc)
const newName = lit('Alice Smith')

await updater.update(
oldName ? [st(alice, FOAF('name'), oldName, doc)] : [], // deletions
[st(alice, FOAF('name'), newName, doc)] // insertions
)

// The store is automatically updated after successful PATCH

Creating New Resources

// PUT a new resource
const newDoc = sym('https://alice.example/notes/note1.ttl')
const note = sym('https://alice.example/notes/note1.ttl#it')

store.add(note, RDF('type'), sym('http://example.org/Note'), newDoc)
store.add(note, RDFS('label'), lit('My First Note'), newDoc)

await updater.put(
newDoc,
store.statementsMatching(null, null, null, newDoc),
'text/turtle'
)

How the UpdateManager Decides What to Do

The UpdateManager reads HTTP response headers to determine whether a document is editable and which update method to use.

Editability

For HTTP(S) URIs, a document is editable when both:

  • The WAC-Allow header grants write access to the current user
  • The response includes an Accept-Patch or MS-Author-Via header (see below)

For non-HTTP URIs (e.g. file://), a document is editable if either:

  • It declares itself a ont:MachineEditableDocument
  • The WAC-Allow header grants write access

Update Method Priority

The UpdateManager picks the first matching method:

PriorityMethodCondition
1N3 PATCHAccept-Patch header contains text/n3
2SPARQL PATCHAccept-Patch is application/sparql-update or application/sparql-update-single-match, or MS-Author-Via contains SPARQL
3PUTMS-Author-Via contains DAV
note

When submitting a form, PATCH is used except when re-ordering elements in an ordered list, which requires PUT.

WebSocket Updates

Real-time updates use the Updates-Via header. If the server provides Updates-Via: wss://example.org, the UpdateManager can subscribe to live changes.

Inspecting Server Headers

You can see what a server supports with:

curl --head https://solidcommunity.net/

Key headers to look for:

WAC-Allow: user="read write", public="read"
Accept-Patch: text/n3, application/sparql-update
MS-Author-Via: SPARQL
Updates-Via: wss://solidcommunity.net

Serialization

Parse RDF

import { graph, parse } from 'rdflib'

const store = graph()
const turtle = `
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
<#me> a foaf:Person ;
foaf:name "Alice" .
`

parse(turtle, store, 'https://alice.example/profile/card', 'text/turtle')

Serialize RDF

import { serialize } from 'rdflib'

// Serialize to Turtle
const turtle = serialize(doc, store, null, 'text/turtle')

// Serialize to JSON-LD
const jsonld = serialize(doc, store, null, 'application/ld+json')

// Serialize to N-Triples
const ntriples = serialize(doc, store, null, 'application/n-triples')

// Serialize to RDF/XML
const rdfxml = serialize(doc, store, null, 'application/rdf+xml')

Common Patterns

async function getProfile(webId) {
await fetcher.load(webId)

const person = sym(webId)
const name = store.any(person, FOAF('name'), null, null)
const friends = store.each(person, FOAF('knows'), null, null)

// Load friend profiles
for (const friend of friends) {
await fetcher.load(friend.doc()) // Load the document containing the friend
}

return { name: name?.value, friends }
}

Type Checking

function isContainer(uri) {
return store.holds(sym(uri), RDF('type'), LDP('Container'), null) ||
store.holds(sym(uri), RDF('type'), LDP('BasicContainer'), null)
}

function isPerson(uri) {
return store.holds(sym(uri), RDF('type'), FOAF('Person'), null) ||
store.holds(sym(uri), RDF('type'), VCARD('Individual'), null)
}

Getting Document from URI

// For a URI like https://example.org/doc#thing
// .doc() returns https://example.org/doc

const thing = sym('https://example.org/doc#thing')
const document = thing.doc() // NamedNode for https://example.org/doc

// Load the document containing a resource
await fetcher.load(thing.doc())

TypeScript Types

import type {
NamedNode,
Literal,
BlankNode,
Variable,
Statement,
IndexedFormula,
Fetcher,
UpdateManager,
Node
} from 'rdflib'

// Node is the union type for all node types
type RDFNode = NamedNode | Literal | BlankNode | Variable

// Statement represents a quad
interface Statement {
subject: NamedNode | BlankNode
predicate: NamedNode
object: Node
graph: NamedNode
}

See Also