Blueprint System Guide

The Gobstopper Blueprint system provides a Flask-like modular structure for organizing routes, middleware, and application logic into reusable components. Blueprints help you build large applications with clean separation of concerns.

Table of Contents

Introduction

Blueprints allow you to:

  • Organize routes into logical modules

  • Reuse components across multiple applications

  • Separate concerns with clean boundaries

  • Scale applications without monolithic route files

  • Team collaboration with independent modules

When to Use Blueprints

Use blueprints when:

  • Your application has distinct feature areas (users, posts, admin)

  • You want to reuse functionality across projects

  • Multiple developers are working on different features

  • You need to organize routes for better maintainability

Basic Usage

Creating a Blueprint

from gobstopper.core.blueprint import Blueprint

# Create a blueprint
users_bp = Blueprint('users', url_prefix='/users')

@users_bp.get('/')
async def list_users(request):
    return JSONResponse({'users': []})

@users_bp.get('/<int:user_id>')
async def get_user(request, user_id: int):
    return JSONResponse({'user_id': user_id})

@users_bp.post('/')
async def create_user(request):
    data = await request.json()
    return JSONResponse(data, status=201)

Registering with Application

from gobstopper import Gobstopper
from gobstopper.http.response import JSONResponse

app = Gobstopper(__name__)

# Register blueprint
app.register_blueprint(users_bp)

# Routes are now available at:
# GET /users/
# GET /users/<id>
# POST /users/

Blueprint Registration

Basic Registration

# Create blueprint
api_bp = Blueprint('api')

@api_bp.get('/health')
async def health_check(request):
    return JSONResponse({'status': 'healthy'})

# Register with app
app = Gobstopper(__name__)
app.register_blueprint(api_bp)

Registration with URL Prefix

# No prefix in blueprint definition
auth_bp = Blueprint('auth')

@auth_bp.post('/login')
async def login(request):
    return JSONResponse({'token': 'abc123'})

# Add prefix during registration
app.register_blueprint(auth_bp, url_prefix='/auth')

# Route available at: POST /auth/login

Multiple Registrations

You can register the same blueprint multiple times with different prefixes:

# API blueprint
api_bp = Blueprint('api')

@api_bp.get('/data')
async def get_data(request):
    return JSONResponse({'data': []})

# Register for different API versions
app.register_blueprint(api_bp, url_prefix='/v1/api')
app.register_blueprint(api_bp, url_prefix='/v2/api')

# Routes available at:
# GET /v1/api/data
# GET /v2/api/data

URL Prefixes

Blueprint-Level Prefix

Define prefix when creating blueprint:

admin_bp = Blueprint('admin', url_prefix='/admin')

@admin_bp.get('/users')
async def admin_users(request):
    return JSONResponse({'users': []})

app.register_blueprint(admin_bp)
# Route: GET /admin/users

Registration-Level Prefix

Override or add prefix during registration:

blog_bp = Blueprint('blog')

@blog_bp.get('/posts')
async def list_posts(request):
    return JSONResponse({'posts': []})

# Register with prefix
app.register_blueprint(blog_bp, url_prefix='/blog')
# Route: GET /blog/posts

# Or register without prefix
app.register_blueprint(blog_bp)
# Route: GET /posts

Combined Prefixes

Both blueprint and registration prefixes are combined:

api_bp = Blueprint('api', url_prefix='/api')

@api_bp.get('/data')
async def get_data(request):
    return JSONResponse({'data': []})

app.register_blueprint(api_bp, url_prefix='/v1')
# Final route: GET /v1/api/data

Nested Blueprints

Blueprints can contain other blueprints for hierarchical organization:

# Parent blueprint: API
api_bp = Blueprint('api', url_prefix='/api')

# Child blueprint: Users
users_bp = Blueprint('users', url_prefix='/users')

@users_bp.get('/')
async def list_users(request):
    return JSONResponse({'users': []})

@users_bp.get('/<int:user_id>')
async def get_user(request, user_id: int):
    return JSONResponse({'user': user_id})

# Child blueprint: Posts
posts_bp = Blueprint('posts', url_prefix='/posts')

@posts_bp.get('/')
async def list_posts(request):
    return JSONResponse({'posts': []})

# Register children with parent
api_bp.register_blueprint(users_bp)
api_bp.register_blueprint(posts_bp)

# Register parent with app
app = Gobstopper(__name__)
app.register_blueprint(api_bp)

