Fastify plugin for serving RDF data with automatic content negotiation.
npm install @lde/fastify-rdf- Content negotiation for all RDF serialisation formats supported by rdf-serialize
- Handles
Acceptheaders to serve data in the requested RDF format (Turtle, N-Triples, JSON-LD, etc.) - Defaults to Turtle when no
Acceptheader is provided - Provides a
reply.sendRdf()decorator for explicit RDF responses - Optional
overrideSendmode to automatically serialise all responses as RDF
import fastify from 'fastify';
import fastifyRdf from '@lde/fastify-rdf';
const app = fastify();
await app.register(fastifyRdf);Use reply.sendRdf() to send RDF data with content negotiation:
import { Store, DataFactory } from 'n3';
const { namedNode, literal, quad } = DataFactory;
app.get('/resource', async (request, reply) => {
const store = new Store();
store.add(
quad(
namedNode('http://example.org/subject'),
namedNode('http://example.org/predicate'),
literal('object'),
),
);
return reply.sendRdf(store);
});Enable overrideSend to automatically serialise all responses as RDF. This is useful for APIs that exclusively serve RDF data:
await app.register(fastifyRdf, {
overrideSend: true,
});
// Simply return DatasetCore or Stream - they will be serialised automatically
app.get('/resource', async () => {
const store = new Store();
store.add(
quad(
namedNode('http://example.org/subject'),
namedNode('http://example.org/predicate'),
literal('object'),
),
);
return store;
});The plugin registers content type parsers for all RDF formats supported by rdf-parse. Individual routes opt in via config: { parseRdf: true } — the body is then parsed into a DatasetCore:
app.post('/data', { config: { parseRdf: true } }, async (request) => {
const dataset = request.body as DatasetCore; // N3 Store
console.log(`Received ${dataset.size} quads`);
});To parse RDF bodies on all routes, enable parseRdf at the plugin level:
await app.register(fastifyRdf, { parseRdf: true });Routes without per-route or plugin-level parseRdf get JSON fallback for application/ld+json (parsed as plain JSON) and 415 Unsupported Media Type for other RDF content types.
Send Hydra error responses with content negotiation. This maps error.message to hydra:title and error.cause (when it is a string) to hydra:description:
app.get('/resource', async (request, reply) => {
const error = new Error('Not Found', {
cause: 'The requested dataset was not found',
}) as Error & { statusCode: number };
error.statusCode = 404;
return reply.sendHydraError(error);
});The status code is taken from error.statusCode (standard in Fastify and http-errors), defaulting to 500.
For Accept: application/ld+json, the response is compact JSON-LD — no jsonld dependency needed:
{
"@context": "http://www.w3.org/ns/hydra/core#",
"@type": "Error",
"title": "Not Found",
"description": "The requested dataset was not found"
}For all other RDF formats (Turtle, N-Triples, etc.), the error is serialised through the standard RDF pipeline.
By default, the plugin uses text/turtle when no Accept header is provided. You can change this:
await app.register(fastifyRdf, {
defaultContentType: 'application/n-triples',
});The plugin supports all content types provided by rdf-serialize, including:
text/turtle(Turtle)application/n-triples(N-Triples)application/n-quads(N-Quads)application/ld+json(JSON-LD)application/rdf+xml(RDF/XML)- And more...
Clients can request their preferred format via the Accept header:
# Request Turtle
curl -H "Accept: text/turtle" http://localhost:3000/resource
# Request N-Triples
curl -H "Accept: application/n-triples" http://localhost:3000/resource
# No Accept header - defaults to Turtle
curl http://localhost:3000/resourceinterface FastifyRdfOptions {
/**
* Default content type when no Accept header is provided.
* @default 'text/turtle'
*/
defaultContentType?: string;
/**
* Override reply.send() to serialise all responses as RDF.
* When enabled, all payloads returned from route handlers will be
* serialised as RDF without type checking.
* @default false
*/
overrideSend?: boolean;
/**
* Parse RDF request bodies into a DatasetCore on all routes.
* @default false
*/
parseRdf?: boolean;
}The plugin accepts the following RDF data types:
- DatasetCore: RDF.js dataset (e.g., from n3
Store) - Stream: RDF.js quad stream (e.g., from parsers)
import type { RdfData, FastifyRdfOptions } from '@lde/fastify-rdf';The plugin includes full TypeScript support with type augmentation for Fastify's reply object:
// reply.sendRdf() is automatically typed
app.get('/data', async (request, reply) => {
return reply.sendRdf(dataset);
});