roster-server
Quick Setup
Production
const Roster = require('roster-server');
const roster = new Roster({
email: 'admin@example.com',
wwwPath: '/srv/www',
greenlockStorePath: '/srv/greenlock.d',
local: true
});
roster.start();
Local Development
const roster = new Roster({
local: true, // HTTP mode, no SSL
wwwPath: './www'
});
roster.start().then(() => {
console.log('example.com:', roster.getUrl('example.com'));
// → http://localhost:9465 (deterministic CRC32-based port)
});
Directory Structure
project/
├── greenlock.d/ # SSL certificates (auto-generated)
├── www/
│ ├── example.com/
│ │ └── index.js # Handler for example.com
│ ├── api.example.com/
│ │ └── index.js # Handler for subdomain
│ └── *.example.com/
│ └── index.js # Wildcard: one handler for all subdomains
└── server.js # Your setup
Handler Patterns
Each www/{domain}/index.js must export a function that receives httpsServer and returns a request handler.
Pattern 1: Basic HTTP Handler
module.exports = (httpsServer) => {
return (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World');
};
};
Pattern 2: Express App
const express = require('express');
module.exports = (httpsServer) => {
const app = express();
app.get('/', (req, res) => res.send('Hello'));
app.post('/api/data', (req, res) => res.json({ ok: true }));
return app;
};
Pattern 3: Socket.IO
const { Server } = require('socket.io');
module.exports = (httpsServer) => {
const io = new Server(httpsServer);
io.on('connection', (socket) => {
socket.on('message', (data) => io.emit('message', data));
});
return (req, res) => {
if (req.url && req.url.startsWith(io.opts.path)) return;
res.writeHead(200);
res.end('Socket.IO running');
};
};
Pattern 4: Manual Registration
// In server.js, before roster.start()
roster.register('example.com', (httpsServer) => {
return (req, res) => {
res.writeHead(200);
res.end('Manual handler');
};
});
// With custom port
roster.register('api.example.com:8443', handler);
// Wildcard: one handler for all subdomains (default port or custom)
roster.register('*.example.com', handler);
roster.register('*.example.com:8080', handler);
Key Configuration Options
new Roster({
email: 'admin@example.com', // Required for SSL
wwwPath: '/srv/www', // Site handlers directory
greenlockStorePath: '/srv/greenlock.d', // SSL storage
dnsChallenge: { ... }, // Optional override. Default is local/manual DNS-01 (acme-dns-01-cli)
// Environment
local: false, // true = HTTP, false = HTTPS
staging: false, // true = Let's Encrypt staging
// Server
hostname: '0.0.0.0',
port: 443, // Default HTTPS port (NOT 80!)
// Local mode
minLocalPort: 4000,
maxLocalPort: 9999,
// Advanced
filename: 'index', // Handler filename (no extension)
basePath: '/srv' // Base for relative paths
})
Core API
roster.start()
Loads sites, generates SSL config, starts servers. Returns Promise<void>.
roster.register(domain, handler)
Manually register a domain handler. Domain can include port: 'api.com:8443'. For wildcards use '*.example.com' or '*.example.com:8080'.
roster.getUrl(domain)
Get environment-aware URL:
- Local mode:
http://localhost:{port} - Production:
https://{domain}orhttps://{domain}:{port} - Returns
nullif domain not registered. Supports wildcard-matched hosts (e.g.getUrl('api.example.com')when*.example.comis registered).
How It Works
Request Flow
- Request arrives → Dispatcher extracts
Hostheader - Strips
www.prefix (301 redirect if present) - Looks up domain → Gets
VirtualServerinstance - Routes to handler via
virtualServer.processRequest(req, res)
VirtualServer Architecture
Each domain gets isolated server instance that simulates http.Server:
- Captures
requestandupgradeevent listeners - Complete separation between domains
- No configuration conflicts between apps
Port Assignment
Production: Default 443, custom via domain:port syntax
Local: CRC32 hash of domain → deterministic port in range 4000-9999
Reserved: Port 80 for ACME challenges only
SSL Management
- Automatic Let's Encrypt certificate generation
- Auto-renewal 45 days before expiration
- SNI support for multiple domains
- Custom ports reuse certificates via SNI callback
- Wildcard (
*.example.com): use folderwww/*.example.com/orroster.register('*.example.com', handler). Default DNS-01 plugin is local/manualacme-dns-01-cli; setdnsChallengeonly when overriding provider integration.
Common Issues & Solutions
Port 443 in use: Use different port { port: 8443 }
Certificate failed: Check firewall (ports 80, 443), verify DNS, try staging: true
Site not found: Verify directory name matches domain, check index.js exports function
Local port conflict: Adjust minLocalPort/maxLocalPort range
Socket.IO not working: Ensure handler checks io.opts.path and returns properly
Best Practices
- Test with staging first:
staging: trueto avoid Let's Encrypt rate limits - Use local mode for dev:
local: truefor faster iteration - Environment variables: Configure via
process.envfor portability - Error handling: Wrap handlers with try/catch, don't expose internals
- Socket.IO paths: Always check
req.url.startsWith(io.opts.path)in returned handler - Port 80: Never use as HTTPS port (reserved for ACME)
Quick Examples
Full Production Setup
const Roster = require('roster-server');
const roster = new Roster({
email: process.env.ADMIN_EMAIL,
wwwPath: '/srv/www',
greenlockStorePath: '/srv/greenlock.d',
staging: process.env.NODE_ENV !== 'production'
});
roster.start().then(() => {
console.log('RosterServer running');
}).catch(err => {
console.error('Startup failed:', err);
process.exit(1);
});
Local Dev with Manual Registration
const roster = new Roster({ local: true, wwwPath: './www' });
roster.register('test.local', (server) => {
return (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', url: roster.getUrl('test.local') }));
};
});
roster.start();
Environment-Aware Configuration
const isProduction = process.env.NODE_ENV === 'production';
const roster = new Roster({
email: process.env.ADMIN_EMAIL || 'admin@example.com',
wwwPath: process.env.WWW_PATH || './www',
greenlockStorePath: process.env.SSL_PATH || './greenlock.d',
local: !isProduction,
staging: !isProduction,
minLocalPort: parseInt(process.env.MIN_PORT) || 4000,
maxLocalPort: parseInt(process.env.MAX_PORT) || 9999
});
roster.start();
Implementation Checklist
When implementing RosterServer:
- Create
www/directory structure with domain folders - Each domain has
index.jsexporting(httpsServer) => handler - Configure email for Let's Encrypt notifications
- Test with
local: truefirst - Test with
staging: truebefore production - Ensure ports 80 and 443 are open (production)
- Verify DNS points to server
- Never use port 80 as HTTPS port
- Use
roster.getUrl(domain)for environment-aware URLs - Handle Socket.IO paths correctly in returned handler
- Implement error handling in handlers