# Final routes:
# GET /api/users/
# GET /api/users/<id>
# GET /api/posts/

Complex Nesting Example

# Root: Admin section
admin_bp = Blueprint('admin', url_prefix='/admin')

# Level 1: Settings
settings_bp = Blueprint('settings', url_prefix='/settings')

@settings_bp.get('/')
async def list_settings(request):
    return JSONResponse({'settings': []})

# Level 2: User settings
user_settings_bp = Blueprint('user_settings', url_prefix='/user')

@user_settings_bp.get('/')
async def user_settings(request):
    return JSONResponse({'user_settings': {}})

# Build hierarchy
settings_bp.register_blueprint(user_settings_bp)
admin_bp.register_blueprint(settings_bp)
app.register_blueprint(admin_bp)

# Final route: GET /admin/settings/user/

Hooks and Middleware

Before Request Hooks

Execute code before handling requests:

auth_bp = Blueprint('auth', url_prefix='/auth')

@auth_bp.before_request
async def check_auth(request):
    """Run before every request in this blueprint."""
    token = request.headers.get('authorization')
    if not token:
        return JSONResponse({'error': 'Unauthorized'}, status=401)
    # Verify token...
    request.state.user_id = decode_token(token)

@auth_bp.get('/profile')
async def get_profile(request):
    user_id = request.state.user_id
    return JSONResponse({'user_id': user_id})

After Request Hooks

Modify responses after handling:

api_bp = Blueprint('api', url_prefix='/api')

@api_bp.after_request
async def add_headers(request, response):
    """Run after every request in this blueprint."""
    response.headers['X-API-Version'] = '1.0'
    response.headers['X-Processed-By'] = 'Gobstopper'
    return response

@api_bp.get('/data')
async def get_data(request):
    return JSONResponse({'data': []})

Blueprint Middleware

Add middleware specific to a blueprint:

from gobstopper.middleware import CORSMiddleware

api_bp = Blueprint('api', url_prefix='/api')

# Add CORS only to API routes
cors_middleware = CORSMiddleware(
    allow_origins=['https://example.com'],
    allow_methods=['GET', 'POST']
)
api_bp.add_middleware(cors_middleware, priority=100)

@api_bp.get('/data')
async def get_data(request):
    return JSONResponse({'data': []})

Route-Level Middleware

Apply middleware to specific routes within a blueprint:

from gobstopper.utils.rate_limiter import TokenBucketLimiter, rate_limit

users_bp = Blueprint('users', url_prefix='/users')

# Rate limiter for specific routes
limiter = TokenBucketLimiter(rate=10, capacity=100)

@users_bp.get('/')
@rate_limit(limiter)  # Only this route is rate limited
async def list_users(request):
    return JSONResponse({'users': []})

@users_bp.get('/<int:user_id>')  # This route is NOT rate limited
async def get_user(request, user_id: int):
    return JSONResponse({'user': user_id})

Static Files and Templates

Blueprint Static Files

Serve static files specific to a blueprint:

admin_bp = Blueprint(
    'admin',
    url_prefix='/admin',
    static_folder='admin/static',
    template_folder='admin/templates'
)

# Static files served from: admin/static/
# Templates loaded from: admin/templates/

Directory structure:

project/
├── app.py
└── admin/
    ├── static/
    │   ├── css/
    │   │   └── admin.css
    │   └── js/
    │       └── admin.js
    └── templates/
        ├── dashboard.html
        └── users.html

Blueprint Templates

Use templates from blueprint’s template folder:

admin_bp = Blueprint(
    'admin',
    url_prefix='/admin',
    template_folder='admin/templates'
)

@admin_bp.get('/dashboard')
async def dashboard(request):
    # Looks for template in: admin/templates/dashboard.html
    return await request.app.render_template('dashboard.html', data={})

Note: Current implementation uses global template paths. Full blueprint template namespacing is planned for future releases.

Complete Examples

Blog Application

# blog.py
from gobstopper.core.blueprint import Blueprint
from gobstopper.http.response import JSONResponse

blog_bp = Blueprint('blog', url_prefix='/blog')

# List posts
@blog_bp.get('/posts')
async def list_posts(request):
    limit = int(request.args.get('limit', 10))
    posts = await fetch_posts(limit)
    return JSONResponse({'posts': posts})

# Get single post
@blog_bp.get('/posts/<int:post_id>')
async def get_post(request, post_id: int):
    post = await fetch_post(post_id)
    if not post:
        return JSONResponse({'error': 'Not found'}, status=404)
    return JSONResponse({'post': post})

