Skip to main content

Flask: Subdomain Routing

Flask: Subdomain Routing

Flask’s routing system supports subdomain routing, enabling developers to map specific subdomains to distinct parts of an application. This is particularly useful for multi-tenant applications, regional endpoints, or separating features like APIs and dashboards. This guide explores Flask subdomain routing, covering key techniques, best practices, and practical applications for building flexible, scalable web applications.


01. Why Use Subdomain Routing in Flask?

Subdomain routing allows Flask applications to handle requests based on the subdomain (e.g., api.example.com vs. dashboard.example.com), improving modularity and user experience. It’s ideal for segregating functionality, supporting multi-tenancy, or providing localized services. Combined with Flask’s Blueprints and NumPy Array Operations for data-heavy routes, subdomain routing enhances the organization and scalability of complex applications.

Example: Basic Subdomain Routing

# app.py
from flask import Flask

app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com:5000'

@app.route('/', subdomain='api')
def api_index():
    return {'message': 'Welcome to the API'}

@app.route('/', subdomain='www')
def www_index():
    return {'message': 'Welcome to the main site'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Output (curl http://api.example.com:5000/):

{
  "message": "Welcome to the API"
}

Explanation:

  • SERVER_NAME - Enables subdomain routing by setting the base domain.
  • subdomain - Maps routes to specific subdomains.
  • Note: For local testing, modify /etc/hosts to map subdomains (e.g., 127.0.0.1 api.example.com).

02. Key Subdomain Routing Techniques

Subdomain routing in Flask leverages the subdomain parameter in routes and Blueprints, supporting static and dynamic subdomains. The table below summarizes key techniques and their applications:

Technique Description Use Case
Static Subdomains Map routes to fixed subdomains API, dashboard, admin interfaces
Dynamic Subdomains Handle variable subdomains Multi-tenant apps, user-specific domains
Blueprint Subdomains Apply subdomains to Blueprint routes Modular feature separation
URL Generation Generate URLs with subdomains Dynamic links, redirects
Error Handling Handle invalid subdomains Custom 404s, fallback routes


2.1 Static Subdomains

Example: API and Admin Subdomains

# app.py
from flask import Flask

app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com:5000'

@app.route('/health', subdomain='api')
def api_health():
    return {'status': 'API healthy'}

@app.route('/dashboard', subdomain='admin')
def admin_dashboard():
    return {'message': 'Admin dashboard'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Output (curl http://api.example.com:5000/health):

{
  "status": "API healthy"
}

Explanation:

  • Static subdomains (api, admin) segregate functionality.
  • Clear separation of API and admin routes.

2.2 Dynamic Subdomains

Example: Multi-Tenant Subdomains

# app.py
from flask import Flask, request

app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com:5000'

@app.route('/profile', subdomain='<tenant>')
def tenant_profile(tenant):
    return {'tenant': tenant, 'profile': f'{tenant} profile'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Output (curl http://acme.example.com:5000/profile):

{
  "tenant": "acme",
  "profile": "acme profile"
}

Explanation:

  • <tenant> - Captures dynamic subdomain as a route parameter.
  • Ideal for multi-tenant applications (e.g., acme.example.com).

2.3 Blueprint Subdomains

Example: Subdomain in Blueprint

# myapp/__init__.py
from flask import Flask
from myapp.api import api_bp

def create_app():
    app = Flask(__name__)
    app.config['SERVER_NAME'] = 'example.com:5000'
    app.register_blueprint(api_bp, subdomain='api')
    return app

# myapp/api.py
from flask import Blueprint, jsonify

api_bp = Blueprint('api', __name__)

@api_bp.route('/users')
def get_users():
    return jsonify({'users': ['Alice', 'Bob']})

Output (curl http://api.example.com:5000/users):

{
  "users": ["Alice", "Bob"]
}

Explanation:

  • Blueprint routes inherit the api subdomain.
  • Modularizes API functionality under a subdomain.

2.4 URL Generation

Example: Generating Subdomain URLs

# app.py
from flask import Flask, url_for

app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com:5000'

@app.route('/link', subdomain='www')
def www_link():
    api_url = url_for('api_data', _external=True, subdomain='api')
    return {'api_url': api_url}

@app.route('/data', subdomain='api')
def api_data():
    return {'message': 'API data'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Output (curl http://www.example.com:5000/link):

{
  "api_url": "http://api.example.com:5000/data"
}

Explanation:

  • url_for - Generates URLs with the correct subdomain.
  • _external=True - Includes the full domain.

2.5 Error Handling

Example: Invalid Subdomain Handling

# app.py
from flask import Flask, jsonify

app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com:5000'

@app.errorhandler(404)
def not_found(error):
    if not request.host.startswith(app.config['SERVER_NAME'].split(':')[0]):
        return jsonify({'error': 'Invalid subdomain'}), 404
    return jsonify({'error': 'Not found'}), 404

@app.route('/', subdomain='app')
def app_index():
    return {'message': 'App home'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Output (curl http://invalid.example.com:5000/):

{
  "error": "Invalid subdomain"
}

Explanation:

  • Custom 404 handler detects invalid subdomains.
  • Improves user experience with clear error messages.

2.6 Incorrect Subdomain Usage

Example: Missing SERVER_NAME

# app.py (Incorrect)
from flask import Flask

app = Flask(__name__)
# Missing SERVER_NAME configuration

@app.route('/', subdomain='api')
def api_index():
    return {'message': 'API home'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Output (curl http://api.example.com:5000/):

RuntimeError: No SERVER_NAME configured

Explanation:

  • Subdomain routing requires SERVER_NAME.
  • Solution: Set app.config['SERVER_NAME'].

03. Effective Usage

3.1 Recommended Practices

  • Use Blueprints with subdomains for modular routing.

Example: Comprehensive Subdomain Setup

# myapp/__init__.py
from flask import Flask, jsonify
from myapp.api import api_bp
from myapp.dashboard import dashboard_bp

def create_app():
    app = Flask(__name__)
    app.config['SERVER_NAME'] = 'example.com:5000'
    app.register_blueprint(api_bp, subdomain='api')
    app.register_blueprint(dashboard_bp, subdomain='dashboard')
    
    @app.errorhandler(404)
    def not_found(error):
        return jsonify({'error': 'Invalid subdomain or resource'}), 404
    
    return app

# myapp/api.py
from flask import Blueprint, jsonify

api_bp = Blueprint('api', __name__)

@api_bp.route('/health')
def health():
    return jsonify({'status': 'API healthy'})

# myapp/dashboard.py
from flask import Blueprint, jsonify, request

dashboard_bp = Blueprint('dashboard', __name__)

@dashboard_bp.before_request
def check_auth():
    if not request.headers.get('Authorization'):
        return jsonify({'error': 'Unauthorized'}), 401

@dashboard_bp.route('/stats')
def stats():
    return jsonify({'stats': 'System metrics'})

Output (curl http://api.example.com:5000/health):

{
  "status": "API healthy"
}
  • Blueprints segregate API and dashboard under distinct subdomains.
  • Middleware enforces subdomain-specific logic.
  • Custom 404 handler improves error clarity.

3.2 Practices to Avoid

  • Avoid conflicting subdomains in Blueprints.

Example: Conflicting Subdomain Routes

# app.py (Incorrect)
from flask import Flask

app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com:5000'

@app.route('/data', subdomain='app')
def app_data():
    return {'data': 'App data'}

@app.route('/data', subdomain='app')
def app_data_conflict():
    return {'data': 'Conflicting data'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Output (app start):

AssertionError: View function mapping is overwriting an existing endpoint
  • Duplicate routes for the same subdomain cause conflicts.
  • Solution: Ensure unique endpoint names or use Blueprints.

04. Common Use Cases

4.1 Multi-Tenant Applications

Route tenant-specific subdomains to customized resources.

Example: Tenant-Based Routing

# app.py
from flask import Flask, request

app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com:5000'

@app.route('/dashboard', subdomain='<tenant>')
def tenant_dashboard(tenant):
    return {'tenant': tenant, 'message': f'{tenant} dashboard'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Output (curl http://client1.example.com:5000/dashboard):

{
  "tenant": "client1",
  "message": "client1 dashboard"
}

Explanation:

  • Dynamic subdomains support tenant-specific dashboards.
  • Scalable for SaaS applications.

4.2 API and Web Separation

Segregate API and web interfaces via subdomains.

Example: API/Web Subdomains

# myapp/__init__.py
from flask import Flask
from myapp.api import api_bp
from myapp.web import web_bp

def create_app():
    app = Flask(__name__)
    app.config['SERVER_NAME'] = 'example.com:5000'
    app.register_blueprint(api_bp, subdomain='api')
    app.register_blueprint(web_bp, subdomain='www')
    return app

# myapp/api.py
from flask import Blueprint, jsonify

api_bp = Blueprint('api', __name__)

@api_bp.route('/products')
def products():
    return jsonify({'products': ['Product A', 'Product B']})

# myapp/web.py
from flask import Blueprint, render_template

web_bp = Blueprint('web', __name__, template_folder='templates/web')

@web_bp.route('/')
def index():
    return render_template('index.html')
# myapp/templates/web/index.html
<!DOCTYPE html>
<html>
<head><title>Home</title></head>
<body><h1>Welcome to the Web</h1></body>
</html>

Output (curl http://api.example.com:5000/products):

{
  "products": ["Product A", "Product B"]
}

Explanation:

  • API and web interfaces are isolated via subdomains.
  • Blueprints enhance modularity with subdomain-specific templates.

Conclusion

Flask subdomain routing enables flexible, modular applications by mapping subdomains to specific routes or Blueprints. Key takeaways:

  • Use static subdomains for feature separation (e.g., API, admin).
  • Leverage dynamic subdomains for multi-tenant apps.
  • Integrate with Blueprints for modular routing.
  • Support URL generation and error handling for robustness.
  • Avoid missing SERVER_NAME or conflicting routes.

With subdomain routing, Flask applications become scalable, organized, and user-friendly!

Comments