@databorg/client
is a highly versatile SPARQL client for modern age. It's built to be both easy to use for newcomers to SPARQL, and extensible, to grow to support large-scale applications and highly customized SPARQL infrastructure.
The @databorg/client
and package exports a method called createClient
which we can use to create the SPARQL client. This central Client manages all of our GraphQL requests and results.
import { DataborgClient } from '@databorg/client';
// create new client
const client = new DataborgClient({
queryEndpoint: 'https://dbpedia.org/sparql',
updateEndpoint: 'https://dbpedia.org/update', // optional, uses queryEndpoint by default
headers: {
Authorization: 'Bearer TOKEN',
},
prefixes: {
foaf: 'http://xmlns.com/foaf/0.1/',
},
});
At the bare minimum we'll need to pass an endpoint URL when we create a client to get started.
Another common options include headers
and prefixes
.
Header option allows us to customize the request headers for all requests, while prefixes option allows us to define custom prefixes used within all queries.
When you're using @databorg/client
to send queries or updates - the client.query
and client.update
methods allow you to do just that.
// execute a query
const result = await client.query({
query: `SELECT ?p WHERE { ?uri ?p ?label } LIMIT 5`,
variables: {
uri: url`http://dbpedia.org/ontology/deathDate`,
label: 'Hello world!',
},
});
// execute an update
const result = await client.update({
query: sparql`INSERT DATA { var:uri a dbpedia:Resource . }`,
variables: { uri: url`http://dbpedia.org/Test` },
});
A notable utility functions are the sparql
and url
tagged template literal function.
Wherever @databorg/client
accepts a query document, we can either pass a string or a SparqlQuery
document.
sparql
is a utility that allows a SparqlQuery
to be created directly.
In most examples we may have passed a string to define a query document, like so:
const basicQuery = `
SELECT ?s ?p ?o WHERE {
?s ?p ?o .
}
`;
We may also use the sparql
tag function to create a SparqlQuery
directly:
import { sparql } from '@databorg/client';
const basicQuery = sparql`
SELECT ?s ?p ?o WHERE {
?s ?p ?o .
}
`;
The url
is an utility that allows us to convert a string into a NamedNode
used in query (i.e. when you want to use value as URI, not as a literal).
import { url } from '@databorg/client';
// execute an update
const result = await client.update({
query: `INSERT DATA { var:uri a dbpedia:Resource . }`,
variables: {
// using url tagged template literal will ensure that the value is a NamedNode
// and is used and an URI, not a literal
uri: url`http://dbpedia.org/Test`,
},
});
This section covers how to install and setup @databorg/client/react
, as well as query and update data, with React.
First, setup the client as described above.
Once the client is created, you will need to provide it to your React app via the Context API.
This may be done with the help of the DataborgProvider
export.
import { DataborgProvider } from '@databorg/client/react';
// wrap your app in a provider
// this will pass the client to all components
const App = ({ children }) => (
<DataborgProvider client={client}>{children}</DataborgProvider>
);
Here we have implemented our first SPARQL query to fetch all triples for http://example.com
resource.
We see that useQuery accepts query and options, and returns an object with results.
The object we then get in return contains a result object, an error object, loading state indicator and a re-execute function.
import { useQuery } from '@databorg/client/react';
// execute a query
const MyQueryComponent = () => {
const { data, error, loading, reexecuteQuery } = useQuery(
`SELECT ?p ?o WHERE { ?uri ?p ?o }`,
{
variables: { uri: url`http://example.com` },
}
);
// reexecute the query when needed
useEffect(() => reexecuteQuery(), [someCondition]);
if (error) return <div>Error: {error.message}</div>;
if (loading) return <div>Loading...</div>;
return <div>{JSON.stringify(data)}</div>;
};
@databorg/client/react
offers a useUpdate
hook to execute update queries.
Contrary to the useQuery
output, useUpdate
returns an tuple.
The first item in the tuple again contains an update function, while second - loading state and error.
Unlike the useQuery
hook, the useUpdate
hook doesn't execute automatically.
To execute our update we have to call the execute function — updateResource
in our example — which is the first item in the tuple.
import { useUpdate } from '@databorg/client/react';
// execute an update
const MyUpdateComponent = () => {
const [updateResource, { loading, error }] = useUpdate(
`INSERT DATA { var:uri a dbpedia:Resource . }`
);
const runUpdate = () =>
updateResource({
variables: { uri: url`http://dbpedia.org/Test` },
});
if (error) return <div>Error: {error.message}</div>;
if (loading) return <div>Loading...</div>;
return <button onClick={runUpdate}>Click to update!</button>;
};
@databorg/client
comes with powerful query template engine.
It allows us to define a query template and pass it variables, building new queries on-demand.
There are two ways to defined templated variables - one for queries and one for updates.
In queries, we can simply use SPARQL variables, like so:
const result = await client.query({
query: `SELECT ?p WHERE { ?uri ?p ?label } LIMIT 5`,
variables: {
uri: url`http://dbpedia.org/ontology/deathDate`,
label: 'Hello world!',
},
});
This will result in the following final query:
SELECT ?p WHERE { <http://dbpedia.org/ontology/deathDate> ?p "Hello world!" } LIMIT 5
Since one cannot use SPARQL variables in updates, @databorg/client
introduces a special var:
prefix for variables in update queries, e.g.:
const result = await client.update({
query: `INSERT DATA { var:uri a dbpedia:Resource . }`,
variables: { uri: url`http://dbpedia.org/Test` },
});
This will result in the following final query:
INSERT DATA { <http://dbpedia.org/Test> a dbpedia:Resource . }
By default, @databorg/client
can parse two different types of results:
- SPARQL JSON results
- CONSTRUCT query results
Type of response is detected automatically based on response headers.
Client also provides a way to switch between three different kinds of parsing for SPARQL JSON results:
-
Simple parsing (used by default) - parses SPARQL JSON response and converts each row into an object with properties mapped to variable names, e.g.:
const result = await client.update({ query: `SELECT ?p ?o WHERE { ?s ?p ?o }`, }); // will result in following const result = [ { o: 'owl:FunctionalProperty', p: 'rdf:type', }, ];
-
Property parsing - parses SPARQL JSON response and converts all results into an object with properties nested based on provided structure, e.g.:
const result = await client.update({ query: `SELECT ?s ?p ?o WHERE { ?s ?p ?o }`, options: { parsing: { type: 'properties', subjectVariable: 's', predicateVariable: 'p', objectVariable: 'o', }, }, }); // will result in following const result = { 'dbo:averageAnnualGeneration': { 'rdf:type': 'owl:FunctionalProperty', }, 'dbo:birthDate': { 'rdf:type': 'owl:FunctionalProperty', }, };
-
Custom row-based parsing - parses SPARQL JSON response using provided parsing function, e.g.:
const result = await client.update({ query: `SELECT ?s ?p ?o WHERE { ?s ?p ?o }`, options: { parsing: { type: 'custom', rowProcessor: (row, result) => { const subject = row.s.value; const predicate = row.p.value; const object = row.o.value; result.push([subject, predicate, object]); }, }, }, }); // will result in following const result = [ [ 'http://dbpedia.org/ontology/deathDate', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'http://www.w3.org/2002/07/owl#FunctionalProperty', ], [ 'http://dbpedia.org/ontology/birthDate', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'http://www.w3.org/2002/07/owl#FunctionalProperty', ], ];
-
Parsing
CONSTRUCT
query results using JSON-LD framing:const { data, error, loading, reexecuteQuery } = useQuery( ` CONSTRUCT { ?uri ?property ?value ; borgwiki:content borgwiki:ContentSeq . borgwiki:ContentSeq a rdf:Seq . borgwiki:ContentSeq ?seqProp ?contentItem . ?contentItem ?contentProp ?contentValue . } WHERE { SELECT ?property ?value ?contentItem ?seqProp ?contentProp ?contentValue WHERE { GRAPH <http://example.org/graph> { ?uri ?property ?value ; borgwiki:content ?mdxSeq . ?mdxSeq a rdf:Seq; ?seqProp ?contentItem . ?contentItem ?contentProp ?contentValue . FILTER(?property != borgwiki:content) } } }`, { variables: { graph: url(graph), uri: url(uri), }, options: { // this tells client to use JSON-LD framing frame: { '@type': 'http://www.w3.org/2000/01/rdf-schema#Class', contains: { '@type': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#Seq', contains: { '@type': 'https://wiki.databorg.ai/Content', }, }, }, }, } ); // will result in following const result = { // prefixes passed from client '@context': { borgwiki: 'https://wiki.databorg.ai/', example: 'http://example.org/', rdfs: 'http://www.w3.org/2000/01/rdf-schema#', }, '@id': 'example:resource', '@type': 'rdfs:Class', 'borgwiki:direct': 'direct property', 'borgwiki:mdx': { '@id': 'borgwiki:MdxSeq', '@type': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#Seq', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#_1': { '@id': 'example:mdx1', '@type': 'borgwiki:Mdx', 'borgwiki:content': '# Markdown title', 'borgwiki:indirect': 'indirect property', }, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#_2': { '@id': 'example:mdx2', '@type': 'borgwiki:Mdx', 'borgwiki:content': '## Another markdown text', 'borgwiki:more': 'some more stuff', 'borgwiki:otherIndirect': 'other indirect property', }, }, 'rdfs:label': 'Test resource', };
The client itself doesn't actually know what to do with any of the operations (be in query
or update
).
Instead, it sends them through "exchanges".
Exchanges are akin to middleware in Redux and have access to all operations and all results.
Multiple exchanges are chained to process our operations and to execute logic on them, one of them being the httpExchange, which as the name implies sends our requests to our HTTP API.
The default set of exchanges that @databorg/client
contains and applies to a client are:
- HttpExchange: Sends an operation to the HTTP API using fetch and adds results to the output stream
- ParseExchange: Parses the response from the SPARQL server and adds it to the output stream
When we don't pass the exchanges
option manually to our client then these are the ones that will be applied.
Exchanged determine a lot of the logic of the client, taking care of things like sending requests to our API, parsing, deduplication, caching, etc.