# Create post
@blog_bp.post('/posts')
async def create_post(request):
    data = await request.json()
    post = await create_post_in_db(data)
    return JSONResponse({'post': post}, status=201)

# Update post
@blog_bp.put('/posts/<int:post_id>')
async def update_post(request, post_id: int):
    data = await request.json()
    post = await update_post_in_db(post_id, data)
    return JSONResponse({'post': post})

# Delete post
@blog_bp.delete('/posts/<int:post_id>')
async def delete_post(request, post_id: int):
    await delete_post_from_db(post_id)
    return JSONResponse({}, status=204)

# Comments sub-blueprint
comments_bp = Blueprint('comments', url_prefix='/comments')

@comments_bp.get('/')
async def list_comments(request):
    post_id = request.args.get('post_id')
    comments = await fetch_comments(post_id)
    return JSONResponse({'comments': comments})

@comments_bp.post('/')
async def create_comment(request):
    data = await request.json()
    comment = await create_comment_in_db(data)
    return JSONResponse({'comment': comment}, status=201)

# Register comments with blog
blog_bp.register_blueprint(comments_bp)

Authentication Module

# auth.py
from gobstopper.core.blueprint import Blueprint
from gobstopper.http.response import JSONResponse
import jwt

auth_bp = Blueprint('auth', url_prefix='/auth')

@auth_bp.post('/register')
async def register(request):
    data = await request.json()

    # Validate input
    if not data.get('email') or not data.get('password'):
        return JSONResponse(
            {'error': 'Email and password required'},
            status=400
        )

    # Check if user exists
    if await user_exists(data['email']):
        return JSONResponse(
            {'error': 'User already exists'},
            status=409
        )

    # Create user
    user = await create_user(data)
    return JSONResponse({'user': user}, status=201)

@auth_bp.post('/login')
async def login(request):
    data = await request.json()

    # Authenticate
    user = await authenticate(data['email'], data['password'])
    if not user:
        return JSONResponse(
            {'error': 'Invalid credentials'},
            status=401
        )

    # Generate token
    token = generate_token(user)
    return JSONResponse({'token': token})

@auth_bp.post('/logout')
async def logout(request):
    # Invalidate token (if using session)
    token = request.headers.get('authorization')
    await invalidate_token(token)
    return JSONResponse({'success': True})

@auth_bp.get('/me')
async def get_current_user(request):
    # Requires authentication middleware
    user_id = request.state.user_id
    user = await fetch_user(user_id)
    return JSONResponse({'user': user})

Admin Panel with Sub-Modules

# admin/__init__.py
from gobstopper.core.blueprint import Blueprint

admin_bp = Blueprint('admin', url_prefix='/admin')

# Admin middleware
@admin_bp.before_request
async def require_admin(request):
    user_role = request.state.get('role')
    if user_role != 'admin':
        return JSONResponse({'error': 'Forbidden'}, status=403)

# Dashboard
@admin_bp.get('/')
async def admin_dashboard(request):
    stats = await fetch_admin_stats()
    return JSONResponse({'stats': stats})

# admin/users.py
users_admin_bp = Blueprint('users_admin', url_prefix='/users')

@users_admin_bp.get('/')
async def list_all_users(request):
    users = await fetch_all_users()
    return JSONResponse({'users': users})

@users_admin_bp.delete('/<int:user_id>')
async def delete_user(request, user_id: int):
    await delete_user_from_db(user_id)
    return JSONResponse({'success': True})

@users_admin_bp.post('/<int:user_id>/ban')
async def ban_user(request, user_id: int):
    await ban_user_in_db(user_id)
    return JSONResponse({'success': True})

# admin/settings.py
settings_admin_bp = Blueprint('settings_admin', url_prefix='/settings')

@settings_admin_bp.get('/')
async def get_settings(request):
    settings = await fetch_settings()
    return JSONResponse({'settings': settings})

@settings_admin_bp.put('/')
async def update_settings(request):
    data = await request.json()
    await update_settings_in_db(data)
    return JSONResponse({'success': True})

# Register sub-blueprints
admin_bp.register_blueprint(users_admin_bp)
admin_bp.register_blueprint(settings_admin_bp)

Main Application Assembly

# app.py
from gobstopper import Gobstopper
from auth import auth_bp
from blog import blog_bp
from admin import admin_bp

