├── .npmignore ├── index.js ├── .gitignore ├── .travis.yml ├── lib └── wildcard-subdomains.js ├── package.json ├── examples └── basic.js ├── README.md └── test └── wildcard-subdomains.js /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | examples 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/wildcard-subdomains') 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.12" 5 | - "iojs" 6 | - "4" 7 | - "5" 8 | branches: 9 | only: 10 | - master 11 | - rc1 12 | -------------------------------------------------------------------------------- /lib/wildcard-subdomains.js: -------------------------------------------------------------------------------- 1 | module.exports = function (options) { 2 | options = options || {} 3 | var path = '/' + (options.namespace || '_sub') + '/' 4 | 5 | // Support previous `www` boolean option 6 | var whitelist = options.www === false ? [] : ['www'] 7 | 8 | if (typeof options.whitelist === 'string') { 9 | whitelist = [options.whitelist] 10 | } else if (Array.isArray(options.whitelist)) { 11 | whitelist = options.whitelist 12 | } 13 | 14 | return function (req, res, next) { 15 | var subdomains = req.subdomains 16 | var host = req.hostname 17 | 18 | var inWhitelist = whitelist.some(function(subdomain) { 19 | var i = subdomains.indexOf(subdomain) 20 | return i > -1 21 | }) 22 | 23 | if (inWhitelist) return next() 24 | 25 | // continue if no subdomains 26 | if (!subdomains.length) return next() 27 | 28 | // rebuild url 29 | req.url = path + subdomains.join('/') + req.url 30 | 31 | // Q.E.D. 32 | next() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wildcard-subdomains", 3 | "description": "Handle dynamic/wildcard subdomains in Express.js", 4 | "version": "1.1.0", 5 | "homepage": "https://github.com/patmood/wildcard-subdomains", 6 | "author": { 7 | "name": "Patrick Moody", 8 | "url": "http://www.patmoody.com" 9 | }, 10 | "scripts": { 11 | "test": "tap ./test/*", 12 | "example": "node ./examples/basic.js", 13 | "dev": "node ./test/* | tap-diff" 14 | }, 15 | "main": "index.js", 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/patmood/wildcard-subdomains" 19 | }, 20 | "bugs": "https://github.com/patmood/wildcard-subdomains/issues", 21 | "keyword": [ 22 | "wildcard subdomains", 23 | "express subdomains", 24 | "dynamic subdomains", 25 | "subdomains" 26 | ], 27 | "devDependencies": { 28 | "express": "^4.13.4", 29 | "supertest": "^1.2.0", 30 | "tap": "^5.7.2", 31 | "tap-diff": "^0.1.1", 32 | "tape": "^4.5.1" 33 | }, 34 | "license": "MIT" 35 | } 36 | -------------------------------------------------------------------------------- /examples/basic.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | var app = express() 3 | var wildcardSubdomains = require('../') 4 | 5 | app.use(wildcardSubdomains()) 6 | 7 | app.get('/', function (req, res) { 8 | res.send('Visit http://foo.vcap.me:5555 (points back to localhost)') 9 | }) 10 | 11 | app.get('/_sub/bar/foo', function (req, res) { 12 | res.end( 13 | 'Subdomains: ' + 14 | JSON.stringify(req.subdomains) + 15 | '\n' + 16 | 'Original Url: ' + 17 | req.originalUrl + 18 | '\n' + 19 | 'New Url: ' + 20 | req.url + 21 | '\n' + 22 | 'Query string: ' + 23 | JSON.stringify(req.query) 24 | ) 25 | }) 26 | 27 | // Generous matching 28 | app.get('/_sub/:firstSubdomain/*', function (req, res) { 29 | res.end( 30 | 'First Subdomain: ' + 31 | req.params.firstSubdomain + 32 | '\n' + 33 | 'Original Url: ' + 34 | req.originalUrl + 35 | '\n' + 36 | 'New Url: ' + 37 | req.url + 38 | '\n' + 39 | 'Query string: ' + 40 | JSON.stringify(req.query) 41 | ) 42 | }) 43 | 44 | app.listen(5555) 45 | console.log('Example started on port 5555') 46 | console.log('============================') 47 | console.log('To test subdomain routing, visit these urls in your browser:') 48 | console.log('http://test.vcap.me:5555/') 49 | console.log('http://another.test.vcap.me:5555/') 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wildcard-subdomains 2 | ================== 3 | 4 | [![Build Status](https://travis-ci.org/patmood/wildcard-subdomains.svg?branch=master)](https://travis-ci.org/patmood/wildcard-subdomains) [![npm version](https://badge.fury.io/js/wildcard-subdomains.svg)](https://badge.fury.io/js/wildcard-subdomains) 5 | 6 | Handle dynamic/wildcard subdomains in Express.js. Perfect for when you have customized subdomains for different users, and need to handle them within your Node app. 7 | 8 | Requests to `foo.yourdomain.com` can be handled with using the route `/_sub/:subdomain` 9 | 10 | Paths and query strings and paths remain intact. For example: 11 | 12 | `foo.yourdomain.com/post/cat?oh=hai` can be handled with using the route `/_sub/foo/post/cat?oh=hai` 13 | 14 | Dependency free! 15 | 16 | ## How to use 17 | 18 | Require the module in app.js: 19 | 20 | `var wildcardSubdomains = require('wildcard-subdomains')` 21 | 22 | Use the module in middleware: 23 | 24 | `app.use(wildcardSubdomains(opts))` 25 | 26 | ### `opts` - Object 27 | | Key | Type | Default | Description | 28 | | --------- | --------------- | -------- | ---------------------- | 29 | | namespace | String | `'_sub'` | Prepended to the path | 30 | | whitelist | String or Array | `['www']`| Subdomains to ignore | 31 | 32 | Example options: 33 | 34 | ``` 35 | app.use(wildcardSubdomains({ 36 | namespace: 's', 37 | whitelist: ['www', 'app'], 38 | })) 39 | ``` 40 | 41 | Handle the new route for your subdomain, for example `foo.yourdomain.com` would be handled with: 42 | 43 | ``` 44 | app.get('/s/foo/', function(req, res){ 45 | res.send("Meow!") 46 | }) 47 | ``` 48 | 49 | By using the `whiteList` option, requests to `app.yourdomain.com` and `www.yourdomain.com` will be ignored and handled normally. 50 | 51 | ## Example 52 | 53 | `npm run example` 54 | 55 | Or check the `examples` directory in this repo 56 | 57 | ## Protip 58 | 59 | For testing subdomains locally, use the domain `vcap.me:3000` 60 | 61 | This is a domain that points back to your localhost, allowing you to test subdomains like `foobar.vcap.me` 62 | 63 | Running your app behind a proxy? You'll likely need to set the appropriate [trust proxy](http://expressjs.com/en/guide/behind-proxies.html) in express 64 | -------------------------------------------------------------------------------- /test/wildcard-subdomains.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var xtest = function () {} 3 | var request = require('supertest') 4 | var express = require('express') 5 | var wildcardSubdomains = require('../') 6 | 7 | test('default subdomain namespace', function (t) { 8 | var app = express() 9 | app.use(wildcardSubdomains({ 10 | domain: 'test.com', 11 | })) 12 | 13 | app.get('/_sub/cat', function (req, res) { 14 | res.end(req.hostname) 15 | }) 16 | 17 | t.plan(1) 18 | request(app) 19 | .get('/') 20 | .set('Host', 'cat.test.com') 21 | .expect('cat.test.com') 22 | .end(function (err, res) { 23 | if (err) return t.fail(err) 24 | t.pass('pass') 25 | }) 26 | }) 27 | 28 | test('custom namespace', function (t) { 29 | var app = express() 30 | app.use(wildcardSubdomains({ 31 | domain: 'test.com', 32 | namespace: 's', 33 | })) 34 | 35 | app.get('/s/cat', function (req, res) { 36 | res.end(req.hostname) 37 | }) 38 | 39 | t.plan(1) 40 | request(app) 41 | .get('/') 42 | .set('Host', 'cat.test.com') 43 | .expect('cat.test.com') 44 | .end(function (err, res) { 45 | if (err) return t.fail(err) 46 | t.pass('pass') 47 | }) 48 | }) 49 | 50 | test('strip www', function (t) { 51 | var app = express() 52 | app.use(wildcardSubdomains({ 53 | domain: 'test.com', 54 | namespace: 's', 55 | www: true, 56 | })) 57 | 58 | app.get('/', function (req, res) { 59 | res.end(req.hostname) 60 | }) 61 | 62 | app.get('/s/cat', function (req, res) { 63 | res.end('cat') 64 | }) 65 | 66 | t.plan(2) 67 | request(app) 68 | .get('/') 69 | .set('Host', 'www.test.com') 70 | .expect('www.test.com') 71 | .end(function (err, res) { 72 | if (err) return t.fail(err) 73 | t.pass('pass') 74 | }) 75 | 76 | request(app) 77 | .get('/') 78 | .set('Host', 'cat.test.com') 79 | .expect('cat') 80 | .end(function (err, res) { 81 | if (err) return t.fail(err) 82 | t.pass('pass') 83 | }) 84 | }) 85 | 86 | test('handle www', function (t) { 87 | var app = express() 88 | app.use(wildcardSubdomains({ 89 | domain: 'test.com', 90 | namespace: 's', 91 | www: false, 92 | })) 93 | 94 | app.get('/', function (req, res) { 95 | res.end(req.hostname) 96 | }) 97 | 98 | app.get('/s/cat', function (req, res) { 99 | res.end('cat') 100 | }) 101 | 102 | app.get('/s/www', function (req, res) { 103 | res.end('custom www handler') 104 | }) 105 | 106 | t.plan(2) 107 | request(app) 108 | .get('/') 109 | .set('Host', 'www.test.com') 110 | .expect('custom www handler') 111 | .end(function (err, res) { 112 | if (err) return t.fail(err) 113 | t.pass('pass') 114 | }) 115 | 116 | request(app) 117 | .get('/') 118 | .set('Host', 'cat.test.com') 119 | .expect('cat') 120 | .end(function (err, res) { 121 | if (err) return t.fail(err) 122 | t.pass('pass') 123 | }) 124 | }) 125 | 126 | test('wildcard subdomains', function (t) { 127 | var app = express() 128 | app.use(wildcardSubdomains({ 129 | domain: 'test.com', 130 | namespace: 's', 131 | })) 132 | 133 | app.get('/s/:subdomain', function (req, res) { 134 | res.end(req.params.subdomain) 135 | }) 136 | 137 | t.plan(2) 138 | request(app) 139 | .get('/') 140 | .set('Host', 'cat.test.com') 141 | .expect('cat') 142 | .end(function (err, res) { 143 | if (err) return t.fail(err) 144 | t.pass('pass') 145 | }) 146 | 147 | request(app) 148 | .get('/') 149 | .set('Host', 'js.test.com') 150 | .expect('js') 151 | .end(function (err, res) { 152 | if (err) return t.fail(err) 153 | t.pass('pass') 154 | }) 155 | }) 156 | 157 | test('multiple subdomains', function (t) { 158 | var app = express() 159 | app.use(wildcardSubdomains({ 160 | domain: 'test.com', 161 | namespace: 's', 162 | })) 163 | 164 | app.get('/s/cat/dog', function (req, res) { 165 | res.json(req.subdomains) 166 | }) 167 | 168 | t.plan(1) 169 | request(app) 170 | .get('/') 171 | .set('Host', 'dog.cat.test.com') 172 | .expect(['cat', 'dog']) 173 | .end(function (err, res) { 174 | if (err) return t.fail(err) 175 | t.pass('pass') 176 | }) 177 | }) 178 | 179 | test('subdomain array in request', function (t) { 180 | var app = express() 181 | app.use(wildcardSubdomains({ 182 | domain: 'test.com', 183 | namespace: 's', 184 | })) 185 | 186 | app.get('/s/:firstSubdomain/:secondSubdomain?', function (req, res) { 187 | res.json(req.subdomains) 188 | }) 189 | 190 | t.plan(2) 191 | request(app) 192 | .get('/') 193 | .set('Host', 'dog.cat.test.com') 194 | .expect(['cat', 'dog']) 195 | .end(function (err, res) { 196 | if (err) return t.fail(err) 197 | t.pass('pass') 198 | }) 199 | 200 | request(app) 201 | .get('/') 202 | .set('Host', 'single.test.com') 203 | .expect(['single']) 204 | .end(function (err, res) { 205 | if (err) return t.fail(err) 206 | t.pass('pass') 207 | }) 208 | }) 209 | 210 | test('whitelist', function (t) { 211 | var app = express() 212 | app.use(wildcardSubdomains({ 213 | domain: 'test.com', 214 | whitelist: ['www', 'support'], 215 | })) 216 | 217 | app.get('/test', function (req, res) { 218 | res.end('correct route') 219 | }) 220 | 221 | app.get('/_sub/cat', function (req, res) { 222 | res.end(req.hostname) 223 | }) 224 | 225 | t.plan(2) 226 | request(app) 227 | .get('/test') 228 | .set('Host', 'support.test.com') 229 | .expect('correct route') 230 | .end(function (err, res) { 231 | if (err) return t.fail(err) 232 | t.pass('pass') 233 | }) 234 | 235 | request(app) 236 | .get('/') 237 | .set('Host', 'cat.test.com') 238 | .expect('cat.test.com') 239 | .end(function (err, res) { 240 | if (err) return t.fail(err) 241 | t.pass('pass') 242 | }) 243 | }) 244 | --------------------------------------------------------------------------------