Claude
Skills
Sign in
Back

flask-smorest-api

Included with Lifetime
$97 forever

Set up Flask REST API with flask-smorest, OpenAPI docs, blueprint architecture, and dataclass models. Use when creating a new Flask API server, building REST endpoints, or setting up a production API.

Backend & APIs

What this skill does


# Flask REST API with flask-smorest Pattern

This skill helps you set up a Flask REST API following a standardized pattern with flask-smorest for OpenAPI documentation, blueprint architecture, and dataclass models for request/response handling.

## When to Use This Skill

Use this skill when:
- Starting a new Flask REST API project
- You want automatic OpenAPI/Swagger documentation
- You need a clean, modular blueprint architecture
- You want type-safe data models using dataclasses with to_dict/from_dict patterns
- You're building a production-ready API server

## What This Skill Creates

1. **Main application file** - Flask app initialization with flask-smorest
2. **Blueprint structure** - Modular endpoint organization
3. **Data models** - Dataclasses with to_dict/from_dict methods and validation
4. **Singleton manager pattern** - Centralized service/database initialization
5. **CORS support** - Cross-origin request handling
6. **Requirements file** - All necessary dependencies

## Step 1: Gather Project Information

**IMPORTANT**: Before creating files, ask the user these questions:

1. **"What is your project name?"** (e.g., "materia-server", "trading-api", "myapp")
   - Use this to derive:
     - Main module: `{project_name}.py` (e.g., `materia_server.py`)
     - Port number (suggest based on project, default: 5000)

2. **"What features/endpoints do you need?"** (e.g., "users", "tokens", "orders")
   - Each feature will become a blueprint

3. **"Do you need database integration?"** (yes/no)
   - If yes, reference postgres-setup skill for database layer

4. **"What port should the server run on?"** (default: 5000)

## Step 2: Create Directory Structure

Create these directories if they don't exist:
```
{project_root}/
├── blueprints/          # Blueprint modules (one per feature)
│   ├── __init__.py
│   └── {feature}.py
├── models/              # Dataclass models with to_dict/from_dict
│   ├── __init__.py
│   └── {feature}.py
└── {project_name}.py    # Main application file
```

## Step 3: Create Main Application File

Create `{project_name}.py` using this template:

```python
import os
import logging
from flask import Flask
from flask_cors import CORS
from flask_smorest import Api
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def create_app():
    app = Flask(__name__)
    app.config['API_TITLE'] = '{Project Name} API'
    app.config['API_VERSION'] = 'v1'
    app.config['OPENAPI_VERSION'] = '3.0.2'
    app.config['OPENAPI_URL_PREFIX'] = '/'
    app.config['OPENAPI_SWAGGER_UI_PATH'] = '/swagger'
    app.config['OPENAPI_SWAGGER_UI_URL'] = 'https://cdn.jsdelivr.net/npm/swagger-ui-dist/'

    CORS(app)
    api = Api(app)

    from blueprints.{feature} import blp as {feature}_blp
    api.register_blueprint({feature}_blp)

    logger.info("Flask app initialized")
    return app

if __name__ == '__main__':
    port = int(os.environ.get('PORT', {port_number}))
    app = create_app()
    logger.info(f"Swagger UI: http://localhost:{port}/swagger")
    app.run(host='0.0.0.0', port=port)
```

**CRITICAL**: Replace:
- `{Project Name}` → Human-readable project name (e.g., "Materia Server")
- `{project_name}` → Snake case project name (e.g., "materia_server")
- `{port_number}` → Actual port number (e.g., 5151)
- `{feature}` → Feature name from user's response

## Step 4: Create Data Models

For each feature, create a models file with dataclasses that include `to_dict` and `from_dict` methods:

### File: `models/{feature}.py`

```python
from dataclasses import dataclass
from typing import Optional


@dataclass
class {Feature}:
    """
    {Feature} data model.

    Includes validation in from_dict and serialization via to_dict.
    """
    id: str
    name: str
    created_at: int
    updated_at: Optional[int] = None

    def to_dict(self) -> dict:
        """Serialize to dictionary for JSON response."""
        return {
            "id": self.id,
            "name": self.name,
            "created_at": self.created_at,
            "updated_at": self.updated_at
        }

    @classmethod
    def from_dict(cls, data: dict) -> "{Feature}":
        """
        Create instance from dictionary with validation.

        Args:
            data: Dictionary with {feature} data

        Returns:
            {Feature} instance

        Raises:
            ValueError: If required fields are missing or invalid
        """
        if "id" not in data:
            raise ValueError("id is required")
        if "name" not in data:
            raise ValueError("name is required")
        if "created_at" not in data:
            raise ValueError("created_at is required")

        return cls(
            id=str(data["id"]),
            name=str(data["name"]),
            created_at=int(data["created_at"]),
            updated_at=int(data["updated_at"]) if data.get("updated_at") else None
        )
```

**CRITICAL**: Replace:
- `{Feature}` → PascalCase feature name (e.g., "TradableToken")
- `{feature}` → Snake case feature name (e.g., "tradable_token")

## Step 5: Create Blueprint Files

For each feature/endpoint, create a blueprint file:

### File: `blueprints/{feature}.py`

```python
import logging
from flask import request, jsonify
from flask.views import MethodView
from flask_smorest import Blueprint, abort

from models.{feature} import {Feature}

logger = logging.getLogger(__name__)

blp = Blueprint('{feature}', __name__, url_prefix='/api', description='{Feature} API')


@blp.route('/{feature}')
class {Feature}ListResource(MethodView):
    def get(self):
        """Get list of {feature}s."""
        try:
            limit = request.args.get('limit', 100, type=int)
            offset = request.args.get('offset', 0, type=int)

            # TODO: Implement logic to fetch {feature}s
            items = []

            return jsonify({
                "data": [item.to_dict() for item in items],
                "limit": limit,
                "offset": offset
            })
        except ValueError as e:
            logger.warning(f"Bad request: {e}")
            abort(400, message=str(e))
        except Exception as e:
            logger.exception(f"Error fetching {feature}s: {e}")
            abort(500, message="Internal server error")

    def post(self):
        """Create a new {feature}."""
        try:
            data = request.get_json()
            if not data:
                abort(400, message="Request body is required")

            item = {Feature}.from_dict(data)

            # TODO: Implement logic to save {feature}

            return jsonify(item.to_dict()), 201
        except ValueError as e:
            logger.warning(f"Validation error: {e}")
            abort(400, message=str(e))
        except Exception as e:
            logger.exception(f"Error creating {feature}: {e}")
            abort(500, message="Internal server error")


@blp.route('/{feature}/<string:item_id>')
class {Feature}Resource(MethodView):
    def get(self, item_id: str):
        """Get a single {feature} by ID."""
        try:
            # TODO: Implement logic to fetch {feature} by ID
            item = None

            if not item:
                abort(404, message=f"{Feature} not found: {item_id}")

            return jsonify(item.to_dict())
        except Exception as e:
            logger.exception(f"Error fetching {feature}: {e}")
            abort(500, message="Internal server error")

    def put(self, item_id: str):
        """Update a {feature}."""
        try:
            data = request.get_json()
            if not data:
                abort(400, message="Request body is required")

            # TODO: Implement logic to update {feature}

            return jsonify({"message": "Updated"})
        except ValueError as e:
            logger.warning(f"Validation error: {e}")
            abort(400, message=str(e))
        except Exception as e:
            

Related in Backend & APIs