app = Gobstopper(__name__)

# Register blueprints
app.register_blueprint(auth_bp)
app.register_blueprint(blog_bp)
app.register_blueprint(admin_bp)

# Main routes
@app.get('/')
async def index(request):
    return await app.render_template('index.html')

if __name__ == '__main__':
    import granian
    granian.serve('app:app', interface='rsgi')

Final route structure:

GET  /                        # Main index
POST /auth/register           # Auth: Register
POST /auth/login              # Auth: Login
POST /auth/logout             # Auth: Logout
GET  /auth/me                 # Auth: Current user
GET  /blog/posts              # Blog: List posts
GET  /blog/posts/<id>         # Blog: Get post
POST /blog/posts              # Blog: Create post
PUT  /blog/posts/<id>         # Blog: Update post
DELETE /blog/posts/<id>       # Blog: Delete post
GET  /blog/comments           # Blog: List comments
POST /blog/comments           # Blog: Create comment
GET  /admin/                  # Admin: Dashboard
GET  /admin/users             # Admin: List users
DELETE /admin/users/<id>      # Admin: Delete user
POST /admin/users/<id>/ban    # Admin: Ban user
GET  /admin/settings          # Admin: Get settings
PUT  /admin/settings          # Admin: Update settings

Best Practices

1. Organize by Feature, Not Type

# ❌ Poor organization (by type)
project/
├── routes/
│   ├── users.py
│   └── posts.py
├── models/
│   ├── user.py
│   └── post.py
└── views/
    ├── user_views.py
    └── post_views.py

# ✅ Good organization (by feature)
project/
├── users/
│   ├── __init__.py      # Blueprint definition
│   ├── routes.py        # User routes
│   ├── models.py        # User models
│   └── templates/       # User templates
├── posts/
│   ├── __init__.py      # Blueprint definition
│   ├── routes.py        # Post routes
│   ├── models.py        # Post models
│   └── templates/       # Post templates
└── app.py               # Main application

2. Use Meaningful Blueprint Names

# ❌ Poor naming
bp = Blueprint('bp')
routes = Blueprint('routes')

# ✅ Good naming
users_bp = Blueprint('users')
admin_bp = Blueprint('admin')
api_v1_bp = Blueprint('api_v1')

3. Keep Blueprints Focused

Each blueprint should have a single, clear responsibility:

# ✅ Focused blueprints
auth_bp = Blueprint('auth')          # Authentication only
users_bp = Blueprint('users')        # User management only
posts_bp = Blueprint('posts')        # Post management only

# ❌ Unfocused blueprint
everything_bp = Blueprint('everything')  # Too broad

4. Document Blueprint Structure

Add docstrings to blueprint modules:

"""
Users Blueprint

Handles all user-related operations including:
- User registration and authentication
- Profile management
- User settings
- Password reset

Routes:
- GET /users/ - List users
- GET /users/<id> - Get user
- POST /users/ - Create user
- PUT /users/<id> - Update user
- DELETE /users/<id> - Delete user
"""
from gobstopper.core.blueprint import Blueprint

users_bp = Blueprint('users', url_prefix='/users')

5. Use Before Request for Common Logic

api_bp = Blueprint('api', url_prefix='/api')

@api_bp.before_request
async def api_before_request(request):
    """Common logic for all API routes."""
    # Authenticate
    token = request.headers.get('authorization')
    if not token:
        return JSONResponse({'error': 'Unauthorized'}, status=401)

    # Rate limit
    if not await rate_limiter.allow(token):
        return JSONResponse({'error': 'Rate limit exceeded'}, status=429)

    # Set user context
    request.state.user = await get_user_from_token(token)

6. Version Your APIs

# v1/users.py
v1_users_bp = Blueprint('users_v1', url_prefix='/users')

@v1_users_bp.get('/')
async def list_users_v1(request):
    return JSONResponse({'users': []})

# v2/users.py
v2_users_bp = Blueprint('users_v2', url_prefix='/users')

@v2_users_bp.get('/')
async def list_users_v2(request):
    # Enhanced response format
    return JSONResponse({'data': {'users': []}, 'meta': {}})

# app.py
api_v1 = Blueprint('api_v1', url_prefix='/v1')
api_v1.register_blueprint(v1_users_bp)

api_v2 = Blueprint('api_v2', url_prefix='/v2')
api_v2.register_blueprint(v2_users_bp)

app.register_blueprint(api_v1)
app.register_blueprint(api_v2)

Migration from Flask

Gobstopper’s Blueprint system is similar to Flask’s but with some differences:

Key Differences

  1. Async by default: All route handlers are async

  2. RSGI protocol: Uses RSGI instead of WSGI

  3. Template folders: Full namespacing not yet implemented

  4. Blueprint factory: No Blueprint.as_view() equivalent

Migration Example

Flask blueprint:

# Flask
from flask import Blueprint, jsonify

users_bp = Blueprint('users', __name__)

@users_bp.route('/users')
def list_users():
    return jsonify({'users': []})

@users_bp.route('/users/<int:user_id>')
def get_user(user_id):
    return jsonify({'user': user_id})

Gobstopper blueprint:

# Gobstopper
from gobstopper.core.blueprint import Blueprint
from gobstopper.http.response import JSONResponse

users_bp = Blueprint('users', url_prefix='/users')

@users_bp.get('/')  # Route path relative to blueprint prefix
async def list_users(request):  # async and explicit request parameter
    return JSONResponse({'users': []})

@users_bp.get('/<int:user_id>')
async def get_user(request, user_id: int):  # async and explicit request parameter
    return JSONResponse({'user': user_id})

Migration Checklist

  • [ ] Convert all route handlers to async

  • [ ] Add explicit request parameter to handlers

  • [ ] Change jsonify() to JSONResponse()

  • [ ] Update route decorators (@route@get, @post, etc.)

  • [ ] Adjust blueprint registration calls

  • [ ] Update template rendering calls

  • [ ] Migrate middleware to Gobstopper middleware system

Advanced Topics

Conditional Blueprint Registration

import os

app = Gobstopper(__name__)

# Register debug routes only in development
if os.getenv('ENV') == 'development':
    app.register_blueprint(debug_bp)

# Register admin routes only if admin feature is enabled
if os.getenv('ENABLE_ADMIN') == 'true':
    app.register_blueprint(admin_bp)

Dynamic Blueprint Configuration

def create_api_blueprint(version: str, features: list[str]):
    """Create API blueprint with specific features."""
    bp = Blueprint(f'api_{version}', url_prefix=f'/{version}')

    if 'users' in features:
        bp.register_blueprint(users_bp)

    if 'posts' in features:
        bp.register_blueprint(posts_bp)

    return bp

# Create different API configurations
basic_api = create_api_blueprint('v1', ['users'])
full_api = create_api_blueprint('v2', ['users', 'posts'])

app.register_blueprint(basic_api)
app.register_blueprint(full_api)

Blueprint Testing

# test_users_blueprint.py
import pytest
from gobstopper import Gobstopper
from users import users_bp

@pytest.fixture
def app():
    app = Gobstopper(__name__)
    app.register_blueprint(users_bp)
    return app

@pytest.fixture
def client(app):
    # Create test client
    return TestClient(app)

def test_list_users(client):
    response = client.get('/users/')
    assert response.status == 200
    assert 'users' in response.json()

Troubleshooting

Routes Not Found (404)

Check URL prefix combination:

# If blueprint has prefix '/users' and route is '/list'
users_bp = Blueprint('users', url_prefix='/users')

@users_bp.get('/list')  # Final URL: /users/list
async def list_users(request):
    pass

# To get /users/, use root path:
@users_bp.get('/')  # Final URL: /users/
async def list_users(request):
    pass

Middleware Not Applied

Ensure middleware is registered before blueprint:

# ✅ Correct order
bp = Blueprint('api')
bp.add_middleware(auth_middleware)
app.register_blueprint(bp)

# ❌ Wrong - middleware added after registration
app.register_blueprint(bp)
bp.add_middleware(auth_middleware)  # Won't work

Before Request Not Running

Verify hook is defined before registration:

# ✅ Correct
bp = Blueprint('api')

@bp.before_request
async def check_auth(request):
    pass

app.register_blueprint(bp)

# ❌ Hook added after registration won't work
app.register_blueprint(bp)

@bp.before_request  # Too late
async def check_auth(request):
    pass

Summary

The Gobstopper Blueprint system provides:

  • ✅ Clean modular organization

  • ✅ Reusable components

  • ✅ Nested blueprint support

  • ✅ URL prefix management

  • ✅ Blueprint-scoped middleware and hooks

  • ✅ Flask-like familiar API

Use blueprints to build scalable, maintainable Gobstopper applications!