Option A: Use JSS (Recommended)
The fastest way to get a full-featured Solid server running.
1
Clone and install
git clone https://github.com/JavaScriptSolidServer/JavaScriptSolidServer.git
cd JavaScriptSolidServer
npm install
2
Start the server
node bin/jss.js
Server runs at http://localhost:3000
3
Test it
# Create a resource
curl -X PUT http://localhost:3000/hello.txt \
-H "Content-Type: text/plain" \
-d "Hello, Solid!"
# Read it back
curl http://localhost:3000/hello.txt
Tip: Enable more features with flags:
# Full-featured server
node bin/jss.js --idp --mashlib --conneg --notifications
Option B: Build from Scratch
Understand the protocol by building a minimal server yourself.
1
Create project
mkdir my-solid-server && cd my-solid-server
npm init -y
npm install fastify
2
Minimal server (server.js)
import Fastify from 'fastify';
import fs from 'fs/promises';
import path from 'path';
const app = Fastify({ logger: true });
const DATA_DIR = './data';
// Ensure data directory exists
await fs.mkdir(DATA_DIR, { recursive: true });
// CORS headers (SLIP-11)
app.addHook('onSend', (req, reply, payload, done) => {
reply.header('Access-Control-Allow-Origin', '*');
reply.header('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
reply.header('Access-Control-Allow-Headers', 'Content-Type');
done();
});
// OPTIONS for CORS preflight
app.options('*', (req, reply) => reply.send());
// GET - Read resource (SLIP-10)
app.get('/*', async (req, reply) => {
const filePath = path.join(DATA_DIR, req.url);
try {
const content = await fs.readFile(filePath, 'utf8');
return reply.type('application/ld+json').send(content);
} catch (e) {
return reply.code(404).send({ error: 'Not Found' });
}
});
// PUT - Create/Update resource (SLIP-10)
app.put('/*', async (req, reply) => {
const filePath = path.join(DATA_DIR, req.url);
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, req.body);
return reply.code(201).send({ created: req.url });
});
// DELETE - Remove resource (SLIP-10)
app.delete('/*', async (req, reply) => {
const filePath = path.join(DATA_DIR, req.url);
try {
await fs.unlink(filePath);
return reply.code(204).send();
} catch (e) {
return reply.code(404).send({ error: 'Not Found' });
}
});
app.listen({ port: 3000 }, () => {
console.log('Solid Lite server running on http://localhost:3000');
});
3
Run it
node server.js
Congratulations! You've implemented SLIPs 10, 11, and 12 — a minimal Solid Lite server.
Next Steps: Add Features
Add Authentication (SLIP-81: Bearer Token)
// Add to your server
const API_KEY = process.env.SOLID_API_KEY || 'secret';
app.addHook('preHandler', (req, reply, done) => {
if (req.method === 'GET') return done(); // Public reads
const auth = req.headers.authorization;
if (auth !== `Bearer ${API_KEY}`) {
return reply.code(401).send({ error: 'Unauthorized' });
}
done();
});
Add JSON-LD Profile (SLIP-20: WebID)
# Create a profile
curl -X PUT http://localhost:3000/profile \
-H "Content-Type: application/ld+json" \
-H "Authorization: Bearer secret" \
-d '{
"@context": "https://www.w3.org/ns/activitystreams",
"@id": "#me",
"type": "Person",
"name": "Alice"
}'
Add Containers (SLIP-40)
// List directory contents as JSON-LD
app.get('/*/', async (req, reply) => {
const dirPath = path.join(DATA_DIR, req.url);
const files = await fs.readdir(dirPath);
return reply.type('application/ld+json').send({
"@context": "http://www.w3.org/ns/ldp#",
"@type": "Container",
"contains": files.map(f => ({ "@id": f }))
});
});
SLIP Combinations (Recipes)
| Profile | SLIPs | Use Case |
|---|---|---|
| Minimal | 10, 11, 12 | Learning, testing |
| Personal | + 81, 90 | Single-user data store |
| Multi-user | + 41, 83, 84, 85, 91 | Hosting for others |
| Nostr-Native | + 21, 82, 91 | Passwordless, no registration |
| Full (JSS) | All SLIPs | Production server |
Testing with curl
# Create
curl -X PUT http://localhost:3000/notes/first.json \
-H "Content-Type: application/ld+json" \
-d '{"@context": "https://schema.org", "@type": "Note", "text": "Hello!"}'
# Read
curl http://localhost:3000/notes/first.json
# Update
curl -X PUT http://localhost:3000/notes/first.json \
-H "Content-Type: application/ld+json" \
-d '{"@context": "https://schema.org", "@type": "Note", "text": "Updated!"}'
# Delete
curl -X DELETE http://localhost:3000/notes/first.json
# List container
curl http://localhost:3000/notes/
Browser App Example
<!DOCTYPE html>
<html>
<head>
<title>Solid Lite Client</title>
</head>
<body>
<h1>My Notes</h1>
<div id="notes"></div>
<input id="text" placeholder="New note...">
<button onclick="addNote()">Add</button>
<script>
const SERVER = 'http://localhost:3000';
async function loadNotes() {
const res = await fetch(`${SERVER}/notes/`);
const data = await res.json();
document.getElementById('notes').innerHTML =
data.contains?.map(n => `<p>${n['@id']}</p>`).join('') || 'No notes';
}
async function addNote() {
const text = document.getElementById('text').value;
const id = Date.now();
await fetch(`${SERVER}/notes/${id}.json`, {
method: 'PUT',
headers: { 'Content-Type': 'application/ld+json' },
body: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Note',
'text': text
})
});
loadNotes();
}
loadNotes();
</script>
</body>
</html>
Download Examples
Ready-to-run example code:
minimal-server.js
~50 lines, SLIPs 10-12
personal-server.js+Auth, +Containers
client.htmlBrowser notes app
Or clone all examples:
git clone https://github.com/solid-lite/draft-spec.git
cd draft-spec/examples
npm install && npm start
Resources
- All SLIPs — Complete specification list
- JSS Source — Reference implementation
- W3C LWS — Standards track
- Solid Project — Parent ecosystem