Dowels
🔨 a tiny but powerful javascript library that performs client-side routing, templating, and REST API communication to help you get your single-page web applications running in seconds
Install / Use
/learn @jaredreich/DowelsREADME

dowels is a tiny but powerful javascript library that performs client-side routing, templating, and REST API communication to help you get your single-page web applications running in seconds without having to learn huge fancy frameworks like Angular, React, etc. Demo: https://jaredreich.com/projects/dowels
Features
- Pure JavaScript (no dependencies)
- Tiny size (4kB minified)
- Easy and intuitive routing (Express style, supports parameters and wildcards)
- Simple and fast (caching) template rendering (Embedded JavaScript style)
- Handy HTTP request helpers
Browser support
- IE 10+
- Firefox 4+
- Chrome 5+
- Safari 6+
- Opera 11.5+
Installation
HTML:
<body>
...
<script src="https://unpkg.com/dowels"></script>
</body>
npm:
npm install dowels
bower:
bower install dowels
Important requirement: your server must allow a changing URL path
This means that your main application URL path (example.com/app) and any other subsequent URL paths (example.com/app/*) must always route to the main index.html file.
How to achieve this:
Node.js & Express
app.use(express.static(__dirname + '/public'));
app.get(['/app', '/app/*'], function(req, res) {
res.sendFile('index.html', { root: __dirname + '/public' });
});
Apache & .htaccess
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^(.*) /index.html [NC,L]
NGINX
server {
...
location /app/ {
/path/to/index.html
}
}
Usage
dowels.config({
root: '/projects/dowels', // root path of your app
containerId: 'app', // HTML container of your app's content
titleBase: 'dowels: app', // base of HTML title for tabs/favorites/history
apiBase: 'https://jaredreich.com/api/todos', // base of API endpoint for requests
onLoadStart: function() {
document.getElementById('loader').style.display = 'block';
},
onLoadStop: function() {
document.getElementById('loader').style.display = 'none';
}
});
dowels.add('/', function() {
dowels.render('app-template-home');
});
dowels.add('/todos', function() {
dowels.renderAfterRequest('app-template-todos', {
method: 'get',
endpoint: '/todos'
}, 'todos');
});
dowels.add('/todos/:id', function(parameters) {
dowels.renderAfterRequest('app-template-todo', {
endpoint: '/todos' + '/' + parameters.id
}, 'todo');
});
dowels.add('/*', function() {
dowels.redirect('/');
});
dowels.initialize();
<body>
...
<!-- Templates -->
<script id="app-template-home" type="text/html">
<h1>home page!</h1>
<button onclick="dowels.route('/todos');">view todos</button>
</script>
<script id="app-template-todos" type="text/html">
<button onclick="dowels.route('/')">← back home</button>
<br>
<input id="todoInput" type="text" placeholder="add a todo">
<button onclick="addTodo(document.getElementById('todoInput').value)">+ add</button>
<# if (todos.length > 0) { #>
<ul id="collection-todos">
<#
for (var i = 0; i < todos.length; i++) {
var todo = todos[i];
#>
<li><span onclick="dowels.route('/todos/' + '<#= todo.id #>')" style="cursor:pointer;text-decoration:underline;"><#= todo.text #></span><button style="padding:0;height:20px;margin-left:10px;" onclick="removeTodo('<#= todo.id #>');">x</button></li>
<# } #>
</ul>
<# } else { #>
<h3>nothing to do!</h3>
<# } #>
</script>
<script id="app-template-test" type="text/html">
<h3>todo selected: <#= todo.text #></h3>
</script>
<script id="app-template-todo" type="text/html">
<button onclick="dowels.route('/todos')">← back to todo list</button>
<# if (err) { #>
<h3><#= err #></h3>
<# } else { #>
<# include app-template-test #>
<# } #>
</script>
<script src="/path/to/dowels.js"></script>
<script>
function addTodo(text) {
document.getElementById('todoInput').value = '';
var data = {
text: text
}
dowels.request({
method: 'post',
endpoint: '/todos',
body: data
}, function(res) {
dowels.renderAfterRequest('app-template-todos', {
method: 'get',
endpoint: '/todos'
}, 'todos');
});
}
function removeTodo(id) {
dowels.request({
method: 'delete',
endpoint: '/todos/' + id
}, function(res) {
dowels.renderAfterRequest('app-template-todos', {
method: 'get',
endpoint: '/todos'
}, 'todos');
});
}
</script>
</body>
Configuration and Options
dowels.config({
// use hash in url instead of hard url
// default = true
// ***NOTE*** see important requirement above if hash is set to false
hash: true,
// root path of your app
// default = '/'
root: '/app',
// HTML container of your app's content
// default = 'app'
containerId: 'app-content',
// base of HTML title for tabs/favorites/history
// default = document.title
titleBase: 'dowels: app',
// base of API endpoint for requests
// default = document.location.protocol + '//' + document.location.hostname + '/api';
apiBase: 'https://dowels.io/api',
// your choice of delimiter for the EJS style templating
// default = '#'
delimiter: '#',
// runs when HTTP requests are in progress
// default = function(){}
onLoadStart: function() {
document.getElementById('loader').style.display = 'block';
},
// runs when HTTP requests change state
// default = function(){}
onLoadUpdate: function(percent) {
document.getElementById('loader').style.width = percent + '%';
},
// runs when HTTP requests are in finished
// default = function(){}
onLoadStop: function() {
document.getElementById('loader').style.display = 'none';
}
});
To-do
- custom delimiter (ex. #, %, etc...)
- tests + coverage
- hash option for routing
- more control over changing of document.title while routing
Related Skills
node-connect
346.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
107.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
346.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
346.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
