Skip to content

Instantly share code, notes, and snippets.

@fasiha
Last active September 27, 2020 01:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fasiha/df973540790923c3392d1e6e94990d59 to your computer and use it in GitHub Desktop.
Save fasiha/df973540790923c3392d1e6e94990d59 to your computer and use it in GitHub Desktop.
Express.js and PouchDB-Server: demo for multi-database Express-controlled "auth" with URL-rewriting https://stackoverflow.com/q/64056888

Introduction via Stack Overflow question & answer

https://stackoverflow.com/q/64056888

Setup

git clone THIS_GIST_URL pouch-demo
cd pouch-demo
mkdir databases
npm install
node server.js

Then in another console tab:

node client.js

Exercise for the reader

All PouchDB clients will connect to and replicate with the /db route, but they can't pick the PouchDB/CouchDB database they want to work with: they must provide X-Pouch-Db header to specify the database name they want. This is a stand-in for cookie/token authentication/authorization that Node/Express control on their own (since PouchDB-Server's security may be problematic, and CouchDB auth is weird). The Express server, specifically the part outside PouchDB-Server, actually rewrites the URL requested to include the PouchDB database name.

Right now, the server just requires the database name (the value of X-Pouch-Db) to be four characters long before it allows the PouchDB database to be created or accessed. But hopefully it's easy to imagine Passport.js-based auth here. This should ensure that if a user is authorized to view only one database, they can't see other databases.

Thanks to @RamblinRose

Please upvote https://stackoverflow.com/a/64065545 if helpful.

// You can run this in Node.js too, in another console tab: "node client.js", but will run in browser too
var PouchDB = require('pouchdb');
PouchDB.plugin(require('pouchdb-adapter-memory'));
PouchDB.plugin(require('pouchdb-upsert'));
var db = new PouchDB('mydb', {adapter: 'memory'});
var remotedb = new PouchDB('http://localhost:3500/db', {
fetch: (url, opts) => {
opts.headers.set('X-Pouch-Db', 'whee');
return PouchDB.fetch(url, opts);
}
});
const dir = (k, v) => console.dir({[k]: v}, {depth: null});
(async function main() {
try {
console.log('remote info', await remotedb.info());
dir('remote alldocs', await remotedb.allDocs({include_docs: true}));
await db.replicate.from(remotedb)
dir('one-shot replication complete, local alldocs', await db.allDocs({include_docs: true}));
db.sync(remotedb, {live: true, retry: true});
remotedb.changes({since: 'now', live: true, include_docs: true}).on('change', change => dir('changed', change));
console.log('synced');
dir('local alldocs post-sync', await db.allDocs({include_docs: true}));
const text = 'world ' + (new Date()).toISOString();
console.log(`upserted [${text}]`, await db.upsert('hello', doc => ({...doc, text})));
dir('remote alldocs 2', await remotedb.allDocs({include_docs: true}));
dir('local alldocs 2', await db.allDocs({include_docs: true}));
} catch (e) { dir('ERR', e); }
})();
{
"name": "pouch-demo",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.17.1",
"express-pouchdb": "^4.2.0",
"pouchdb": "^7.2.2",
"pouchdb-upsert": "^2.2.0"
},
"devDependencies": {
"pouchdb-adapter-memory": "^7.2.2"
}
}
// Run this in Node.js: "node server.js"
const express = require('express');
const PouchDB = require('pouchdb');
const configPouchDB = PouchDB.defaults({prefix: './databases/'});
const db = require('express-pouchdb')(configPouchDB, {mode: 'minimumForPouchDB'});
const app = express();
app.use(require('cors')());
app.use('/db', (req, res) => {
const subdb = req.get('X-Pouch-Db');
if (!subdb) {
res.status(400).json({message: 'X-Pouch-Db header required'});
return;
}
if (subdb.length !== 4) {
res.status(400).json({message: 'Invalid X-Pouch-Db header'});
return;
}
// Rewrite req.url to prepend the db we want to use, based on `subdb`.
req.url = `/${subdb}${req.url}`;
// NOTA BENE: this works when `mode: 'minimumForPouchDB'` above!!!
// Express-PouchDB looks at ***originalUrl*** when `mode: 'fullCouchDB'` (default)!
// In that case use the following:
// ```js
// req.originalUrl = req.originalUrl.replace(req.baseUrl, `${req.baseUrl}/${subdb}`);
// ```
console.log(Object.entries(req)
.filter(([k, v]) => typeof v === 'string')
.concat([['subdb', subdb]])
.map(([k, v]) => [k, v].join(' => '))
.join('\n- '));
db(req, res);
});
const port = 3500;
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment